• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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.development;
18 
19 import android.app.Activity;
20 import android.app.PendingIntent;
21 import android.app.Dialog;
22 import android.app.AlertDialog;
23 import android.content.res.Resources;
24 import android.content.res.TypedArray;
25 import android.content.pm.RegisteredServicesCache;
26 import android.content.pm.RegisteredServicesCacheListener;
27 import android.content.SyncAdapterType;
28 import android.content.ISyncAdapter;
29 import android.content.ISyncContext;
30 import android.content.ServiceConnection;
31 import android.content.ComponentName;
32 import android.content.SyncResult;
33 import android.content.Intent;
34 import android.content.Context;
35 import android.os.Bundle;
36 import android.os.IBinder;
37 import android.os.RemoteException;
38 import android.os.UserHandle;
39 import android.widget.ArrayAdapter;
40 import android.widget.AdapterView;
41 import android.widget.Spinner;
42 import android.widget.Button;
43 import android.widget.TextView;
44 import android.widget.ListView;
45 import android.util.AttributeSet;
46 import android.provider.Settings;
47 import android.accounts.Account;
48 import android.accounts.AccountManager;
49 import android.view.View;
50 import android.view.LayoutInflater;
51 
52 import java.util.Collection;
53 
54 public class SyncAdapterDriver extends Activity
55         implements RegisteredServicesCacheListener<SyncAdapterType>,
56         AdapterView.OnItemClickListener {
57     private Spinner mSyncAdapterSpinner;
58 
59     private Button mBindButton;
60     private Button mUnbindButton;
61     private TextView mBoundAdapterTextView;
62     private Button mStartSyncButton;
63     private Button mCancelSyncButton;
64     private TextView mStatusTextView;
65     private Object[] mSyncAdapters;
66     private SyncAdaptersCache mSyncAdaptersCache;
67     private final Object mSyncAdaptersLock = new Object();
68 
69     private static final int DIALOG_ID_PICK_ACCOUNT = 1;
70     private ListView mAccountPickerView = null;
71 
72     @Override
onCreate(Bundle savedInstanceState)73     protected void onCreate(Bundle savedInstanceState) {
74         super.onCreate(savedInstanceState);
75         mSyncAdaptersCache = new SyncAdaptersCache(this);
76         setContentView(R.layout.sync_adapter_driver);
77 
78         mSyncAdapterSpinner = (Spinner) findViewById(R.id.sync_adapters_spinner);
79         mBindButton = (Button) findViewById(R.id.bind_button);
80         mUnbindButton = (Button) findViewById(R.id.unbind_button);
81         mBoundAdapterTextView = (TextView) findViewById(R.id.bound_adapter_text_view);
82 
83         mStartSyncButton = (Button) findViewById(R.id.start_sync_button);
84         mCancelSyncButton = (Button) findViewById(R.id.cancel_sync_button);
85 
86         mStatusTextView = (TextView) findViewById(R.id.status_text_view);
87 
88         getSyncAdapters();
89         mSyncAdaptersCache.setListener(this, null /* Handler */);
90     }
91 
getSyncAdapters()92     private void getSyncAdapters() {
93         Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> all =
94                 mSyncAdaptersCache.getAllServices(UserHandle.myUserId());
95         synchronized (mSyncAdaptersLock) {
96             mSyncAdapters = new Object[all.size()];
97             String[] names = new String[mSyncAdapters.length];
98             int i = 0;
99             for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> item : all) {
100                 mSyncAdapters[i] = item;
101                 names[i] = item.type.authority + " - " + item.type.accountType;
102                 i++;
103             }
104 
105             ArrayAdapter<String> adapter =
106                     new ArrayAdapter<String>(this,
107                     R.layout.sync_adapter_item, names);
108             mSyncAdapterSpinner.setAdapter(adapter);
109         }
110     }
111 
updateUi()112     void updateUi() {
113         boolean isBound;
114         boolean hasServiceConnection;
115         synchronized (mServiceConnectionLock) {
116             hasServiceConnection = mActiveServiceConnection != null;
117             isBound = hasServiceConnection && mActiveServiceConnection.mBoundSyncAdapter != null;
118         }
119         mStartSyncButton.setEnabled(isBound);
120         mCancelSyncButton.setEnabled(isBound);
121         mBindButton.setEnabled(!hasServiceConnection);
122         mUnbindButton.setEnabled(hasServiceConnection);
123     }
124 
startSyncSelected(View view)125     public void startSyncSelected(View view) {
126         synchronized (mServiceConnectionLock) {
127             ISyncAdapter syncAdapter = null;
128             if (mActiveServiceConnection != null) {
129                 syncAdapter = mActiveServiceConnection.mBoundSyncAdapter;
130             }
131 
132             if (syncAdapter != null) {
133                 removeDialog(DIALOG_ID_PICK_ACCOUNT);
134 
135                 mAccountPickerView = (ListView) LayoutInflater.from(this).inflate(
136                         R.layout.account_list_view, null);
137                 mAccountPickerView.setOnItemClickListener(this);
138                 Account accounts[] = AccountManager.get(this).getAccountsByType(
139                         mActiveServiceConnection.mSyncAdapter.type.accountType);
140                 String[] accountNames = new String[accounts.length];
141                 for (int i = 0; i < accounts.length; i++) {
142                     accountNames[i] = accounts[i].name;
143                 }
144                 ArrayAdapter<String> adapter =
145                         new ArrayAdapter<String>(SyncAdapterDriver.this,
146                         android.R.layout.simple_list_item_1, accountNames);
147                 mAccountPickerView.setAdapter(adapter);
148 
149                 showDialog(DIALOG_ID_PICK_ACCOUNT);
150             }
151         }
152         updateUi();
153     }
154 
startSync(String accountName)155     private void startSync(String accountName) {
156         synchronized (mServiceConnectionLock) {
157             ISyncAdapter syncAdapter = null;
158             if (mActiveServiceConnection != null) {
159                 syncAdapter = mActiveServiceConnection.mBoundSyncAdapter;
160             }
161 
162             if (syncAdapter != null) {
163                 try {
164                     mStatusTextView.setText(
165                             getString(R.string.status_starting_sync_format, accountName));
166                     Account account = new Account(accountName,
167                             mActiveServiceConnection.mSyncAdapter.type.accountType);
168                     syncAdapter.startSync(mActiveServiceConnection,
169                             mActiveServiceConnection.mSyncAdapter.type.authority,
170                             account, new Bundle());
171                 } catch (RemoteException e) {
172                     mStatusTextView.setText(
173                             getString(R.string.status_remote_exception_while_starting_sync));
174                 }
175             }
176         }
177         updateUi();
178     }
179 
cancelSync(View view)180     public void cancelSync(View view) {
181         synchronized (mServiceConnectionLock) {
182             ISyncAdapter syncAdapter = null;
183             if (mActiveServiceConnection != null) {
184                 syncAdapter = mActiveServiceConnection.mBoundSyncAdapter;
185             }
186 
187             if (syncAdapter != null) {
188                 try {
189                     mStatusTextView.setText(getString(R.string.status_canceled_sync));
190                     syncAdapter.cancelSync(mActiveServiceConnection);
191                 } catch (RemoteException e) {
192                     mStatusTextView.setText(
193                             getString(R.string.status_remote_exception_while_canceling_sync));
194                 }
195             }
196         }
197         updateUi();
198     }
199 
onServiceChanged(SyncAdapterType type, int userId, boolean removed)200     public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) {
201         getSyncAdapters();
202     }
203 
204     @Override
onCreateDialog(final int id)205     protected Dialog onCreateDialog(final int id) {
206         if (id == DIALOG_ID_PICK_ACCOUNT) {
207             AlertDialog.Builder builder = new AlertDialog.Builder(this);
208             builder.setMessage(R.string.select_account_to_sync);
209             builder.setInverseBackgroundForced(true);
210             builder.setView(mAccountPickerView);
211             return builder.create();
212         }
213         return super.onCreateDialog(id);
214     }
215 
onItemClick(AdapterView<?> parent, View view, int position, long id)216     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
217         TextView item = (TextView) view;
218         final String accountName = item.getText().toString();
219         dismissDialog(DIALOG_ID_PICK_ACCOUNT);
220         startSync(accountName);
221     }
222 
223     private class MyServiceConnection extends ISyncContext.Stub implements ServiceConnection {
224         private volatile ISyncAdapter mBoundSyncAdapter;
225         final RegisteredServicesCache.ServiceInfo<SyncAdapterType> mSyncAdapter;
226 
MyServiceConnection( RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter)227         public MyServiceConnection(
228                 RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter) {
229             mSyncAdapter = syncAdapter;
230         }
231 
onServiceConnected(ComponentName name, IBinder service)232         public void onServiceConnected(ComponentName name, IBinder service) {
233             mBoundSyncAdapter = ISyncAdapter.Stub.asInterface(service);
234             final SyncAdapterType type = mActiveServiceConnection.mSyncAdapter.type;
235             mBoundAdapterTextView.setText(getString(R.string.binding_connected_format,
236                     type.authority, type.accountType));
237             updateUi();
238         }
239 
onServiceDisconnected(ComponentName name)240         public void onServiceDisconnected(ComponentName name) {
241             mBoundAdapterTextView.setText(getString(R.string.binding_not_connected));
242             mBoundSyncAdapter = null;
243             updateUi();
244         }
245 
sendHeartbeat()246         public void sendHeartbeat() {
247             runOnUiThread(new Runnable() {
248                 public void run() {
249                     uiThreadSendHeartbeat();
250                 }
251             });
252         }
253 
uiThreadSendHeartbeat()254         public void uiThreadSendHeartbeat() {
255             mStatusTextView.setText(getString(R.string.status_received_heartbeat));
256         }
257 
uiThreadOnFinished(SyncResult result)258         public void uiThreadOnFinished(SyncResult result) {
259             if (result.hasError()) {
260                 mStatusTextView.setText(
261                         getString(R.string.status_sync_failed_format, result.toString()));
262             } else {
263                 mStatusTextView.setText(
264                         getString(R.string.status_sync_succeeded_format, result.toString()));
265             }
266         }
267 
onFinished(final SyncResult result)268         public void onFinished(final SyncResult result) throws RemoteException {
269             runOnUiThread(new Runnable() {
270                 public void run() {
271                     uiThreadOnFinished(result);
272                 }
273             });
274         }
275     }
276 
277     final Object mServiceConnectionLock = new Object();
278     MyServiceConnection mActiveServiceConnection;
279 
initiateBind(View view)280     public void initiateBind(View view) {
281         synchronized (mServiceConnectionLock) {
282             if (mActiveServiceConnection != null) {
283                 mStatusTextView.setText(getString(R.string.status_already_bound));
284                 return;
285             }
286 
287             RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter =
288                     getSelectedSyncAdapter();
289             if (syncAdapter == null) {
290                 mStatusTextView.setText(getString(R.string.status_sync_adapter_not_selected));
291                 return;
292             }
293 
294             mActiveServiceConnection = new MyServiceConnection(syncAdapter);
295 
296             Intent intent = new Intent();
297             intent.setAction("android.content.SyncAdapter");
298             intent.setComponent(syncAdapter.componentName);
299             intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
300                     com.android.internal.R.string.sync_binding_label);
301             intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
302                     this, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0));
303             if (!bindService(intent, mActiveServiceConnection, Context.BIND_AUTO_CREATE)) {
304                 mBoundAdapterTextView.setText(getString(R.string.binding_bind_failed));
305                 mActiveServiceConnection = null;
306                 return;
307             }
308             mBoundAdapterTextView.setText(getString(R.string.binding_waiting_for_connection));
309         }
310         updateUi();
311     }
312 
initiateUnbind(View view)313     public void initiateUnbind(View view) {
314         synchronized (mServiceConnectionLock) {
315             if (mActiveServiceConnection == null) {
316                 return;
317             }
318             mBoundAdapterTextView.setText("");
319             unbindService(mActiveServiceConnection);
320             mActiveServiceConnection = null;
321         }
322         updateUi();
323     }
324 
getSelectedSyncAdapter()325     private RegisteredServicesCache.ServiceInfo<SyncAdapterType> getSelectedSyncAdapter() {
326         synchronized (mSyncAdaptersLock) {
327             final int position = mSyncAdapterSpinner.getSelectedItemPosition();
328             if (position == AdapterView.INVALID_POSITION) {
329                 return null;
330             }
331             try {
332                 //noinspection unchecked
333                 return (RegisteredServicesCache.ServiceInfo<SyncAdapterType>)
334                         mSyncAdapters[position];
335             } catch (Exception e) {
336                 return null;
337             }
338         }
339     }
340 
341     static class SyncAdaptersCache extends RegisteredServicesCache<SyncAdapterType> {
342         private static final String SERVICE_INTERFACE = "android.content.SyncAdapter";
343         private static final String SERVICE_META_DATA = "android.content.SyncAdapter";
344         private static final String ATTRIBUTES_NAME = "sync-adapter";
345 
SyncAdaptersCache(Context context)346         SyncAdaptersCache(Context context) {
347             super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, null);
348         }
349 
parseServiceAttributes(Resources res, String packageName, AttributeSet attrs)350         public SyncAdapterType parseServiceAttributes(Resources res,
351                 String packageName, AttributeSet attrs) {
352             TypedArray sa = res.obtainAttributes(attrs,
353                     com.android.internal.R.styleable.SyncAdapter);
354             try {
355                 final String authority =
356                         sa.getString(com.android.internal.R.styleable.SyncAdapter_contentAuthority);
357                 final String accountType =
358                         sa.getString(com.android.internal.R.styleable.SyncAdapter_accountType);
359                 if (authority == null || accountType == null) {
360                     return null;
361                 }
362                 final boolean userVisible = sa.getBoolean(
363                         com.android.internal.R.styleable.SyncAdapter_userVisible, true);
364                 final boolean supportsUploading = sa.getBoolean(
365                         com.android.internal.R.styleable.SyncAdapter_supportsUploading, true);
366                 final boolean isAlwaysSyncable = sa.getBoolean(
367                         com.android.internal.R.styleable.SyncAdapter_isAlwaysSyncable, false);
368                 final boolean allowParallelSyncs = sa.getBoolean(
369                         com.android.internal.R.styleable.SyncAdapter_allowParallelSyncs, false);
370                 final String settingsActivity =
371                         sa.getString(com.android.internal.R.styleable
372                                 .SyncAdapter_settingsActivity);
373                 // TODO: Why is this using private API?
374                 return new SyncAdapterType(authority, accountType, userVisible, supportsUploading,
375                         isAlwaysSyncable, allowParallelSyncs, settingsActivity,
376                         mContext.getPackageName());
377             } finally {
378                 sa.recycle();
379             }
380         }
381     }
382 }
383