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.Activity; 21 import android.content.ContentUris; 22 import android.content.ContentValues; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.res.Resources; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.preference.ListPreference; 29 import android.preference.Preference; 30 import android.preference.Preference.OnPreferenceChangeListener; 31 import android.preference.PreferenceActivity; 32 import android.util.Log; 33 import android.view.MenuItem; 34 35 import com.android.email.Email; 36 import com.android.email.FolderProperties; 37 import com.android.email.R; 38 import com.android.email.RefreshManager; 39 import com.android.emailcommon.Logging; 40 import com.android.emailcommon.provider.Account; 41 import com.android.emailcommon.provider.Policy; 42 import com.android.emailcommon.provider.EmailContent.AccountColumns; 43 import com.android.emailcommon.provider.EmailContent.MailboxColumns; 44 import com.android.emailcommon.provider.Mailbox; 45 import com.android.emailcommon.utility.EmailAsyncTask; 46 import com.google.common.base.Objects; 47 import com.google.common.base.Preconditions; 48 49 /** 50 * "Mailbox settings" activity. 51 * 52 * It's used to update per-mailbox sync settings. It normally updates Mailbox settings, unless 53 * the target mailbox is Inbox, in which case it updates Account settings instead. 54 * 55 * All changes made by the user will not be immediately saved to the database, as changing the 56 * sync window may result in removal of messages. Instead, we only save to the database in {@link 57 * #onDestroy()}, unless it's called for configuration changes. 58 */ 59 public class MailboxSettings extends PreferenceActivity { 60 private static final String EXTRA_MAILBOX_ID = "MAILBOX_ID"; 61 private static final String BUNDLE_ACCOUNT = "MailboxSettings.account"; 62 private static final String BUNDLE_MAILBOX = "MailboxSettings.mailbox"; 63 private static final String BUNDLE_NEEDS_SAVE = "MailboxSettings.needsSave"; 64 65 private static final String PREF_CHECK_FREQUENCY_KEY = "check_frequency"; 66 private static final String PREF_SYNC_WINDOW_KEY = "sync_window"; 67 68 private final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker(); 69 70 // Account and Mailbox -- directly loaded by LoadMailboxTask 71 private Account mAccount; 72 private Mailbox mMailbox; 73 private boolean mNeedsSave; 74 75 private ListPreference mSyncIntervalPref; 76 private ListPreference mSyncLookbackPref; 77 78 /** 79 * Starts the activity for a mailbox. 80 */ 81 public static final void start(Activity parent, long mailboxId) { 82 Intent i = new Intent(parent, MailboxSettings.class); 83 i.putExtra(EXTRA_MAILBOX_ID, mailboxId); 84 parent.startActivity(i); 85 } 86 87 @Override 88 protected void onCreate(Bundle savedInstanceState) { 89 super.onCreate(savedInstanceState); 90 91 final long mailboxId = getIntent().getLongExtra(EXTRA_MAILBOX_ID, Mailbox.NO_MAILBOX); 92 if (mailboxId == Mailbox.NO_MAILBOX) { 93 finish(); 94 return; 95 } 96 97 addPreferencesFromResource(R.xml.mailbox_preferences); 98 99 mSyncIntervalPref = (ListPreference) findPreference(PREF_CHECK_FREQUENCY_KEY); 100 mSyncLookbackPref = (ListPreference) findPreference(PREF_SYNC_WINDOW_KEY); 101 102 mSyncIntervalPref.setOnPreferenceChangeListener(mPreferenceChanged); 103 mSyncLookbackPref.setOnPreferenceChangeListener(mPreferenceChanged); 104 105 // Make them disabled until we load data 106 enablePreferences(false); 107 108 if (savedInstanceState != null) { 109 mAccount = savedInstanceState.getParcelable(BUNDLE_ACCOUNT); 110 mMailbox = savedInstanceState.getParcelable(BUNDLE_MAILBOX); 111 mNeedsSave = savedInstanceState.getBoolean(BUNDLE_NEEDS_SAVE); 112 } 113 if (mAccount == null) { 114 new LoadMailboxTask(mailboxId).executeParallel((Void[]) null); 115 } else { 116 onDataLoaded(); 117 } 118 119 // Always show "app up" as we expect our parent to be an Email activity. 120 ActionBar actionBar = getActionBar(); 121 if (actionBar != null) { 122 actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP); 123 } 124 } 125 126 private void enablePreferences(boolean enabled) { 127 mSyncIntervalPref.setEnabled(enabled); 128 mSyncLookbackPref.setEnabled(enabled); 129 } 130 131 @Override 132 protected void onSaveInstanceState(Bundle outState) { 133 super.onSaveInstanceState(outState); 134 outState.putParcelable(BUNDLE_ACCOUNT, mAccount); 135 outState.putParcelable(BUNDLE_MAILBOX, mMailbox); 136 outState.putBoolean(BUNDLE_NEEDS_SAVE, mNeedsSave); 137 } 138 139 /** 140 * We save all the settings in onDestroy, *unless it's for configuration changes*. 141 */ 142 @Override 143 protected void onDestroy() { 144 mTaskTracker.cancellAllInterrupt(); 145 if (!isChangingConfigurations()) { 146 saveToDatabase(); 147 } 148 super.onDestroy(); 149 } 150 151 /** 152 * Loads {@link #mAccount} and {@link #mMailbox}. 153 */ 154 private class LoadMailboxTask extends EmailAsyncTask<Void, Void, Void> { 155 private final long mMailboxId; 156 157 public LoadMailboxTask(long mailboxId) { 158 super(mTaskTracker); 159 mMailboxId = mailboxId; 160 } 161 162 @Override 163 protected Void doInBackground(Void... params) { 164 final Context c = MailboxSettings.this; 165 mMailbox = Mailbox.restoreMailboxWithId(c, mMailboxId); 166 if (mMailbox != null) { 167 mAccount = Account.restoreAccountWithId(c, mMailbox.mAccountKey); 168 } 169 return null; 170 } 171 172 @Override 173 protected void onSuccess(Void result) { 174 if ((mAccount == null) || (mMailbox == null)) { 175 finish(); // Account or mailbox removed. 176 return; 177 } 178 onDataLoaded(); 179 } 180 } 181 182 /** 183 * Setup the entries and entry values for the sync lookback preference 184 * @param context the caller's context 185 * @param pref a ListPreference to be set up 186 * @param account the Account (or owner of a Mailbox) whose preference is being set 187 */ 188 public static void setupLookbackPreferenceOptions(Context context, ListPreference pref, 189 Account account) { 190 Resources resources = context.getResources(); 191 // Load the complete list of entries/values 192 CharSequence[] entries = 193 resources.getTextArray(R.array.account_settings_mail_window_entries); 194 CharSequence[] values = 195 resources.getTextArray(R.array.account_settings_mail_window_values); 196 // If we have a maximum lookback policy, enforce it 197 if (account.mPolicyKey > 0) { 198 Policy policy = Policy.restorePolicyWithId(context, account.mPolicyKey); 199 if (policy != null && (policy.mMaxEmailLookback != 0)) { 200 int maxEntry = policy.mMaxEmailLookback + 1; 201 // Copy the proper number of values into new entries/values array 202 CharSequence[] policyEntries = new CharSequence[maxEntry]; 203 CharSequence[] policyValues = new CharSequence[maxEntry]; 204 for (int i = 0; i < maxEntry; i++) { 205 policyEntries[i] = entries[i]; 206 policyValues[i] = values[i]; 207 } 208 // Point entries/values to the new arrays 209 entries = policyEntries; 210 values = policyValues; 211 } 212 } 213 // Set up the preference 214 pref.setEntries(entries); 215 pref.setEntryValues(values); 216 } 217 218 /** 219 * Called when {@link #mAccount} and {@link #mMailbox} are loaded (either by the async task 220 * or from the saved state). 221 */ 222 private void onDataLoaded() { 223 Preconditions.checkNotNull(mAccount); 224 Preconditions.checkNotNull(mMailbox); 225 226 // Update the title with the mailbox name. 227 ActionBar actionBar = getActionBar(); 228 String mailboxName = FolderProperties.getInstance(this).getDisplayName(mMailbox); 229 if (actionBar != null) { 230 actionBar.setTitle(mailboxName); 231 actionBar.setSubtitle(getString(R.string.mailbox_settings_activity_title)); 232 } else { 233 setTitle(getString(R.string.mailbox_settings_activity_title_with_mailbox, mailboxName)); 234 } 235 236 setupLookbackPreferenceOptions(this, mSyncLookbackPref, mAccount); 237 238 // Set default value & update summary 239 mSyncIntervalPref.setValue(String.valueOf(getSyncInterval())); 240 mSyncLookbackPref.setValue(String.valueOf(getSyncLookback())); 241 242 updatePreferenceSummary(); 243 244 // Make then enabled 245 enablePreferences(true); 246 } 247 248 private void updatePreferenceSummary() { 249 mSyncIntervalPref.setSummary(mSyncIntervalPref.getEntry()); 250 mSyncLookbackPref.setSummary(mSyncLookbackPref.getEntry()); 251 } 252 253 /** 254 * @return current sync interval setting from the objects 255 */ 256 private int getSyncInterval() { 257 int syncInterval; 258 if (mMailbox.mType == Mailbox.TYPE_INBOX) { 259 syncInterval = mAccount.mSyncInterval; 260 } else { 261 if (mMailbox.mSyncInterval == 0) { 262 // 0 is the default value, and it means "don't sync" (for non-inbox mailboxes) 263 syncInterval = Mailbox.CHECK_INTERVAL_NEVER; 264 } else { 265 syncInterval = mMailbox.mSyncInterval; 266 } 267 } 268 // In the case of the internal push states, use "push" 269 if (syncInterval == Mailbox.CHECK_INTERVAL_PING || 270 syncInterval == Mailbox.CHECK_INTERVAL_PUSH_HOLD) { 271 syncInterval = Mailbox.CHECK_INTERVAL_PUSH; 272 } 273 return syncInterval; 274 } 275 276 /** 277 * @return current sync lookback setting from the objects 278 */ 279 private int getSyncLookback() { 280 if (mMailbox.mType == Mailbox.TYPE_INBOX) { 281 return mAccount.mSyncLookback; 282 } else { 283 // Here, 0 is valid and means "use the account default sync window". 284 return mMailbox.mSyncLookback; 285 } 286 } 287 288 private final OnPreferenceChangeListener mPreferenceChanged = new OnPreferenceChangeListener() { 289 @Override 290 public boolean onPreferenceChange(Preference preference, Object newValue) { 291 final ListPreference lp = (ListPreference) preference; 292 if (Objects.equal(lp.getValue(), newValue)) { 293 return false; 294 } 295 mNeedsSave = true; 296 if (Email.DEBUG) { 297 Log.i(Logging.LOG_TAG, "Setting changed"); 298 } 299 // In order to set the current entry to the summary, we need to udpate the value 300 // manually, rather than letting the framework do that (by returning true). 301 lp.setValue((String) newValue); 302 updatePreferenceSummary(); 303 updateObjects(); 304 return false; 305 } 306 }; 307 308 /** 309 * Updates {@link #mAccount}/{@link #mMailbox}, but doesn't save to the database yet. 310 */ 311 private void updateObjects() { 312 final int syncInterval = Integer.valueOf(mSyncIntervalPref.getValue()); 313 final int syncLookback = Integer.valueOf(mSyncLookbackPref.getValue()); 314 if (Email.DEBUG) { 315 Log.i(Logging.LOG_TAG, "Updating object: " + syncInterval + "," + syncLookback); 316 } 317 if (mMailbox.mType == Mailbox.TYPE_INBOX) { 318 mAccount.mSyncInterval = syncInterval; 319 mAccount.mSyncLookback = syncLookback; 320 } else { 321 mMailbox.mSyncInterval = syncInterval; 322 mMailbox.mSyncLookback = syncLookback; 323 } 324 } 325 326 /** 327 * Save changes to the database. 328 * 329 * Note it's called from {@link #onDestroy()}, which is called on the UI thread where we're not 330 * allowed to touch the database, so it uses {@link EmailAsyncTask} to do the save on a bg 331 * thread. This unfortunately means there's a chance that the app gets killed before the save is 332 * finished. 333 */ 334 private void saveToDatabase() { 335 if (!mNeedsSave) { 336 return; 337 } 338 Log.i(Logging.LOG_TAG, "Saving mailbox settings..."); 339 enablePreferences(false); 340 341 // Since the activity will be destroyed... 342 // Create local references (Although it's really okay to touch members of a destroyed 343 // activity...) 344 final Account account = mAccount; 345 final Mailbox mailbox = mMailbox; 346 final Context context = getApplicationContext(); 347 348 new EmailAsyncTask<Void, Void, Void> (null /* no cancel */) { 349 @Override 350 protected Void doInBackground(Void... params) { 351 final ContentValues cv = new ContentValues(); 352 final Uri uri; 353 354 if (mailbox.mType == Mailbox.TYPE_INBOX) { 355 cv.put(AccountColumns.SYNC_INTERVAL, account.mSyncInterval); 356 cv.put(AccountColumns.SYNC_LOOKBACK, account.mSyncLookback); 357 uri = ContentUris.withAppendedId(Account.CONTENT_URI, account.mId); 358 } else { 359 cv.put(MailboxColumns.SYNC_INTERVAL, mailbox.mSyncInterval); 360 cv.put(MailboxColumns.SYNC_LOOKBACK, mailbox.mSyncLookback); 361 uri = ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailbox.mId); 362 } 363 context.getContentResolver().update(uri, cv, null, null); 364 365 Log.i(Logging.LOG_TAG, "Saved: " + uri); 366 return null; 367 } 368 369 @Override 370 protected void onSuccess(Void result) { 371 // must be called on the ui thread 372 RefreshManager.getInstance(context).refreshMessageList(account.mId, mailbox.mId, 373 true); 374 } 375 }.executeSerial((Void [])null); 376 } 377 378 @Override 379 public boolean onOptionsItemSelected(MenuItem item) { 380 if (item.getItemId() == android.R.id.home) { 381 onBackPressed(); 382 return true; 383 } 384 return super.onOptionsItemSelected(item); 385 } 386 } 387