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