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.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; 49 50 import java.util.Collection; 51 52 public class SyncAdapterDriver extends Activity 53 implements RegisteredServicesCacheListener<SyncAdapterType>, 54 AdapterView.OnItemClickListener { 55 private Spinner mSyncAdapterSpinner; 56 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(); 66 67 private static final int DIALOG_ID_PICK_ACCOUNT = 1; 68 private ListView mAccountPickerView = null; 69 70 @Override onCreate(Bundle savedInstanceState)71 protected void onCreate(Bundle savedInstanceState) { 72 super.onCreate(savedInstanceState); 73 mSyncAdaptersCache = new SyncAdaptersCache(this); 74 setContentView(R.layout.sync_adapter_driver); 75 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); 80 81 mStartSyncButton = (Button) findViewById(R.id.start_sync_button); 82 mCancelSyncButton = (Button) findViewById(R.id.cancel_sync_button); 83 84 mStatusTextView = (TextView) findViewById(R.id.status_text_view); 85 86 getSyncAdapters(); 87 mSyncAdaptersCache.setListener(this, null /* Handler */); 88 } 89 onDestroy()90 protected void onDestroy() { 91 mSyncAdaptersCache.close(); 92 super.onDestroy(); 93 } 94 getSyncAdapters()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]; 101 int i = 0; 102 for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> item : all) { 103 mSyncAdapters[i] = item; 104 names[i] = item.type.authority + " - " + item.type.accountType; 105 i++; 106 } 107 108 ArrayAdapter<String> adapter = 109 new ArrayAdapter<String>(this, 110 R.layout.sync_adapter_item, names); 111 mSyncAdapterSpinner.setAdapter(adapter); 112 } 113 } 114 updateUi()115 void updateUi() { 116 boolean isBound; 117 boolean hasServiceConnection; 118 synchronized (mServiceConnectionLock) { 119 hasServiceConnection = mActiveServiceConnection != null; 120 isBound = hasServiceConnection && mActiveServiceConnection.mBoundSyncAdapter != null; 121 } 122 mStartSyncButton.setEnabled(isBound); 123 mCancelSyncButton.setEnabled(isBound); 124 mBindButton.setEnabled(!hasServiceConnection); 125 mUnbindButton.setEnabled(hasServiceConnection); 126 } 127 startSyncSelected(View view)128 public void startSyncSelected(View view) { 129 synchronized (mServiceConnectionLock) { 130 ISyncAdapter syncAdapter = null; 131 if (mActiveServiceConnection != null) { 132 syncAdapter = mActiveServiceConnection.mBoundSyncAdapter; 133 } 134 135 if (syncAdapter != null) { 136 removeDialog(DIALOG_ID_PICK_ACCOUNT); 137 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; 146 } 147 ArrayAdapter<String> adapter = 148 new ArrayAdapter<String>(SyncAdapterDriver.this, 149 android.R.layout.simple_list_item_1, accountNames); 150 mAccountPickerView.setAdapter(adapter); 151 152 showDialog(DIALOG_ID_PICK_ACCOUNT); 153 } 154 } 155 updateUi(); 156 } 157 startSync(String accountName)158 private void startSync(String accountName) { 159 synchronized (mServiceConnectionLock) { 160 ISyncAdapter syncAdapter = null; 161 if (mActiveServiceConnection != null) { 162 syncAdapter = mActiveServiceConnection.mBoundSyncAdapter; 163 } 164 165 if (syncAdapter != null) { 166 try { 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)); 177 } 178 } 179 } 180 updateUi(); 181 } 182 cancelSync(View view)183 public void cancelSync(View view) { 184 synchronized (mServiceConnectionLock) { 185 ISyncAdapter syncAdapter = null; 186 if (mActiveServiceConnection != null) { 187 syncAdapter = mActiveServiceConnection.mBoundSyncAdapter; 188 } 189 190 if (syncAdapter != null) { 191 try { 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)); 197 } 198 } 199 } 200 updateUi(); 201 } 202 onServiceChanged(SyncAdapterType type, boolean removed)203 public void onServiceChanged(SyncAdapterType type, boolean removed) { 204 getSyncAdapters(); 205 } 206 207 @Override onCreateDialog(final int id)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(); 215 } 216 return super.onCreateDialog(id); 217 } 218 onItemClick(AdapterView<?> parent, View view, int position, long 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); 224 } 225 226 private class MyServiceConnection extends ISyncContext.Stub implements ServiceConnection { 227 private volatile ISyncAdapter mBoundSyncAdapter; 228 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> mSyncAdapter; 229 MyServiceConnection( RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter)230 public MyServiceConnection( 231 RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter) { 232 mSyncAdapter = syncAdapter; 233 } 234 onServiceConnected(ComponentName name, IBinder service)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)); 240 updateUi(); 241 } 242 onServiceDisconnected(ComponentName name)243 public void onServiceDisconnected(ComponentName name) { 244 mBoundAdapterTextView.setText(getString(R.string.binding_not_connected)); 245 mBoundSyncAdapter = null; 246 updateUi(); 247 } 248 sendHeartbeat()249 public void sendHeartbeat() { 250 runOnUiThread(new Runnable() { 251 public void run() { 252 uiThreadSendHeartbeat(); 253 } 254 }); 255 } 256 uiThreadSendHeartbeat()257 public void uiThreadSendHeartbeat() { 258 mStatusTextView.setText(getString(R.string.status_received_heartbeat)); 259 } 260 uiThreadOnFinished(SyncResult result)261 public void uiThreadOnFinished(SyncResult result) { 262 if (result.hasError()) { 263 mStatusTextView.setText( 264 getString(R.string.status_sync_failed_format, result.toString())); 265 } else { 266 mStatusTextView.setText( 267 getString(R.string.status_sync_succeeded_format, result.toString())); 268 } 269 } 270 onFinished(final SyncResult result)271 public void onFinished(final SyncResult result) throws RemoteException { 272 runOnUiThread(new Runnable() { 273 public void run() { 274 uiThreadOnFinished(result); 275 } 276 }); 277 } 278 } 279 280 final Object mServiceConnectionLock = new Object(); 281 MyServiceConnection mActiveServiceConnection; 282 initiateBind(View view)283 public void initiateBind(View view) { 284 synchronized (mServiceConnectionLock) { 285 if (mActiveServiceConnection != null) { 286 mStatusTextView.setText(getString(R.string.status_already_bound)); 287 return; 288 } 289 290 RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter = 291 getSelectedSyncAdapter(); 292 if (syncAdapter == null) { 293 mStatusTextView.setText(getString(R.string.status_sync_adapter_not_selected)); 294 return; 295 } 296 297 mActiveServiceConnection = new MyServiceConnection(syncAdapter); 298 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; 309 return; 310 } 311 mBoundAdapterTextView.setText(getString(R.string.binding_waiting_for_connection)); 312 } 313 updateUi(); 314 } 315 initiateUnbind(View view)316 public void initiateUnbind(View view) { 317 synchronized (mServiceConnectionLock) { 318 if (mActiveServiceConnection == null) { 319 return; 320 } 321 mBoundAdapterTextView.setText(""); 322 unbindService(mActiveServiceConnection); 323 mActiveServiceConnection = null; 324 } 325 updateUi(); 326 } 327 getSelectedSyncAdapter()328 private RegisteredServicesCache.ServiceInfo<SyncAdapterType> getSelectedSyncAdapter() { 329 synchronized (mSyncAdaptersLock) { 330 final int position = mSyncAdapterSpinner.getSelectedItemPosition(); 331 if (position == AdapterView.INVALID_POSITION) { 332 return null; 333 } 334 try { 335 //noinspection unchecked 336 return (RegisteredServicesCache.ServiceInfo<SyncAdapterType>) 337 mSyncAdapters[position]; 338 } catch (Exception e) { 339 return null; 340 } 341 } 342 } 343 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"; 348 SyncAdaptersCache(Context context)349 SyncAdaptersCache(Context context) { 350 super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, null); 351 } 352 parseServiceAttributes(String packageName, AttributeSet attrs)353 public SyncAdapterType parseServiceAttributes(String packageName, AttributeSet attrs) { 354 TypedArray sa = mContext.getResources().obtainAttributes(attrs, 355 com.android.internal.R.styleable.SyncAdapter); 356 try { 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) { 362 return null; 363 } 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); 369 } finally { 370 sa.recycle(); 371 } 372 } 373 } 374 } 375