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