com.groksolutions.grok.mobile.notification.NotificationListActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.groksolutions.grok.mobile.notification.NotificationListActivity.java

Source

/*
 * Numenta Platform for Intelligent Computing (NuPIC)
 * Copyright (C) 2015, Numenta, Inc.  Unless you have purchased from
 * Numenta, Inc. a separate commercial license for this software code, the
 * following terms and conditions apply:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero Public License for more details.
 *
 * You should have received a copy of the GNU Affero Public License
 * along with this program.  If not, see http://www.gnu.org/licenses.
 *
 * http://numenta.org/licenses/
 *
 */

package com.groksolutions.grok.mobile.notification;

import com.groksolutions.grok.mobile.HTMITApplication;
import com.groksolutions.grok.mobile.R;
import com.groksolutions.grok.mobile.data.GrokDatabase;
import com.groksolutions.grok.mobile.dialog.ConfirmDialogFragment;
import com.numenta.core.data.Metric;
import com.numenta.core.data.Notification;
import com.numenta.core.service.NotificationService;
import com.numenta.core.utils.Log;

import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.widget.SimpleCursorAdapter;
import android.support.v4.widget.SimpleCursorAdapter.ViewBinder;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

@SuppressLint("ClickableViewAccessibility")
public class NotificationListActivity extends FragmentActivity
        implements ConfirmDialogFragment.ConfirmDialogListener, GestureDetector.OnGestureListener {

    private static final float DELETION_FLING_THRESHOLD = 20f;
    static final String TAG = NotificationListActivity.class.getCanonicalName();

    private GrokDatabase grokDb;
    private int doomedNotificationIndex = -1;
    private BroadcastReceiver _notificationsReceiver;
    private SimpleCursorAdapter adapter;
    private final DateFormat sdf = new SimpleDateFormat("HH:mm a EEE MM/dd/yy", Locale.US);
    private ListView listView;
    private Button dismissButton;
    private Button closeButton;
    private TextView noNotificationsText;
    private GestureDetector gestureDetector;
    private int notificationSize;
    private long unreadNotificationSize;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        gestureDetector = new GestureDetector(getApplicationContext(), this);
        setContentView(R.layout.activity_notification_list);
        setVisible(false);

        listView = (ListView) findViewById(R.id.notification_list_view);
        dismissButton = (Button) findViewById(R.id.action_dismiss_all_notifications);
        closeButton = (Button) findViewById(R.id.action_close_notifications);
        noNotificationsText = (TextView) findViewById(R.id.no_notifications_text);

        // For the cursor adapter, specify which columns go into which views
        String[] fromColumns = { "timestamp", "description", "metric_id", "read" };
        int[] toViews = { R.id.notification_time, R.id.notification_description, R.id.notification_delete,
                R.id.notification_unread }; // The TextView in simple_list_item_1
        grokDb = HTMITApplication.getDatabase();
        adapter = new SimpleCursorAdapter(this, R.layout.fragment_notification_list_item, null, fromColumns,
                toViews, 0);

        new AsyncTask<Void, Void, Cursor>() {
            @Override
            protected Cursor doInBackground(Void... params) {
                unreadNotificationSize = grokDb.getUnreadNotificationCount();
                return grokDb.getNotificationCursor();
            }

            @Override
            protected void onPostExecute(Cursor cursor) {
                setVisible(true);
                adapter.changeCursor(cursor);
                notificationSize = cursor.getCount();
                updateButtons();
            }
        }.execute();

        _notificationsReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (adapter != null) {
                    new AsyncTask<Void, Void, Cursor>() {
                        @Override
                        protected Cursor doInBackground(Void... params) {
                            if (isCancelled())
                                return null;
                            return grokDb.getNotificationCursor();
                        }

                        @Override
                        protected void onPostExecute(Cursor cursor) {
                            adapter.changeCursor(cursor);
                            updateButtons();
                        }
                    }.execute();
                }
            }
        };

        adapter.setViewBinder(new ViewBinder() {

            @Override
            public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
                final int viewId = view.getId();

                switch (viewId) {
                case R.id.notification_time:
                    // Converts the timestamp to a readable time.
                    final int timeIndex = cursor.getColumnIndex("timestamp");
                    Date date = new Date(cursor.getLong(timeIndex));
                    ((TextView) view).setText(sdf.format(date));
                    break;
                case R.id.notification_unread:
                    // Hides notification icon if already read.
                    if (cursor.getInt(cursor.getColumnIndex("read")) < 1) {
                        view.setVisibility(View.VISIBLE);
                    } else {
                        view.setVisibility(View.INVISIBLE);
                    }
                    break;
                case R.id.notification_delete:
                    // Adds click handler for notification deletions
                    view.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            Log.i(TAG, "{TAG:ANDROID.ACTION.NOTIFICATION.DELETE} Delete notification clicked");
                            View layout = (View) v.getParent();
                            int position = listView.getPositionForView(layout);
                            NotificationListActivity.this.removeNotificationAt(position);
                        }
                    });
                    break;
                default:
                    return false;
                }
                return true;
            }
        });

        listView.setAdapter(adapter);

        // Clicks on the notifications themselves navigates to the detail view.
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, final View view, int position, long id) {
                Log.i(TAG,
                        "{TAG:ANDROID.ACTION.NOTIFICATION.SELECT} Notification navigation should occur here to notification "
                                + position);
                Cursor cursor = (Cursor) adapter.getItem(position);
                int localIdx = cursor.getColumnIndex("_id");
                final int localId = cursor.getInt(localIdx);

                new AsyncTask<Void, Void, Intent>() {
                    @Override
                    protected Intent doInBackground(Void... v) {
                        // Get the metric necessary for the new intent to view
                        // the metric detail
                        // page.
                        Notification notification = grokDb.getNotificationByLocalId(localId);
                        if (notification == null) {
                            // The notification or metric was deleted as the
                            // user view the list
                            return null;
                        }
                        Metric metric = grokDb.getMetric(notification.getMetricId());
                        if (metric == null) {
                            // the metric was deleted, so nowhere to go
                            grokDb.deleteNotification(localId);
                            return null;
                        }
                        Intent metricDetail = NotificationUtils
                                .createMetricDetailIntent(NotificationListActivity.this, notification);
                        // Mark the notification as read

                        if (!notification.isRead()) {
                            grokDb.markNotificationRead(localId);
                        }
                        ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancelAll();

                        return metricDetail;
                    }

                    @Override
                    protected void onPostExecute(Intent metricDetail) {
                        if (metricDetail == null) {
                            Toast.makeText(NotificationListActivity.this, R.string.notification_expired,
                                    Toast.LENGTH_LONG).show();
                            NotificationListActivity.this.finish();
                            return;
                        }
                        // Show detail page
                        startActivity(metricDetail);
                        // Hide the 'unread' indication.
                        view.findViewById(R.id.notification_unread).setVisibility(View.INVISIBLE);
                    }
                }.execute();

            }
        });

        // This catches "fling" events on items to delete notifications within
        // the list.
        // Defers touch events to a GestureDetector, which isolates fling events
        // from touch events.
        listView.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return gestureDetector.onTouchEvent(event);
            }
        });

        dismissButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.i(TAG, "{TAG:ANDROID.ACTION.NOTIFICATION.DELETE_ALL}");
                deleteAllNotifications();
            }
        });

        closeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                cancelNotifications();
            }
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        HTMITApplication.setActivityLastUsed();
        HTMITApplication.incrementActivityCount();
    }

    @Override
    public void onStop() {
        super.onStop();
        HTMITApplication.setActivityLastUsed();
        HTMITApplication.decrementActivityCount();
    }

    protected void removeNotificationAt(int position) {
        doomedNotificationIndex = position;
        String question = getString(R.string.confirm_dismiss_notification);
        String yes = getString(R.string.confirm_dialog_positive);
        String no = getString(R.string.confirm_dialog_negative);
        ConfirmDialogFragment dialog = ConfirmDialogFragment.newInstance(question, yes, no);
        dialog.show(getSupportFragmentManager(), "dismissNotification");
        // See the onDialogPositiveClick() and onNegativeClick() functions for
        // confirm dialog handling.
    }

    protected void deleteAllNotifications() {
        String question = getString(R.string.confirm_dismiss_all);
        String yes = getString(R.string.confirm_dialog_positive);
        String no = getString(R.string.confirm_dialog_negative);
        ConfirmDialogFragment dialog = ConfirmDialogFragment.newInstance(question, yes, no);
        dialog.show(this.getSupportFragmentManager(), "dismissAllNotifications");
    }

    protected void cancelNotifications() {
        finish();
    }

    private void updateButtons() {
        if (notificationSize == 0) {
            dismissButton.setVisibility(View.GONE);
            noNotificationsText.setVisibility(View.VISIBLE);
        } else {
            dismissButton.setVisibility(View.VISIBLE);
            closeButton.setVisibility(View.VISIBLE);
            noNotificationsText.setVisibility(View.GONE);
        }
    }

    @Override
    public void onDialogPositiveClick(final DialogFragment dialog) {
        String tag = dialog.getTag();
        if (tag.equals("dismissAllNotifications")) {
            // Remove in background.
            new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... v) {
                    NotificationService.deleteAllNotifications();
                    // Also cancel any pending OS notifications.
                    ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancelAll();
                    return null;
                }

                @Override
                protected void onPostExecute(Void v) {
                    dialog.dismiss();
                    NotificationListActivity.this.finish();
                    finish();
                }
            }.execute();
        } else if (tag.equals("dismissNotification")) {
            removeDoomedNotification();
            dialog.dismiss();
        } else {
            Log.d(TAG, "Unknown dialog click: " + tag);
        }
    }

    @Override
    public void onDialogNegativeClick(DialogFragment dialog) {
        dialog.dismiss();
    }

    private void removeDoomedNotification() {
        if (doomedNotificationIndex > -1) {
            Cursor doomed = (Cursor) adapter.getItem(doomedNotificationIndex);
            int notificationId = doomed.getInt(0);
            final int isRead = doomed.getInt(doomed.getColumnIndex("read"));

            new AsyncTask<Integer, Void, Cursor>() {
                @Override
                protected Cursor doInBackground(Integer... params) {
                    int id = params[0];
                    long deleted = NotificationService.deleteNotification(id);
                    Log.d(TAG, "Deleted notification " + doomedNotificationIndex + "? : " + deleted);
                    return grokDb.getNotificationCursor();
                }

                @Override
                protected void onPostExecute(Cursor cursor) {
                    adapter.changeCursor(cursor);
                    adapter.notifyDataSetChanged();
                    notificationSize = cursor.getCount();
                    if (isRead == 0) {
                        unreadNotificationSize--;
                    }

                    if (unreadNotificationSize > 1) {
                        // if the notification size is greater than 1, we really
                        // only care about
                        // the notification size. The rest of the data is
                        // unused.
                        NotificationUtils.createOSNotification("", System.currentTimeMillis(), 0,
                                unreadNotificationSize);
                    } else if (unreadNotificationSize == 1) {
                        // need to pull out the data from the cursor because we
                        // don't want to
                        // access the database from here.
                        int localId = cursor.getInt(cursor.getColumnIndex("_id"));
                        String description = cursor.getString(cursor.getColumnIndex("description"));
                        long timestamp = cursor.getLong(cursor.getColumnIndex("timestamp"));
                        NotificationUtils.createOSNotification(description, timestamp, localId, 1);
                    } else if (unreadNotificationSize == 0) {
                        ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancelAll();
                    }

                    updateButtons();
                }
            }.execute(notificationId);
        }
    }

    /*
     * (non-Javadoc)
     * @see android.support.v4.app.FragmentActivity#onPause()
     */
    @Override
    protected void onPause() {
        super.onPause();
        LocalBroadcastManager.getInstance(this).registerReceiver(_notificationsReceiver,
                new IntentFilter(NotificationService.NOTIFICATION_CHANGED_EVENT));
    }

    /*
     * (non-Javadoc)
     * @see android.support.v4.app.FragmentActivity#onResume()
     */
    @Override
    protected void onResume() {
        super.onResume();
        LocalBroadcastManager.getInstance(this).unregisterReceiver(_notificationsReceiver);
    }

    @Override
    public boolean onFling(MotionEvent downEvent, MotionEvent moveEvent, float velocityX, float velocityY) {
        // Only fling horizontally for delete, ignore vertical flings, which
        // should scroll through the list.
        float yDelta = Math.abs(downEvent.getY() - moveEvent.getY());
        if (yDelta < DELETION_FLING_THRESHOLD) {
            int position = listView.pointToPosition((int) downEvent.getX(), (int) downEvent.getY());
            Log.i(TAG, "{TAG:ANDROID.ACTION.NOTIFICATION.FLING} Notification deleted by fling.");
            NotificationListActivity.this.removeNotificationAt(position);
            return true;
        }
        return false;
    }

    /*
     * See below for unimplemented methods of OnGestureListener. This is one
     * thing that sucks about Java.
     */

    @Override
    public boolean onDown(MotionEvent e) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {
        // Do nothing
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {
        // Do nothing
    }
}