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 17 package com.android.exchange.service; 18 19 import android.app.Notification; 20 import android.app.NotificationManager; 21 import android.app.PendingIntent; 22 import android.app.Service; 23 import android.content.AbstractThreadedSyncAdapter; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.ServiceConnection; 28 import android.content.SyncResult; 29 import android.database.Cursor; 30 import android.net.Uri; 31 import android.os.IBinder; 32 import android.text.format.DateUtils; 33 34 import com.android.emailcommon.provider.Account; 35 import com.android.emailcommon.provider.EmailContent; 36 import com.android.emailcommon.service.EmailServiceStatus; 37 import com.android.emailcommon.service.IEmailService; 38 import com.android.emailcommon.utility.IntentUtilities; 39 import com.android.exchange.R; 40 import com.android.mail.utils.LogUtils; 41 42 /** 43 * Base class for services that handle sync requests from the system SyncManager. 44 * This class covers the boilerplate for using an {@link AbstractThreadedSyncAdapter}. Subclasses 45 * should just implement their sync adapter, and override {@link #getSyncAdapter}. 46 */ 47 public abstract class AbstractSyncAdapterService extends Service { 48 private static final String TAG = LogUtils.TAG; 49 50 // The call to ServiceConnection.onServiceConnected is asynchronous to bindService. It's 51 // possible for that to be delayed if, in which case, a call to onPerformSync 52 // could occur before we have a connection to the service. 53 // In onPerformSync, if we don't yet have our EasService, we will wait for up to 10 54 // seconds for it to appear. If it takes longer than that, we will fail the sync. 55 private static final long MAX_WAIT_FOR_SERVICE_MS = 10 * DateUtils.SECOND_IN_MILLIS; 56 57 public AbstractSyncAdapterService() { 58 super(); 59 } 60 61 protected IEmailService mEasService; 62 protected ServiceConnection mConnection; 63 64 @Override 65 public void onCreate() { 66 super.onCreate(); 67 // Make sure EmailContent is initialized in Exchange app 68 EmailContent.init(this); 69 mConnection = new ServiceConnection() { 70 @Override 71 public void onServiceConnected(ComponentName name, IBinder binder) { 72 LogUtils.v(TAG, "onServiceConnected"); 73 synchronized (mConnection) { 74 mEasService = IEmailService.Stub.asInterface(binder); 75 mConnection.notify(); 76 } 77 } 78 79 @Override 80 public void onServiceDisconnected(ComponentName name) { 81 mEasService = null; 82 } 83 }; 84 bindService(new Intent(this, EasService.class), mConnection, Context.BIND_AUTO_CREATE); 85 } 86 87 @Override 88 public void onDestroy() { 89 super.onDestroy(); 90 unbindService(mConnection); 91 } 92 93 @Override 94 public IBinder onBind(Intent intent) { 95 return getSyncAdapter().getSyncAdapterBinder(); 96 } 97 98 /** 99 * Subclasses should override this to supply an instance of its sync adapter. Best practice is 100 * to create a singleton and return that. 101 * @return An instance of the sync adapter. 102 */ 103 protected abstract AbstractThreadedSyncAdapter getSyncAdapter(); 104 105 /** 106 * Create and return an intent to display (and edit) settings for a specific account, or -1 107 * for any/all accounts. If an account name string is provided, a warning dialog will be 108 * displayed as well. 109 */ 110 public static Intent createAccountSettingsIntent(long accountId, String accountName) { 111 final Uri.Builder builder = IntentUtilities.createActivityIntentUrlBuilder( 112 IntentUtilities.PATH_SETTINGS); 113 IntentUtilities.setAccountId(builder, accountId); 114 IntentUtilities.setAccountName(builder, accountName); 115 return new Intent(Intent.ACTION_EDIT, builder.build()); 116 } 117 118 protected void showAuthNotification(long accountId, String accountName) { 119 final PendingIntent pendingIntent = PendingIntent.getActivity( 120 this, 121 0, 122 createAccountSettingsIntent(accountId, accountName), 123 0); 124 125 final Notification notification = new Notification.Builder(this) 126 .setContentTitle(this.getString(R.string.auth_error_notification_title)) 127 .setContentText(this.getString( 128 R.string.auth_error_notification_text, accountName)) 129 .setSmallIcon(R.drawable.stat_notify_auth) 130 .setContentIntent(pendingIntent) 131 .setAutoCancel(true) 132 .getNotification(); 133 134 final NotificationManager nm = (NotificationManager) 135 this.getSystemService(Context.NOTIFICATION_SERVICE); 136 nm.notify("AuthError", 0, notification); 137 } 138 139 /** 140 * Interpret a result code from an {@link IEmailService.sync()} and, if it's an error, write 141 * it to the appropriate field in {@link android.content.SyncResult}. 142 * @param result 143 * @param syncResult 144 * @return Whether an error code was written to syncResult. 145 */ 146 public static boolean writeResultToSyncResult(final int result, final SyncResult syncResult) { 147 switch (result) { 148 case EmailServiceStatus.SUCCESS: 149 return false; 150 151 case EmailServiceStatus.REMOTE_EXCEPTION: 152 case EmailServiceStatus.LOGIN_FAILED: 153 case EmailServiceStatus.SECURITY_FAILURE: 154 case EmailServiceStatus.CLIENT_CERTIFICATE_ERROR: 155 case EmailServiceStatus.ACCESS_DENIED: 156 syncResult.stats.numAuthExceptions = 1; 157 return true; 158 159 case EmailServiceStatus.HARD_DATA_ERROR: 160 case EmailServiceStatus.INTERNAL_ERROR: 161 syncResult.databaseError = true; 162 return true; 163 164 case EmailServiceStatus.CONNECTION_ERROR: 165 case EmailServiceStatus.IO_ERROR: 166 syncResult.stats.numIoExceptions = 1; 167 return true; 168 169 case EmailServiceStatus.TOO_MANY_REDIRECTS: 170 syncResult.tooManyRetries = true; 171 return true; 172 173 case EmailServiceStatus.IN_PROGRESS: 174 case EmailServiceStatus.MESSAGE_NOT_FOUND: 175 case EmailServiceStatus.ATTACHMENT_NOT_FOUND: 176 case EmailServiceStatus.FOLDER_NOT_DELETED: 177 case EmailServiceStatus.FOLDER_NOT_RENAMED: 178 case EmailServiceStatus.FOLDER_NOT_CREATED: 179 case EmailServiceStatus.ACCOUNT_UNINITIALIZED: 180 case EmailServiceStatus.PROTOCOL_ERROR: 181 LogUtils.e(TAG, "Unexpected sync result %d", result); 182 return false; 183 } 184 return false; 185 } 186 187 protected final boolean waitForService() { 188 synchronized(mConnection) { 189 if (mEasService == null) { 190 LogUtils.d(TAG, "service not yet connected"); 191 try { 192 mConnection.wait(MAX_WAIT_FOR_SERVICE_MS); 193 } catch (InterruptedException e) { 194 LogUtils.wtf(TAG, "InterrupedException waiting for EasService to connect"); 195 return false; 196 } 197 if (mEasService == null) { 198 LogUtils.wtf(TAG, "timed out waiting for EasService to connect"); 199 return false; 200 } 201 } 202 } 203 return true; 204 } 205 206 protected final Account getAccountFromAndroidAccount(final android.accounts.Account acct) { 207 final Account emailAccount; 208 emailAccount = Account.restoreAccountWithAddress(this, acct.name); 209 return emailAccount; 210 } 211 212 } 213 214