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 android.support.v7.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