Java tutorial
/* * Copyright (c) 2017. Nuvolect LLC * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * Contact legal@nuvolect.com for a less restrictive commercial license if you would like to use the * software without the GPLv3 restrictions. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, * see <http://www.gnu.org/licenses/>. * */ package com.nuvolect.securesuite.main; import android.annotation.TargetApi; import android.app.ActionBar; import android.app.ActionBar.OnNavigationListener; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentTransaction; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.preference.PreferenceManager; import android.support.v4.app.FragmentActivity; import android.support.v4.content.ContextCompat; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import com.nuvolect.securesuite.R; import com.nuvolect.securesuite.data.BackupRestore; import com.nuvolect.securesuite.data.ImportVcard; import com.nuvolect.securesuite.data.MyContacts; import com.nuvolect.securesuite.data.MyGroups; import com.nuvolect.securesuite.data.SqlCipher; import com.nuvolect.securesuite.license.LicenseManager; import com.nuvolect.securesuite.license.LicensePersist; import com.nuvolect.securesuite.license.LicenseUtil; import com.nuvolect.securesuite.util.ActionBarUtil; import com.nuvolect.securesuite.util.AppTheme; import com.nuvolect.securesuite.util.Cryp; import com.nuvolect.securesuite.util.DbPassphrase; import com.nuvolect.securesuite.util.FileBrowserDbRestore; import com.nuvolect.securesuite.util.LogUtil; import com.nuvolect.securesuite.util.LogUtil.LogType; import com.nuvolect.securesuite.util.PermissionUtil; import com.nuvolect.securesuite.util.Persist; import com.nuvolect.securesuite.util.UriUtil; import com.nuvolect.securesuite.util.Util; import com.nuvolect.securesuite.util.WorkerService; import com.nuvolect.securesuite.webserver.CrypServer; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; /** * An activity representing a list of Contacts. This activity has different * presentations for handset and tablet-size devices. On handsets, the activity * presents a list of items, which when touched, lead to a * {@link ContactDetailActivity} representing item details. On tablets, the * activity presents the list of items and item details side-by-side using two * vertical panes. * <p> * The activity makes heavy use of fragments. The list of items is a * {@link ContactListFragment} and the item details (if present) is a * {@link ContactDetailFragment}. * <p> * This activity also implements the required * {@link ContactListFragment.Callbacks} interface to listen for item selections. */ public class ContactListActivity extends FragmentActivity implements ContactListFragment.Callbacks, ContactEditFragment.Callbacks, ContactDetailFragment.Callbacks { private final static boolean DEBUG = LogUtil.DEBUG; public boolean mTwoPane; //Whether or not the activity is in two-pane mode, i.e. running on a tablet device. private ContactListFragment m_clf_fragment; private static Activity m_act; private static Context m_ctx; private ActionBar actionBar; private ArrayAdapter<CharSequence> adapter; private OnNavigationListener navigationListener; private Messenger mService = null; private boolean mIsBound; private long m_contact_id; private int m_theme; private Bundle m_savedInstanceState; private static String mNewDbPassphrase; private static String mNewDbPath; // IncomingHandler mHandler = new IncomingHandler( this ); private static Handler mainHandler = new Handler(); private String m_pendingImportSingleContactId; // @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (DEBUG) { String state = savedInstanceState == null ? "null" : "not null"; LogUtil.log("ContactListActivity onCreate savedInstanceState: " + state); } m_act = this; m_ctx = getApplicationContext(); m_savedInstanceState = savedInstanceState; mTwoPane = false; // Action bar progress setup. Needs to be called before setting the content view requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); m_theme = AppTheme.activateTheme(m_act); setContentView(R.layout.contact_list_activity); /** * Kick off the license manager. Among other tasks, the license manager captures * a license account. The license account is used as part of the database encryption key. * Consequently, do not initialize the database prior to capturing the license account. */ LicenseManager.getInstance(m_act).checkLicense(m_act, mLicenseManagerListener); } LicenseManager.LicenseCallbacks mLicenseManagerListener = new LicenseManager.LicenseCallbacks() { @Override public void licenseResult(LicenseManager.LicenseResult license) { if (DEBUG) LogUtil.log("License result: " + license.toString()); LicensePersist.setLicenseResult(m_ctx, license); switch (license) { case NIL: break; case REJECTED_TERMS: m_act.finish(); break; case WHITELIST_USER: case PREMIUM_USER: { SqlCipher.getInstance(m_ctx); if (LockActivity.lockDisabled || !LockActivity.lockCodePresent(m_ctx)) startGui(); else { Intent i = new Intent(getApplicationContext(), LockActivity.class); i.putExtra(CConst.VALIDATE_LOCK_CODE, Cryp.getLockCode(m_ctx)); startActivityForResult(i, CConst.VALIDATE_LOCK_CODE_ACTION); } break; } default: break; } if (CrypServer.isServerEnabled()) { CrypServer.enableServer(m_act, true); } } }; @Override protected void onResume() { super.onResume(); if (DEBUG) LogUtil.log("ContactListActivity onResume"); setProgressBarIndeterminateVisibility(Persist.getProgressBarActive(m_ctx)); m_theme = AppTheme.activateWhenChanged(m_act, m_theme); mTwoPane = findViewById(R.id.contact_detail_container) != null; if (actionBar != null) actionBar.setSelectedNavigationItem(Persist.getNavChoice(m_act)); if (m_clf_fragment == null) m_clf_fragment = (ContactListFragment) getFragmentManager() .findFragmentByTag(CConst.CONTACT_LIST_FRAGMENT_TAG); /* * If the import is running, restore the progress dialog. The dialog is required * to not only show progress, but to block the user from using the app until * the import is completed. */ if (Persist.getImportInProgress(m_act) > 0)//import_cloud CloudImportDialog.cloudImportProgressDialog(m_act); } @Override protected void onStart() { super.onStart(); // Start the communications framework. doBindService(); } @Override protected void onStop() { super.onStop(); // Stop the communications framework. doUnbindService(); } @Override protected void onPause() { super.onPause(); if (DEBUG) LogUtil.log("ContactListActivity onPause"); CloudImportDialog.dismissProgressDialog(m_act); } @Override public void onDestroy() { super.onDestroy(); if (DEBUG) LogUtil.log("ContactListActivity onDestroy"); CloudImportDialog.dismissProgressDialog(m_act); } @Override public void onBackPressed() { super.onBackPressed(); if (DEBUG) LogUtil.log("CLA.onBackPressed"); /** * When the user presses back button, force lock screen to appear * next time user starts the app. This also solves the problem * of presenting the lock screen when this activity is restarted * for internal reasons. */ // Make sure the database is present to solve odd java.lang.NullPointerException in sqlCipher.getCryp SqlCipher.getInstance(m_act); LockActivity.lockDisabled = false; if (LockActivity.lockCodePresent(m_act)) Toast.makeText(m_act, "Lock Enabled", Toast.LENGTH_SHORT).show(); finish(); } /** * Start the GUI, method assumes that a master account is already set */ private void startGui() { SqlCipher.getInstance(m_ctx); /** * Detect app upgrade and provide a placeholder for managing upgrades, database changes, etc. */ boolean appUpgraded = LicenseUtil.appUpgraded(m_act); if (appUpgraded) { Toast.makeText(getApplicationContext(), "Application upgraded", Toast.LENGTH_LONG).show(); // Execute upgrade methods } // Set default settings PreferenceManager.setDefaultValues(this, R.xml.settings, false); // Set the progress bar to off, otherwise some devices will default to on setProgressBarIndeterminateVisibility(false); // Load group data into memory, used for group titles and people counts MyGroups.loadGroupMemory(); boolean isFirstTime = Persist.isStartingUp(m_ctx); if (isFirstTime) { String account = LicensePersist.getLicenseAccount(m_ctx); Cryp.setCurrentAccount(account); MyGroups.addBaseGroupsToNewAccount(m_ctx, account); Cryp.setCurrentGroup(MyGroups.getDefaultGroup(account)); try { // Import a default contact when starting first time InputStream vcf = getResources().getAssets().open(CConst.APP_VCF); ImportVcard.importVcf(m_ctx, vcf, Cryp.getCurrentGroup()); } catch (IOException e) { LogUtil.logException(ContactListActivity.class, e); } // First time, request phone management access PermissionUtil.requestFirstTimePermissions(m_act); } // Support for action bar pull down menu adapter = ArrayAdapter.createFromResource(this, R.array.action_bar_spinner_menu, android.R.layout.simple_spinner_dropdown_item); // Action bar spinner menu callback navigationListener = new OnNavigationListener() { // List items from resource String[] navItems = getResources().getStringArray(R.array.action_bar_spinner_menu); @Override public boolean onNavigationItemSelected(int position, long id) { if (DEBUG) LogUtil.log("ContactListActivity NavigationItemSelected: " + navItems[position]); // Do stuff when navigation item is selected switch (CConst.NavMenu.values()[position]) { case contacts: { // Persist the navigation selection for fragments to pick up Persist.setNavChoice(m_act, position, navItems[position]); break; } case groups: { // Persist the navigation selection for fragments to pick up Persist.setNavChoice(m_act, position, navItems[position]); // Dispatch to the main group list activity Intent intent = new Intent(m_act, GroupListActivity.class); startActivity(intent); // Remove this activity from the stack // Group list is the only activity on the stack m_act.finish(); break; } case passwords: { actionBar.setSelectedNavigationItem(Persist.getNavChoice(m_act)); PasswordFragment f = PasswordFragment.newInstance(m_act); f.start(); break; } case calendar: { Intent intent = new Intent(m_act, CalendarActivity.class); startActivity(intent); break; } case finder: { Intent intent = new Intent(m_act, FinderActivity.class); startActivity(intent); break; } case server: { /** * Restore the spinner such that the Password is never persisted * and never shows. */ actionBar.setSelectedNavigationItem(Persist.getNavChoice(m_act)); ServerFragment f = ServerFragment.newInstance(m_act); f.start(); break; } default: } return true; } }; actionBar = getActionBar(); ActionBarUtil.setNavigationMode(actionBar, ActionBar.NAVIGATION_MODE_LIST); ActionBarUtil.setDisplayShowTitleEnabled(actionBar, false); ActionBarUtil.setListNavigationCallbacks(actionBar, adapter, navigationListener); AppTheme.applyActionBarTheme(m_act, actionBar); // Start with the previous contact or reset to a valid contact m_contact_id = Persist.getCurrentContactId(m_act); if (m_contact_id <= 0 || !SqlCipher.validContactId(m_contact_id)) { m_contact_id = SqlCipher.getFirstContactID(); Persist.setCurrentContactId(m_act, m_contact_id); } // savedInstanceState is non-null when there is fragment state // saved from previous configurations of this activity // (e.g. when rotating the screen from portrait to landscape). // In this case, the fragment will automatically be re-added // to its container so we don't need to manually add it. // For more information, see the Fragments API guide at: // // http://developer.android.com/guide/components/fragments.html // if (isFirstTime || m_savedInstanceState == null) { if (findViewById(R.id.contact_detail_container) != null) { // Setup for single or dual fragments depending on display size // The detail container view will be present only in the large-screen layouts // (res/values-large and res/values-sw600dp). If this view is present, then the // activity should be in two-pane mode. mTwoPane = true; m_clf_fragment = startContactListFragment(); // In two-pane mode, list items should be given the 'activated' state when touched. m_clf_fragment.setActivateOnItemClick(true); // In two-pane mode, show the detail view in this activity by // adding or replacing the detail fragment using a fragment transaction. startContactDetailFragment(); } else { mTwoPane = false; m_clf_fragment = startContactListFragment(); } } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu items for use in the action bar MenuInflater inflater = getMenuInflater(); if (mTwoPane) { Fragment editFrag = getFragmentManager().findFragmentByTag(CConst.CONTACT_EDIT_FRAGMENT_TAG); if (editFrag != null && editFrag.isVisible()) { inflater.inflate(R.menu.contact_list_contact_edit, menu); LogUtil.log("=============CLA.onOptionsMenu: contact_list_contact_edit"); } else { inflater.inflate(R.menu.contact_list_contact_detail, menu); LogUtil.log("=============CLA.onOptionsMenu: contact_list_contact_detail"); } } else { inflater.inflate(R.menu.contact_list_single_menu, menu); LogUtil.log("=============CLA.onOptionsMenu: contact_list_single_menu"); } if ((LicenseManager.mIsWhitelistUser || Boolean.valueOf(m_act.getString(R.string.verbose_logging))) && DeveloperDialog.isEnabled()) { Util.showMenu(menu, R.id.menu_developer); } return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { SharedMenu.POST_CMD post_cmd = SharedMenu.POST_CMD.NIL; switch (item.getItemId()) { // Handle presses on the action bar items case android.R.id.home: { if (DEBUG) LogUtil.log("CLA.onOptionItemSelect home button"); break; } case R.id.menu_restore_from_storage: { if (hasPermission(READ_EXTERNAL_STORAGE)) { ConfirmRestoreDatabaseDialogFragment dialog = new ConfirmRestoreDatabaseDialogFragment(); dialog.show(getFragmentManager(), "ConfirmRestoreTag"); } else { PermissionUtil.requestReadExternalStorage(m_act, CConst.RESTORE_CONTACTS_DATABASE); } return true; } default: post_cmd = SharedMenu.processCmd(m_act, item, m_contact_id, postCmdCallbacks); } doPostCommand(post_cmd); return super.onOptionsItemSelected(item); } private void doPostCommand(SharedMenu.POST_CMD post_cmd) { switch (post_cmd) { case ACT_RECREATE: m_act.recreate(); break; case DONE: break; case NIL: break; case REFRESH_LEFT_DEFAULT_RIGHT: startContactListFragment(); invalidateOptionsMenu(); if (mTwoPane) startContactDetailFragment(); break; case SETTINGS_FRAG: break; case START_CONTACT_DETAIL: startContactDetailFragment(); break; case START_CONTACT_EDIT: m_contact_id = Persist.getCurrentContactId(m_act); startContactEditFragment(); break; } } SharedMenu.PostCmdCallbacks postCmdCallbacks = new SharedMenu.PostCmdCallbacks() { @Override public void postCommand(SharedMenu.POST_CMD post_cmd) { doPostCommand(post_cmd); } }; @Override public void onLongPressContact(net.sqlcipher.Cursor cursor) { LongPressContact.longPress(m_act, cursor, longPressContactCallbacks); } LongPressContact.LongPressContactCallbacks longPressContactCallbacks = new LongPressContact.LongPressContactCallbacks() { public void postCommand(SharedMenu.POST_CMD post_cmd) { switch (post_cmd) { case ACT_RECREATE: m_act.recreate(); break; case REFRESH_LEFT_DEFAULT_RIGHT: startContactListFragment(); if (mTwoPane) startContactDetailFragment(); break; case START_CONTACT_EDIT: m_contact_id = Persist.getCurrentContactId(m_act); startContactEditFragment(); break; case NIL: case DONE: default: } } }; @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { LogUtil.log("CLA.onActivityResult() requestCode: " + requestCode); switch (requestCode) { case CConst.BROWSE_IMPORT_PHOTO_ACTION: { if (resultCode == RESULT_OK && data != null && data.getData() != null) { Uri _uri = data.getData(); String path = UriUtil.getPathFromUri(m_act, _uri); boolean fileExists = path != null && new File(path).exists(); boolean isFile = path != null && new File(path).isFile(); boolean goodToGo = fileExists && isFile; if (goodToGo) { ContactEditFragment fragment = (ContactEditFragment) getFragmentManager() .findFragmentByTag(CConst.CONTACT_EDIT_FRAGMENT_TAG); fragment.readPhoto(path); } else { Toast.makeText(m_act, "Image import failed", Toast.LENGTH_SHORT).show(); LogUtil.log(LogType.CONTACT_EDIT, "image path is null"); } } break; } case CConst.BROWSE_RESTORE_FOLDER_ACTION: { if (data == null) break; Bundle activityResultBundle = data.getExtras(); if (activityResultBundle == null) break; mNewDbPath = activityResultBundle.getString(CConst.RESTORE_BACKUP_PATH); RestoreDatabaseDialogFragment dialog = new RestoreDatabaseDialogFragment(); dialog.show(getFragmentManager(), "RestoreTag"); break; } case CConst.VALIDATE_LOCK_CODE_ACTION: { if (resultCode == RESULT_OK) startGui(); else finish(); break; } case CConst.VALIDATE_LOCK_CODE_TEST_ACTION: { if (resultCode == RESULT_OK) Toast.makeText(m_act, "Lock code validated", Toast.LENGTH_SHORT).show(); else Toast.makeText(m_act, "Lock code validation failed", Toast.LENGTH_SHORT).show(); break; } case CConst.CHANGE_LOCK_CODE_TEST_ACTION: { if (resultCode == RESULT_OK) { Bundle activityResultBundle = data.getExtras(); String lockCode = activityResultBundle.getString(CConst.CHANGE_LOCK_CODE); Cryp.setLockCode(m_act, lockCode); if (lockCode.isEmpty()) Toast.makeText(m_act, "Lock code cleared", Toast.LENGTH_SHORT).show(); else Toast.makeText(m_act, "Lock code changed", Toast.LENGTH_SHORT).show(); } else Toast.makeText(m_act, "Lock code change failed", Toast.LENGTH_SHORT).show(); break; } case CConst.NO_ACTION: break; default: /** * Manage request in common class for all activities */ SharedMenu.sharedOnActivityResult(m_act, requestCode, resultCode, data); break; } super.onActivityResult(requestCode, resultCode, data); } private boolean hasPermission(String perm) { return (ContextCompat.checkSelfPermission(this, perm) == PackageManager.PERMISSION_GRANTED); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case CConst.RESTORE_CONTACTS_DATABASE: { ConfirmRestoreDatabaseDialogFragment dialog = new ConfirmRestoreDatabaseDialogFragment(); dialog.show(getFragmentManager(), "ConfirmRestoreTag"); } default: SharedMenu.sharedOnRequestPermissionsResult(m_act, requestCode, permissions, grantResults); } } //FUTURE move to ShareDialog.java, integrated with ShareMenu and activity callbacks public static class ConfirmRestoreDatabaseDialogFragment extends DialogFragment { public ConfirmRestoreDatabaseDialogFragment() { } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Do you want to restore a database?"); builder.setMessage("Your current database will be lost. " + "You will be asked to select the database to restore " + "followed by a passphrase."); builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // Kickoff a browser activity here. // When user selects folder, onActivityResult called with the result. Intent intent = new Intent(); intent.setClass(m_act, FileBrowserDbRestore.class); getActivity().startActivityForResult(intent, CConst.BROWSE_RESTORE_FOLDER_ACTION); } }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { Toast.makeText(m_act, "Action cancelled", Toast.LENGTH_SHORT).show(); // User cancelled the dialog } }); // Create the AlertDialog object and return it return builder.create(); } } //FUTURE move to ShareDialog.java, integrated with ShareMenu and activity callbacks public static class RestoreDatabaseDialogFragment extends DialogFragment { private EditText m_passphraseEt; private AlertDialog.Builder builder; public RestoreDatabaseDialogFragment() { } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Please enter the passphrase"); m_passphraseEt = new EditText(m_act); builder.setView(m_passphraseEt); builder.setPositiveButton("Continue", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { //Do nothing here because we override this button later to change the close behavior. //However, we still need this because on older versions of Android unless we //pass a handler the button doesn't get instantiated } }); builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { Toast.makeText(m_act, "Action cancelled", Toast.LENGTH_SHORT).show(); // User cancelled the dialog } }); final AlertDialog dialog = builder.create(); dialog.show(); Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mNewDbPassphrase = m_passphraseEt.getText().toString(); if (mNewDbPassphrase.length() < 4) { Toast.makeText(m_act, "Passphrase minimum length is 4 characters", Toast.LENGTH_LONG) .show(); Toast.makeText(m_act, "No changes made", Toast.LENGTH_SHORT).show(); return; } File a = null; File b = null; try { // Put aside the current database files in case they need to be restored a = BackupRestore.renameDbTemp(m_ctx, SqlCipher.ACCOUNT_DB_NAME); b = BackupRestore.renameDbTemp(m_ctx, SqlCipher.DETAIL_DB_NAME); // Check if database is CrypSafe or SecureSuite app if (BackupRestore.testCrypSafeRestore(m_ctx, mNewDbPath)) { // Copy CrypSafe database files to the app BackupRestore.copyCrypSafeDbToApp(m_ctx, mNewDbPath, "crypsafe1_db"); BackupRestore.copyCrypSafeDbToApp(m_ctx, mNewDbPath, "crypsafe2_db"); } else { // Copy the database files to the app BackupRestore.copyDbToApp(m_ctx, mNewDbPath, SqlCipher.ACCOUNT_DB_NAME); BackupRestore.copyDbToApp(m_ctx, mNewDbPath, SqlCipher.DETAIL_DB_NAME); } } catch (IOException e) { LogUtil.logException(m_ctx, LogType.RESTORE_DB, e); Toast.makeText(m_act, "Database exception", Toast.LENGTH_SHORT).show(); } boolean success = SqlCipher.testPassphrase(m_ctx, mNewDbPassphrase); if (success) { LogUtil.log("Restore backup success"); //Save the passphrase, cleanup old database, inform user and restart DbPassphrase.setDbPassphrase(m_ctx, mNewDbPassphrase); BackupRestore.deleteDbTemp(m_ctx, a); BackupRestore.deleteDbTemp(m_ctx, b); Toast.makeText(m_act, "Restore successful", Toast.LENGTH_LONG).show(); Toast.makeText(m_act, "Restarting...", Toast.LENGTH_LONG).show(); Util.restartApplication(m_ctx); } else { LogUtil.log("Restore backup fail"); //Inform user of failure, restore database, let user try another passphrase Toast.makeText(m_act, "Passphrase or database invalid", Toast.LENGTH_LONG).show(); Toast.makeText(m_act, "Passphrase or database invalid", Toast.LENGTH_LONG).show(); BackupRestore.restoreDbTemp(m_ctx, a); BackupRestore.restoreDbTemp(m_ctx, b); } } }); return dialog; } } /** * Handler of incoming messages from service. */ static class IncomingHandler extends Handler { WeakReference<ContactListActivity> mContactListActivity; public IncomingHandler(ContactListActivity incomingHandler) { mContactListActivity = new WeakReference<ContactListActivity>(incomingHandler); } @Override public void handleMessage(Message msg) { if (mContactListActivity.get() == null) { ContactListActivity contactListActivity = new ContactListActivity(); contactListActivity._handleMessage(msg); } else mContactListActivity.get()._handleMessage(msg); super.handleMessage(msg); } } /** * This class and method receives message commands and the message handler * on a separate thread. You can enter messages from any thread. */ public void _handleMessage(Message msg) { Bundle bundle = msg.getData(); WorkerService.WorkTask cmd = WorkerService.WorkTask.values()[msg.what]; switch (cmd) { /* * Messages are sent from the server for each contact imported. */ case IMPORT_CLOUD_CONTACTS_UPDATE: {//import_cloud CloudImportDialog.updateProgress(m_act, bundle); break; } /* * A final message is sent when the import is complete. */ case IMPORT_CLOUD_CONTACTS_COMPLETE: { /** * Importing an account a second time sometimes hangs on account_db.beginTransaction(); * Yet when checked, the database is not in a transaction. */ String mainAccountImported = CloudImportDialog.getMainAccountImported(); if (!mainAccountImported.isEmpty()) { LogUtil.log("CLA _handleMessage importedAccount: " + mainAccountImported); // next line hangs Cryp.setCurrentAccount(mainAccountImported); int group = MyGroups.getDefaultGroup(mainAccountImported); Cryp.setCurrentGroup(group); long contactId = MyContacts.getFirstContactInGroup(group); Cryp.setCurrentContact(m_act, contactId); } CloudImportDialog.complete(m_act); break; } case REFRESH_USER_INTERFACE: { if (DEBUG) LogUtil.log(LogType.CLA, "" + cmd + bundle); if (bundle.getString(CConst.UI_TYPE_KEY).contentEquals(CConst.RECREATE)) { m_act.recreate();//FUTURE refresh specific fragments } else if (DEBUG) LogUtil.log(LogType.CLA, "UI_TYPE_KEY no match: " + bundle.getString(CConst.UI_TYPE_KEY)); } default: if (DEBUG) LogUtil.log(LogType.CLA, "_handleMessage default: " + cmd + " " + bundle); break; } } /** * Target we publish for clients to send messages to IncomingHandler. */ Messenger mMessenger = new Messenger(new IncomingHandler(null)); /** * Class for interacting with the main interface of WorkerService. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been // established, giving us the service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. mService = new Messenger(service); if (DEBUG) LogUtil.log(LogType.CLA, "onServiceConnected: " + className.getClassName()); // We want to monitor the service for as long as we are // connected to it. try { Message msg = Message.obtain(null, WorkerService.MSG_REGISTER_CLIENT); msg.replyTo = mMessenger; mService.send(msg); } catch (RemoteException e) { // In this case the service has crashed before we could even // do anything with it; we can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. } } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. mService = null; // As part of the sample, tell the user what happened. Toast.makeText(getApplicationContext(), "Service disconnected", Toast.LENGTH_SHORT).show(); } }; /** * Starts the communications framework. */ void doBindService() { // Establish a connection with the service. We use an explicit // class name because there is no reason to be able to let other // applications replace our component. bindService(new Intent(this, WorkerService.class), mConnection, Context.BIND_AUTO_CREATE); mIsBound = true; } /** * Stops the communications framework. */ void doUnbindService() { if (mIsBound) { // If we have received the service, and hence registered with // it, then now is the time to unregister. if (mService != null) { try { Message msg = Message.obtain(null, WorkerService.MSG_UNREGISTER_CLIENT); msg.replyTo = mMessenger; mService.send(msg); } catch (RemoteException e) { // There is nothing special we need to do if the service has crashed } } // Detach our existing connection. unbindService(mConnection); mIsBound = false; } } /** * Callback method from {@link ContactListFragment.Callbacks} indicating that * the item with the given ID was selected. */ @Override public void onContactSelected() { m_contact_id = Persist.getCurrentContactId(m_ctx); startContactDetailFragment(); } @Override public void onAccountSelected(String account, long first_contact_id) { if (mTwoPane) { m_contact_id = first_contact_id; Persist.setCurrentContactId(m_ctx, first_contact_id); startContactDetailFragment(); } } @Override public void onGroupSelected(int group_id, long first_contact_id) { if (mTwoPane && first_contact_id > 0) { m_contact_id = first_contact_id; Persist.setCurrentContactId(m_ctx, first_contact_id); startContactDetailFragment(); } } private void startContactEditFragment() { if (mTwoPane) { ContactEditFragment fragment = new ContactEditFragment(); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.contact_detail_container, fragment, CConst.CONTACT_EDIT_FRAGMENT_TAG); ft.commit(); } else { Intent i = new Intent(getApplicationContext(), ContactEditActivity.class); startActivity(i); } } private void startContactDetailFragment() { if (mTwoPane) { ContactDetailFragment frag = new ContactDetailFragment(); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.contact_detail_container, frag, CConst.CONTACT_DETAIL_FRAGMENT_TAG); ft.commit(); } else { Intent i = new Intent(getApplicationContext(), ContactDetailActivity.class); startActivity(i); } } /** * Called when the editor has finished with a contact. Show the detail of the contact. * Modifications may or may not have been made. */ @Override public void onEditContactFinish(boolean contactModified) { // Edit finished, restore contact detail fragment if (contactModified) { startContactListFragment(); startContactDetailFragment(); } else startContactDetailFragment(); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private ContactListFragment startContactListFragment() { boolean isDestroyed = false; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && m_act.isDestroyed()) isDestroyed = true; if (m_act == null || isDestroyed || m_act.isFinishing()) return null; ContactListFragment clf = new ContactListFragment(); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.contact_list_container, clf, CConst.CONTACT_LIST_FRAGMENT_TAG); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.addToBackStack(null); ft.commit(); return clf; } /** * No need to do anything. The group list will never be shown when this activity is running. * But all activities implementing ContactDetailFragment must implement it. */ @Override public void refreshGroupList() { } }