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