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