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 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 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 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 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 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 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 200 public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) { 201 getSyncAdapters(); 202 } 203 204 @Override 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 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 227 public MyServiceConnection( 228 RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter) { 229 mSyncAdapter = syncAdapter; 230 } 231 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 240 public void onServiceDisconnected(ComponentName name) { 241 mBoundAdapterTextView.setText(getString(R.string.binding_not_connected)); 242 mBoundSyncAdapter = null; 243 updateUi(); 244 } 245 246 public void sendHeartbeat() { 247 runOnUiThread(new Runnable() { 248 public void run() { 249 uiThreadSendHeartbeat(); 250 } 251 }); 252 } 253 254 public void uiThreadSendHeartbeat() { 255 mStatusTextView.setText(getString(R.string.status_received_heartbeat)); 256 } 257 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 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 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 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 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 346 SyncAdaptersCache(Context context) { 347 super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, null); 348 } 349 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