Home | History | Annotate | Download | only in setup
      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 android.support.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      */
     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
     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
    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
    142     protected boolean isValidFragment(String fragmentName) {
    143         // Activity is not exported
    144         return true;
    145     }
    146 
    147     @Override
    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      */
    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
    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
    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
    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 
    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 
    250         public MailboxSettingsFragment() {}
    251 
    252         @Override
    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 
    281         private void enablePreferences(boolean enabled) {
    282             mSyncEnabledPref.setEnabled(enabled);
    283             mSyncLookbackPref.setEnabled(enabled);
    284         }
    285 
    286         @Override
    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
    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 
    322             private MailboxLoader(Context context, long mailboxId) {
    323                 super(context);
    324                 mMailboxId = mailboxId;
    325             }
    326 
    327             @Override
    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
    358             protected void onDiscardResult(Map<String, Object> result) {}
    359         }
    360 
    361         private class MailboxLoaderCallbacks
    362                 implements LoaderManager.LoaderCallbacks<Map<String, Object>> {
    363             @Override
    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
    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
    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          */
    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          */
    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