| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.development; |
| |
| import android.app.Activity; |
| import android.app.PendingIntent; |
| import android.app.Dialog; |
| import android.app.AlertDialog; |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.content.pm.RegisteredServicesCache; |
| import android.content.pm.RegisteredServicesCacheListener; |
| import android.content.SyncAdapterType; |
| import android.content.ISyncAdapter; |
| import android.content.ISyncContext; |
| import android.content.ServiceConnection; |
| import android.content.ComponentName; |
| import android.content.SyncResult; |
| import android.content.Intent; |
| import android.content.Context; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.widget.ArrayAdapter; |
| import android.widget.AdapterView; |
| import android.widget.Spinner; |
| import android.widget.Button; |
| import android.widget.TextView; |
| import android.widget.ListView; |
| import android.util.AttributeSet; |
| import android.provider.Settings; |
| import android.accounts.Account; |
| import android.accounts.AccountManager; |
| import android.view.View; |
| import android.view.LayoutInflater; |
| |
| import java.util.Collection; |
| |
| public class SyncAdapterDriver extends Activity |
| implements RegisteredServicesCacheListener<SyncAdapterType>, |
| AdapterView.OnItemClickListener { |
| private Spinner mSyncAdapterSpinner; |
| |
| private Button mBindButton; |
| private Button mUnbindButton; |
| private TextView mBoundAdapterTextView; |
| private Button mStartSyncButton; |
| private Button mCancelSyncButton; |
| private TextView mStatusTextView; |
| private Object[] mSyncAdapters; |
| private SyncAdaptersCache mSyncAdaptersCache; |
| private final Object mSyncAdaptersLock = new Object(); |
| |
| private static final int DIALOG_ID_PICK_ACCOUNT = 1; |
| private ListView mAccountPickerView = null; |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| mSyncAdaptersCache = new SyncAdaptersCache(this); |
| setContentView(R.layout.sync_adapter_driver); |
| |
| mSyncAdapterSpinner = (Spinner) findViewById(R.id.sync_adapters_spinner); |
| mBindButton = (Button) findViewById(R.id.bind_button); |
| mUnbindButton = (Button) findViewById(R.id.unbind_button); |
| mBoundAdapterTextView = (TextView) findViewById(R.id.bound_adapter_text_view); |
| |
| mStartSyncButton = (Button) findViewById(R.id.start_sync_button); |
| mCancelSyncButton = (Button) findViewById(R.id.cancel_sync_button); |
| |
| mStatusTextView = (TextView) findViewById(R.id.status_text_view); |
| |
| getSyncAdapters(); |
| mSyncAdaptersCache.setListener(this, null /* Handler */); |
| } |
| |
| private void getSyncAdapters() { |
| Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> all = |
| mSyncAdaptersCache.getAllServices(UserHandle.myUserId()); |
| synchronized (mSyncAdaptersLock) { |
| mSyncAdapters = new Object[all.size()]; |
| String[] names = new String[mSyncAdapters.length]; |
| int i = 0; |
| for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> item : all) { |
| mSyncAdapters[i] = item; |
| names[i] = item.type.authority + " - " + item.type.accountType; |
| i++; |
| } |
| |
| ArrayAdapter<String> adapter = |
| new ArrayAdapter<String>(this, |
| R.layout.sync_adapter_item, names); |
| mSyncAdapterSpinner.setAdapter(adapter); |
| } |
| } |
| |
| void updateUi() { |
| boolean isBound; |
| boolean hasServiceConnection; |
| synchronized (mServiceConnectionLock) { |
| hasServiceConnection = mActiveServiceConnection != null; |
| isBound = hasServiceConnection && mActiveServiceConnection.mBoundSyncAdapter != null; |
| } |
| mStartSyncButton.setEnabled(isBound); |
| mCancelSyncButton.setEnabled(isBound); |
| mBindButton.setEnabled(!hasServiceConnection); |
| mUnbindButton.setEnabled(hasServiceConnection); |
| } |
| |
| public void startSyncSelected(View view) { |
| synchronized (mServiceConnectionLock) { |
| ISyncAdapter syncAdapter = null; |
| if (mActiveServiceConnection != null) { |
| syncAdapter = mActiveServiceConnection.mBoundSyncAdapter; |
| } |
| |
| if (syncAdapter != null) { |
| removeDialog(DIALOG_ID_PICK_ACCOUNT); |
| |
| mAccountPickerView = (ListView) LayoutInflater.from(this).inflate( |
| R.layout.account_list_view, null); |
| mAccountPickerView.setOnItemClickListener(this); |
| Account accounts[] = AccountManager.get(this).getAccountsByType( |
| mActiveServiceConnection.mSyncAdapter.type.accountType); |
| String[] accountNames = new String[accounts.length]; |
| for (int i = 0; i < accounts.length; i++) { |
| accountNames[i] = accounts[i].name; |
| } |
| ArrayAdapter<String> adapter = |
| new ArrayAdapter<String>(SyncAdapterDriver.this, |
| android.R.layout.simple_list_item_1, accountNames); |
| mAccountPickerView.setAdapter(adapter); |
| |
| showDialog(DIALOG_ID_PICK_ACCOUNT); |
| } |
| } |
| updateUi(); |
| } |
| |
| private void startSync(String accountName) { |
| synchronized (mServiceConnectionLock) { |
| ISyncAdapter syncAdapter = null; |
| if (mActiveServiceConnection != null) { |
| syncAdapter = mActiveServiceConnection.mBoundSyncAdapter; |
| } |
| |
| if (syncAdapter != null) { |
| try { |
| mStatusTextView.setText( |
| getString(R.string.status_starting_sync_format, accountName)); |
| Account account = new Account(accountName, |
| mActiveServiceConnection.mSyncAdapter.type.accountType); |
| syncAdapter.startSync(mActiveServiceConnection, |
| mActiveServiceConnection.mSyncAdapter.type.authority, |
| account, new Bundle()); |
| } catch (RemoteException e) { |
| mStatusTextView.setText( |
| getString(R.string.status_remote_exception_while_starting_sync)); |
| } |
| } |
| } |
| updateUi(); |
| } |
| |
| public void cancelSync(View view) { |
| synchronized (mServiceConnectionLock) { |
| ISyncAdapter syncAdapter = null; |
| if (mActiveServiceConnection != null) { |
| syncAdapter = mActiveServiceConnection.mBoundSyncAdapter; |
| } |
| |
| if (syncAdapter != null) { |
| try { |
| mStatusTextView.setText(getString(R.string.status_canceled_sync)); |
| syncAdapter.cancelSync(mActiveServiceConnection); |
| } catch (RemoteException e) { |
| mStatusTextView.setText( |
| getString(R.string.status_remote_exception_while_canceling_sync)); |
| } |
| } |
| } |
| updateUi(); |
| } |
| |
| public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) { |
| getSyncAdapters(); |
| } |
| |
| @Override |
| protected Dialog onCreateDialog(final int id) { |
| if (id == DIALOG_ID_PICK_ACCOUNT) { |
| AlertDialog.Builder builder = new AlertDialog.Builder(this); |
| builder.setMessage(R.string.select_account_to_sync); |
| builder.setInverseBackgroundForced(true); |
| builder.setView(mAccountPickerView); |
| return builder.create(); |
| } |
| return super.onCreateDialog(id); |
| } |
| |
| public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| TextView item = (TextView) view; |
| final String accountName = item.getText().toString(); |
| dismissDialog(DIALOG_ID_PICK_ACCOUNT); |
| startSync(accountName); |
| } |
| |
| private class MyServiceConnection extends ISyncContext.Stub implements ServiceConnection { |
| private volatile ISyncAdapter mBoundSyncAdapter; |
| final RegisteredServicesCache.ServiceInfo<SyncAdapterType> mSyncAdapter; |
| |
| public MyServiceConnection( |
| RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter) { |
| mSyncAdapter = syncAdapter; |
| } |
| |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| mBoundSyncAdapter = ISyncAdapter.Stub.asInterface(service); |
| final SyncAdapterType type = mActiveServiceConnection.mSyncAdapter.type; |
| mBoundAdapterTextView.setText(getString(R.string.binding_connected_format, |
| type.authority, type.accountType)); |
| updateUi(); |
| } |
| |
| public void onServiceDisconnected(ComponentName name) { |
| mBoundAdapterTextView.setText(getString(R.string.binding_not_connected)); |
| mBoundSyncAdapter = null; |
| updateUi(); |
| } |
| |
| public void sendHeartbeat() { |
| runOnUiThread(new Runnable() { |
| public void run() { |
| uiThreadSendHeartbeat(); |
| } |
| }); |
| } |
| |
| public void uiThreadSendHeartbeat() { |
| mStatusTextView.setText(getString(R.string.status_received_heartbeat)); |
| } |
| |
| public void uiThreadOnFinished(SyncResult result) { |
| if (result.hasError()) { |
| mStatusTextView.setText( |
| getString(R.string.status_sync_failed_format, result.toString())); |
| } else { |
| mStatusTextView.setText( |
| getString(R.string.status_sync_succeeded_format, result.toString())); |
| } |
| } |
| |
| public void onFinished(final SyncResult result) throws RemoteException { |
| runOnUiThread(new Runnable() { |
| public void run() { |
| uiThreadOnFinished(result); |
| } |
| }); |
| } |
| } |
| |
| final Object mServiceConnectionLock = new Object(); |
| MyServiceConnection mActiveServiceConnection; |
| |
| public void initiateBind(View view) { |
| synchronized (mServiceConnectionLock) { |
| if (mActiveServiceConnection != null) { |
| mStatusTextView.setText(getString(R.string.status_already_bound)); |
| return; |
| } |
| |
| RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter = |
| getSelectedSyncAdapter(); |
| if (syncAdapter == null) { |
| mStatusTextView.setText(getString(R.string.status_sync_adapter_not_selected)); |
| return; |
| } |
| |
| mActiveServiceConnection = new MyServiceConnection(syncAdapter); |
| |
| Intent intent = new Intent(); |
| intent.setAction("android.content.SyncAdapter"); |
| intent.setComponent(syncAdapter.componentName); |
| intent.putExtra(Intent.EXTRA_CLIENT_LABEL, |
| com.android.internal.R.string.sync_binding_label); |
| intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( |
| this, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0)); |
| if (!bindService(intent, mActiveServiceConnection, Context.BIND_AUTO_CREATE)) { |
| mBoundAdapterTextView.setText(getString(R.string.binding_bind_failed)); |
| mActiveServiceConnection = null; |
| return; |
| } |
| mBoundAdapterTextView.setText(getString(R.string.binding_waiting_for_connection)); |
| } |
| updateUi(); |
| } |
| |
| public void initiateUnbind(View view) { |
| synchronized (mServiceConnectionLock) { |
| if (mActiveServiceConnection == null) { |
| return; |
| } |
| mBoundAdapterTextView.setText(""); |
| unbindService(mActiveServiceConnection); |
| mActiveServiceConnection = null; |
| } |
| updateUi(); |
| } |
| |
| private RegisteredServicesCache.ServiceInfo<SyncAdapterType> getSelectedSyncAdapter() { |
| synchronized (mSyncAdaptersLock) { |
| final int position = mSyncAdapterSpinner.getSelectedItemPosition(); |
| if (position == AdapterView.INVALID_POSITION) { |
| return null; |
| } |
| try { |
| //noinspection unchecked |
| return (RegisteredServicesCache.ServiceInfo<SyncAdapterType>) |
| mSyncAdapters[position]; |
| } catch (Exception e) { |
| return null; |
| } |
| } |
| } |
| |
| static class SyncAdaptersCache extends RegisteredServicesCache<SyncAdapterType> { |
| private static final String SERVICE_INTERFACE = "android.content.SyncAdapter"; |
| private static final String SERVICE_META_DATA = "android.content.SyncAdapter"; |
| private static final String ATTRIBUTES_NAME = "sync-adapter"; |
| |
| SyncAdaptersCache(Context context) { |
| super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, null); |
| } |
| |
| public SyncAdapterType parseServiceAttributes(Resources res, |
| String packageName, AttributeSet attrs) { |
| TypedArray sa = res.obtainAttributes(attrs, |
| com.android.internal.R.styleable.SyncAdapter); |
| try { |
| final String authority = |
| sa.getString(com.android.internal.R.styleable.SyncAdapter_contentAuthority); |
| final String accountType = |
| sa.getString(com.android.internal.R.styleable.SyncAdapter_accountType); |
| if (authority == null || accountType == null) { |
| return null; |
| } |
| final boolean userVisible = sa.getBoolean( |
| com.android.internal.R.styleable.SyncAdapter_userVisible, true); |
| final boolean supportsUploading = sa.getBoolean( |
| com.android.internal.R.styleable.SyncAdapter_supportsUploading, true); |
| final boolean isAlwaysSyncable = sa.getBoolean( |
| com.android.internal.R.styleable.SyncAdapter_isAlwaysSyncable, false); |
| final boolean allowParallelSyncs = sa.getBoolean( |
| com.android.internal.R.styleable.SyncAdapter_allowParallelSyncs, false); |
| final String settingsActivity = |
| sa.getString(com.android.internal.R.styleable |
| .SyncAdapter_settingsActivity); |
| // TODO: Why is this using private API? |
| return new SyncAdapterType(authority, accountType, userVisible, supportsUploading, |
| isAlwaysSyncable, allowParallelSyncs, settingsActivity, |
| mContext.getPackageName()); |
| } finally { |
| sa.recycle(); |
| } |
| } |
| } |
| } |