EmailBroadcastProcessorService.java revision 9e521deb6bb525b33365cc2926cb2d0faa7095e2
1/* 2 * Copyright (C) 2010 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 17package com.android.email.service; 18 19import android.accounts.AccountManager; 20import android.app.IntentService; 21import android.content.ComponentName; 22import android.content.ContentResolver; 23import android.content.ContentUris; 24import android.content.ContentValues; 25import android.content.Context; 26import android.content.Intent; 27import android.content.PeriodicSync; 28import android.content.pm.PackageManager; 29import android.database.Cursor; 30import android.net.Uri; 31import android.os.Bundle; 32import android.provider.CalendarContract; 33import android.provider.ContactsContract; 34import android.text.format.DateUtils; 35 36import com.android.email.Preferences; 37import com.android.email.R; 38import com.android.email.SecurityPolicy; 39import com.android.email.activity.setup.AccountSettings; 40import com.android.email.provider.AccountReconciler; 41import com.android.emailcommon.Logging; 42import com.android.emailcommon.VendorPolicyLoader; 43import com.android.emailcommon.provider.Account; 44import com.android.emailcommon.provider.EmailContent; 45import com.android.emailcommon.provider.EmailContent.AccountColumns; 46import com.android.emailcommon.provider.HostAuth; 47import com.android.mail.utils.LogUtils; 48import com.google.common.collect.Maps; 49 50import java.util.Collections; 51import java.util.List; 52import java.util.Map; 53 54/** 55 * The service that really handles broadcast intents on a worker thread. 56 * 57 * We make it a service, because: 58 * <ul> 59 * <li>So that it's less likely for the process to get killed. 60 * <li>Even if it does, the Intent that have started it will be re-delivered by the system, 61 * and we can start the process again. (Using {@link #setIntentRedelivery}). 62 * </ul> 63 * 64 * This also handles the DeviceAdminReceiver in SecurityPolicy, because it is also 65 * a BroadcastReceiver and requires the same processing semantics. 66 */ 67public class EmailBroadcastProcessorService extends IntentService { 68 // Action used for BroadcastReceiver entry point 69 private static final String ACTION_BROADCAST = "broadcast_receiver"; 70 71 // Dialing "*#*#36245#*#*" to open the debug screen. "36245" = "email" 72 private static final String ACTION_SECRET_CODE = "android.provider.Telephony.SECRET_CODE"; 73 private static final String SECRET_CODE_HOST_DEBUG_SCREEN = "36245"; 74 75 // This is a helper used to process DeviceAdminReceiver messages 76 private static final String ACTION_DEVICE_POLICY_ADMIN = "com.android.email.devicepolicy"; 77 private static final String EXTRA_DEVICE_POLICY_ADMIN = "message_code"; 78 79 // Action used for EmailUpgradeBroadcastReceiver. 80 private static final String ACTION_UPGRADE_BROADCAST = "upgrade_broadcast_receiver"; 81 82 public EmailBroadcastProcessorService() { 83 // Class name will be the thread name. 84 super(EmailBroadcastProcessorService.class.getName()); 85 86 // Intent should be redelivered if the process gets killed before completing the job. 87 setIntentRedelivery(true); 88 } 89 90 /** 91 * Entry point for {@link EmailBroadcastReceiver}. 92 */ 93 public static void processBroadcastIntent(Context context, Intent broadcastIntent) { 94 Intent i = new Intent(context, EmailBroadcastProcessorService.class); 95 i.setAction(ACTION_BROADCAST); 96 i.putExtra(Intent.EXTRA_INTENT, broadcastIntent); 97 context.startService(i); 98 } 99 100 public static void processUpgradeBroadcastIntent(final Context context) { 101 final Intent i = new Intent(context, EmailBroadcastProcessorService.class); 102 i.setAction(ACTION_UPGRADE_BROADCAST); 103 context.startService(i); 104 } 105 106 /** 107 * Entry point for {@link com.android.email.SecurityPolicy.PolicyAdmin}. These will 108 * simply callback to {@link 109 * com.android.email.SecurityPolicy#onDeviceAdminReceiverMessage(Context, int)}. 110 */ 111 public static void processDevicePolicyMessage(Context context, int message) { 112 Intent i = new Intent(context, EmailBroadcastProcessorService.class); 113 i.setAction(ACTION_DEVICE_POLICY_ADMIN); 114 i.putExtra(EXTRA_DEVICE_POLICY_ADMIN, message); 115 context.startService(i); 116 } 117 118 @Override 119 protected void onHandleIntent(Intent intent) { 120 // This method is called on a worker thread. 121 122 // Dispatch from entry point 123 final String action = intent.getAction(); 124 if (ACTION_BROADCAST.equals(action)) { 125 final Intent broadcastIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT); 126 final String broadcastAction = broadcastIntent.getAction(); 127 128 if (Intent.ACTION_BOOT_COMPLETED.equals(broadcastAction)) { 129 onBootCompleted(); 130 } else if (ACTION_SECRET_CODE.equals(broadcastAction) 131 && SECRET_CODE_HOST_DEBUG_SCREEN.equals(broadcastIntent.getData().getHost())) { 132 AccountSettings.actionSettingsWithDebug(this); 133 } else if (AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.equals(broadcastAction)) { 134 onSystemAccountChanged(); 135 } 136 } else if (ACTION_DEVICE_POLICY_ADMIN.equals(action)) { 137 int message = intent.getIntExtra(EXTRA_DEVICE_POLICY_ADMIN, -1); 138 SecurityPolicy.onDeviceAdminReceiverMessage(this, message); 139 } else if (ACTION_UPGRADE_BROADCAST.equals(action)) { 140 onAppUpgrade(); 141 } 142 } 143 144 private void disableComponent(final Class<?> klass) { 145 getPackageManager().setComponentEnabledSetting(new ComponentName(this, klass), 146 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); 147 } 148 149 private void updateAccountManagerAccountsOfType(final String amAccountType, 150 final Map<String, String> protocolMap) { 151 final android.accounts.Account[] amAccounts = 152 AccountManager.get(this).getAccountsByType(amAccountType); 153 154 for (android.accounts.Account amAccount: amAccounts) { 155 EmailServiceUtils.updateAccountManagerType(this, amAccount, protocolMap); 156 } 157 } 158 159 /** 160 * Delete all periodic syncs for an account. 161 * @param amAccount The account for which to disable syncs. 162 * @param authority The authority for which to disable syncs. 163 */ 164 private static void removePeriodicSyncs(final android.accounts.Account amAccount, 165 final String authority) { 166 final List<PeriodicSync> syncs = 167 ContentResolver.getPeriodicSyncs(amAccount, authority); 168 for (final PeriodicSync sync : syncs) { 169 ContentResolver.removePeriodicSync(amAccount, authority, sync.extras); 170 } 171 } 172 173 /** 174 * Remove all existing periodic syncs for an account type, and add the necessary syncs. 175 * @param amAccountType The account type to handle. 176 * @param syncIntervals The map of all account addresses to sync intervals in the DB. 177 */ 178 private void fixPeriodicSyncs(final String amAccountType, 179 final Map<String, Integer> syncIntervals) { 180 final android.accounts.Account[] amAccounts = 181 AccountManager.get(this).getAccountsByType(amAccountType); 182 for (android.accounts.Account amAccount : amAccounts) { 183 // First delete existing periodic syncs. 184 removePeriodicSyncs(amAccount, EmailContent.AUTHORITY); 185 removePeriodicSyncs(amAccount, CalendarContract.AUTHORITY); 186 removePeriodicSyncs(amAccount, ContactsContract.AUTHORITY); 187 188 // Add back a sync for this account if necessary (i.e. the account has a positive 189 // sync interval in the DB). This assumes that the email app requires unique email 190 // addresses for each account, which is currently the case. 191 final Integer syncInterval = syncIntervals.get(amAccount.name); 192 if (syncInterval != null && syncInterval > 0) { 193 // Sync interval is stored in minutes in DB, but we want the value in seconds. 194 ContentResolver.addPeriodicSync(amAccount, EmailContent.AUTHORITY, Bundle.EMPTY, 195 syncInterval * DateUtils.MINUTE_IN_MILLIS / DateUtils.SECOND_IN_MILLIS); 196 } 197 } 198 } 199 200 /** Projection used for getting sync intervals for all accounts. */ 201 private static final String[] ACCOUNT_SYNC_INTERVAL_PROJECTION = 202 { AccountColumns.EMAIL_ADDRESS, AccountColumns.SYNC_INTERVAL }; 203 private static final int ACCOUNT_SYNC_INTERVAL_ADDRESS_COLUMN = 0; 204 private static final int ACCOUNT_SYNC_INTERVAL_INTERVAL_COLUMN = 1; 205 206 /** 207 * Get the sync interval for all accounts, as stored in the DB. 208 * @return The map of all sync intervals by account email address. 209 */ 210 private Map<String, Integer> getSyncIntervals() { 211 final Cursor c = getContentResolver().query(Account.CONTENT_URI, 212 ACCOUNT_SYNC_INTERVAL_PROJECTION, null, null, null); 213 if (c != null) { 214 final Map<String, Integer> periodicSyncs = 215 Maps.newHashMapWithExpectedSize(c.getCount()); 216 try { 217 while (c.moveToNext()) { 218 periodicSyncs.put(c.getString(ACCOUNT_SYNC_INTERVAL_ADDRESS_COLUMN), 219 c.getInt(ACCOUNT_SYNC_INTERVAL_INTERVAL_COLUMN)); 220 } 221 } finally { 222 c.close(); 223 } 224 return periodicSyncs; 225 } 226 return Collections.emptyMap(); 227 } 228 229 private void onAppUpgrade() { 230 // TODO: Only do this for Email2Google. 231 // When upgrading to Email2Google, we need to essentially rename the account manager 232 // type for all existing accounts, so we add new ones and delete the old. 233 // We specify the translations in this map. We map from old protocol name to new protocol 234 // name, and from protocol name + "_type" to new account manager type name. (Email1 did 235 // not use distinct account manager types for POP and IMAP, but Email2 does, hence this 236 // weird mapping.) 237 final Map<String, String> protocolMap = Maps.newHashMapWithExpectedSize(4); 238 protocolMap.put("imap", getString(R.string.protocol_legacy_imap)); 239 protocolMap.put("pop3", getString(R.string.protocol_pop3)); 240 protocolMap.put("imap_type", getString(R.string.account_manager_type_legacy_imap)); 241 protocolMap.put("pop3_type", getString(R.string.account_manager_type_pop3)); 242 updateAccountManagerAccountsOfType("com.android.email", protocolMap); 243 244 protocolMap.clear(); 245 protocolMap.put("eas", getString(R.string.protocol_eas)); 246 protocolMap.put("eas_type", getString(R.string.account_manager_type_exchange)); 247 updateAccountManagerAccountsOfType("com.android.exchange", protocolMap); 248 249 // Disable the old authenticators. 250 disableComponent(LegacyEmailAuthenticatorService.class); 251 disableComponent(LegacyEasAuthenticatorService.class); 252 253 // Fix periodic syncs. 254 final Map<String, Integer> syncIntervals = getSyncIntervals(); 255 final List<EmailServiceUtils.EmailServiceInfo> serviceList = 256 EmailServiceUtils.getServiceInfoList(this); 257 for (final EmailServiceUtils.EmailServiceInfo service : serviceList) { 258 fixPeriodicSyncs(service.accountType, syncIntervals); 259 } 260 261 // Disable the upgrade broadcast receiver now that we're fully upgraded. 262 disableComponent(EmailUpgradeBroadcastReceiver.class); 263 } 264 265 /** 266 * Handles {@link Intent#ACTION_BOOT_COMPLETED}. Called on a worker thread. 267 */ 268 private void onBootCompleted() { 269 performOneTimeInitialization(); 270 reconcileAndStartServices(); 271 } 272 273 private void reconcileAndStartServices() { 274 // Reconcile accounts 275 AccountReconciler.reconcileAccounts(this); 276 // Starts remote services, if any 277 EmailServiceUtils.startRemoteServices(this); 278 } 279 280 private void performOneTimeInitialization() { 281 final Preferences pref = Preferences.getPreferences(this); 282 int progress = pref.getOneTimeInitializationProgress(); 283 final int initialProgress = progress; 284 285 if (progress < 1) { 286 LogUtils.i(Logging.LOG_TAG, "Onetime initialization: 1"); 287 progress = 1; 288 if (VendorPolicyLoader.getInstance(this).useAlternateExchangeStrings()) { 289 setComponentEnabled(EasAuthenticatorServiceAlternate.class, true); 290 setComponentEnabled(EasAuthenticatorService.class, false); 291 } 292 } 293 294 if (progress < 2) { 295 LogUtils.i(Logging.LOG_TAG, "Onetime initialization: 2"); 296 progress = 2; 297 setImapDeletePolicy(this); 298 } 299 300 // Add your initialization steps here. 301 // Use "progress" to skip the initializations that's already done before. 302 // Using this preference also makes it safe when a user skips an upgrade. (i.e. upgrading 303 // version N to version N+2) 304 305 if (progress != initialProgress) { 306 pref.setOneTimeInitializationProgress(progress); 307 LogUtils.i(Logging.LOG_TAG, "Onetime initialization: completed."); 308 } 309 } 310 311 /** 312 * Sets the delete policy to the correct value for all IMAP accounts. This will have no 313 * effect on either EAS or POP3 accounts. 314 */ 315 /*package*/ static void setImapDeletePolicy(Context context) { 316 ContentResolver resolver = context.getContentResolver(); 317 Cursor c = resolver.query(Account.CONTENT_URI, Account.CONTENT_PROJECTION, 318 null, null, null); 319 try { 320 while (c.moveToNext()) { 321 long recvAuthKey = c.getLong(Account.CONTENT_HOST_AUTH_KEY_RECV_COLUMN); 322 HostAuth recvAuth = HostAuth.restoreHostAuthWithId(context, recvAuthKey); 323 String legacyImapProtocol = context.getString(R.string.protocol_legacy_imap); 324 if (legacyImapProtocol.equals(recvAuth.mProtocol)) { 325 int flags = c.getInt(Account.CONTENT_FLAGS_COLUMN); 326 flags &= ~Account.FLAGS_DELETE_POLICY_MASK; 327 flags |= Account.DELETE_POLICY_ON_DELETE << Account.FLAGS_DELETE_POLICY_SHIFT; 328 ContentValues cv = new ContentValues(); 329 cv.put(AccountColumns.FLAGS, flags); 330 long accountId = c.getLong(Account.CONTENT_ID_COLUMN); 331 Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId); 332 resolver.update(uri, cv, null, null); 333 } 334 } 335 } finally { 336 c.close(); 337 } 338 } 339 340 private void setComponentEnabled(Class<?> clazz, boolean enabled) { 341 final ComponentName c = new ComponentName(this, clazz.getName()); 342 getPackageManager().setComponentEnabledSetting(c, 343 enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED 344 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 345 PackageManager.DONT_KILL_APP); 346 } 347 348 private void onSystemAccountChanged() { 349 LogUtils.i(Logging.LOG_TAG, "System accounts updated."); 350 reconcileAndStartServices(); 351 } 352} 353