/* * Copyright (C) 2010 The Android Open Source Project * * 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. */ package com.android.email.activity; import android.app.Activity; import android.app.Fragment; import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.os.Handler; import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import com.android.email.Controller; import com.android.email.ControllerResultUiThreadWrapper; import com.android.email.Email; import com.android.email.MessageListContext; import com.android.email.MessagingExceptionStrings; import com.android.email.R; import com.android.emailcommon.Logging; import com.android.emailcommon.mail.MessagingException; import com.android.emailcommon.provider.Account; import com.android.emailcommon.provider.EmailContent.Message; import com.android.emailcommon.provider.Mailbox; import com.android.emailcommon.utility.EmailAsyncTask; import com.android.emailcommon.utility.IntentUtilities; import com.google.common.base.Preconditions; import java.util.ArrayList; /** * The main Email activity, which is used on both the tablet and the phone. * * Because this activity is device agnostic, so most of the UI aren't owned by this, but by * the UIController. */ public class EmailActivity extends Activity implements View.OnClickListener, FragmentInstallable { public static final String EXTRA_ACCOUNT_ID = "ACCOUNT_ID"; public static final String EXTRA_MAILBOX_ID = "MAILBOX_ID"; public static final String EXTRA_MESSAGE_ID = "MESSAGE_ID"; public static final String EXTRA_QUERY_STRING = "QUERY_STRING"; /** Loader IDs starting with this is safe to use from UIControllers. */ static final int UI_CONTROLLER_LOADER_ID_BASE = 100; /** Loader IDs starting with this is safe to use from ActionBarController. */ static final int ACTION_BAR_CONTROLLER_LOADER_ID_BASE = 200; private static float sLastFontScale = -1; private Controller mController; private Controller.Result mControllerResult; private UIControllerBase mUIController; private final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker(); /** Banner to display errors */ private BannerController mErrorBanner; /** Id of the account that had a messaging exception most recently. */ private long mLastErrorAccountId; /** * Create an intent to launch and open account's inbox. * * @param accountId If -1, default account will be used. */ public static Intent createOpenAccountIntent(Activity fromActivity, long accountId) { Intent i = IntentUtilities.createRestartAppIntent(fromActivity, EmailActivity.class); if (accountId != -1) { i.putExtra(EXTRA_ACCOUNT_ID, accountId); } return i; } /** * Create an intent to launch and open a mailbox. * * @param accountId must not be -1. * @param mailboxId must not be -1. Magic mailboxes IDs (such as * {@link Mailbox#QUERY_ALL_INBOXES}) don't work. */ public static Intent createOpenMailboxIntent(Activity fromActivity, long accountId, long mailboxId) { if (accountId == -1 || mailboxId == -1) { throw new IllegalArgumentException(); } Intent i = IntentUtilities.createRestartAppIntent(fromActivity, EmailActivity.class); i.putExtra(EXTRA_ACCOUNT_ID, accountId); i.putExtra(EXTRA_MAILBOX_ID, mailboxId); return i; } /** * Create an intent to launch and open a message. * * @param accountId must not be -1. * @param mailboxId must not be -1. Magic mailboxes IDs (such as * {@link Mailbox#QUERY_ALL_INBOXES}) don't work. * @param messageId must not be -1. */ public static Intent createOpenMessageIntent(Activity fromActivity, long accountId, long mailboxId, long messageId) { if (accountId == -1 || mailboxId == -1 || messageId == -1) { throw new IllegalArgumentException(); } Intent i = IntentUtilities.createRestartAppIntent(fromActivity, EmailActivity.class); i.putExtra(EXTRA_ACCOUNT_ID, accountId); i.putExtra(EXTRA_MAILBOX_ID, mailboxId); i.putExtra(EXTRA_MESSAGE_ID, messageId); return i; } /** * Create an intent to launch search activity. * * @param accountId ID of the account for the mailbox. Must not be {@link Account#NO_ACCOUNT}. * @param mailboxId ID of the mailbox to search, or {@link Mailbox#NO_MAILBOX} to perform * global search. * @param query query string. */ public static Intent createSearchIntent(Activity fromActivity, long accountId, long mailboxId, String query) { Preconditions.checkArgument(Account.isNormalAccount(accountId), "Can only search in normal accounts"); // Note that a search doesn't use a restart intent, as we want another instance of // the activity to sit on the stack for search. Intent i = new Intent(fromActivity, EmailActivity.class); i.putExtra(EXTRA_ACCOUNT_ID, accountId); i.putExtra(EXTRA_MAILBOX_ID, mailboxId); i.putExtra(EXTRA_QUERY_STRING, query); i.setAction(Intent.ACTION_SEARCH); return i; } /** * Initialize {@link #mUIController}. */ private void initUIController() { mUIController = UiUtilities.useTwoPane(this) ? new UIControllerTwoPane(this) : new UIControllerOnePane(this); } @Override protected void onCreate(Bundle savedInstanceState) { if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onCreate"); float fontScale = getResources().getConfiguration().fontScale; if (sLastFontScale != -1 && sLastFontScale != fontScale) { // If the font scale has been initialized, and has been detected to be different than // the last time the Activity ran, it means the user changed the font while no // Email Activity was running - we still need to purge static information though. onFontScaleChangeDetected(); } sLastFontScale = fontScale; // UIController is used in onPrepareOptionsMenu(), which can be called from within // super.onCreate(), so we need to initialize it here. initUIController(); super.onCreate(savedInstanceState); ActivityHelper.debugSetWindowFlags(this); setContentView(mUIController.getLayoutId()); mUIController.onActivityViewReady(); mController = Controller.getInstance(this); mControllerResult = new ControllerResultUiThreadWrapper<ControllerResult>(new Handler(), new ControllerResult()); mController.addResultCallback(mControllerResult); // Set up views // TODO Probably better to extract mErrorMessageView related code into a separate class, // so that it'll be easy to reuse for the phone activities. TextView errorMessage = (TextView) findViewById(R.id.error_message); errorMessage.setOnClickListener(this); int errorBannerHeight = getResources().getDimensionPixelSize(R.dimen.error_message_height); mErrorBanner = new BannerController(this, errorMessage, errorBannerHeight); if (savedInstanceState != null) { mUIController.onRestoreInstanceState(savedInstanceState); } else { final Intent intent = getIntent(); final MessageListContext viewContext = MessageListContext.forIntent(this, intent); if (viewContext == null) { // This might happen if accounts were deleted on another thread, and there aren't // any remaining Welcome.actionStart(this); finish(); return; } else { final long messageId = intent.getLongExtra(EXTRA_MESSAGE_ID, Message.NO_MESSAGE); mUIController.open(viewContext, messageId); } } mUIController.onActivityCreated(); } @Override protected void onSaveInstanceState(Bundle outState) { if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, this + " onSaveInstanceState"); } super.onSaveInstanceState(outState); mUIController.onSaveInstanceState(outState); } // FragmentInstallable @Override public void onInstallFragment(Fragment fragment) { if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, this + " onInstallFragment fragment=" + fragment); } mUIController.onInstallFragment(fragment); } // FragmentInstallable @Override public void onUninstallFragment(Fragment fragment) { if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, this + " onUninstallFragment fragment=" + fragment); } mUIController.onUninstallFragment(fragment); } @Override protected void onStart() { if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onStart"); super.onStart(); mUIController.onActivityStart(); } @Override protected void onResume() { if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onResume"); super.onResume(); mUIController.onActivityResume(); /** * In {@link MessageList#onResume()}, we go back to {@link Welcome} if an account * has been added/removed. We don't need to do that here, because we fetch the most * up-to-date account list. Additionally, we detect and do the right thing if all * of the accounts have been removed. */ } @Override protected void onPause() { if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onPause"); super.onPause(); mUIController.onActivityPause(); } @Override protected void onStop() { if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onStop"); super.onStop(); mUIController.onActivityStop(); } @Override protected void onDestroy() { if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onDestroy"); mController.removeResultCallback(mControllerResult); mTaskTracker.cancellAllInterrupt(); mUIController.onActivityDestroy(); super.onDestroy(); } @Override public void onBackPressed() { if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, this + " onBackPressed"); } if (!mUIController.onBackPressed(true)) { // Not handled by UIController -- perform the default. i.e. close the app. super.onBackPressed(); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.error_message: dismissErrorMessage(); break; } } /** * Force dismiss the error banner. */ private void dismissErrorMessage() { mErrorBanner.dismiss(); } @Override public boolean onCreateOptionsMenu(Menu menu) { return mUIController.onCreateOptionsMenu(getMenuInflater(), menu); } @Override public boolean onPrepareOptionsMenu(Menu menu) { return mUIController.onPrepareOptionsMenu(getMenuInflater(), menu); } /** * Called when the search key is pressd. * * Use the below command to emulate the key press on devices without the search key. * adb shell input keyevent 84 */ @Override public boolean onSearchRequested() { if (Email.DEBUG) { Log.d(Logging.LOG_TAG, this + " onSearchRequested"); } mUIController.onSearchRequested(); return true; // Event handled. } @Override @SuppressWarnings("deprecation") public boolean onOptionsItemSelected(MenuItem item) { if (mUIController.onOptionsItemSelected(item)) { return true; } return super.onOptionsItemSelected(item); } /** * A {@link Controller.Result} to detect connection status. */ private class ControllerResult extends Controller.Result { @Override public void sendMailCallback( MessagingException result, long accountId, long messageId, int progress) { handleError(result, accountId, progress); } @Override public void serviceCheckMailCallback( MessagingException result, long accountId, long mailboxId, int progress, long tag) { handleError(result, accountId, progress); } @Override public void updateMailboxCallback(MessagingException result, long accountId, long mailboxId, int progress, int numNewMessages, ArrayList<Long> addedMessages) { handleError(result, accountId, progress); } @Override public void updateMailboxListCallback( MessagingException result, long accountId, int progress) { handleError(result, accountId, progress); } @Override public void loadAttachmentCallback(MessagingException result, long accountId, long messageId, long attachmentId, int progress) { handleError(result, accountId, progress); } @Override public void loadMessageForViewCallback(MessagingException result, long accountId, long messageId, int progress) { handleError(result, accountId, progress); } private void handleError(final MessagingException result, final long accountId, int progress) { if (accountId == -1) { return; } if (result == null) { if (progress > 0) { // Connection now working; clear the error message banner if (mLastErrorAccountId == accountId) { dismissErrorMessage(); } } } else { Account account = Account.restoreAccountWithId(EmailActivity.this, accountId); if (account == null) return; String message = MessagingExceptionStrings.getErrorString(EmailActivity.this, result); if (!TextUtils.isEmpty(account.mDisplayName)) { // TODO Use properly designed layout. Don't just concatenate strings; // which is generally poor for I18N. message = message + " (" + account.mDisplayName + ")"; } if (mErrorBanner.show(message)) { mLastErrorAccountId = accountId; } } } } /** * Handle a change to the system font size. This invalidates some static caches we have. */ private void onFontScaleChangeDetected() { MessageListItem.resetDrawingCaches(); } }