create a test app that allows direct control of a sync adapter for testing purposes

http://b/issue?id=2239791
This commit is contained in:
Fred Quintana
2009-11-03 17:18:33 -08:00
parent 8d700bdf67
commit d7a3ea819d
6 changed files with 534 additions and 0 deletions

View File

@@ -84,6 +84,14 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="SyncAdapterDriver" android:label="Sync Tester"
android:theme="@android:style/Theme.Light">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.TEST" />
</intent-filter>
</activity>
<activity android:name="DataList"> <activity android:name="DataList">
</activity> </activity>
<activity android:name="Details"> <activity android:name="Details">

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<ListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:id="@+id/sync_adapters_spinner_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22dip"
android:text="@string/sync_adapters_spinner_label"/>
<Spinner android:id="@+id/sync_adapters_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="52dip">
<Button
android:id="@+id/bind_button"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:text="@string/bind_button"
android:onClick="initiateBind"
android:layout_weight="2"/>
<Button
android:id="@+id/unbind_button"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:text="@string/unbind_button"
android:onClick="initiateUnbind"
android:layout_weight="2"/>
</LinearLayout>
<TextView android:id="@+id/bound_adapter_text_view"
android:layout_width="wrap_content"
android:textSize="20dip"
android:layout_height="wrap_content"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="52dip">
<Button
android:id="@+id/start_sync_button"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:text="@string/start_sync_button"
android:onClick="startSyncSelected"
android:layout_weight="2"/>
<Button
android:id="@+id/cancel_sync_button"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:text="@string/cancel_sync_button"
android:onClick="cancelSync"
android:layout_weight="2"/>
</LinearLayout>
<TextView android:id="@+id/status_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 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.
*/
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
style="?android:attr/spinnerItemStyle"
android:singleLine="true"
android:layout_width="fill_parent"
android:layout_height="52dip"
android:ellipsize="marquee" />

View File

@@ -155,4 +155,25 @@
<string name="accounts_tester_edit_properties">Properties</string> <string name="accounts_tester_edit_properties">Properties</string>
<string name="accounts_tester_desired_authtokentype_label">authtoken type:</string> <string name="accounts_tester_desired_authtokentype_label">authtoken type:</string>
<string name="accounts_tester_desired_features_label">features:</string> <string name="accounts_tester_desired_features_label">features:</string>
<!-- SyncAdapterDriver -->
<string name="bind_button">bind</string>
<string name="unbind_button">unbind</string>
<string name="start_sync_button">start sync</string>
<string name="cancel_sync_button">cancel sync</string>
<string name="sync_adapters_spinner_label">Registered Sync Adapters:</string>
<string name="status_starting_sync_format">Starting a sync of account %s...</string>
<string name="status_remote_exception_while_starting_sync">Got a RemoteException while starting the sync</string>
<string name="status_canceled_sync">Canceled the sync</string>
<string name="status_remote_exception_while_canceling_sync">Got a RemoteException while canceling the sync</string>
<string name="status_received_heartbeat">Received heartbeat</string>
<string name="status_sync_failed_format">Sync failed: %s</string>
<string name="status_sync_succeeded_format">Sync succeeded: %s</string>
<string name="status_already_bound">Already bound to sync adapter</string>
<string name="status_sync_adapter_not_selected">No selected sync adapter</string>
<string name="binding_connected_format">Connected to Sync Adapter\n\tauthority: %s\n\taccount type: %s</string>
<string name="binding_not_connected">Not connected to a sync adapter</string>
<string name="binding_bind_failed">Bind failed</string>
<string name="binding_waiting_for_connection">Waiting for service to be connected...</string>
<string name="select_account_to_sync">Select account to sync</string>
</resources> </resources>

View File

@@ -0,0 +1,373 @@
/*
* 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.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.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, 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);
}
protected void onDestroy() {
mSyncAdaptersCache.close();
super.onDestroy();
}
private void getSyncAdapters() {
Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> all =
mSyncAdaptersCache.getAllServices();
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 onRegisteredServicesCacheChanged() {
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);
}
public SyncAdapterType parseServiceAttributes(String packageName, AttributeSet attrs) {
TypedArray sa = mContext.getResources().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);
return new SyncAdapterType(authority, accountType, userVisible, supportsUploading);
} finally {
sa.recycle();
}
}
}
}