blob: b11e7c7fd36d4cf0b408a32ffface8256a796dd9 [file] [log] [blame]
/*
* 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();
}
}
}
}