1 /******************************************************************************* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 *******************************************************************************/ 17 18 package com.android.mail.ui; 19 20 import android.app.Dialog; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.res.Configuration; 24 import android.nfc.NdefMessage; 25 import android.nfc.NdefRecord; 26 import android.nfc.NfcAdapter; 27 import android.nfc.NfcEvent; 28 import android.os.Bundle; 29 import android.view.DragEvent; 30 import android.view.KeyEvent; 31 import android.view.Menu; 32 import android.view.MenuItem; 33 import android.view.MotionEvent; 34 import android.view.View; 35 import android.view.accessibility.AccessibilityManager; 36 37 import com.android.mail.compose.ComposeActivity; 38 import com.android.mail.providers.Folder; 39 import com.android.mail.utils.StorageLowState; 40 import com.android.mail.utils.Utils; 41 42 import java.io.UnsupportedEncodingException; 43 import java.net.URLEncoder; 44 45 /** 46 * This is the root activity container that holds the left navigation fragment 47 * (usually a list of folders), and the main content fragment (either a 48 * conversation list or a conversation view). 49 */ 50 public class MailActivity extends AbstractMailActivity implements ControllableActivity { 51 /** 52 * The activity controller to which we delegate most Activity lifecycle events. 53 */ 54 private ActivityController mController; 55 56 private ViewMode mViewMode; 57 58 private ToastBarOperation mPendingToastOp; 59 private boolean mAccessibilityEnabled; 60 private AccessibilityManager mAccessibilityManager; 61 62 protected ConversationListHelper mConversationListHelper; 63 64 /** 65 * The account name currently in use. Used to construct the NFC mailto: message. This needs 66 * to be static since the {@link ComposeActivity} needs to statically change the account name 67 * and have the NFC message changed accordingly. 68 */ 69 protected static String sAccountName = null; 70 71 /** 72 * Create an NFC message (in the NDEF: Nfc Data Exchange Format) to instruct the recepient to 73 * send an email to the current account. 74 */ 75 private static class NdefMessageMaker implements NfcAdapter.CreateNdefMessageCallback { 76 @Override 77 public NdefMessage createNdefMessage(NfcEvent event) { 78 if (sAccountName == null) { 79 return null; 80 } 81 return getMailtoNdef(sAccountName); 82 } 83 84 /** 85 * Returns an NDEF message with a single mailto URI record 86 * for the given email address. 87 */ 88 private static NdefMessage getMailtoNdef(String account) { 89 byte[] accountBytes; 90 try { 91 accountBytes = URLEncoder.encode(account, "UTF-8").getBytes("UTF-8"); 92 } catch (UnsupportedEncodingException e) { 93 accountBytes = account.getBytes(); 94 } 95 byte prefix = 0x06; // mailto: 96 byte[] recordBytes = new byte[accountBytes.length + 1]; 97 recordBytes[0] = prefix; 98 System.arraycopy(accountBytes, 0, recordBytes, 1, accountBytes.length); 99 NdefRecord mailto = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, 100 new byte[0], recordBytes); 101 return new NdefMessage(new NdefRecord[] { mailto }); 102 } 103 } 104 105 private final NdefMessageMaker mNdefHandler = new NdefMessageMaker(); 106 107 public MailActivity() { 108 super(); 109 mConversationListHelper = new ConversationListHelper(); 110 } 111 112 @Override 113 public boolean dispatchTouchEvent(MotionEvent ev) { 114 mController.onTouchEvent(ev); 115 return super.dispatchTouchEvent(ev); 116 } 117 118 /** 119 * Default implementation returns a null view mode. 120 */ 121 @Override 122 public ViewMode getViewMode() { 123 return mViewMode; 124 } 125 126 @Override 127 public void onActivityResult(int requestCode, int resultCode, Intent data) { 128 mController.onActivityResult(requestCode, resultCode, data); 129 } 130 131 @Override 132 public void onBackPressed() { 133 if (!mController.onBackPressed()) { 134 super.onBackPressed(); 135 } 136 } 137 138 @Override 139 public void onCreate(Bundle savedState) { 140 super.onCreate(savedState); 141 142 mViewMode = new ViewMode(); 143 final boolean tabletUi = Utils.useTabletUI(this.getResources()); 144 mController = ControllerFactory.forActivity(this, mViewMode, tabletUi); 145 mController.onCreate(savedState); 146 147 mAccessibilityManager = 148 (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE); 149 mAccessibilityEnabled = mAccessibilityManager.isEnabled(); 150 final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); 151 if (nfcAdapter != null) { 152 nfcAdapter.setNdefPushMessageCallback(mNdefHandler, this); 153 } 154 } 155 156 @Override 157 protected void onPostCreate(Bundle savedInstanceState) { 158 super.onPostCreate(savedInstanceState); 159 160 mController.onPostCreate(savedInstanceState); 161 } 162 163 @Override 164 public void onConfigurationChanged(Configuration newConfig) { 165 super.onConfigurationChanged(newConfig); 166 mController.onConfigurationChanged(newConfig); 167 } 168 169 @Override 170 protected void onRestart() { 171 super.onRestart(); 172 mController.onRestart(); 173 } 174 175 /** 176 * Constructs and sets the default NFC message. This message instructs the receiver to send 177 * email to the account provided as the argument. This message is to be shared with 178 * "zero-clicks" using NFC. The message will be available as long as the current activity is in 179 * the foreground. 180 * 181 * @param account The email address to send mail to. 182 */ 183 public static void setNfcMessage(String account) { 184 sAccountName = account; 185 } 186 187 @Override 188 protected void onRestoreInstanceState(Bundle savedInstanceState) { 189 super.onRestoreInstanceState(savedInstanceState); 190 mController.onRestoreInstanceState(savedInstanceState); 191 } 192 193 @Override 194 public Dialog onCreateDialog(int id, Bundle bundle) { 195 final Dialog dialog = mController.onCreateDialog(id, bundle); 196 return dialog == null ? super.onCreateDialog(id, bundle) : dialog; 197 } 198 199 @Override 200 public boolean onCreateOptionsMenu(Menu menu) { 201 return mController.onCreateOptionsMenu(menu) || super.onCreateOptionsMenu(menu); 202 } 203 204 @Override 205 public boolean onKeyDown(int keyCode, KeyEvent event) { 206 return mController.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); 207 } 208 209 @Override 210 public boolean onOptionsItemSelected(MenuItem item) { 211 return mController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item); 212 } 213 214 @Override 215 public void onPause() { 216 super.onPause(); 217 mController.onPause(); 218 } 219 220 @Override 221 public void onPrepareDialog(int id, Dialog dialog, Bundle bundle) { 222 super.onPrepareDialog(id, dialog, bundle); 223 mController.onPrepareDialog(id, dialog, bundle); 224 } 225 226 @Override 227 public boolean onPrepareOptionsMenu(Menu menu) { 228 mController.onPrepareOptionsMenu(menu); 229 return super.onPrepareOptionsMenu(menu); 230 } 231 232 @Override 233 public void onResume() { 234 super.onResume(); 235 mController.onResume(); 236 final boolean enabled = mAccessibilityManager.isEnabled(); 237 if (enabled != mAccessibilityEnabled) { 238 onAccessibilityStateChanged(enabled); 239 } 240 // App has resumed, re-check the top-level storage situation. 241 StorageLowState.checkStorageLowMode(this); 242 } 243 244 @Override 245 public void onSaveInstanceState(Bundle outState) { 246 super.onSaveInstanceState(outState); 247 mController.onSaveInstanceState(outState); 248 } 249 250 @Override 251 protected void onStart() { 252 super.onStart(); 253 mController.onStart(); 254 } 255 256 @Override 257 public boolean onSearchRequested() { 258 mController.startSearch(); 259 return true; 260 } 261 262 @Override 263 public void onStop() { 264 super.onStop(); 265 mController.onStop(); 266 } 267 268 @Override 269 protected void onDestroy() { 270 super.onDestroy(); 271 mController.onDestroy(); 272 } 273 274 @Override 275 public void onWindowFocusChanged(boolean hasFocus) { 276 super.onWindowFocusChanged(hasFocus); 277 mController.onWindowFocusChanged(hasFocus); 278 } 279 280 @Override 281 public String toString() { 282 final StringBuilder sb = new StringBuilder(super.toString()); 283 sb.append("{ViewMode="); 284 sb.append(mViewMode); 285 sb.append(" controller="); 286 sb.append(mController); 287 sb.append("}"); 288 return sb.toString(); 289 } 290 291 @Override 292 public ConversationListCallbacks getListHandler() { 293 return mController; 294 } 295 296 @Override 297 public FolderChangeListener getFolderChangeListener() { 298 return mController; 299 } 300 301 @Override 302 public FolderSelector getFolderSelector() { 303 return mController; 304 } 305 306 @Override 307 public FolderController getFolderController() { 308 return mController; 309 } 310 311 @Override 312 public ConversationSelectionSet getSelectedSet() { 313 return mController.getSelectedSet(); 314 } 315 316 @Override 317 public boolean supportsDrag(DragEvent event, Folder folder) { 318 return mController.supportsDrag(event, folder); 319 } 320 321 @Override 322 public void handleDrop(DragEvent event, Folder folder) { 323 mController.handleDrop(event, folder); 324 } 325 326 @Override 327 public void onUndoAvailable(ToastBarOperation undoOp) { 328 mController.onUndoAvailable(undoOp); 329 } 330 331 @Override 332 public Folder getHierarchyFolder() { 333 return mController.getHierarchyFolder(); 334 } 335 336 @Override 337 public ConversationUpdater getConversationUpdater() { 338 return mController; 339 } 340 341 @Override 342 public ErrorListener getErrorListener() { 343 return mController; 344 } 345 346 @Override 347 public void setPendingToastOperation(ToastBarOperation op) { 348 mPendingToastOp = op; 349 } 350 351 @Override 352 public ToastBarOperation getPendingToastOperation() { 353 return mPendingToastOp; 354 } 355 356 /** 357 * Sole purpose of this method is to stop clicks where we don't want them in 358 * the action bar. This can be hooked as a listener to view with 359 * android:onClick. 360 * 361 * @param v 362 */ 363 public void doNothingClickHandler(View v) { 364 // Do nothing. 365 } 366 367 @Override 368 public void onAnimationEnd(AnimatedAdapter animatedAdapter) { 369 mController.onAnimationEnd(animatedAdapter); 370 } 371 372 @Override 373 public AccountController getAccountController() { 374 return mController; 375 } 376 377 @Override 378 public RecentFolderController getRecentFolderController() { 379 return mController; 380 } 381 382 @Override 383 public UpOrBackController getUpOrBackController() { 384 return mController; 385 } 386 387 @Override 388 public void onFooterViewErrorActionClick(Folder folder, int errorStatus) { 389 mController.onFooterViewErrorActionClick(folder, errorStatus); 390 } 391 392 @Override 393 public void onFooterViewLoadMoreClick(Folder folder) { 394 mController.onFooterViewLoadMoreClick(folder); 395 } 396 397 @Override 398 public void startDragMode() { 399 mController.startDragMode(); 400 } 401 402 @Override 403 public void stopDragMode() { 404 mController.stopDragMode(); 405 } 406 407 @Override 408 public boolean isAccessibilityEnabled() { 409 return mAccessibilityEnabled; 410 } 411 412 public void onAccessibilityStateChanged(boolean enabled) { 413 mAccessibilityEnabled = enabled; 414 mController.onAccessibilityStateChanged(); 415 } 416 417 @Override 418 public final ConversationListHelper getConversationListHelper() { 419 return mConversationListHelper; 420 } 421 422 @Override 423 public FragmentLauncher getFragmentLauncher() { 424 return mController; 425 } 426 } 427