1 /* 2 * Copyright (C) 2012 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 17 package com.android.mail.ui; 18 19 import android.app.ActionBar; 20 import android.app.Activity; 21 import android.app.Fragment; 22 import android.app.FragmentTransaction; 23 import android.appwidget.AppWidgetManager; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.Intent; 27 import android.database.DataSetObservable; 28 import android.database.DataSetObserver; 29 import android.os.Bundle; 30 import android.view.DragEvent; 31 import android.view.View; 32 import android.view.View.OnClickListener; 33 import android.widget.Button; 34 import android.widget.ListView; 35 36 import com.android.mail.R; 37 import com.android.mail.providers.Account; 38 import com.android.mail.providers.Folder; 39 import com.android.mail.providers.FolderWatcher; 40 import com.android.mail.utils.LogTag; 41 import com.android.mail.utils.LogUtils; 42 import com.android.mail.utils.Observable; 43 import com.android.mail.utils.Utils; 44 import com.android.mail.utils.VeiledAddressMatcher; 45 import com.android.mail.widget.WidgetProvider; 46 47 import java.util.ArrayList; 48 49 /** 50 * This activity displays the list of available folders for the current account. 51 */ 52 public class FolderSelectionActivity extends Activity implements OnClickListener, 53 DialogInterface.OnClickListener, FolderChangeListener, ControllableActivity, 54 FolderSelector { 55 public static final String EXTRA_ACCOUNT_SHORTCUT = "account-shortcut"; 56 57 private static final String LOG_TAG = LogTag.getLogTag(); 58 59 private static final int CONFIGURE = 0; 60 61 private static final int VIEW = 1; 62 63 private Account mAccount; 64 private Folder mSelectedFolder; 65 private boolean mConfigureShortcut; 66 protected boolean mConfigureWidget; 67 private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; 68 private int mMode = -1; 69 /** Empty placeholder for communicating to the consumer of the drawer observer. */ 70 private final DataSetObservable mDrawerObservers = new Observable("Drawer"); 71 72 private final AccountController mAccountController = new AccountController() { 73 @Override 74 public void registerAccountObserver(DataSetObserver observer) { 75 // Do nothing 76 } 77 78 @Override 79 public void unregisterAccountObserver(DataSetObserver observer) { 80 // Do nothing 81 } 82 83 @Override 84 public Account getAccount() { 85 return mAccount; 86 } 87 88 @Override 89 public void registerAllAccountObserver(DataSetObserver observer) { 90 // Do nothing 91 } 92 93 @Override 94 public void unregisterAllAccountObserver(DataSetObserver observer) { 95 // Do nothing 96 } 97 98 @Override 99 public Account[] getAllAccounts() { 100 return new Account[]{mAccount}; 101 } 102 103 @Override 104 public VeiledAddressMatcher getVeiledAddressMatcher() { 105 return null; 106 } 107 108 @Override 109 public void changeAccount(Account account) { 110 // Never gets called, so do nothing here. 111 LogUtils.wtf(LOG_TAG, 112 "FolderSelectionActivity.changeAccount() called when NOT expected."); 113 } 114 115 @Override 116 public void switchToDefaultInboxOrChangeAccount(Account account) { 117 // Never gets called, so do nothing here. 118 LogUtils.wtf(LOG_TAG,"FolderSelectionActivity.switchToDefaultInboxOrChangeAccount() " + 119 "called when NOT expected."); 120 } 121 122 @Override 123 public void registerDrawerClosedObserver(final DataSetObserver observer) { 124 mDrawerObservers.registerObserver(observer); 125 } 126 127 @Override 128 public void unregisterDrawerClosedObserver(final DataSetObserver observer) { 129 mDrawerObservers.unregisterObserver(observer); 130 } 131 132 /** 133 * Since there is no drawer to wait for, notifyChanged to the observers. 134 */ 135 @Override 136 public void closeDrawer(final boolean hasNewFolderOrAccount, 137 Account account, Folder folder) { 138 mDrawerObservers.notifyChanged(); 139 } 140 141 @Override 142 public void setFolderWatcher(FolderWatcher watcher) { 143 // Unsupported. 144 } 145 146 @Override 147 public boolean isDrawerPullEnabled() { 148 // Unsupported 149 return false; 150 } 151 152 @Override 153 public int getFolderListViewChoiceMode() { 154 return ListView.CHOICE_MODE_NONE; 155 } 156 }; 157 158 @Override 159 public void onCreate(Bundle icicle) { 160 super.onCreate(icicle); 161 162 setContentView(R.layout.folders_activity); 163 164 final Intent intent = getIntent(); 165 final String action = intent.getAction(); 166 mConfigureShortcut = Intent.ACTION_CREATE_SHORTCUT.equals(action); 167 mConfigureWidget = AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action); 168 if (!mConfigureShortcut && !mConfigureWidget) { 169 LogUtils.wtf(LOG_TAG, "unexpected intent: %s", intent); 170 } 171 if (mConfigureShortcut || mConfigureWidget) { 172 ActionBar actionBar = getActionBar(); 173 if (actionBar != null) { 174 actionBar.setIcon(R.mipmap.ic_launcher_shortcut_folder); 175 } 176 mMode = CONFIGURE; 177 } else { 178 mMode = VIEW; 179 } 180 181 if (mConfigureWidget) { 182 mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 183 AppWidgetManager.INVALID_APPWIDGET_ID); 184 if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { 185 LogUtils.wtf(LOG_TAG, "invalid widgetId"); 186 } 187 } 188 189 mAccount = intent.getParcelableExtra(EXTRA_ACCOUNT_SHORTCUT); 190 final Button firstButton = (Button) findViewById(R.id.first_button); 191 firstButton.setVisibility(View.VISIBLE); 192 // TODO(mindyp) disable the manage folders buttons until we have a manage folders screen. 193 if (mMode == VIEW) { 194 firstButton.setEnabled(false); 195 } 196 firstButton.setOnClickListener(this); 197 createFolderListFragment(FolderListFragment.ofTopLevelTree(mAccount.folderListUri, 198 getExcludedFolderTypes())); 199 } 200 201 /** 202 * Create a Fragment showing this folder and its children. 203 */ 204 private void createFolderListFragment(Fragment fragment) { 205 final FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); 206 fragmentTransaction.replace(R.id.content_pane, fragment); 207 fragmentTransaction.commitAllowingStateLoss(); 208 } 209 210 /** 211 * Gets an {@link ArrayList} of canonical names of any folders to exclude from displaying. 212 * By default, this list is empty. 213 * 214 * @return An {@link ArrayList} of folder canonical names 215 */ 216 protected ArrayList<Integer> getExcludedFolderTypes() { 217 return new ArrayList<Integer>(); 218 } 219 220 @Override 221 protected void onResume() { 222 super.onResume(); 223 224 // TODO: (mindyp) Make sure we're operating on the same account as 225 // before. If the user switched accounts, switch back. 226 } 227 228 @Override 229 public void onClick(View v) { 230 final int id = v.getId(); 231 if (id == R.id.first_button) { 232 if (mMode == CONFIGURE) { 233 doCancel(); 234 } else { 235 // TODO (mindyp): open manage folders screen. 236 } 237 } 238 } 239 240 private void doCancel() { 241 setResult(RESULT_CANCELED); 242 finish(); 243 } 244 245 /** 246 * Create a widget for the specified account and folder 247 */ 248 protected void createWidget(int id, Account account, Folder selectedFolder) { 249 WidgetProvider.updateWidget(this, id, account, selectedFolder.type, 250 selectedFolder.folderUri.fullUri, selectedFolder.conversationListUri, 251 selectedFolder.name); 252 final Intent result = new Intent(); 253 result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id); 254 setResult(RESULT_OK, result); 255 finish(); 256 } 257 258 @Override 259 public void onClick(DialogInterface dialog, int which) { 260 if (which == DialogInterface.BUTTON_POSITIVE) { 261 // The only dialog that is 262 createWidget(mAppWidgetId, mAccount, mSelectedFolder); 263 } else { 264 doCancel(); 265 } 266 } 267 268 @Override 269 public void onFolderChanged(Folder folder, final boolean force) { 270 if (!folder.equals(mSelectedFolder)) { 271 mSelectedFolder = folder; 272 Intent resultIntent = new Intent(); 273 274 if (mConfigureShortcut) { 275 /* 276 * Create the shortcut Intent based on it with the additional 277 * information that we have in this activity: name of the 278 * account, calculate the human readable name of the folder and 279 * use it as the shortcut name, etc... 280 */ 281 final Intent clickIntent = Utils.createViewFolderIntent(this, 282 mSelectedFolder.folderUri.fullUri, mAccount); 283 resultIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, clickIntent); 284 resultIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, 285 Intent.ShortcutIconResource.fromContext(this, 286 R.mipmap.ic_launcher_shortcut_folder)); 287 /** 288 * Note: Email1 created shortcuts using R.mipmap#ic_launcher_email 289 * so don't delete that resource until we have an upgrade/migration solution 290 */ 291 292 final CharSequence humanFolderName = mSelectedFolder.name; 293 294 resultIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, humanFolderName); 295 296 // Now ask the user what name they want for this shortcut. Pass 297 // the 298 // shortcut intent that we just created, the user can modify the 299 // folder in 300 // ShortcutNameActivity. 301 final Intent shortcutNameIntent = new Intent(this, ShortcutNameActivity.class); 302 shortcutNameIntent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY 303 | Intent.FLAG_ACTIVITY_FORWARD_RESULT); 304 shortcutNameIntent.putExtra(ShortcutNameActivity.EXTRA_FOLDER_CLICK_INTENT, 305 resultIntent); 306 shortcutNameIntent.putExtra(ShortcutNameActivity.EXTRA_SHORTCUT_NAME, 307 humanFolderName); 308 309 startActivity(shortcutNameIntent); 310 finish(); 311 } else if (mConfigureWidget) { 312 createWidget(mAppWidgetId, mAccount, mSelectedFolder); 313 } 314 } 315 } 316 317 @Override 318 public String getHelpContext() { 319 // TODO Auto-generated method stub 320 return null; 321 } 322 323 @Override 324 public Context getActivityContext() { 325 return this; 326 } 327 328 @Override 329 public ViewMode getViewMode() { 330 return null; 331 } 332 333 @Override 334 public ConversationListCallbacks getListHandler() { 335 return null; 336 } 337 338 @Override 339 public FolderChangeListener getFolderChangeListener() { 340 return this; 341 } 342 343 @Override 344 public ConversationSelectionSet getSelectedSet() { 345 return null; 346 } 347 348 private Folder mNavigatedFolder; 349 @Override 350 public void onFolderSelected(Folder folder) { 351 if (folder.hasChildren && !folder.equals(mNavigatedFolder)) { 352 mNavigatedFolder = folder; 353 // Replace this fragment with a new FolderListFragment 354 // showing this folder's children if we are not already looking 355 // at the child view for this folder. 356 createFolderListFragment(FolderListFragment.ofTree(folder)); 357 return; 358 } 359 onFolderChanged(folder, false /* force */); 360 } 361 362 @Override 363 public FolderSelector getFolderSelector() { 364 return this; 365 } 366 367 @Override 368 public boolean supportsDrag(DragEvent event, Folder folder) { 369 return false; 370 } 371 372 @Override 373 public void handleDrop(DragEvent event, Folder folder) { 374 // Do nothing. 375 } 376 377 @Override 378 public void onUndoAvailable(ToastBarOperation undoOp) { 379 // Do nothing. 380 } 381 382 @Override 383 public Folder getHierarchyFolder() { 384 return null; 385 } 386 387 @Override 388 public ConversationUpdater getConversationUpdater() { 389 return null; 390 } 391 392 @Override 393 public ErrorListener getErrorListener() { 394 return null; 395 } 396 397 @Override 398 public void setPendingToastOperation(ToastBarOperation op) { 399 // Do nothing. 400 } 401 402 @Override 403 public ToastBarOperation getPendingToastOperation() { 404 return null; 405 } 406 407 @Override 408 public FolderController getFolderController() { 409 return null; 410 } 411 412 @Override 413 public void onAnimationEnd(AnimatedAdapter animatedAdapter) { 414 } 415 416 @Override 417 public AccountController getAccountController() { 418 return mAccountController; 419 } 420 421 @Override 422 public void onFooterViewErrorActionClick(Folder folder, int errorStatus) { 423 // Unsupported 424 } 425 426 @Override 427 public void onFooterViewLoadMoreClick(Folder folder) { 428 // Unsupported 429 } 430 431 @Override 432 public void startDragMode() { 433 // Unsupported 434 } 435 436 @Override 437 public void stopDragMode() { 438 // Unsupported 439 } 440 441 @Override 442 public RecentFolderController getRecentFolderController() { 443 // Unsupported 444 return null; 445 } 446 447 @Override 448 public UpOrBackController getUpOrBackController() { 449 // Unsupported 450 return null; 451 } 452 453 @Override 454 public boolean isAccessibilityEnabled() { 455 // Unsupported 456 return true; 457 } 458 459 @Override 460 public ConversationListHelper getConversationListHelper() { 461 // Unsupported 462 return null; 463 } 464 465 @Override 466 public FragmentLauncher getFragmentLauncher() { 467 // Unsupported 468 return null; 469 } 470 } 471