Home | History | Annotate | Download | only in development
      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