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