• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.email.activity.setup;
18 
19 import android.app.ActionBar;
20 import android.app.LoaderManager;
21 import android.content.ContentUris;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.CursorLoader;
25 import android.content.Intent;
26 import android.content.Loader;
27 import android.content.res.Resources;
28 import android.database.Cursor;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.preference.CheckBoxPreference;
32 import android.preference.ListPreference;
33 import android.preference.Preference;
34 import android.preference.Preference.OnPreferenceChangeListener;
35 import android.preference.PreferenceActivity;
36 import android.preference.PreferenceFragment;
37 import androidx.annotation.NonNull;
38 import android.text.TextUtils;
39 import android.view.MenuItem;
40 
41 import com.android.email.R;
42 import com.android.emailcommon.Logging;
43 import com.android.emailcommon.provider.Account;
44 import com.android.emailcommon.provider.EmailContent.AccountColumns;
45 import com.android.emailcommon.provider.EmailContent.MailboxColumns;
46 import com.android.emailcommon.provider.Mailbox;
47 import com.android.emailcommon.provider.Policy;
48 import com.android.emailcommon.utility.EmailAsyncTask;
49 import com.android.emailcommon.utility.Utility;
50 import com.android.mail.providers.Folder;
51 import com.android.mail.providers.UIProvider;
52 import com.android.mail.ui.MailAsyncTaskLoader;
53 import com.android.mail.utils.LogUtils;
54 import com.google.common.base.Preconditions;
55 
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.HashMap;
59 import java.util.List;
60 import java.util.Map;
61 
62 /**
63  * "Mailbox settings" activity.
64  *
65  * It's used to update per-mailbox sync settings.  It normally updates Mailbox settings, unless
66  * the target mailbox is Inbox, in which case it updates Account settings instead.
67  *
68  * All changes made by the user will not be immediately saved to the database, as changing the
69  * sync window may result in removal of messages.  Instead, we only save to the database in {@link
70  * #onDestroy()}, unless it's called for configuration changes.
71  */
72 public class MailboxSettings extends PreferenceActivity {
73     private static final String EXTRA_FOLDERS_URI = "FOLDERS_URI";
74     private static final String EXTRA_INBOX_ID = "INBOX_ID";
75 
76     private static final int FOLDERS_LOADER_ID = 0;
77     private Uri mFoldersUri;
78     private int mInboxId;
79     private final List<Folder> mFolders = new ArrayList<>();
80 
81     /**
82      * Starts the activity
83      */
getIntent(Context context, Uri foldersUri, Folder inbox)84     public static Intent getIntent(Context context, Uri foldersUri, Folder inbox) {
85         final Intent i = new Intent(context, MailboxSettings.class);
86         i.putExtra(EXTRA_FOLDERS_URI, foldersUri);
87         i.putExtra(EXTRA_INBOX_ID, inbox.id);
88         return i;
89     }
90 
91     @Override
onCreate(Bundle savedInstanceState)92     protected void onCreate(Bundle savedInstanceState) {
93         // This needs to happen before super.onCreate() since that calls onBuildHeaders()
94         mInboxId = getIntent().getIntExtra(EXTRA_INBOX_ID, -1);
95         mFoldersUri = getIntent().getParcelableExtra(EXTRA_FOLDERS_URI);
96 
97         if (mFoldersUri != null) {
98             getLoaderManager().initLoader(FOLDERS_LOADER_ID, null,
99                     new MailboxSettingsFolderLoaderCallbacks());
100         }
101 
102         super.onCreate(savedInstanceState);
103 
104         // Always show "app up" as we expect our parent to be an Email activity.
105         ActionBar actionBar = getActionBar();
106         if (actionBar != null) {
107             actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP);
108             // Hide the app icon.
109             actionBar.setIcon(android.R.color.transparent);
110             actionBar.setDisplayUseLogoEnabled(false);
111         }
112     }
113 
114     @Override
onBuildHeaders(List<Header> target)115     public void onBuildHeaders(List<Header> target) {
116         if (mFolders.isEmpty()) {
117             final Header dummy = new Header();
118             dummy.titleRes = R.string.mailbox_name_display_inbox;
119             dummy.fragment = MailboxSettingsFragment.class.getName();
120             dummy.fragmentArguments = MailboxSettingsFragment.getArguments(mInboxId);
121             target.add(dummy);
122         } else {
123             for (final Folder f : mFolders) {
124                 final Header h = new Header();
125                 if (!TextUtils.isEmpty(f.hierarchicalDesc)) {
126                     h.title = f.hierarchicalDesc;
127                 } else {
128                     h.title = f.name;
129                 }
130                 h.fragment = MailboxSettingsFragment.class.getName();
131                 h.fragmentArguments = MailboxSettingsFragment.getArguments(f.id);
132                 if (f.id == mInboxId) {
133                     target.add(0, h);
134                 } else {
135                     target.add(h);
136                 }
137             }
138         }
139     }
140 
141     @Override
isValidFragment(String fragmentName)142     protected boolean isValidFragment(String fragmentName) {
143         // Activity is not exported
144         return true;
145     }
146 
147     @Override
onOptionsItemSelected(MenuItem item)148     public boolean onOptionsItemSelected(MenuItem item) {
149         if (item.getItemId() == android.R.id.home) {
150             onBackPressed();
151             return true;
152         }
153         return super.onOptionsItemSelected(item);
154     }
155 
156     /**
157      * Setup the entries and entry values for the sync lookback preference
158      * @param context the caller's context
159      * @param pref a ListPreference to be set up
160      * @param maxLookback The maximum lookback allowed, or 0 if no max.
161      * @param showWithDefault Whether to show the version with default, or without.
162      */
setupLookbackPreferenceOptions(final Context context, final ListPreference pref, final int maxLookback, final boolean showWithDefault)163     public static void setupLookbackPreferenceOptions(final Context context,
164             final ListPreference pref, final int maxLookback, final boolean showWithDefault) {
165         final Resources resources = context.getResources();
166         // Load the complete list of entries/values
167         CharSequence[] entries;
168         CharSequence[] values;
169         final int offset;
170         if (showWithDefault) {
171             entries = resources.getTextArray(
172                     R.array.account_settings_mail_window_entries_with_default);
173             values = resources.getTextArray(
174                     R.array.account_settings_mail_window_values_with_default);
175             offset = 1;
176         } else {
177             entries = resources.getTextArray(R.array.account_settings_mail_window_entries);
178             values = resources.getTextArray(R.array.account_settings_mail_window_values);
179             offset = 0;
180         }
181         // If we have a maximum lookback policy, enforce it
182         if (maxLookback > 0) {
183             final int size = maxLookback + offset;
184             entries = Arrays.copyOf(entries, size);
185             values = Arrays.copyOf(values, size);
186         }
187         // Set up the preference
188         pref.setEntries(entries);
189         pref.setEntryValues(values);
190         pref.setSummary(pref.getEntry());
191     }
192 
193     private class MailboxSettingsFolderLoaderCallbacks
194             implements LoaderManager.LoaderCallbacks<Cursor> {
195 
196         @Override
onCreateLoader(int i, Bundle bundle)197         public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
198             return new CursorLoader(MailboxSettings.this, mFoldersUri,
199                     UIProvider.FOLDERS_PROJECTION, null, null, null);
200         }
201 
202         @Override
onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor)203         public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
204             if (cursor == null) {
205                 return;
206             }
207             mFolders.clear();
208 
209             while(cursor.moveToNext()) {
210                 final Folder folder = new Folder(cursor);
211                 if (!folder.supportsCapability(UIProvider.FolderCapabilities.IS_VIRTUAL) &&
212                         !folder.isTrash() && !folder.isDraft() && !folder.isOutbox()) {
213                     mFolders.add(folder);
214                 }
215             }
216 
217             invalidateHeaders();
218         }
219 
220         @Override
onLoaderReset(Loader<Cursor> cursorLoader)221         public void onLoaderReset(Loader<Cursor> cursorLoader) {
222             mFolders.clear();
223         }
224     }
225 
226     public static class MailboxSettingsFragment extends PreferenceFragment {
227         private static final String EXTRA_MAILBOX_ID = "MailboxId";
228 
229         private static final String BUNDLE_MAILBOX = "MailboxSettings.mailbox";
230         private static final String BUNDLE_MAX_LOOKBACK = "MailboxSettings.maxLookback";
231         private static final String BUNDLE_SYNC_ENABLED_VALUE = "MailboxSettings.syncEnabled";
232         private static final String BUNDLE_SYNC_WINDOW_VALUE = "MailboxSettings.syncWindow";
233 
234         private static final String PREF_SYNC_ENABLED_KEY = "sync_enabled";
235         private static final String PREF_SYNC_WINDOW_KEY = "sync_window";
236 
237         private Mailbox mMailbox;
238         /** The maximum lookback allowed for this mailbox, or 0 if no max. */
239         private int mMaxLookback;
240 
241         private CheckBoxPreference mSyncEnabledPref;
242         private ListPreference mSyncLookbackPref;
243 
getArguments(long mailboxId)244         private static Bundle getArguments(long mailboxId) {
245             final Bundle b = new Bundle(1);
246             b.putLong(EXTRA_MAILBOX_ID, mailboxId);
247             return b;
248         }
249 
MailboxSettingsFragment()250         public MailboxSettingsFragment() {}
251 
252         @Override
onActivityCreated(Bundle savedInstanceState)253         public void onActivityCreated(Bundle savedInstanceState) {
254             super.onActivityCreated(savedInstanceState);
255             final long mailboxId = getArguments().getLong(EXTRA_MAILBOX_ID, Mailbox.NO_MAILBOX);
256             if (mailboxId == Mailbox.NO_MAILBOX) {
257                 getActivity().finish();
258             }
259 
260             addPreferencesFromResource(R.xml.mailbox_preferences);
261 
262             mSyncEnabledPref = (CheckBoxPreference) findPreference(PREF_SYNC_ENABLED_KEY);
263             mSyncLookbackPref = (ListPreference) findPreference(PREF_SYNC_WINDOW_KEY);
264 
265             mSyncLookbackPref.setOnPreferenceChangeListener(mPreferenceChanged);
266 
267             if (savedInstanceState != null) {
268                 mMailbox = savedInstanceState.getParcelable(BUNDLE_MAILBOX);
269                 mMaxLookback = savedInstanceState.getInt(BUNDLE_MAX_LOOKBACK);
270                 mSyncEnabledPref
271                         .setChecked(savedInstanceState.getBoolean(BUNDLE_SYNC_ENABLED_VALUE));
272                 mSyncLookbackPref.setValue(savedInstanceState.getString(BUNDLE_SYNC_WINDOW_VALUE));
273                 onDataLoaded();
274             } else {
275                 // Make them disabled until we load data
276                 enablePreferences(false);
277                 getLoaderManager().initLoader(0, getArguments(), new MailboxLoaderCallbacks());
278             }
279         }
280 
enablePreferences(boolean enabled)281         private void enablePreferences(boolean enabled) {
282             mSyncEnabledPref.setEnabled(enabled);
283             mSyncLookbackPref.setEnabled(enabled);
284         }
285 
286         @Override
onSaveInstanceState(@onNull Bundle outState)287         public void onSaveInstanceState(@NonNull Bundle outState) {
288             super.onSaveInstanceState(outState);
289             outState.putParcelable(BUNDLE_MAILBOX, mMailbox);
290             outState.putInt(BUNDLE_MAX_LOOKBACK, mMaxLookback);
291             outState.putBoolean(BUNDLE_SYNC_ENABLED_VALUE, mSyncEnabledPref.isChecked());
292             outState.putString(BUNDLE_SYNC_WINDOW_VALUE, mSyncLookbackPref.getValue());
293         }
294 
295         /**
296          * We save all the settings in onDestroy, *unless it's for configuration changes*.
297          */
298         @Override
onDestroy()299         public void onDestroy() {
300             super.onDestroy();
301             if (!getActivity().isChangingConfigurations()) {
302                 saveToDatabase();
303             }
304         }
305 
306         private static class MailboxLoader extends MailAsyncTaskLoader<Map<String, Object>> {
307             /** Projection for loading an account's policy key. */
308             private static final String[] POLICY_KEY_PROJECTION =
309                     { AccountColumns.POLICY_KEY };
310             private static final int POLICY_KEY_COLUMN = 0;
311 
312             /** Projection for loading the max email lookback. */
313             private static final String[] MAX_EMAIL_LOOKBACK_PROJECTION =
314                     { Policy.MAX_EMAIL_LOOKBACK };
315             private static final int MAX_EMAIL_LOOKBACK_COLUMN = 0;
316 
317             public static final String RESULT_KEY_MAILBOX = "mailbox";
318             public static final String RESULT_KEY_MAX_LOOKBACK = "maxLookback";
319 
320             private final long mMailboxId;
321 
MailboxLoader(Context context, long mailboxId)322             private MailboxLoader(Context context, long mailboxId) {
323                 super(context);
324                 mMailboxId = mailboxId;
325             }
326 
327             @Override
loadInBackground()328             public Map<String, Object> loadInBackground() {
329                 final Map<String, Object> result = new HashMap<>();
330 
331                 final Mailbox mailbox = Mailbox.restoreMailboxWithId(getContext(), mMailboxId);
332                 result.put(RESULT_KEY_MAILBOX, mailbox);
333                 result.put(RESULT_KEY_MAX_LOOKBACK, 0);
334 
335                 if (mailbox == null) {
336                     return result;
337                 }
338 
339                 // Get the max lookback from our policy, if we have one.
340                 final Long policyKey = Utility.getFirstRowLong(getContext(),
341                         ContentUris.withAppendedId(Account.CONTENT_URI, mailbox.mAccountKey),
342                         POLICY_KEY_PROJECTION, null, null, null, POLICY_KEY_COLUMN);
343                 if (policyKey == null) {
344                     // No policy, nothing to look up.
345                     return result;
346                 }
347 
348                 final int maxLookback = Utility.getFirstRowInt(getContext(),
349                         ContentUris.withAppendedId(Policy.CONTENT_URI, policyKey),
350                         MAX_EMAIL_LOOKBACK_PROJECTION, null, null, null,
351                         MAX_EMAIL_LOOKBACK_COLUMN, 0);
352                 result.put(RESULT_KEY_MAX_LOOKBACK, maxLookback);
353 
354                 return result;
355             }
356 
357             @Override
onDiscardResult(Map<String, Object> result)358             protected void onDiscardResult(Map<String, Object> result) {}
359         }
360 
361         private class MailboxLoaderCallbacks
362                 implements LoaderManager.LoaderCallbacks<Map<String, Object>> {
363             @Override
onCreateLoader(int id, Bundle args)364             public Loader<Map<String, Object>> onCreateLoader(int id, Bundle args) {
365                 final long mailboxId = args.getLong(EXTRA_MAILBOX_ID);
366                 return new MailboxLoader(getActivity(), mailboxId);
367             }
368 
369             @Override
onLoadFinished(Loader<Map<String, Object>> loader, Map<String, Object> data)370             public void onLoadFinished(Loader<Map<String, Object>> loader,
371                     Map<String, Object> data) {
372                 final Mailbox mailbox = (Mailbox)
373                         (data == null ? null : data.get(MailboxLoader.RESULT_KEY_MAILBOX));
374                 if (mailbox == null) {
375                     getActivity().finish();
376                     return;
377                 }
378 
379                 mMailbox = mailbox;
380                 mMaxLookback = (Integer) data.get(MailboxLoader.RESULT_KEY_MAX_LOOKBACK);
381 
382                 mSyncEnabledPref.setChecked(mMailbox.mSyncInterval != 0);
383                 mSyncLookbackPref.setValue(String.valueOf(mMailbox.mSyncLookback));
384                 onDataLoaded();
385                 if (mMailbox.mType != Mailbox.TYPE_DRAFTS) {
386                     enablePreferences(true);
387                 }
388             }
389 
390             @Override
onLoaderReset(Loader<Map<String, Object>> loader)391             public void onLoaderReset(Loader<Map<String, Object>> loader) {}
392         }
393 
394         /**
395          * Called when {@link #mMailbox} is loaded (either by the loader or from the saved state).
396          */
onDataLoaded()397         private void onDataLoaded() {
398             Preconditions.checkNotNull(mMailbox);
399 
400             // Update the title with the mailbox name.
401             final ActionBar actionBar = getActivity().getActionBar();
402             final String mailboxName = mMailbox.mDisplayName;
403             if (actionBar != null) {
404                 actionBar.setTitle(mailboxName);
405                 actionBar.setSubtitle(getString(R.string.mailbox_settings_activity_title));
406             } else {
407                 getActivity().setTitle(
408                         getString(R.string.mailbox_settings_activity_title_with_mailbox,
409                                 mailboxName));
410             }
411 
412             MailboxSettings.setupLookbackPreferenceOptions(getActivity(), mSyncLookbackPref,
413                     mMaxLookback, true);
414         }
415 
416 
417         private final OnPreferenceChangeListener mPreferenceChanged =
418                 new OnPreferenceChangeListener() {
419             @Override
420             public boolean onPreferenceChange(Preference preference, Object newValue) {
421                 mSyncLookbackPref.setValue((String) newValue);
422                 mSyncLookbackPref.setSummary(mSyncLookbackPref.getEntry());
423                 return false;
424             }
425         };
426 
427         /**
428          * Save changes to the database.
429          *
430          * Note it's called from {@link #onDestroy()}, which is called on the UI thread where we're
431          * not allowed to touch the database, so it uses {@link EmailAsyncTask} to do the save on a
432          * bg thread. This unfortunately means there's a chance that the app gets killed before the
433          * save is finished.
434          */
saveToDatabase()435         private void saveToDatabase() {
436             if (mMailbox == null) {
437                 // We haven't loaded yet, nothing to save.
438                 return;
439             }
440             final int syncInterval = mSyncEnabledPref.isChecked() ? 1 : 0;
441             final int syncLookback = Integer.valueOf(mSyncLookbackPref.getValue());
442 
443             final boolean syncIntervalChanged = syncInterval != mMailbox.mSyncInterval;
444             final boolean syncLookbackChanged = syncLookback != mMailbox.mSyncLookback;
445 
446             // Only save if a preference has changed value.
447             if (!syncIntervalChanged && !syncLookbackChanged) {
448                 return;
449             }
450 
451             LogUtils.i(Logging.LOG_TAG, "Saving mailbox settings...");
452             enablePreferences(false);
453 
454             final long id = mMailbox.mId;
455             final Context context = getActivity().getApplicationContext();
456 
457             new EmailAsyncTask<Void, Void, Void> (null /* no cancel */) {
458                 @Override
459                 protected Void doInBackground(Void... params) {
460                     final ContentValues cv = new ContentValues(2);
461                     final Uri uri;
462                     if (syncIntervalChanged) {
463                         cv.put(MailboxColumns.SYNC_INTERVAL, syncInterval);
464                     }
465                     if (syncLookbackChanged) {
466                         cv.put(MailboxColumns.SYNC_LOOKBACK, syncLookback);
467                     }
468                     uri = ContentUris.withAppendedId(Mailbox.CONTENT_URI, id);
469                     context.getContentResolver().update(uri, cv, null, null);
470 
471                     LogUtils.i(Logging.LOG_TAG, "Saved: " + uri);
472                     return null;
473                 }
474             }.executeSerial((Void [])null);
475         }
476     }
477 }
478