com.numenta.taurus.TaurusBaseActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.numenta.taurus.TaurusBaseActivity.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.numenta.taurus;

import com.numenta.core.service.DataSyncService;
import com.numenta.core.utils.DataUtils;
import com.numenta.core.utils.Log;
import com.numenta.core.utils.NetUtils;
import com.numenta.taurus.dialog.RefreshDialogFragment;
import com.numenta.taurus.preference.AboutActivity;
import com.numenta.taurus.preference.SettingsActivity;
import com.numenta.taurus.tutorial.TutorialActivity;

import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.support.v4.content.LocalBroadcastManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ConcurrentLinkedQueue;

import static com.numenta.core.preference.PreferencesConstants.PREF_LAST_CONNECTED_TIME;

/**
 * <p>
 * Base activity showing a common menu and handle all other common functions of a typical activity.
 * All other activities must inherit from this class.
 * This activity will handle the following:
 * <ul>
 * <li>Options Menu (<i>"Refresh", "Settings", "Support", ...</i>)</li>
 * <li>Google Analytics</li>
 * </ul>
 * </p>
 */
public abstract class TaurusBaseActivity extends Activity {

    public static final int RESULT_SETTINGS = 1;

    protected final String TAG = getClass().getCanonicalName();

    private final BroadcastReceiver _isRefreshingReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            updateRefresh();
        }
    };

    private MenuItem _refresh;

    private View _refreshView;

    private ConcurrentLinkedQueue<AsyncTask> _taskList;

    @SuppressLint("AlwaysShowAction")
    private void updateRefresh() {
        if (_refresh != null) {
            if (TaurusApplication.isRefreshing()) {
                _refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
                _refresh.setActionView(_refreshView);

                // Calculate the interval since last connection to the server
                final SharedPreferences pref = PreferenceManager
                        .getDefaultSharedPreferences(getApplicationContext());
                final long now = System.currentTimeMillis();
                final long lastConnected = pref.getLong(PREF_LAST_CONNECTED_TIME, now);
                final long interval = now - lastConnected;

                // Notify the user if the last time we connected was more than one hour ago
                if (interval > DataUtils.MILLIS_PER_HOUR) {
                    final long hour = interval / DataUtils.MILLIS_PER_HOUR;
                    final long minutes = (interval % DataUtils.MILLIS_PER_HOUR) / DataUtils.MILLIS_PER_MINUTE;
                    if (hour > 0) {
                        String title;
                        // Format message: "X hours ago" or "X hours and Y minutes ago"
                        if (minutes > 0) {
                            title = getApplicationContext().getString(R.string.loading_metrics_title_hours_minutes,
                                    hour, minutes);
                        } else {
                            title = getApplicationContext().getString(R.string.loading_metrics_title_hours, hour);
                        }
                        Toast.makeText(this, title, Toast.LENGTH_LONG).show();
                    }
                }
            } else {
                // Check if we have an error message
                String refreshError = TaurusApplication.getLastError();
                if (refreshError == null) {
                    _refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
                    // Restore refresh icon
                    _refresh.setIcon(R.drawable.ic_action_refresh);
                } else {
                    _refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
                    // Show error icon
                    _refresh.setIcon(R.drawable.ic_action_connection_error);
                }
                _refresh.setActionView(null);
            }
        }
    }

    @SuppressLint("InflateParams")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "{TAG:ANDROID.APP.CREATE}");
        final ActionBar actionBar = getActionBar();
        if (actionBar != null) {
            actionBar.setDisplayShowTitleEnabled(false);
        }
        LayoutInflater inflater = getLayoutInflater();

        _refreshView = inflater.inflate(R.layout.actionbar_indeterminate_progress, null);

    }

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

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

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        validateUserSettings();
    }

    @Override
    protected void onResume() {
        Log.i(TAG, "{TAG:ANDROID.APP.RESUME}");
        super.onResume();
        LocalBroadcastManager.getInstance(this).registerReceiver(_isRefreshingReceiver,
                new IntentFilter(DataSyncService.REFRESH_STATE_EVENT));

        updateRefresh();
    }

    @Override
    protected void onPause() {
        Log.i(TAG, "{TAG:ANDROID.APP.PAUSE}");
        super.onPause();
        LocalBroadcastManager.getInstance(this).unregisterReceiver(_isRefreshingReceiver);

        // Cancel pending tasks
        if (_taskList != null) {
            AsyncTask task;
            while ((task = _taskList.poll()) != null) {
                if (isFinishing()) {
                    task.cancel(true);
                }
            }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);

        _refresh = menu.findItem(R.id.menu_refresh);
        updateRefresh();
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_settings:
            // Open the User Settings Screen
            Intent settings = new Intent(this, SettingsActivity.class);
            startActivityForResult(settings, RESULT_SETTINGS);
            break;
        case R.id.menu_about:
            // Open the About Screen
            Intent about = new Intent(this, AboutActivity.class);
            startActivity(about);
            break;

        case R.id.menu_tutorial:
            Intent intent = new Intent(getApplicationContext(), TutorialActivity.class);
            startActivity(intent);
            break;

        case R.id.menu_feedback:
            // Take a screenshot and share

            // Pop up a user feedback email view
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage(getString(R.string.feedback_dialog_message));
            builder.setTitle(getString(R.string.title_feedback_dialog));
            builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    StringBuffer sb = new StringBuffer();
                    sb.append("App Id:").append(BuildConfig.APPLICATION_ID).append("\n");
                    sb.append("Version Name:").append(BuildConfig.VERSION_NAME).append("\n");
                    sb.append("Version Code:").append(BuildConfig.VERSION_CODE).append("\n");
                    TaurusBaseActivity.this.emailFeedback(sb);
                }
            });

            builder.setNegativeButton(android.R.string.cancel, null);
            builder.show();
            break;
        case R.id.menu_share:
            // Take a screenshot and share
            this.shareScreenCapture();
            break;
        case R.id.menu_refresh:
            // Refresh
            TaurusApplication.refresh();
            // Show error dialog
            String refreshError = TaurusApplication.getLastError();
            if (refreshError != null) {
                RefreshDialogFragment.show(refreshError, getFragmentManager());
            }
            break;
        case android.R.id.home:
            NavUtils.navigateUpFromSameTask(this);
            return true;
        default:
            break;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        switch (requestCode) {
        case RESULT_SETTINGS:
            validateUserSettings();
            break;
        default:
            break;
        }
    }

    private void validateUserSettings() {
        // TODO Validate User Settings, for now just validate server connection
        TaurusApplication.checkConnection();
    }

    /**
     * Capture the screen and return the URI of the image
     */
    private Uri takeScreenCapture(boolean isRetryOk) {
        String fileName = "FILE_" + new SimpleDateFormat("yyyyMMddhhmm'.jpg'", Locale.US).format(new Date());

        File screenShot = new File(getCacheDir(), fileName);

        // create bitmap screen capture
        View v1 = getWindow().getDecorView().getRootView();
        v1.setDrawingCacheEnabled(true);
        v1.invalidate();
        v1.buildDrawingCache(true);
        Bitmap bitmap = null;
        FileOutputStream fOut = null;

        try {
            bitmap = Bitmap.createBitmap(v1.getDrawingCache(true));
            v1.setDrawingCacheEnabled(false);
            fOut = new FileOutputStream(screenShot);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 75, fOut);

        } catch (FileNotFoundException e) {
            Log.e(TAG, "Screen shot file not found", e);
        } catch (OutOfMemoryError e) {
            Log.e(TAG, "Out of Memory Error creating screenshot", e);
            // retry one time on out of memory
            if (isRetryOk) {
                return takeScreenCapture(false);
            }
            return writeTextFileToSend("screenshot.txt", "Out of Memory: Failed to generate screenshot");
        } finally {
            // recycle the bitmap on the heap to free up space and help prevent
            // out of memory errors
            if (bitmap != null) {
                bitmap.recycle();
                //noinspection UnusedAssignment
                bitmap = null;
            }
            System.gc();
            try {
                if (fOut != null) {
                    fOut.flush();
                    fOut.close();
                }
            } catch (IOException e) {
                Log.e(TAG, "Error saving the screenshot file", e);
            }
        }

        return Uri.parse("content://" + getApplication().getPackageName() + "/" + fileName);
    }

    private Uri writeTextFileToSend(String fileName, String message) {
        File textFile = new File(getCacheDir(), fileName);
        FileWriter writer = null;

        try {
            writer = new FileWriter(textFile);
            writer.write(message);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Text file not found", e);
        } catch (IOException e) {
            Log.e(TAG, "I/O Error accessing text file", e);
        } finally {
            try {
                if (writer != null) {
                    writer.flush();
                    writer.close();
                }
            } catch (IOException e) {
                Log.e(TAG, "Error saving the file", e);
            }
        }

        return Uri.parse("content://" + getApplication().getPackageName() + "/" + fileName);
    }

    /**
     * Capture the screen and return the URI of the image
     */
    private Uri writeTextFileToSend(String message) {
        return writeTextFileToSend("report.txt", message);
    }

    /**
     * Share a screen capture via email or another provider
     */
    private void shareScreenCapture() {
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        Uri uri = this.takeScreenCapture(true);
        shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
        shareIntent.setData(uri);
        shareIntent.setType("image/jpeg");
        shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.title_share)));
    }

    /**
     * Send user feedback via email with pre-populated email address, screen capture and optional
     * upload identifier
     * <p/>
     *
     * @param uploadId the identifier of the uploaded information; null if none
     */
    protected void emailFeedback(final CharSequence uploadId) {
        Intent feedbackIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
        feedbackIntent.putExtra(Intent.EXTRA_EMAIL,
                new String[] { getResources().getText(R.string.feedback_email_address).toString() });
        String subject = getResources().getText(R.string.feedback_email_subject).toString();

        feedbackIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
        feedbackIntent.setType("message/rfc822");
        ArrayList<Uri> uris = new ArrayList<Uri>();
        uris.add(takeScreenCapture(true));
        if (uploadId != null) {
            uris.add(writeTextFileToSend(uploadId.toString()));
        }
        feedbackIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);

        startActivity(Intent.createChooser(feedbackIntent, getResources().getText(R.string.title_feedback)));
    }

    /**
     * Track background tasks making sure to synchronize the background task lifecycle with the
     * activity lifecycle.
     *
     * @see #onPause()
     */
    public void trackBackgroundTask(AsyncTask task) {
        if (_taskList == null) {
            _taskList = new ConcurrentLinkedQueue<AsyncTask>();
        }
        _taskList.add(task);
    }

    /**
     * Cancel background task previously tracked via {@link #trackBackgroundTask(android.os.AsyncTask)}
     *
     * @param task The task to cancel
     */
    public void cancelTrackedBackgroundTask(AsyncTask task) {
        if (_taskList != null && task != null) {
            task.cancel(true);
            _taskList.remove(task);
        }
    }

    /**
     * Check if the network status, notifying the user if the network is unavailable.
     * @return {@code true} if network is available, {@code false} otherwise
     */
    public boolean checkNetworkConnection() {
        if (!NetUtils.isConnected()) {
            new AlertDialog.Builder(this).setMessage(R.string.error_network_unavailable)
                    .setPositiveButton(android.R.string.ok, null).show();
            return false;
        }
        return true;
    }
}