com.aimfire.main.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.aimfire.main.MainActivity.java

Source

package com.aimfire.main;

/*
 * Copyright (c) 2016 Aimfire Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.aimfire.audio.AudioConfigure;
import com.aimfire.camarada.R;
import com.aimfire.constants.ActivityCode;
import com.aimfire.demo.CamcorderActivity;
import com.aimfire.gallery.CopyToClipboardActivity;
import com.aimfire.service.AimfireService;
import com.aimfire.service.AimfireServiceConn;
import com.aimfire.utilities.FileUtils;
import com.aimfire.backend.Consts;
import com.aimfire.camarada.BuildConfig;
import com.aimfire.gallery.MediaScanner;
import com.aimfire.gallery.ThumbsFragment;
import com.aimfire.gallery.service.SamplesDownloader;
import com.aimfire.intro.IntroductionActivity;
import com.google.firebase.analytics.FirebaseAnalytics;

import android.accounts.AccountManager;
import android.app.Activity;
import android.content.ComponentName;
import android.content.pm.LabeledIntent;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.ActionBar.Tab;
import android.support.v7.app.ActionBar.TabListener;
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.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.Surface;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Toast;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;

/**
 * MainActivity for AimFireVR
 */
@SuppressWarnings("deprecation")
public class MainActivity extends AppCompatActivity implements TabListener {
    private static final String TAG = "MainActivity";

    private static final String KEY_TAB_POSITION = "KTP";

    public static final int TAB_INDEX_MY_MEDIA = 0;
    public static final int TAB_INDEX_SHARED_MEDIA = 1;
    private static final int NUM_OF_TABS = 2;

    /*
     * show intro at first launch after installation (will not show for app update)
     */
    private boolean mShowIntro = true;

    /*
     * show survey to the user
     */
    private boolean mShowSurvey = false;

    /*
     * how many times we are launched by the user
     */
    private int mLaunchCount = 0;

    /*
     * whether we have already incremented count for the current user launch (this static variable
     * is used to make sure we don't increment the count if user switches our app between back and
     * foreground)
     */
    private static boolean sLaunchCountInc = false;

    /*
     * if a new version is available
     */
    private boolean mUpgradeAvailable = false;

    /*
     * firebase analytics
     */
    private FirebaseAnalytics mFirebaseAnalytics;

    /*
     * UI components 
     */
    private ViewPager mThumbsPager;
    private ThumbsPagerAdapter mThumbsPagerAdapter;
    private Tab mMyMediaTab;
    private Tab mSharedMediaTab;
    private int mTabPosition;
    private FloatingActionButton mCameraFab;

    /*
     * AimfireService object
     */
    private AimfireService mAimfireService;
    private AimfireServiceConn mAimfireServiceConn;

    /*
     * cloud backend ready flags. true if signed in to google cloud service
     * regReq with cloud 
     */
    private boolean mCloudReady = false;

    /**
     * BroadcastReceiver for incoming cloud service messages.
     */
    private BroadcastReceiver mAimfireServiceMsgReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            int messageCode = intent.getIntExtra(MainConsts.EXTRA_WHAT, -1);
            switch (messageCode) {
            case MainConsts.MSG_AIMFIRE_SERVICE_CLOUD_SERVICE_READY:
                /*
                 * if cloud service is ready (signed in)
                 */
                onCloudServiceReady();
                break;
            default:
                break;
            }
        }
    };

    Runnable mAimfireServiceInitTask = new Runnable() {
        public void run() {
            while ((mAimfireService = mAimfireServiceConn.getAimfireService()) == null) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    if (BuildConfig.DEBUG)
                        Log.d(TAG, "mAimfireServiceInitTask interrupted, exiting");
                    return;
                }
            }

            /*
             * check if credential is available (first time launch), this will start 
             * a google activity to sign in if authentication is required AND account
             * was not set. if account name was set (not first time launch), then the
             * cloud backend service has already signed in, thus we do nothing. ideally 
             * the backend service should handle all of this by itself, but being a 
             * service, it doesn't support launching an activity (the account picker) 
             * for result. therefore we have to do it here
             */
            if (!mAimfireService.isCloudReady()) {
                chooseAccount(false/*overrideCurrent*/);
            }
        }
    };

    /*
     * will launch the intro activity (if necessary) after MainActivity 
     * onCreate/onResume is done
     */
    private Runnable mIntroTask = new Runnable() {
        public void run() {
            Intent intent = new Intent(MainActivity.this, IntroductionActivity.class);
            startActivityForResult(intent, ActivityCode.INTRO.getValue());
        }
    };

    /*
     * will launch the survey activity (if necessary) after MainActivity
     * onCreate/onResume is done
     */
    private Runnable mSurveyTask = new Runnable() {
        public void run() {
            Intent intent = SurveyActivity.createIntent(MainActivity.this);
            startActivity(intent);
        }
    };

    /**
     * Override Activity lifecycle method.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (savedInstanceState == null) {
            /*
             * if this is initial launch, start the service
             */
            startService(new Intent(this, AimfireService.class));
        }

        PreferenceManager.setDefaultValues(this, getString(R.string.settings_file), MODE_PRIVATE,
                R.xml.pref_settings, true);

        /*
         * check persistence storage, if this is the first time we run, save certain
         * flags (like features available). check showIntro flag and show intro if 
         * necessary
         */
        checkPreferences();

        /*
         * set up UI
         */
        setContentView(R.layout.activity_main);

        /*
         * show overflow button even if we have a physical menu button
         */
        forceOverflowButton();

        /*
         * make a backup if we find old, non-compatible cvr files
         */
        if (mShowIntro) {
            checkOldCvr();
        }

        /*
         * Obtain the FirebaseAnalytics instance.
         */
        mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);

        /*
         * create directories if not exist
         */
        if (!FileUtils.initStorage()) {
            Toast.makeText(this, R.string.error_accessing_storage, Toast.LENGTH_LONG).show();
            finish();
        }

        mThumbsPagerAdapter = new ThumbsPagerAdapter(getSupportFragmentManager());

        mThumbsPager = (ViewPager) findViewById(R.id.thumbs_pager);
        mThumbsPager.setAdapter(mThumbsPagerAdapter);

        mThumbsPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                // When swiping between pages, select the
                // corresponding tab.
                mTabPosition = position;
                getSupportActionBar().setSelectedNavigationItem(position);
            }
        });

        ActionBar bar = getSupportActionBar();
        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        mMyMediaTab = bar.newTab();
        mMyMediaTab.setText(getResources().getString(R.string.tab_my_media_name));
        mMyMediaTab.setTabListener(this);

        mSharedMediaTab = bar.newTab();
        mSharedMediaTab.setText(getResources().getString(R.string.tab_shared_with_me_name));
        mSharedMediaTab.setTabListener(this);

        bar.addTab(mMyMediaTab, TAB_INDEX_MY_MEDIA);
        bar.addTab(mSharedMediaTab, TAB_INDEX_SHARED_MEDIA);

        /*
         * first launch of this activity, "Shared with Me" tab is the default.
         */
        if (savedInstanceState != null) {
            /*
             * restore tab position after screen rotation
             */
            mTabPosition = savedInstanceState.getInt(KEY_TAB_POSITION);
            if (BuildConfig.DEBUG)
                Log.d(TAG, "onCreate: restored tab position=" + mTabPosition);
        } else {
            //mTabPosition = TAB_INDEX_MY_MEDIA;
            mTabPosition = TAB_INDEX_SHARED_MEDIA;
            if (BuildConfig.DEBUG)
                Log.d(TAG, "onCreate: set initial tab position=" + mTabPosition);
        }

        mCameraFab = (FloatingActionButton) findViewById(R.id.cameraFAB);
        mCameraFab.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                WifiManager manager = (WifiManager) getSystemService(Context.WIFI_SERVICE);

                if (!manager.isWifiEnabled()) {
                    /*
                     * if wifi not enabled, no point to continue
                     */
                    Toast.makeText(getApplicationContext(), R.string.error_wifi_off, Toast.LENGTH_LONG).show();
                    return;
                }

                if (AudioConfigure.getAudioRouting() != AudioConfigure.AUDIO_ROUTING_BUILTIN) {
                    Toast.makeText(getApplicationContext(), R.string.error_headset, Toast.LENGTH_LONG).show();
                    return;
                }

                mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_ACTION_CAMERA, null);

                /*
                 * initiate discovery request
                 */
                mAimfireService.initDemo(true, ActivityCode.CAMCORDER.getValue(), null);
                Intent intent = new Intent(getApplicationContext(), CamcorderActivity.class);
                startActivityForResult(intent, ActivityCode.CAMCORDER.getValue());
            }
        });

        /*
         * initializes Aimfire service. if it is already started, bind to it
         */
        mAimfireServiceConn = new AimfireServiceConn(this);

        /*
         * binding doesn't happen until later. wait for it to happen in another 
         * thread and do the necessary initialization on it.
         */
        (new Thread(mAimfireServiceInitTask)).start();

        /*
         * display app build time in debug window
         */
        displayVersion();

        /*
         * show introduction with a delay (to allow onCreate init to finish)
         */
        if (mShowIntro) {
            mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.TUTORIAL_BEGIN, null);

            Handler mHandler = new Handler();
            mHandler.postDelayed(mIntroTask, 1000/*ms*/);

            /*
             * check if gyroscope is supported (important for Cardboard mode).
             * only show warning once after fresh install
             */
            checkGyroscope();
        } else if (mShowSurvey) {
            mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_SHOW_SURVEY, null);

            Handler mHandler = new Handler();
            mHandler.postDelayed(mSurveyTask, 1000/*ms*/);
        }

        if ((savedInstanceState == null) && isDeviceOnline()) {
            /*
             * check if new version is available
             */
            startService(new Intent(this, VersionChecker.class));

            /*
             * download samples in the background (if necessary)
             */
            startService(new Intent(this, SamplesDownloader.class));

            /*
             * notify user if upgrade is available
             */
            if (mUpgradeAvailable) {
                notifyUpgrade();
            }
        }

        //(new Thread(mLatencyTestTask)).start();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        boolean isMyMedia = intent.getBooleanExtra(MainConsts.EXTRA_MSG, false);
        mTabPosition = isMyMedia ? TAB_INDEX_MY_MEDIA : TAB_INDEX_SHARED_MEDIA;
        if (BuildConfig.DEBUG)
            Log.d(TAG, "onNewIntent: selected position=" + mTabPosition);
    }

    @Override
    protected void onStart() {
        super.onStart();
    }

    /**
     * Override Activity lifecycle method.
     */
    @Override
    protected void onResume() {
        /*
         * we disable the wifi on/off feature as it is quite confusing. we show only a
         * toast to warn user.
         */
        WifiManager manager = (WifiManager) getSystemService(Context.WIFI_SERVICE);

        if (!manager.isWifiEnabled()) {
            Toast.makeText(this, R.string.error_wifi_off, Toast.LENGTH_LONG).show();
        }

        /*
         * register for intents sent by the cloud backend service
         */
        LocalBroadcastManager.getInstance(this).registerReceiver(mAimfireServiceMsgReceiver,
                new IntentFilter(MainConsts.AIMFIRE_SERVICE_MESSAGE));

        /*
         * navigate the selected tab
         */
        getSupportActionBar().setSelectedNavigationItem(mTabPosition);

        super.onResume();
    }

    /**
     * Override Activity lifecycle method.
     */
    @Override
    protected void onPause() {
        /*
         * unregister for intents sent by the cloud backend service
         */
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mAimfireServiceMsgReceiver);

        super.onPause();
    }

    /**
     * Override Activity lifecycle method.
     */
    @Override
    protected void onDestroy() {
        if (mAimfireServiceConn != null)
            mAimfireServiceConn.unbind();

        /*
         * stop the service (we are not using cloud for discovery, so no need to keep it around)
         */
        stopService(new Intent(this, AimfireService.class));

        super.onDestroy();
    }

    /**
     * Override Activity lifecycle method.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putInt(KEY_TAB_POSITION, mTabPosition);
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
    }

    /**
     * Override Activity lifecycle method.
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.activity_main, menu);

        /*
          * To show icon (instead of only text) in action bar overflow
          */
        if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
            try {
                Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
                m.setAccessible(true);
                m.invoke(menu, true);
            } catch (NoSuchMethodException e) {
                if (BuildConfig.DEBUG)
                    Log.e(TAG, "onMenuOpened", e);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        return super.onCreateOptionsMenu(menu);
    }

    /**
     * Override Activity lifecycle method.
     */
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        MenuItem loginItem = menu.findItem(R.id.action_switch_account);
        loginItem.setVisible(Consts.IS_AUTH_ENABLED);

        return true;
    }

    /**
     * Override Activity lifecycle method.
     * <p>
     * To add more option menu items in your client, add the item to menu/activity_main.xml,
     * and provide additional case statements in this method.
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Intent intent;
        switch (item.getItemId()) {
        case R.id.action_help:
            mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_ACTION_HELP, null);
            intent = new Intent(this, IntroductionActivity.class);
            startActivity(intent);
            break;
        case R.id.action_tutorial:
            mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_ACTION_TUTORIAL, null);
            intent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.app_youtube_tutorial)));
            startActivity(intent);
            break;
        case R.id.action_feedback:
            mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_ACTION_FEEDBACK, null);
            intent = new Intent(Intent.ACTION_VIEW);
            Uri data = Uri.parse("mailto:" + getString(R.string.app_support_email) + "?subject="
                    + getString(R.string.feedbackEmailSubject));
            intent.setData(data);
            startActivity(intent);
            break;
        case R.id.action_switch_account:
            mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_ACTION_SWITCH_ACCOUNT, null);
            mCloudReady = false;
            chooseAccount(true/*overrideCurrent*/);
            break;

        case R.id.action_settings:

            intent = new Intent(this, SettingsActivity.class);
            startActivity(intent);

            break;

        default:
            return super.onOptionsItemSelected(item);
        }

        return true;
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        /*
         * force MainActivity in portrait mode
         */
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        mTabPosition = tab.getPosition();
        mThumbsPager.setCurrentItem(mTabPosition);
    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
    }

    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }

    public class ThumbsPagerAdapter extends FragmentPagerAdapter {
        public ThumbsPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public int getCount() {
            return NUM_OF_TABS;
        }

        @Override
        public Fragment getItem(int position) {
            Bundle data = new Bundle();
            data.putInt(MainConsts.EXTRA_INDEX, position);

            ThumbsFragment tf = new ThumbsFragment();
            tf.setArguments(data);
            return tf;
        }
    }

    /**
     * Override Activity lifecycle method.
     * handles media player and intro activity results
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // handle request codes
        ActivityCode code = ActivityCode.values()[requestCode];

        switch (code) {
        case INTRO:
            mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.TUTORIAL_COMPLETE, null);
            showHint();
            break;
        case CAMCORDER:
            if (resultCode == Activity.RESULT_CANCELED) {
                /*
                 * if user didn't attempt to connect
                 */
                showInvite();
            } else {
                /*
                 * remember if this device ever attempted to pair with another device
                 */
                updateDualModePref();
            }
            break;
        case ACCOUNT_PICKER:
            if (data != null && data.getExtras() != null) {
                // set the picked account name to the credential
                String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
                mAimfireService.getCredential().setSelectedAccountName(accountName);

                // save account name to shared pref
                SharedPreferences.Editor e = getSharedPreferences(Consts.PREF_KEY_CLOUD_BACKEND,
                        Context.MODE_PRIVATE).edit();
                e.putString(Consts.PREF_KEY_ACCOUNT_NAME, accountName);
                e.commit();
            }

            // post create initialization
            mAimfireService.setAccount();
            break;
        default:
            break;
        }

        // call super method to ensure unhandled result codes are handled
        super.onActivityResult(requestCode, resultCode, data);
    }

    public void onCloudServiceReady() {
        if (BuildConfig.DEBUG)
            Log.d(TAG, "onCloudServiceReady: mCloudReady was " + mCloudReady + " changing to True");
        mCloudReady = true;
    }

    public void showHint() {
        new MaterialTapTargetPrompt.Builder(this).setTarget(mCameraFab).setPrimaryText(R.string.cameraFabHintTitle)
                .setSecondaryText(R.string.cameraFabHintText).show();
    }

    private void showInvite() {
        SharedPreferences settings = getSharedPreferences(getString(R.string.settings_file), Context.MODE_PRIVATE);

        /*
         * here settings != null doesn't mean the file necessarily exist!
         */
        if (settings != null) {
            boolean dualModeAttempted = settings.getBoolean(MainConsts.DUAL_MODE_ATTEMPTED_PREFS_KEY, false);
            boolean canShowInvite = !settings.getBoolean(MainConsts.DONT_SHOW_INVITE_PREFS_KEY, false);

            if (!dualModeAttempted && canShowInvite) {
                AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this,
                        R.style.AppCompatAlertDialogStyle);
                alertDialogBuilder.setTitle(R.string.information);
                alertDialogBuilder.setMessage(R.string.info_share_prompt);

                alertDialogBuilder.setPositiveButton(R.string.share, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        inviteFriend();
                    }
                });

                alertDialogBuilder.setNeutralButton(R.string.dontAskAgain, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        updateShowInvitePref();
                    }
                });

                alertDialogBuilder.setNegativeButton(R.string.later, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // do nothing
                    }
                });

                AlertDialog alertDialog = alertDialogBuilder.create();
                alertDialog.show();
            }
        }
    }

    /**
     * share only to certain apps. code based on "http://stackoverflow.com/questions/
     * 9730243/how-to-filter-specific-apps-for-action-send-intent-and-set-a-different-
     * text-for/18980872#18980872"
     *
     * "copy link" inspired by http://cketti.de/2016/06/15/share-url-to-clipboard/
     *
     * in general, "deep linking" is supported by the apps below. Facebook, Wechat,
     * Telegram are exceptions. click on the link would bring users to the landing
     * page.
     *
     * Facebook doesn't take our EXTRA_TEXT so user will have to "copy link" first
     * then paste the link
     */
    private void inviteFriend() {
        mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_INVITE, null);

        Resources resources = getResources();

        /*
         * construct link
         */
        String appLink = resources.getString(R.string.app_store_link);

        /*
         * message subject and text
         */
        String emailSubject, emailText, twitterText;

        emailSubject = resources.getString(R.string.emailSubjectInviteFriend);
        emailText = resources.getString(R.string.emailBodyInviteFriend) + appLink;
        twitterText = resources.getString(R.string.emailBodyInviteFriend) + appLink + ", "
                + resources.getString(R.string.app_hashtag);

        Intent emailIntent = new Intent();
        emailIntent.setAction(Intent.ACTION_SEND);
        // Native email client doesn't currently support HTML, but it doesn't hurt to try in case they fix it
        emailIntent.putExtra(Intent.EXTRA_SUBJECT, emailSubject);
        emailIntent.putExtra(Intent.EXTRA_TEXT, emailText);
        //emailIntent.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(resources.getString(R.string.share_email_native)));
        emailIntent.setType("message/rfc822");

        PackageManager pm = getPackageManager();
        Intent sendIntent = new Intent(Intent.ACTION_SEND);
        sendIntent.setType("text/plain");

        Intent openInChooser = Intent.createChooser(emailIntent, resources.getString(R.string.share_chooser_text));

        List<ResolveInfo> resInfo = pm.queryIntentActivities(sendIntent, 0);
        List<LabeledIntent> intentList = new ArrayList<LabeledIntent>();
        for (int i = 0; i < resInfo.size(); i++) {
            // Extract the label, append it, and repackage it in a LabeledIntent
            ResolveInfo ri = resInfo.get(i);
            String packageName = ri.activityInfo.packageName;
            if (packageName.contains("android.email")) {
                emailIntent.setPackage(packageName);
            } else if (packageName.contains("twitter") || packageName.contains("facebook")
                    || packageName.contains("whatsapp") || packageName.contains("tencent.mm") || //wechat
                    packageName.contains("line") || packageName.contains("skype") || packageName.contains("viber")
                    || packageName.contains("kik") || packageName.contains("sgiggle") || //tango
                    packageName.contains("kakao") || packageName.contains("telegram")
                    || packageName.contains("nimbuzz") || packageName.contains("hike")
                    || packageName.contains("imoim") || packageName.contains("bbm")
                    || packageName.contains("threema") || packageName.contains("mms")
                    || packageName.contains("android.apps.messaging") || //google messenger
                    packageName.contains("android.talk") || //google hangouts
                    packageName.contains("android.gm")) {
                Intent intent = new Intent();
                intent.setComponent(new ComponentName(packageName, ri.activityInfo.name));
                intent.setAction(Intent.ACTION_SEND);
                intent.setType("text/plain");
                if (packageName.contains("twitter")) {
                    intent.putExtra(Intent.EXTRA_TEXT, twitterText);
                } else if (packageName.contains("facebook")) {
                    /*
                     * the warning below is wrong! at least on GS5, Facebook client does take
                     * our text, however it seems it takes only the first hyperlink in the
                     * text.
                     *
                     * Warning: Facebook IGNORES our text. They say "These fields are intended
                     * for users to express themselves. Pre-filling these fields erodes the
                     * authenticity of the user voice."
                     * One workaround is to use the Facebook SDK to post, but that doesn't
                     * allow the user to choose how they want to share. We can also make a
                     * custom landing page, and the link will show the <meta content ="...">
                     * text from that page with our link in Facebook.
                     */
                    intent.putExtra(Intent.EXTRA_TEXT, appLink);
                } else if (packageName.contains("tencent.mm")) //wechat
                {
                    /*
                     * wechat appears to do this similar to Facebook
                     */
                    intent.putExtra(Intent.EXTRA_TEXT, appLink);
                } else if (packageName.contains("android.gm")) {
                    // If Gmail shows up twice, try removing this else-if clause and the reference to "android.gm" above
                    intent.putExtra(Intent.EXTRA_SUBJECT, emailSubject);
                    intent.putExtra(Intent.EXTRA_TEXT, emailText);
                    //intent.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(resources.getString(R.string.share_email_gmail)));
                    intent.setType("message/rfc822");
                } else if (packageName.contains("android.apps.docs")) {
                    /*
                     * google drive - no reason to send link to it
                     */
                    continue;
                } else {
                    intent.putExtra(Intent.EXTRA_TEXT, emailText);
                }

                intentList.add(new LabeledIntent(intent, packageName, ri.loadLabel(pm), ri.icon));
            }
        }

        /*
         *  create "Copy Link To Clipboard" Intent
         */
        Intent clipboardIntent = new Intent(this, CopyToClipboardActivity.class);
        clipboardIntent.setData(Uri.parse(appLink));
        intentList.add(new LabeledIntent(clipboardIntent, getPackageName(),
                getResources().getString(R.string.clipboard_activity_name), R.drawable.ic_copy_link));

        // convert intentList to array
        LabeledIntent[] extraIntents = intentList.toArray(new LabeledIntent[intentList.size()]);

        openInChooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents);
        startActivity(openInChooser);
    }

    private void updateDualModePref() {
        SharedPreferences settings = getSharedPreferences(getString(R.string.settings_file), Context.MODE_PRIVATE);

        /*
         * here settings != null doesn't mean the file necessarily exist!
         */
        if (settings != null) {
            /*
             * disable hint for subsequent launches
             */
            SharedPreferences.Editor editor = settings.edit();
            editor.putBoolean(MainConsts.DUAL_MODE_ATTEMPTED_PREFS_KEY, true);
            editor.commit();
        }
    }

    private void updateShowInvitePref() {
        SharedPreferences settings = getSharedPreferences(getString(R.string.settings_file), Context.MODE_PRIVATE);

        /*
         * here settings != null doesn't mean the file necessarily exist!
         */
        if (settings != null) {
            /*
             * disable hint for subsequent launches
             */
            SharedPreferences.Editor editor = settings.edit();
            editor.putBoolean(MainConsts.DONT_SHOW_INVITE_PREFS_KEY, true);
            editor.commit();
        }
    }

    /**
     * Checks whether the device currently has a network connection.
     * @return true if the device has a network connection, false otherwise.
     */
    private boolean isDeviceOnline() {
        ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
        return (networkInfo != null && networkInfo.isConnected());
    }

    /**
     * c.f. "http://stackoverflow.com/questions/9286822/how-to-force-use-of-overflow-
     * menu-on-devices-with-menu-button"
     */
    private void forceOverflowButton() {
        try {
            ViewConfiguration config = ViewConfiguration.get(this);
            Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
            if (menuKeyField != null) {
                menuKeyField.setAccessible(true);
                menuKeyField.setBoolean(config, false);
            }
        } catch (Exception ex) {
            // Ignore
        }
    }

    /**
     * Signs in to the application with an account. Notify App Engine of regId
     *
     * @param overrideCurrent {@code true} if user can choose an account even if
     *            already signed in, {@code false} if the user can choose an
     *            account only if there is no currently signed in user
     */
    public void chooseAccount(boolean overrideCurrent) {
        if (Consts.IS_AUTH_ENABLED) {
            String accountName = getSharedPreferences(Consts.PREF_KEY_CLOUD_BACKEND, Context.MODE_PRIVATE)
                    .getString(Consts.PREF_KEY_ACCOUNT_NAME, null);
            if (accountName == null || overrideCurrent) {
                super.startActivityForResult(mAimfireService.getCredential().newChooseAccountIntent(),
                        ActivityCode.ACCOUNT_PICKER.getValue());
                return;
            }
        }
    }

    /*
     * check if optional feature Gyroscope (use-feature required = false) is present 
     * on the device, and warn if it is not.
     */
    private void checkGyroscope() {
        PackageManager pm = getPackageManager();

        if (!pm.hasSystemFeature(PackageManager.FEATURE_SENSOR_GYROSCOPE)) {
            // warn the user
            AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this,
                    R.style.AppCompatAlertDialogStyle);
            alertDialogBuilder.setTitle(R.string.warning);
            alertDialogBuilder.setMessage(R.string.warning_device_no_gyro);

            alertDialogBuilder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            });

            AlertDialog alertDialog = alertDialogBuilder.create();
            alertDialog.show();
        }
    }

    /*
     * check if old format cvr is left from previous versions. if so, notify
     * the user that we will make a backup. also need to delete drive_record
     */
    private void checkOldCvr() {
        if (MediaScanner.oldCvrExists()) {
            FileUtils.backupStorage();
            clearDriveFileRecord();

            // warn the user
            AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this,
                    R.style.AppCompatAlertDialogStyle);
            alertDialogBuilder.setTitle(R.string.warning);
            alertDialogBuilder.setMessage(
                    getString(R.string.warning_old_cvr_found) + MainConsts.MEDIA_3D_ROOT_DIR.getPath() + ".bak");

            alertDialogBuilder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            });

            AlertDialog alertDialog = alertDialogBuilder.create();
            alertDialog.show();
        }
    }

    private void clearDriveFileRecord() {
        SharedPreferences driveFileRecord = getSharedPreferences(getString(R.string.drive_file_record),
                Context.MODE_PRIVATE);

        if (driveFileRecord != null) {
            SharedPreferences.Editor editor = driveFileRecord.edit();
            editor.clear();
            editor.commit();
        }
    }

    private void displayVersion() {
        try {
            ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), 0);
            ZipFile zf = new ZipFile(ai.sourceDir);
            ZipEntry ze = zf.getEntry("classes.dex");
            long time = ze.getTime();
            String s = SimpleDateFormat.getInstance().format(new java.util.Date(time));
            zf.close();

            if (BuildConfig.DEBUG)
                printDebugMsg("App creation time: " + s + "\n");

        } catch (Exception e) {
        }
    }

    private void checkPreferences() {
        SharedPreferences settings = getSharedPreferences(getString(R.string.settings_file), Context.MODE_PRIVATE);

        if (settings != null) {
            /*
             * whether we show introduction at app startup
             */
            mShowIntro = settings.getBoolean(MainConsts.SHOW_INTRO_PREFS_KEY, true);

            /*
             * TODO: survey activity crashes because it uses attributes as color which is not
             * allowed before API level 21 (see here: http://stackoverflow.com/questions/27986204/
             * cant-convert-to-color-type-0x2-error-when-inflating-layout-in-fragment-but-onl).
             * temporarily disallow survey activity for API < 21 before we find a fix
             */
            if (!sLaunchCountInc && (Build.VERSION.SDK_INT >= 21)) {
                sLaunchCountInc = true;
                mLaunchCount = settings.getInt(MainConsts.LAUNCH_COUNT_PREFS_KEY, -1);
                mLaunchCount++;

                if ((mLaunchCount == 0) && !isFirstInstall()) {
                    /*
                     * show survey if user just upgraded (from a version that doesn't have the
                     * survey) and launch for the first time
                     */
                    mShowSurvey = true;
                } else {
                    /*
                     * show survey if
                     * 1. this app is launched PROMPT_SURVEY_LAUNCH_COUNT times. or
                     * 2. we have prompted before, and user asks to prompt later, and launch times
                     * modulo PROMPT_SURVEY_LAUNCH_COUNT is 0
                     */
                    boolean canShowSurvey = settings.getBoolean(MainConsts.SHOW_SURVEY_PREFS_KEY, true);
                    if (canShowSurvey && (mLaunchCount % MainConsts.PROMPT_SURVEY_LAUNCH_COUNT == 0)) {
                        mShowSurvey = true;
                    }
                }
            }

            /*
             * we show intro after first install. write to registry so we don't do it again.
             *
            * Offline Mode = true - we will use P2P for everything
            * Offline Mode = false - we will use cloud for initial discovery and messaging, and
            * P2P for file transfer
            *
            * set DEMO_MODE_PREFS_KEY here, for other activities and AimfireService to pick up.
             * to test non-offline mode, set DEMO_MODE_PREFS_KEY below to false
             */
            SharedPreferences.Editor editor = settings.edit();
            editor.putBoolean(MainConsts.SHOW_INTRO_PREFS_KEY, false);
            editor.putBoolean(MainConsts.DEMO_MODE_PREFS_KEY, true);
            editor.putInt(MainConsts.LAUNCH_COUNT_PREFS_KEY, mLaunchCount);
            editor.commit();

            if (mShowIntro) {
                return;
            }

            int latestCode = settings.getInt(MainConsts.LATEST_VERSION_CODE_KEY, -1);

            int currCode = -1;
            PackageInfo pInfo;
            try {
                pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
                currCode = pInfo.versionCode;
            } catch (NameNotFoundException e) {
                if (BuildConfig.DEBUG)
                    Log.e(TAG, "parseVerTxt: couldn't get current version" + e.getMessage());
                return;
            }

            if (latestCode > currCode) {
                mUpgradeAvailable = true;
            }
        }
    }

    private boolean isFirstInstall() {
        try {
            long firstInstallTime = getPackageManager().getPackageInfo(getPackageName(), 0).firstInstallTime;
            long lastUpdateTime = getPackageManager().getPackageInfo(getPackageName(), 0).lastUpdateTime;
            return firstInstallTime == lastUpdateTime;
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
    }

    private void notifyUpgrade() {
        // warn the user
        AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this, R.style.AppCompatAlertDialogStyle);
        alertDialogBuilder.setTitle(R.string.notice);
        alertDialogBuilder.setMessage(R.string.warning_new_version_available);

        alertDialogBuilder.setPositiveButton(R.string.upgrade, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                final String appPackageName = getPackageName(); // getPackageName() from Context or Activity object
                try {
                    startActivity(
                            new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName)));
                } catch (android.content.ActivityNotFoundException anfe) {
                    startActivity(new Intent(Intent.ACTION_VIEW,
                            Uri.parse("https://play.google.com/store/apps/details?id=" + appPackageName)));
                }
            }
        });

        alertDialogBuilder.setNegativeButton(R.string.later, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                //do nothing
            }
        });

        AlertDialog alertDialog = alertDialogBuilder.create();
        alertDialog.show();
    }

    public int getScreenOrientation() {
        int rotation = getWindowManager().getDefaultDisplay().getRotation();
        int orientation = getResources().getConfiguration().orientation;
        if (orientation == Configuration.ORIENTATION_PORTRAIT) {
            if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_270) {
                return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
            } else {
                return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
            }
        }
        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
            if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) {
                return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
            } else {
                return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
            }
        }
        return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
    }

    /**
     * print debug message to on-screen console
     */
    public void printDebugMsg(String s) {
        if (BuildConfig.DEBUG)
            Log.d(TAG, s);

        // no longer has a on-screen console
    }
}