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 
AbstractSyncAdapterService()57     public AbstractSyncAdapterService() {
58         super();
59     }
60 
61     protected IEmailService mEasService;
62     protected ServiceConnection mConnection;
63 
64     @Override
onCreate()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
onDestroy()88     public void onDestroy() {
89         super.onDestroy();
90         unbindService(mConnection);
91     }
92 
93     @Override
onBind(Intent intent)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      */
getSyncAdapter()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      */
createAccountSettingsIntent(long accountId, String accountName)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 
showAuthNotification(long accountId, String accountName)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      */
writeResultToSyncResult(final int result, final SyncResult syncResult)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 
waitForService()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 
getAccountFromAndroidAccount(final android.accounts.Account acct)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