2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.development;
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.TypedArray;
24 import android.content.pm.RegisteredServicesCache;
25 import android.content.pm.RegisteredServicesCacheListener;
26 import android.content.SyncAdapterType;
27 import android.content.ISyncAdapter;
28 import android.content.ISyncContext;
29 import android.content.ServiceConnection;
30 import android.content.ComponentName;
31 import android.content.SyncResult;
32 import android.content.Intent;
33 import android.content.Context;
34 import android.os.Bundle;
35 import android.os.IBinder;
36 import android.os.RemoteException;
37 import android.widget.ArrayAdapter;
38 import android.widget.AdapterView;
39 import android.widget.Spinner;
40 import android.widget.Button;
41 import android.widget.TextView;
42 import android.widget.ListView;
43 import android.util.AttributeSet;
44 import android.provider.Settings;
45 import android.accounts.Account;
46 import android.accounts.AccountManager;
47 import android.view.View;
48 import android.view.LayoutInflater;
50 import java.util.Collection;
52 public class SyncAdapterDriver extends Activity
53 implements RegisteredServicesCacheListener<SyncAdapterType>,
54 AdapterView.OnItemClickListener {
55 private Spinner mSyncAdapterSpinner;
57 private Button mBindButton;
58 private Button mUnbindButton;
59 private TextView mBoundAdapterTextView;
60 private Button mStartSyncButton;
61 private Button mCancelSyncButton;
62 private TextView mStatusTextView;
63 private Object[] mSyncAdapters;
64 private SyncAdaptersCache mSyncAdaptersCache;
65 private final Object mSyncAdaptersLock = new Object();
67 private static final int DIALOG_ID_PICK_ACCOUNT = 1;
68 private ListView mAccountPickerView = null;
71 protected void onCreate(Bundle savedInstanceState) {
72 super.onCreate(savedInstanceState);
73 mSyncAdaptersCache = new SyncAdaptersCache(this);
74 setContentView(R.layout.sync_adapter_driver);
76 mSyncAdapterSpinner = (Spinner) findViewById(R.id.sync_adapters_spinner);
77 mBindButton = (Button) findViewById(R.id.bind_button);
78 mUnbindButton = (Button) findViewById(R.id.unbind_button);
79 mBoundAdapterTextView = (TextView) findViewById(R.id.bound_adapter_text_view);
81 mStartSyncButton = (Button) findViewById(R.id.start_sync_button);
82 mCancelSyncButton = (Button) findViewById(R.id.cancel_sync_button);
84 mStatusTextView = (TextView) findViewById(R.id.status_text_view);
87 mSyncAdaptersCache.setListener(this, null /* Handler */);
90 protected void onDestroy() {
91 mSyncAdaptersCache.close();
95 private void getSyncAdapters() {
96 Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> all =
97 mSyncAdaptersCache.getAllServices();
98 synchronized (mSyncAdaptersLock) {
99 mSyncAdapters = new Object[all.size()];
100 String[] names = new String[mSyncAdapters.length];
102 for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> item : all) {
103 mSyncAdapters[i] = item;
104 names[i] = item.type.authority + " - " + item.type.accountType;
108 ArrayAdapter<String> adapter =
109 new ArrayAdapter<String>(this,
110 R.layout.sync_adapter_item, names);
111 mSyncAdapterSpinner.setAdapter(adapter);
117 boolean hasServiceConnection;
118 synchronized (mServiceConnectionLock) {
119 hasServiceConnection = mActiveServiceConnection != null;
120 isBound = hasServiceConnection && mActiveServiceConnection.mBoundSyncAdapter != null;
122 mStartSyncButton.setEnabled(isBound);
123 mCancelSyncButton.setEnabled(isBound);
124 mBindButton.setEnabled(!hasServiceConnection);
125 mUnbindButton.setEnabled(hasServiceConnection);
128 public void startSyncSelected(View view) {
129 synchronized (mServiceConnectionLock) {
130 ISyncAdapter syncAdapter = null;
131 if (mActiveServiceConnection != null) {
132 syncAdapter = mActiveServiceConnection.mBoundSyncAdapter;
135 if (syncAdapter != null) {
136 removeDialog(DIALOG_ID_PICK_ACCOUNT);
138 mAccountPickerView = (ListView) LayoutInflater.from(this).inflate(
139 R.layout.account_list_view, null);
140 mAccountPickerView.setOnItemClickListener(this);
141 Account accounts[] = AccountManager.get(this).getAccountsByType(
142 mActiveServiceConnection.mSyncAdapter.type.accountType);
143 String[] accountNames = new String[accounts.length];
144 for (int i = 0; i < accounts.length; i++) {
145 accountNames[i] = accounts[i].name;
147 ArrayAdapter<String> adapter =
148 new ArrayAdapter<String>(SyncAdapterDriver.this,
149 android.R.layout.simple_list_item_1, accountNames);
150 mAccountPickerView.setAdapter(adapter);
152 showDialog(DIALOG_ID_PICK_ACCOUNT);
158 private void startSync(String accountName) {
159 synchronized (mServiceConnectionLock) {
160 ISyncAdapter syncAdapter = null;
161 if (mActiveServiceConnection != null) {
162 syncAdapter = mActiveServiceConnection.mBoundSyncAdapter;
165 if (syncAdapter != null) {
167 mStatusTextView.setText(
168 getString(R.string.status_starting_sync_format, accountName));
169 Account account = new Account(accountName,
170 mActiveServiceConnection.mSyncAdapter.type.accountType);
171 syncAdapter.startSync(mActiveServiceConnection,
172 mActiveServiceConnection.mSyncAdapter.type.authority,
173 account, new Bundle());
174 } catch (RemoteException e) {
175 mStatusTextView.setText(
176 getString(R.string.status_remote_exception_while_starting_sync));
183 public void cancelSync(View view) {
184 synchronized (mServiceConnectionLock) {
185 ISyncAdapter syncAdapter = null;
186 if (mActiveServiceConnection != null) {
187 syncAdapter = mActiveServiceConnection.mBoundSyncAdapter;
190 if (syncAdapter != null) {
192 mStatusTextView.setText(getString(R.string.status_canceled_sync));
193 syncAdapter.cancelSync(mActiveServiceConnection);
194 } catch (RemoteException e) {
195 mStatusTextView.setText(
196 getString(R.string.status_remote_exception_while_canceling_sync));
203 public void onServiceChanged(SyncAdapterType type, boolean removed) {
208 protected Dialog onCreateDialog(final int id) {
209 if (id == DIALOG_ID_PICK_ACCOUNT) {
210 AlertDialog.Builder builder = new AlertDialog.Builder(this);
211 builder.setMessage(R.string.select_account_to_sync);
212 builder.setInverseBackgroundForced(true);
213 builder.setView(mAccountPickerView);
214 return builder.create();
216 return super.onCreateDialog(id);
219 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
220 TextView item = (TextView) view;
221 final String accountName = item.getText().toString();
222 dismissDialog(DIALOG_ID_PICK_ACCOUNT);
223 startSync(accountName);
226 private class MyServiceConnection extends ISyncContext.Stub implements ServiceConnection {
227 private volatile ISyncAdapter mBoundSyncAdapter;
228 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> mSyncAdapter;
230 public MyServiceConnection(
231 RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter) {
232 mSyncAdapter = syncAdapter;
235 public void onServiceConnected(ComponentName name, IBinder service) {
236 mBoundSyncAdapter = ISyncAdapter.Stub.asInterface(service);
237 final SyncAdapterType type = mActiveServiceConnection.mSyncAdapter.type;
238 mBoundAdapterTextView.setText(getString(R.string.binding_connected_format,
239 type.authority, type.accountType));
243 public void onServiceDisconnected(ComponentName name) {
244 mBoundAdapterTextView.setText(getString(R.string.binding_not_connected));
245 mBoundSyncAdapter = null;
249 public void sendHeartbeat() {
250 runOnUiThread(new Runnable() {
252 uiThreadSendHeartbeat();
257 public void uiThreadSendHeartbeat() {
258 mStatusTextView.setText(getString(R.string.status_received_heartbeat));
261 public void uiThreadOnFinished(SyncResult result) {
262 if (result.hasError()) {
263 mStatusTextView.setText(
264 getString(R.string.status_sync_failed_format, result.toString()));
266 mStatusTextView.setText(
267 getString(R.string.status_sync_succeeded_format, result.toString()));
271 public void onFinished(final SyncResult result) throws RemoteException {
272 runOnUiThread(new Runnable() {
274 uiThreadOnFinished(result);
280 final Object mServiceConnectionLock = new Object();
281 MyServiceConnection mActiveServiceConnection;
283 public void initiateBind(View view) {
284 synchronized (mServiceConnectionLock) {
285 if (mActiveServiceConnection != null) {
286 mStatusTextView.setText(getString(R.string.status_already_bound));
290 RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter =
291 getSelectedSyncAdapter();
292 if (syncAdapter == null) {
293 mStatusTextView.setText(getString(R.string.status_sync_adapter_not_selected));
297 mActiveServiceConnection = new MyServiceConnection(syncAdapter);
299 Intent intent = new Intent();
300 intent.setAction("android.content.SyncAdapter");
301 intent.setComponent(syncAdapter.componentName);
302 intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
303 com.android.internal.R.string.sync_binding_label);
304 intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
305 this, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0));
306 if (!bindService(intent, mActiveServiceConnection, Context.BIND_AUTO_CREATE)) {
307 mBoundAdapterTextView.setText(getString(R.string.binding_bind_failed));
308 mActiveServiceConnection = null;
311 mBoundAdapterTextView.setText(getString(R.string.binding_waiting_for_connection));
316 public void initiateUnbind(View view) {
317 synchronized (mServiceConnectionLock) {
318 if (mActiveServiceConnection == null) {
321 mBoundAdapterTextView.setText("");
322 unbindService(mActiveServiceConnection);
323 mActiveServiceConnection = null;
328 private RegisteredServicesCache.ServiceInfo<SyncAdapterType> getSelectedSyncAdapter() {
329 synchronized (mSyncAdaptersLock) {
330 final int position = mSyncAdapterSpinner.getSelectedItemPosition();
331 if (position == AdapterView.INVALID_POSITION) {
335 //noinspection unchecked
336 return (RegisteredServicesCache.ServiceInfo<SyncAdapterType>)
337 mSyncAdapters[position];
338 } catch (Exception e) {
344 static class SyncAdaptersCache extends RegisteredServicesCache<SyncAdapterType> {
345 private static final String SERVICE_INTERFACE = "android.content.SyncAdapter";
346 private static final String SERVICE_META_DATA = "android.content.SyncAdapter";
347 private static final String ATTRIBUTES_NAME = "sync-adapter";
349 SyncAdaptersCache(Context context) {
350 super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, null);
353 public SyncAdapterType parseServiceAttributes(String packageName, AttributeSet attrs) {
354 TypedArray sa = mContext.getResources().obtainAttributes(attrs,
355 com.android.internal.R.styleable.SyncAdapter);
357 final String authority =
358 sa.getString(com.android.internal.R.styleable.SyncAdapter_contentAuthority);
359 final String accountType =
360 sa.getString(com.android.internal.R.styleable.SyncAdapter_accountType);
361 if (authority == null || accountType == null) {
364 final boolean userVisible = sa.getBoolean(
365 com.android.internal.R.styleable.SyncAdapter_userVisible, true);
366 final boolean supportsUploading = sa.getBoolean(
367 com.android.internal.R.styleable.SyncAdapter_supportsUploading, true);
368 return new SyncAdapterType(authority, accountType, userVisible, supportsUploading);