UIControllerBase.java revision 147e41d00aed3eac469567c4c7f50616ef3df994
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.email.activity; 18 19import com.android.email.Email; 20import com.android.email.R; 21import com.android.email.RefreshManager; 22import com.android.email.activity.setup.AccountSettings; 23import com.android.emailcommon.Logging; 24import com.android.emailcommon.provider.EmailContent.Account; 25import com.android.emailcommon.provider.EmailContent.Message; 26import com.android.emailcommon.provider.Mailbox; 27import com.android.emailcommon.utility.EmailAsyncTask; 28 29import android.app.Fragment; 30import android.app.FragmentManager; 31import android.app.FragmentTransaction; 32import android.content.Context; 33import android.os.Bundle; 34import android.util.Log; 35import android.view.Menu; 36import android.view.MenuInflater; 37import android.view.MenuItem; 38 39import java.util.ArrayList; 40 41/** 42 * Base class for the UI controller. 43 * 44 * Note: Always use {@link #commitFragmentTransaction} and {@link #popBackStack} to operate fragment 45 * transactions. 46 * (Currently we use synchronous transactions only, but we may want to switch back to asynchronous 47 * later.) 48 */ 49abstract class UIControllerBase { 50 protected static final String BUNDLE_KEY_ACCOUNT_ID = "UIController.state.account_id"; 51 protected static final String BUNDLE_KEY_MAILBOX_ID = "UIController.state.mailbox_id"; 52 protected static final String BUNDLE_KEY_MESSAGE_ID = "UIController.state.message_id"; 53 54 /** The owner activity */ 55 final EmailActivity mActivity; 56 57 final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker(); 58 59 final RefreshManager mRefreshManager; 60 61 /** 62 * List of fragments that are restored by the framework while the activity is being re-created 63 * for configuration changes (e.g. screen rotation). We'll install them later when the activity 64 * is created in {@link #installRestoredFragments()}. 65 */ 66 private final ArrayList<Fragment> mRestoredFragments = new ArrayList<Fragment>(); 67 68 /** 69 * Whether fragment installation should be hold. 70 * We hold installing fragments until {@link #installRestoredFragments()} is called. 71 */ 72 private boolean mHoldFragmentInstallation = true; 73 74 private final RefreshManager.Listener mRefreshListener 75 = new RefreshManager.Listener() { 76 @Override 77 public void onMessagingError(final long accountId, long mailboxId, final String message) { 78 updateRefreshProgress(); 79 } 80 81 @Override 82 public void onRefreshStatusChanged(long accountId, long mailboxId) { 83 updateRefreshProgress(); 84 } 85 }; 86 87 public UIControllerBase(EmailActivity activity) { 88 mActivity = activity; 89 mRefreshManager = RefreshManager.getInstance(mActivity); 90 } 91 92 /** @return the layout ID for the activity. */ 93 public abstract int getLayoutId(); 94 95 /** 96 * @return true if the UI controller currently can install fragments. 97 */ 98 protected final boolean isFragmentInstallable() { 99 return !mHoldFragmentInstallation; 100 } 101 102 /** 103 * Must be called just after the activity sets up the content view. Used to initialize views. 104 * 105 * (Due to the complexity regarding class/activity initialization order, we can't do this in 106 * the constructor.) 107 */ 108 public void onActivityViewReady() { 109 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 110 Log.d(Logging.LOG_TAG, this + " onActivityViewReady"); 111 } 112 } 113 114 /** 115 * Called at the end of {@link EmailActivity#onCreate}. 116 */ 117 public void onActivityCreated() { 118 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 119 Log.d(Logging.LOG_TAG, this + " onActivityCreated"); 120 } 121 mRefreshManager.registerListener(mRefreshListener); 122 } 123 124 /** 125 * Handles the {@link android.app.Activity#onStart} callback. 126 */ 127 public void onActivityStart() { 128 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 129 Log.d(Logging.LOG_TAG, this + " onActivityStart"); 130 } 131 } 132 133 /** 134 * Handles the {@link android.app.Activity#onResume} callback. 135 */ 136 public void onActivityResume() { 137 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 138 Log.d(Logging.LOG_TAG, this + " onActivityResume"); 139 } 140 } 141 142 /** 143 * Handles the {@link android.app.Activity#onPause} callback. 144 */ 145 public void onActivityPause() { 146 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 147 Log.d(Logging.LOG_TAG, this + " onActivityPause"); 148 } 149 } 150 151 /** 152 * Handles the {@link android.app.Activity#onStop} callback. 153 */ 154 public void onActivityStop() { 155 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 156 Log.d(Logging.LOG_TAG, this + " onActivityStop"); 157 } 158 } 159 160 /** 161 * Handles the {@link android.app.Activity#onDestroy} callback. 162 */ 163 public void onActivityDestroy() { 164 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 165 Log.d(Logging.LOG_TAG, this + " onActivityDestroy"); 166 } 167 mHoldFragmentInstallation = true; // No more fragment installation. 168 mRefreshManager.unregisterListener(mRefreshListener); 169 mTaskTracker.cancellAllInterrupt(); 170 } 171 172 /** 173 * Install all the fragments kept in {@link #mRestoredFragments}. 174 * 175 * Must be called at the end of {@link EmailActivity#onCreate}. 176 */ 177 public final void installRestoredFragments() { 178 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 179 Log.d(Logging.LOG_TAG, this + " installRestoredFragments"); 180 } 181 182 mHoldFragmentInstallation = false; 183 184 // Install all the fragments restored by the framework. 185 for (Fragment fragment : mRestoredFragments) { 186 installFragment(fragment); 187 } 188 mRestoredFragments.clear(); 189 } 190 191 /** 192 * Handles the {@link android.app.Activity#onSaveInstanceState} callback. 193 */ 194 public void onSaveInstanceState(Bundle outState) { 195 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 196 Log.d(Logging.LOG_TAG, this + " onSaveInstanceState"); 197 } 198 } 199 200 /** 201 * Handles the {@link android.app.Activity#onRestoreInstanceState} callback. 202 */ 203 public void restoreInstanceState(Bundle savedInstanceState) { 204 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 205 Log.d(Logging.LOG_TAG, this + " restoreInstanceState"); 206 } 207 } 208 209 /** 210 * Handles the {@link android.app.Activity#onAttachFragment} callback. 211 * 212 * If the activity has already been created, we initialize the fragment here. Otherwise we 213 * keep the fragment in {@link #mRestoredFragments} and initialize it after the activity's 214 * onCreate. 215 */ 216 public final void onAttachFragment(Fragment fragment) { 217 if (mHoldFragmentInstallation) { 218 // Fragment being restored by the framework during the activity recreation. 219 mRestoredFragments.add(fragment); 220 return; 221 } 222 installFragment(fragment); 223 } 224 225 private void installFragment(Fragment fragment) { 226 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 227 Log.d(Logging.LOG_TAG, this + " installFragment fragment=" + fragment); 228 } 229 if (fragment instanceof MailboxListFragment) { 230 installMailboxListFragment((MailboxListFragment) fragment); 231 } else if (fragment instanceof MessageListFragment) { 232 installMessageListFragment((MessageListFragment) fragment); 233 } else if (fragment instanceof MessageViewFragment) { 234 installMessageViewFragment((MessageViewFragment) fragment); 235 } else { 236 // Ignore -- uninteresting fragments such as dialogs. 237 } 238 } 239 240 protected abstract void installMailboxListFragment(MailboxListFragment fragment); 241 242 protected abstract void installMessageListFragment(MessageListFragment fragment); 243 244 protected abstract void installMessageViewFragment(MessageViewFragment fragment); 245 246 // not used 247 protected final void popBackStack(FragmentManager fm, String name, int flags) { 248 fm.popBackStackImmediate(name, flags); 249 } 250 251 protected final void commitFragmentTransaction(FragmentTransaction ft) { 252 ft.commit(); 253 mActivity.getFragmentManager().executePendingTransactions(); 254 } 255 256 /** 257 * @return the currently selected account ID, *or* {@link Account#ACCOUNT_ID_COMBINED_VIEW}. 258 * 259 * @see #getActualAccountId() 260 */ 261 public abstract long getUIAccountId(); 262 263 /** 264 * @return true if an account is selected, or the current view is the combined view. 265 */ 266 public final boolean isAccountSelected() { 267 return getUIAccountId() != Account.NO_ACCOUNT; 268 } 269 270 /** 271 * @return if an actual account is selected. (i.e. {@link Account#ACCOUNT_ID_COMBINED_VIEW} 272 * is not considered "actual".s) 273 */ 274 public final boolean isActualAccountSelected() { 275 return isAccountSelected() && (getUIAccountId() != Account.ACCOUNT_ID_COMBINED_VIEW); 276 } 277 278 /** 279 * @return the currently selected account ID. If the current view is the combined view, 280 * it'll return {@link Account#NO_ACCOUNT}. 281 * 282 * @see #getUIAccountId() 283 */ 284 public final long getActualAccountId() { 285 return isActualAccountSelected() ? getUIAccountId() : Account.NO_ACCOUNT; 286 } 287 288 /** 289 * Show the default view for the given account. 290 * 291 * No-op if the given account is already selected. 292 * 293 * @param accountId ID of the account to load. Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}. 294 * Must never be {@link Account#NO_ACCOUNT}. 295 */ 296 public final void switchAccount(long accountId) { 297 if (accountId == getUIAccountId()) { 298 // Do nothing if the account is already selected. Not even going back to the inbox. 299 return; 300 } 301 openAccount(accountId); 302 } 303 304 /** 305 * Shortcut for {@link #open} with {@link Mailbox#NO_MAILBOX} and {@link Message#NO_MESSAGE}. 306 */ 307 protected final void openAccount(long accountId) { 308 open(accountId, Mailbox.NO_MAILBOX, Message.NO_MESSAGE); 309 } 310 311 /** 312 * Shortcut for {@link #open} with {@link Message#NO_MESSAGE}. 313 */ 314 protected final void openMailbox(long accountId, long mailboxId) { 315 open(accountId, mailboxId, Message.NO_MESSAGE); 316 } 317 318 /** 319 * Loads the given account and optionally selects the given mailbox and message. Used to open 320 * a particular view at a request from outside of the activity, such as the widget. 321 * 322 * @param accountId ID of the account to load. Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}. 323 * Must never be {@link Account#NO_ACCOUNT}. 324 * @param mailboxId ID of the mailbox to load. If {@link Mailbox#NO_MAILBOX}, 325 * load the account's inbox. 326 * @param messageId ID of the message to load. If {@link Message#NO_MESSAGE}, 327 * do not open a message. 328 */ 329 public abstract void open(long accountId, long mailboxId, long messageId); 330 331 /** 332 * Performs the back action. 333 * 334 * @param isSystemBackKey <code>true</code> if the system back key was pressed. 335 * <code>false</code> if it's caused by the "home" icon click on the action bar. 336 */ 337 public abstract boolean onBackPressed(boolean isSystemBackKey); 338 339 /** 340 * Handles the {@link android.app.Activity#onCreateOptionsMenu} callback. 341 */ 342 public boolean onCreateOptionsMenu(MenuInflater inflater, Menu menu) { 343 inflater.inflate(R.menu.email_activity_options, menu); 344 return true; 345 } 346 347 /** 348 * Handles the {@link android.app.Activity#onPrepareOptionsMenu} callback. 349 */ 350 public boolean onPrepareOptionsMenu(MenuInflater inflater, Menu menu) { 351 352 // Update the refresh button. 353 MenuItem item = menu.findItem(R.id.refresh); 354 if (isRefreshEnabled()) { 355 item.setVisible(true); 356 if (isRefreshInProgress()) { 357 item.setActionView(R.layout.action_bar_indeterminate_progress); 358 } else { 359 item.setActionView(null); 360 } 361 } else { 362 item.setVisible(false); 363 } 364 return true; 365 } 366 367 /** 368 * Handles the {@link android.app.Activity#onOptionsItemSelected} callback. 369 * 370 * @return true if the option item is handled. 371 */ 372 public boolean onOptionsItemSelected(MenuItem item) { 373 switch (item.getItemId()) { 374 case android.R.id.home: 375 // Comes from the action bar when the app icon on the left is pressed. 376 // It works like a back press, but it won't close the activity. 377 return onBackPressed(false); 378 case R.id.compose: 379 return onCompose(); 380 case R.id.refresh: 381 onRefresh(); 382 return true; 383 case R.id.account_settings: 384 return onAccountSettings(); 385 } 386 return false; 387 } 388 389 /** 390 * Opens the message compose activity. 391 */ 392 private boolean onCompose() { 393 if (!isAccountSelected()) { 394 return false; // this shouldn't really happen 395 } 396 MessageCompose.actionCompose(mActivity, getActualAccountId()); 397 return true; 398 } 399 400 /** 401 * Handles the "Settings" option item. Opens the settings activity. 402 */ 403 private boolean onAccountSettings() { 404 AccountSettings.actionSettings(mActivity, getActualAccountId()); 405 return true; 406 } 407 408 /** 409 * STOPSHIP For experimental UI. Remove this. 410 * 411 * @return mailbox ID which we search for messages. 412 */ 413 public abstract long getSearchMailboxId(); 414 415 /** 416 * STOPSHIP For experimental UI. Remove this. 417 * 418 * @return mailbox ID for "mailbox settings" option. 419 */ 420 public abstract long getMailboxSettingsMailboxId(); 421 422 /** 423 * STOPSHIP For experimental UI. Make it abstract protected. 424 * 425 * Performs "refesh". 426 */ 427 public abstract void onRefresh(); 428 429 /** 430 * @return true if refresh is in progress for the current mailbox. 431 */ 432 protected abstract boolean isRefreshInProgress(); 433 434 /** 435 * @return true if the UI should enable the "refresh" command. 436 */ 437 protected abstract boolean isRefreshEnabled(); 438 439 440 /** 441 * Start/stop the "refresh" animation on the action bar according to the current refresh state. 442 * 443 * (We start the animation if {@link #isRefreshInProgress} returns true, 444 * and stop otherwise.) 445 */ 446 protected void updateRefreshProgress() { 447 mActivity.invalidateOptionsMenu(); 448 } 449} 450