Merge "- Some change on the DrmManager in order to support feature request 4082089. In DrmManager, we currently lock both processDrmInfo() and onInfo() which is ok for now since processDrmInfo() is async call, and it will return without waiting for onInfo() call. However, if we send an event in processDrmInfo(), we will got deadlock here because we need to invoke onInf() which will wait for processDrmInfo() to release the lock. Use different lock for onInfo(). - Remove some redundent mutex lock." into honeycomb-mr1
diff --git a/api/current.xml b/api/current.xml
index fb4cdde0..5bc2dd3 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -28643,6 +28643,17 @@
visibility="public"
>
</method>
+<method name="dismissAllowingStateLoss"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="getDialog"
return="android.app.Dialog"
abstract="false"
@@ -113283,7 +113294,7 @@
<parameter name="objectHandle" type="int">
</parameter>
</method>
-<method name="getStorageID"
+<method name="getStorageId"
return="long"
abstract="false"
native="false"
@@ -267031,7 +267042,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="t" type="T">
+<parameter name="arg0" type="T">
</parameter>
</method>
</interface>
diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java
index dee1ef3..cce7cd6 100644
--- a/core/java/android/app/DialogFragment.java
+++ b/core/java/android/app/DialogFragment.java
@@ -258,6 +258,16 @@
dismissInternal(false);
}
+ /**
+ * Version of {@link #dismiss()} that uses
+ * {@link FragmentTransaction#commitAllowingStateLoss()
+ * FragmentTransaction.commitAllowingStateLoss()}. See linked
+ * documentation for further details.
+ */
+ public void dismissAllowingStateLoss() {
+ dismissInternal(true);
+ }
+
void dismissInternal(boolean allowStateLoss) {
if (mDismissed) {
return;
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 495fa21..5df2343 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -44,9 +44,9 @@
/* Sets the default package for a USB device
* (or clears it if the package name is null)
*/
- oneway void setDevicePackage(in UsbDevice device, String packageName);
+ void setDevicePackage(in UsbDevice device, String packageName);
- /* Sets the default package for a USB device
+ /* Sets the default package for a USB accessory
* (or clears it if the package name is null)
*/
void setAccessoryPackage(in UsbAccessory accessory, String packageName);
@@ -80,5 +80,5 @@
boolean hasDefaults(String packageName);
/* Clears default preferences and permissions for the package */
- oneway void clearDefaults(String packageName);
+ void clearDefaults(String packageName);
}
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 0e2cad8..7bf278a 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -367,7 +367,7 @@
* This may result in a system dialog being displayed to the user
* if permission had not already been granted.
* Success or failure is returned via the {@link android.app.PendingIntent} pi.
- * If successful, this grants the caller permission to access the device only
+ * If successful, this grants the caller permission to access the accessory only
* until the device is disconnected.
*
* The following extras will be added to pi:
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 2b609ea..ad0bc84 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -1049,7 +1049,7 @@
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.replace(com.android.internal.R.id.prefs, f);
- transaction.commit();
+ transaction.commitAllowingStateLoss();
}
/**
@@ -1144,7 +1144,7 @@
} else {
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
}
- transaction.commit();
+ transaction.commitAllowingStateLoss();
}
/**
@@ -1184,7 +1184,7 @@
}
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(BACK_STACK_PREFS);
- transaction.commit();
+ transaction.commitAllowingStateLoss();
}
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 2124f73..8ef745b 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -671,6 +671,7 @@
static final int SELECT_AT = 135;
static final int SCREEN_ON = 136;
+ static final int ENTER_FULLSCREEN_VIDEO = 137;
private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
private static final int LAST_PACKAGE_MSG_ID = SET_TOUCH_HIGHLIGHT_RECTS;
@@ -725,7 +726,8 @@
"SET_AUTOFILLABLE", // = 133;
"AUTOFILL_COMPLETE", // = 134;
"SELECT_AT", // = 135;
- "SCREEN_ON" // = 136;
+ "SCREEN_ON", // = 136;
+ "ENTER_FULLSCREEN_VIDEO" // = 137;
};
// If the site doesn't use the viewport meta tag to specify the viewport,
@@ -7845,6 +7847,11 @@
setKeepScreenOn(msg.arg1 == 1);
break;
+ case ENTER_FULLSCREEN_VIDEO:
+ int layerId = msg.arg1;
+ Log.v(LOGTAG, "Display the video layer " + layerId + " fullscreen");
+ break;
+
case SHOW_FULLSCREEN: {
View view = (View) msg.obj;
int npp = msg.arg1;
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index b920a30..979eb2b 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -480,6 +480,15 @@
mCallbackProxy.setInstallableWebApp();
}
+ /**
+ * Notify the webview that we want to display the video layer fullscreen.
+ */
+ protected void enterFullscreenForVideoLayer(int layerId) {
+ if (mWebView == null) return;
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.ENTER_FULLSCREEN_VIDEO, layerId, 0).sendToTarget();
+ }
+
//-------------------------------------------------------------------------
// JNI methods
//-------------------------------------------------------------------------
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index 1d77388..72052a6 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -777,12 +777,15 @@
}
private class ScaleDetectorListener implements ScaleGestureDetector.OnScaleGestureListener {
+ private float mAccumulatedSpan;
+
public boolean onScaleBegin(ScaleGestureDetector detector) {
mInitialZoomOverview = false;
dismissZoomPicker();
mFocusMovementQueue.clear();
mWebView.mViewManager.startZoom();
mWebView.onPinchToZoomAnimationStart();
+ mAccumulatedSpan = 0;
return true;
}
@@ -797,8 +800,15 @@
FloatMath.sqrt((mFocusX - prevFocusX) * (mFocusX - prevFocusX)
+ (mFocusY - prevFocusY) * (mFocusY - prevFocusY));
mFocusMovementQueue.add(focusDelta);
- float deltaSpan = Math.abs(detector.getCurrentSpan() - detector.getPreviousSpan());
- return mFocusMovementQueue.getSum() > deltaSpan;
+ float deltaSpan = detector.getCurrentSpan() - detector.getPreviousSpan() +
+ mAccumulatedSpan;
+ final boolean result = mFocusMovementQueue.getSum() > Math.abs(deltaSpan);
+ if (result) {
+ mAccumulatedSpan += deltaSpan;
+ } else {
+ mAccumulatedSpan = 0;
+ }
+ return result;
}
public boolean handleScale(ScaleGestureDetector detector) {
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index fb2a72b..2e56996 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -68,7 +68,7 @@
protected void onCreate(Bundle savedInstanceState, Intent intent,
CharSequence title, Intent[] initialIntents, List<ResolveInfo> rList,
- boolean alwaysUseOption, boolean alwaysChoose) {
+ boolean alwaysUseOption) {
super.onCreate(savedInstanceState);
mPm = getPackageManager();
intent.setComponent(null);
@@ -91,7 +91,7 @@
}
mAdapter = new ResolveListAdapter(this, intent, initialIntents, rList);
int count = mAdapter.getCount();
- if (count > 1 || (count == 1 && alwaysChoose)) {
+ if (count > 1) {
ap.mAdapter = mAdapter;
} else if (count == 1) {
startActivity(mAdapter.intentForPosition(0));
@@ -104,12 +104,6 @@
setupAlert();
}
- protected void onCreate(Bundle savedInstanceState, Intent intent,
- CharSequence title, Intent[] initialIntents, List<ResolveInfo> rList,
- boolean alwaysUseOption) {
- onCreate(savedInstanceState, intent, title, initialIntents, rList, alwaysUseOption, false);
- }
-
public void onClick(DialogInterface dialog, int which) {
ResolveInfo ri = mAdapter.resolveInfoForPosition(which);
Intent intent = mAdapter.intentForPosition(which);
diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp
index 88de94f..c4e5878 100644
--- a/core/jni/android/graphics/SurfaceTexture.cpp
+++ b/core/jni/android/graphics/SurfaceTexture.cpp
@@ -19,6 +19,7 @@
#include <stdio.h>
#include <gui/SurfaceTexture.h>
+#include <gui/SurfaceTextureClient.h>
#include <android_runtime/AndroidRuntime.h>
@@ -64,6 +65,15 @@
return surfaceTexture;
}
+sp<ANativeWindow> android_SurfaceTexture_getNativeWindow(
+ JNIEnv* env, jobject thiz)
+{
+ sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
+ sp<SurfaceTextureClient> surfaceTextureClient(surfaceTexture != NULL ?
+ new SurfaceTextureClient(surfaceTexture) : NULL);
+ return surfaceTextureClient;
+}
+
// ----------------------------------------------------------------------------
class JNISurfaceTextureContext : public SurfaceTexture::FrameAvailableListener
diff --git a/docs/html/guide/developing/tools/monkeyrunner_concepts.jd b/docs/html/guide/developing/tools/monkeyrunner_concepts.jd
index 97c7c1f..c0795d7 100644
--- a/docs/html/guide/developing/tools/monkeyrunner_concepts.jd
+++ b/docs/html/guide/developing/tools/monkeyrunner_concepts.jd
@@ -128,7 +128,7 @@
device.press('KEYCODE_MENU','DOWN_AND_UP')
# Takes a screenshot
-result = device.takeSnapShot()
+result = device.takeSnapshot()
# Writes the screenshot to a file
result.writeToFile('myproject/shot1.png','png')
diff --git a/include/android_runtime/android_graphics_SurfaceTexture.h b/include/android_runtime/android_graphics_SurfaceTexture.h
new file mode 100644
index 0000000..8e6fc6e
--- /dev/null
+++ b/include/android_runtime/android_graphics_SurfaceTexture.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef _ANDROID_GRAPHICS_SURFACETEXTURE_H
+#define _ANDROID_GRAPHICS_SURFACETEXTURE_H
+
+#include <android/native_window.h>
+
+#include "jni.h"
+
+namespace android {
+
+extern sp<ANativeWindow> android_SurfaceTexture_getNativeWindow(
+ JNIEnv* env, jobject thiz);
+
+} // namespace android
+
+#endif // _ANDROID_GRAPHICS_SURFACETEXTURE_H
diff --git a/libs/usb/Android.mk b/libs/usb/Android.mk
index b4e1fbf..129828f 100644
--- a/libs/usb/Android.mk
+++ b/libs/usb/Android.mk
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2008 The Android Open Source Project
+# Copyright (C) 2011 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.
diff --git a/libs/usb/tests/AccessoryChat/Android.mk b/libs/usb/tests/AccessoryChat/Android.mk
index 98b6090..d555961 100644
--- a/libs/usb/tests/AccessoryChat/Android.mk
+++ b/libs/usb/tests/AccessoryChat/Android.mk
@@ -1,3 +1,19 @@
+#
+# Copyright (C) 2011 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.
+#
+
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
diff --git a/libs/usb/tests/AccessoryChat/AndroidManifest.xml b/libs/usb/tests/AccessoryChat/AndroidManifest.xml
index 97e2ade..d6093ae 100644
--- a/libs/usb/tests/AccessoryChat/AndroidManifest.xml
+++ b/libs/usb/tests/AccessoryChat/AndroidManifest.xml
@@ -1,3 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.accessorychat">
diff --git a/media/java/android/mtp/MtpConstants.java b/media/java/android/mtp/MtpConstants.java
index ad67bb9..d245f588 100644
--- a/media/java/android/mtp/MtpConstants.java
+++ b/media/java/android/mtp/MtpConstants.java
@@ -271,7 +271,7 @@
/**
* Returns true if the object is abstract (that is, it has no representation
- * in the underlying file system.
+ * in the underlying file system).
*
* @param format the format of the object
* @return true if the object is abstract
diff --git a/media/java/android/mtp/MtpDevice.java b/media/java/android/mtp/MtpDevice.java
index db2cebd..af37c9e 100644
--- a/media/java/android/mtp/MtpDevice.java
+++ b/media/java/android/mtp/MtpDevice.java
@@ -198,7 +198,7 @@
* @param objectHandle handle of the object to query
* @return the object's storage unit ID
*/
- public long getStorageID(int objectHandle) {
+ public long getStorageId(int objectHandle) {
return native_get_storage_id(objectHandle);
}
diff --git a/media/mtp/MtpDataPacket.cpp b/media/mtp/MtpDataPacket.cpp
index 801edb0..0b0c80d 100644
--- a/media/mtp/MtpDataPacket.cpp
+++ b/media/mtp/MtpDataPacket.cpp
@@ -28,7 +28,7 @@
namespace android {
MtpDataPacket::MtpDataPacket()
- : MtpPacket(512),
+ : MtpPacket(16384), // MAX_USBFS_BUFFER_SIZE
mOffset(MTP_CONTAINER_HEADER_SIZE)
{
}
@@ -399,10 +399,10 @@
if (length >= MTP_CONTAINER_HEADER_SIZE) {
// look at the length field to see if the data spans multiple packets
uint32_t totalLength = MtpPacket::getUInt32(MTP_CONTAINER_LENGTH_OFFSET);
+ allocate(totalLength);
while (totalLength > length) {
- allocate(length + mAllocationIncrement);
request->buffer = mBuffer + length;
- request->buffer_length = mAllocationIncrement;
+ request->buffer_length = totalLength - length;
int ret = transfer(request);
if (ret >= 0)
length += ret;
diff --git a/native/android/native_window.cpp b/native/android/native_window.cpp
index 7f92eec..219cd19 100644
--- a/native/android/native_window.cpp
+++ b/native/android/native_window.cpp
@@ -20,6 +20,7 @@
#include <android/native_window_jni.h>
#include <surfaceflinger/Surface.h>
#include <android_runtime/android_view_Surface.h>
+#include <android_runtime/android_graphics_SurfaceTexture.h>
using namespace android;
@@ -31,6 +32,14 @@
return win.get();
}
+ANativeWindow* ANativeWindow_fromSurfaceTexture(JNIEnv* env, jobject surfaceTexture) {
+ sp<ANativeWindow> win = android_SurfaceTexture_getNativeWindow(env, surfaceTexture);
+ if (win != NULL) {
+ win->incStrong((void*)ANativeWindow_acquire);
+ }
+ return win.get();
+}
+
void ANativeWindow_acquire(ANativeWindow* window) {
window->incStrong((void*)ANativeWindow_acquire);
}
diff --git a/native/include/android/native_window_jni.h b/native/include/android/native_window_jni.h
index b9e72ef..408c263 100644
--- a/native/include/android/native_window_jni.h
+++ b/native/include/android/native_window_jni.h
@@ -33,6 +33,14 @@
*/
ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface);
+/**
+ * Return the ANativeWindow associated with a Java SurfaceTexture object,
+ * for interacting with it through native code. This acquires a reference
+ * on the ANativeWindow that is returned; be sure to use ANativeWindow_release()
+ * when done with it so that it doesn't leak.
+ */
+ANativeWindow* ANativeWindow_fromSurfaceTexture(JNIEnv* env, jobject surfaceTexture);
+
#ifdef __cplusplus
};
#endif
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index ecd6fb6..bbe146d6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -41,6 +41,15 @@
</activity>
<!-- started from UsbDeviceSettingsManager -->
+ <activity android:name=".usb.UsbConfirmActivity"
+ android:exported="true"
+ android:permission="android.permission.MANAGE_USB"
+ android:theme="@*android:style/Theme.Holo.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true">
+ </activity>
+
+ <!-- started from UsbDeviceSettingsManager -->
<activity android:name=".usb.UsbPermissionActivity"
android:exported="true"
android:permission="android.permission.MANAGE_USB"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 06c8ed9..8998674 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -122,6 +122,12 @@
<!-- Prompt for the USB accessory permission dialog [CHAR LIMIT=80] -->
<string name="usb_accessory_permission_prompt">Allow the application %1$s to access the USB accessory?</string>
+ <!-- Prompt for the USB device confirm dialog [CHAR LIMIT=80] -->
+ <string name="usb_device_confirm_prompt">Open %1$s when this USB device is connected?</string>
+
+ <!-- Prompt for the USB accessory confirm dialog [CHAR LIMIT=80] -->
+ <string name="usb_accessory_confirm_prompt">Open %1$s when this USB accessory is connected?</string>
+
<!-- Prompt for the USB accessory URI dialog [CHAR LIMIT=80] -->
<string name="usb_accessory_uri_prompt">Additional information for this device may be found at: %1$s</string>
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
new file mode 100644
index 0000000..4e6f81f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2011 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.systemui.usb;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.hardware.usb.IUsbManager;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.TextView;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+import com.android.systemui.R;
+
+public class UsbConfirmActivity extends AlertActivity
+ implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
+
+ private static final String TAG = "UsbConfirmActivity";
+
+ private CheckBox mAlwaysUse;
+ private TextView mClearDefaultHint;
+ private UsbDevice mDevice;
+ private UsbAccessory mAccessory;
+ private ResolveInfo mResolveInfo;
+ private boolean mPermissionGranted;
+ private UsbDisconnectedReceiver mDisconnectedReceiver;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ Intent intent = getIntent();
+ mDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
+ mResolveInfo = (ResolveInfo)intent.getParcelableExtra("rinfo");
+
+ PackageManager packageManager = getPackageManager();
+ String appName = mResolveInfo.loadLabel(packageManager).toString();
+
+ final AlertController.AlertParams ap = mAlertParams;
+ ap.mIcon = mResolveInfo.loadIcon(packageManager);
+ ap.mTitle = appName;
+ if (mDevice == null) {
+ ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName);
+ mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
+ } else {
+ ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName);
+ mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
+ }
+ ap.mPositiveButtonText = getString(com.android.internal.R.string.ok);
+ ap.mNegativeButtonText = getString(com.android.internal.R.string.cancel);
+ ap.mPositiveButtonListener = this;
+ ap.mNegativeButtonListener = this;
+
+ // add "always use" checkbox
+ LayoutInflater inflater = (LayoutInflater)getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
+ mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
+ mAlwaysUse.setText(com.android.internal.R.string.alwaysUse);
+ mAlwaysUse.setOnCheckedChangeListener(this);
+ mClearDefaultHint = (TextView)ap.mView.findViewById(
+ com.android.internal.R.id.clearDefaultHint);
+ mClearDefaultHint.setVisibility(View.GONE);
+
+ setupAlert();
+
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == AlertDialog.BUTTON_POSITIVE) {
+ try {
+ IBinder b = ServiceManager.getService(USB_SERVICE);
+ IUsbManager service = IUsbManager.Stub.asInterface(b);
+ int uid = mResolveInfo.activityInfo.applicationInfo.uid;
+ boolean alwaysUse = mAlwaysUse.isChecked();
+ Intent intent = null;
+
+ if (mDevice != null) {
+ intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
+
+ // grant permission for the device
+ service.grantDevicePermission(mDevice, uid);
+ // set or clear default setting
+ if (alwaysUse) {
+ service.setDevicePackage(mDevice, mResolveInfo.activityInfo.packageName);
+ } else {
+ service.setDevicePackage(mDevice, null);
+ }
+ } else if (mAccessory != null) {
+ intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
+ intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
+
+ // grant permission for the accessory
+ service.grantAccessoryPermission(mAccessory, uid);
+ // set or clear default setting
+ if (alwaysUse) {
+ service.setAccessoryPackage(mAccessory,
+ mResolveInfo.activityInfo.packageName);
+ } else {
+ service.setAccessoryPackage(mAccessory, null);
+ }
+ }
+
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setComponent(
+ new ComponentName(mResolveInfo.activityInfo.packageName,
+ mResolveInfo.activityInfo.name));
+ startActivity(intent);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to start activity", e);
+ }
+ }
+ finish();
+ }
+
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (mClearDefaultHint == null) return;
+
+ if(isChecked) {
+ mClearDefaultHint.setVisibility(View.VISIBLE);
+ } else {
+ mClearDefaultHint.setVisibility(View.GONE);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
index f1784df..27cce6d 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
@@ -49,7 +49,7 @@
private static final String TAG = "UsbPermissionActivity";
- private CheckBox mAlwaysCheck;
+ private CheckBox mAlwaysUse;
private TextView mClearDefaultHint;
private UsbDevice mDevice;
private UsbAccessory mAccessory;
@@ -100,9 +100,9 @@
LayoutInflater inflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
- mAlwaysCheck = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
- mAlwaysCheck.setText(com.android.internal.R.string.alwaysUse);
- mAlwaysCheck.setOnCheckedChangeListener(this);
+ mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
+ mAlwaysUse.setText(com.android.internal.R.string.alwaysUse);
+ mAlwaysUse.setOnCheckedChangeListener(this);
mClearDefaultHint = (TextView)ap.mView.findViewById(
com.android.internal.R.id.clearDefaultHint);
mClearDefaultHint.setVisibility(View.GONE);
@@ -123,7 +123,7 @@
intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
if (mPermissionGranted) {
service.grantDevicePermission(mDevice, mUid);
- if (mAlwaysCheck.isChecked()) {
+ if (mAlwaysUse.isChecked()) {
service.setDevicePackage(mDevice, mPackageName);
}
}
@@ -132,7 +132,7 @@
intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
if (mPermissionGranted) {
service.grantAccessoryPermission(mAccessory, mUid);
- if (mAlwaysCheck.isChecked()) {
+ if (mAlwaysUse.isChecked()) {
service.setAccessoryPackage(mAccessory, mPackageName);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
index 84d73dd..7c63820 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
@@ -56,11 +56,7 @@
ArrayList<ResolveInfo> rList = intent.getParcelableArrayListExtra(EXTRA_RESOLVE_INFOS);
CharSequence title = getResources().getText(com.android.internal.R.string.chooseUsbActivity);
super.onCreate(savedInstanceState, target, title, null, rList,
- true, /* Set alwaysUseOption to true to enable "always use this app" checkbox. */
- true /* Set alwaysChoose to display activity when only one choice is available.
- This is necessary because this activity is needed for the user to allow
- the application permission to access the device */
- );
+ true /* Set alwaysUseOption to true to enable "always use this app" checkbox. */ );
mDevice = (UsbDevice)target.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (mDevice != null) {
diff --git a/services/java/com/android/server/usb/UsbDeviceSettingsManager.java b/services/java/com/android/server/usb/UsbDeviceSettingsManager.java
index 7fde67a..de0b114 100644
--- a/services/java/com/android/server/usb/UsbDeviceSettingsManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceSettingsManager.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
@@ -64,6 +65,7 @@
private static final File sSettingsFile = new File("/data/system/usb_device_manager.xml");
private final Context mContext;
+ private final PackageManager mPackageManager;
// Temporary mapping USB device name to list of UIDs with permissions for the device
private final HashMap<String, SparseBooleanArray> mDevicePermissionMap =
@@ -187,6 +189,14 @@
return false;
}
+ public boolean matches(DeviceFilter f) {
+ if (mVendorId != -1 && f.mVendorId != mVendorId) return false;
+ if (mProductId != -1 && f.mProductId != mProductId) return false;
+
+ // check device class/subclass/protocol
+ return matches(f.mClass, f.mSubclass, f.mProtocol);
+ }
+
@Override
public boolean equals(Object obj) {
// can't compare if we have wildcard strings
@@ -294,6 +304,13 @@
return true;
}
+ public boolean matches(AccessoryFilter f) {
+ if (mManufacturer != null && !f.mManufacturer.equals(mManufacturer)) return false;
+ if (mModel != null && !f.mModel.equals(mModel)) return false;
+ if (mVersion != null && !f.mVersion.equals(mVersion)) return false;
+ return true;
+ }
+
@Override
public boolean equals(Object obj) {
// can't compare if we have wildcard strings
@@ -331,19 +348,24 @@
}
private class MyPackageMonitor extends PackageMonitor {
+
+ public void onPackageAdded(String packageName, int uid) {
+ handlePackageUpdate(packageName);
+ }
+
+ public void onPackageChanged(String packageName, int uid, String[] components) {
+ handlePackageUpdate(packageName);
+ }
+
public void onPackageRemoved(String packageName, int uid) {
- synchronized (mLock) {
- // clear all activity preferences for the package
- if (clearPackageDefaultsLocked(packageName)) {
- writeSettingsLocked();
- }
- }
+ clearDefaults(packageName);
}
}
MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
public UsbDeviceSettingsManager(Context context) {
mContext = context;
+ mPackageManager = context.getPackageManager();
synchronized (mLock) {
readSettingsLocked();
}
@@ -445,11 +467,10 @@
private boolean packageMatchesLocked(ResolveInfo info, String metaDataName,
UsbDevice device, UsbAccessory accessory) {
ActivityInfo ai = info.activityInfo;
- PackageManager pm = mContext.getPackageManager();
XmlResourceParser parser = null;
try {
- parser = ai.loadXmlMetaData(pm, metaDataName);
+ parser = ai.loadXmlMetaData(mPackageManager, metaDataName);
if (parser == null) {
Log.w(TAG, "no meta-data for " + info);
return false;
@@ -482,8 +503,7 @@
private final ArrayList<ResolveInfo> getDeviceMatchesLocked(UsbDevice device, Intent intent) {
ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
- PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent,
+ List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent,
PackageManager.GET_META_DATA);
int count = resolveInfos.size();
for (int i = 0; i < count; i++) {
@@ -498,8 +518,7 @@
private final ArrayList<ResolveInfo> getAccessoryMatchesLocked(
UsbAccessory accessory, Intent intent) {
ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
- PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent,
+ List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent,
PackageManager.GET_META_DATA);
int count = resolveInfos.size();
for (int i = 0; i < count; i++) {
@@ -636,17 +655,122 @@
Log.e(TAG, "startActivity failed", e);
}
} else {
- // start UsbResolverActivity so user can choose an activity
Intent resolverIntent = new Intent();
- resolverIntent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbResolverActivity");
resolverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- resolverIntent.putExtra(Intent.EXTRA_INTENT, intent);
- resolverIntent.putParcelableArrayListExtra("rlist", matches);
+
+ if (count == 1) {
+ // start UsbConfirmActivity if there is only one choice
+ resolverIntent.setClassName("com.android.systemui",
+ "com.android.systemui.usb.UsbConfirmActivity");
+ resolverIntent.putExtra("rinfo", matches.get(0));
+
+ if (device != null) {
+ resolverIntent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ } else {
+ resolverIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+ }
+ } else {
+ // start UsbResolverActivity so user can choose an activity
+ resolverIntent.setClassName("com.android.systemui",
+ "com.android.systemui.usb.UsbResolverActivity");
+ resolverIntent.putParcelableArrayListExtra("rlist", matches);
+ resolverIntent.putExtra(Intent.EXTRA_INTENT, intent);
+ }
try {
mContext.startActivity(resolverIntent);
} catch (ActivityNotFoundException e) {
- Log.e(TAG, "unable to start UsbResolverActivity");
+ Log.e(TAG, "unable to start activity " + resolverIntent);
+ }
+ }
+ }
+
+ private boolean clearCompatibleMatchesLocked(String packageName, DeviceFilter filter) {
+ boolean changed = false;
+ for (DeviceFilter test : mDevicePreferenceMap.keySet()) {
+ if (filter.matches(test)) {
+ mDevicePreferenceMap.remove(test);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ private boolean clearCompatibleMatchesLocked(String packageName, AccessoryFilter filter) {
+ boolean changed = false;
+ for (AccessoryFilter test : mAccessoryPreferenceMap.keySet()) {
+ if (filter.matches(test)) {
+ mAccessoryPreferenceMap.remove(test);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ private boolean handlePackageUpdateLocked(String packageName, ActivityInfo aInfo,
+ String metaDataName) {
+ XmlResourceParser parser = null;
+ boolean changed = false;
+
+ try {
+ parser = aInfo.loadXmlMetaData(mPackageManager, metaDataName);
+ if (parser == null) return false;
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if ("usb-device".equals(tagName)) {
+ DeviceFilter filter = DeviceFilter.read(parser);
+ if (clearCompatibleMatchesLocked(packageName, filter)) {
+ changed = true;
+ }
+ }
+ else if ("usb-accessory".equals(tagName)) {
+ AccessoryFilter filter = AccessoryFilter.read(parser);
+ if (clearCompatibleMatchesLocked(packageName, filter)) {
+ changed = true;
+ }
+ }
+ XmlUtils.nextElement(parser);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to load component info " + aInfo.toString(), e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ return changed;
+ }
+
+ // Check to see if the package supports any USB devices or accessories.
+ // If so, clear any non-matching preferences for matching devices/accessories.
+ private void handlePackageUpdate(String packageName) {
+ synchronized (mLock) {
+ PackageInfo info;
+ boolean changed = false;
+
+ try {
+ info = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
+ return;
+ }
+
+ ActivityInfo[] activities = info.activities;
+ if (activities == null) return;
+ for (int i = 0; i < activities.length; i++) {
+ // check for meta-data, both for devices and accessories
+ if (handlePackageUpdateLocked(packageName, activities[i],
+ UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+ changed = true;
+ }
+ if (handlePackageUpdateLocked(packageName, activities[i],
+ UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ writeSettingsLocked();
}
}
}
@@ -688,7 +812,7 @@
// compare uid with packageName to foil apps pretending to be someone else
try {
- ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0);
+ ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
if (aInfo.uid != uid) {
throw new IllegalArgumentException("package " + packageName +
" does not match caller's uid " + uid);
diff --git a/tests/HugeBackup/Android.mk b/tests/HugeBackup/Android.mk
new file mode 100644
index 0000000..4789bc8
--- /dev/null
+++ b/tests/HugeBackup/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := HugeBackup
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+include $(BUILD_PACKAGE)
diff --git a/tests/HugeBackup/AndroidManifest.xml b/tests/HugeBackup/AndroidManifest.xml
new file mode 100644
index 0000000..923881b
--- /dev/null
+++ b/tests/HugeBackup/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<!-- Declare the contents of this Android application. The namespace
+ attribute brings in the Android platform namespace, and the package
+ supplies a unique name for the application. When writing your
+ own application, the package name must be changed from "com.example.*"
+ to come from a domain that you own or have control over. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.hugebackup"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <!-- The backup/restore mechanism was introduced in API version 8 -->
+ <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" />
+
+ <application android:label="Huge Backup"
+ android:backupAgent="HugeAgent">
+
+ <meta-data android:name="com.google.android.backup.api_key"
+ android:value="AEdPqrEAAAAINyoagzQOEEpIH3yw7LYCFN7CRX4FMd6TGIGVaA" />
+
+ <activity android:name="HugeBackupActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+</manifest>
diff --git a/tests/HugeBackup/proguard.flags b/tests/HugeBackup/proguard.flags
new file mode 100644
index 0000000..b4d01bf
--- /dev/null
+++ b/tests/HugeBackup/proguard.flags
@@ -0,0 +1,3 @@
+-keepclassmembers class com.android.hugebackup.HugeBackupActivity {
+ public void onRestoreButtonClick(android.view.View);
+}
diff --git a/tests/HugeBackup/res/layout/backup_restore.xml b/tests/HugeBackup/res/layout/backup_restore.xml
new file mode 100644
index 0000000..7f11984
--- /dev/null
+++ b/tests/HugeBackup/res/layout/backup_restore.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<!-- Layout description of the BackupRestore sample's main activity -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <ScrollView
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView android:text="@string/filling_text"
+ android:textSize="20dp"
+ android:layout_marginTop="20dp"
+ android:layout_marginBottom="10dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <RadioGroup android:id="@+id/filling_group"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="20dp"
+ android:orientation="vertical">
+
+ <RadioButton android:id="@+id/bacon"
+ android:text="@string/bacon_label"/>
+ <RadioButton android:id="@+id/pastrami"
+ android:text="@string/pastrami_label"/>
+ <RadioButton android:id="@+id/hummus"
+ android:text="@string/hummus_label"/>
+
+ </RadioGroup>
+
+ <TextView android:text="@string/extras_text"
+ android:textSize="20dp"
+ android:layout_marginTop="20dp"
+ android:layout_marginBottom="10dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <CheckBox android:id="@+id/mayo"
+ android:text="@string/mayo_text"
+ android:layout_marginLeft="20dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <CheckBox android:id="@+id/tomato"
+ android:text="@string/tomato_text"
+ android:layout_marginLeft="20dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ </ScrollView>
+
+ <Button android:id="@+id/restore_button"
+ android:text="@string/restore_text"
+ android:onClick="onRestoreButtonClick"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_weight="0" />
+
+</LinearLayout>
diff --git a/tests/HugeBackup/res/values/strings.xml b/tests/HugeBackup/res/values/strings.xml
new file mode 100644
index 0000000..c0b9226
--- /dev/null
+++ b/tests/HugeBackup/res/values/strings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<resources>
+ <string name="filling_text">Choose a sandwich filling:</string>
+ <string name="bacon_label">Bacon</string>
+ <string name="pastrami_label">Pastrami</string>
+ <string name="hummus_label">Hummus</string>
+
+ <string name="extras_text">Extras:</string>
+ <string name="mayo_text">Mayonnaise\?</string>
+ <string name="tomato_text">Tomato\?</string>
+
+ <string name="restore_text">Restore last data</string>
+</resources>
diff --git a/tests/HugeBackup/src/com/android/hugebackup/HugeAgent.java b/tests/HugeBackup/src/com/android/hugebackup/HugeAgent.java
new file mode 100644
index 0000000..f90bd9f
--- /dev/null
+++ b/tests/HugeBackup/src/com/android/hugebackup/HugeAgent.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2011 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.hugebackup;
+
+import android.app.backup.BackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.os.ParcelFileDescriptor;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * This is the backup/restore agent class for the BackupRestore sample
+ * application. This particular agent illustrates using the backup and
+ * restore APIs directly, without taking advantage of any helper classes.
+ */
+public class HugeAgent extends BackupAgent {
+ /**
+ * We put a simple version number into the state files so that we can
+ * tell properly how to read "old" versions if at some point we want
+ * to change what data we back up and how we store the state blob.
+ */
+ static final int AGENT_VERSION = 1;
+
+ /**
+ * Pick an arbitrary string to use as the "key" under which the
+ * data is backed up. This key identifies different data records
+ * within this one application's data set. Since we only maintain
+ * one piece of data we don't need to distinguish, so we just pick
+ * some arbitrary tag to use.
+ */
+ static final String APP_DATA_KEY = "alldata";
+ static final String HUGE_DATA_KEY = "colossus";
+
+ /** The app's current data, read from the live disk file */
+ boolean mAddMayo;
+ boolean mAddTomato;
+ int mFilling;
+
+ /** The location of the application's persistent data file */
+ File mDataFile;
+
+ /** For convenience, we set up the File object for the app's data on creation */
+ @Override
+ public void onCreate() {
+ mDataFile = new File(getFilesDir(), HugeBackupActivity.DATA_FILE_NAME);
+ }
+
+ /**
+ * The set of data backed up by this application is very small: just
+ * two booleans and an integer. With such a simple dataset, it's
+ * easiest to simply store a copy of the backed-up data as the state
+ * blob describing the last dataset backed up. The state file
+ * contents can be anything; it is private to the agent class, and
+ * is never stored off-device.
+ *
+ * <p>One thing that an application may wish to do is tag the state
+ * blob contents with a version number. This is so that if the
+ * application is upgraded, the next time it attempts to do a backup,
+ * it can detect that the last backup operation was performed by an
+ * older version of the agent, and might therefore require different
+ * handling.
+ */
+ @Override
+ public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) throws IOException {
+ // First, get the current data from the application's file. This
+ // may throw an IOException, but in that case something has gone
+ // badly wrong with the app's data on disk, and we do not want
+ // to back up garbage data. If we just let the exception go, the
+ // Backup Manager will handle it and simply skip the current
+ // backup operation.
+ synchronized (HugeBackupActivity.sDataLock) {
+ RandomAccessFile file = new RandomAccessFile(mDataFile, "r");
+ mFilling = file.readInt();
+ mAddMayo = file.readBoolean();
+ mAddTomato = file.readBoolean();
+ }
+
+ // If the new state file descriptor is null, this is the first time
+ // a backup is being performed, so we know we have to write the
+ // data. If there <em>is</em> a previous state blob, we want to
+ // double check whether the current data is actually different from
+ // our last backup, so that we can avoid transmitting redundant
+ // data to the storage backend.
+ boolean doBackup = (oldState == null);
+ if (!doBackup) {
+ doBackup = compareStateFile(oldState);
+ }
+
+ // If we decided that we do in fact need to write our dataset, go
+ // ahead and do that. The way this agent backs up the data is to
+ // flatten it into a single buffer, then write that to the backup
+ // transport under the single key string.
+ if (doBackup) {
+ ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
+
+ // We use a DataOutputStream to write structured data into
+ // the buffering stream
+ DataOutputStream outWriter = new DataOutputStream(bufStream);
+ outWriter.writeInt(mFilling);
+ outWriter.writeBoolean(mAddMayo);
+ outWriter.writeBoolean(mAddTomato);
+
+ // Okay, we've flattened the data for transmission. Pull it
+ // out of the buffering stream object and send it off.
+ byte[] buffer = bufStream.toByteArray();
+ int len = buffer.length;
+ data.writeEntityHeader(APP_DATA_KEY, len);
+ data.writeEntityData(buffer, len);
+
+ // ***** pathological behavior *****
+ // Now, in order to incur deliberate too-much-data failures,
+ // try to back up 20 MB of data besides what we already pushed.
+ final int MEGABYTE = 1024*1024;
+ final int NUM_MEGS = 20;
+ buffer = new byte[MEGABYTE];
+ data.writeEntityHeader(HUGE_DATA_KEY, NUM_MEGS * MEGABYTE);
+ for (int i = 0; i < NUM_MEGS; i++) {
+ data.writeEntityData(buffer, MEGABYTE);
+ }
+ }
+
+ // Finally, in all cases, we need to write the new state blob
+ writeStateFile(newState);
+ }
+
+ /**
+ * Helper routine - read a previous state file and decide whether to
+ * perform a backup based on its contents.
+ *
+ * @return <code>true</code> if the application's data has changed since
+ * the last backup operation; <code>false</code> otherwise.
+ */
+ boolean compareStateFile(ParcelFileDescriptor oldState) {
+ FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
+ DataInputStream in = new DataInputStream(instream);
+
+ try {
+ int stateVersion = in.readInt();
+ if (stateVersion > AGENT_VERSION) {
+ // Whoops; the last version of the app that backed up
+ // data on this device was <em>newer</em> than the current
+ // version -- the user has downgraded. That's problematic.
+ // In this implementation, we recover by simply rewriting
+ // the backup.
+ return true;
+ }
+
+ // The state data we store is just a mirror of the app's data;
+ // read it from the state file then return 'true' if any of
+ // it differs from the current data.
+ int lastFilling = in.readInt();
+ boolean lastMayo = in.readBoolean();
+ boolean lastTomato = in.readBoolean();
+
+ return (lastFilling != mFilling)
+ || (lastTomato != mAddTomato)
+ || (lastMayo != mAddMayo);
+ } catch (IOException e) {
+ // If something went wrong reading the state file, be safe
+ // and back up the data again.
+ return true;
+ }
+ }
+
+ /**
+ * Write out the new state file: the version number, followed by the
+ * three bits of data as we sent them off to the backup transport.
+ */
+ void writeStateFile(ParcelFileDescriptor stateFile) throws IOException {
+ FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
+ DataOutputStream out = new DataOutputStream(outstream);
+
+ out.writeInt(AGENT_VERSION);
+ out.writeInt(mFilling);
+ out.writeBoolean(mAddMayo);
+ out.writeBoolean(mAddTomato);
+ }
+
+ /**
+ * This application does not do any "live" restores of its own data,
+ * so the only time a restore will happen is when the application is
+ * installed. This means that the activity itself is not going to
+ * be running while we change its data out from under it. That, in
+ * turn, means that there is no need to send out any sort of notification
+ * of the new data: we only need to read the data from the stream
+ * provided here, build the application's new data file, and then
+ * write our new backup state blob that will be consulted at the next
+ * backup operation.
+ *
+ * <p>We don't bother checking the versionCode of the app who originated
+ * the data because we have never revised the backup data format. If
+ * we had, the 'appVersionCode' parameter would tell us how we should
+ * interpret the data we're about to read.
+ */
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode,
+ ParcelFileDescriptor newState) throws IOException {
+ // We should only see one entity in the data stream, but the safest
+ // way to consume it is using a while() loop
+ while (data.readNextHeader()) {
+ String key = data.getKey();
+ int dataSize = data.getDataSize();
+
+ if (APP_DATA_KEY.equals(key)) {
+ // It's our saved data, a flattened chunk of data all in
+ // one buffer. Use some handy structured I/O classes to
+ // extract it.
+ byte[] dataBuf = new byte[dataSize];
+ data.readEntityData(dataBuf, 0, dataSize);
+ ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
+ DataInputStream in = new DataInputStream(baStream);
+
+ mFilling = in.readInt();
+ mAddMayo = in.readBoolean();
+ mAddTomato = in.readBoolean();
+
+ // Now we are ready to construct the app's data file based
+ // on the data we are restoring from.
+ synchronized (HugeBackupActivity.sDataLock) {
+ RandomAccessFile file = new RandomAccessFile(mDataFile, "rw");
+ file.setLength(0L);
+ file.writeInt(mFilling);
+ file.writeBoolean(mAddMayo);
+ file.writeBoolean(mAddTomato);
+ }
+ } else {
+ // Curious! This entity is data under a key we do not
+ // understand how to process. Just skip it.
+ data.skipEntityData();
+ }
+ }
+
+ // The last thing to do is write the state blob that describes the
+ // app's data as restored from backup.
+ writeStateFile(newState);
+ }
+}
diff --git a/tests/HugeBackup/src/com/android/hugebackup/HugeBackupActivity.java b/tests/HugeBackup/src/com/android/hugebackup/HugeBackupActivity.java
new file mode 100644
index 0000000..84e31aa
--- /dev/null
+++ b/tests/HugeBackup/src/com/android/hugebackup/HugeBackupActivity.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2011 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.hugebackup;
+
+import android.app.Activity;
+import android.app.backup.BackupManager;
+import android.app.backup.RestoreObserver;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.RadioGroup;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Deliberately back up waaaaaaay too much data. Cloned with some alterations
+ * from the Backup/Restore sample application.
+ */
+public class HugeBackupActivity extends Activity {
+ static final String TAG = "HugeBackupActivity";
+
+ /**
+ * We serialize access to our persistent data through a global static
+ * object. This ensures that in the unlikely event of the our backup/restore
+ * agent running to perform a backup while our UI is updating the file, the
+ * agent will not accidentally read partially-written data.
+ *
+ * <p>Curious but true: a zero-length array is slightly lighter-weight than
+ * merely allocating an Object, and can still be synchronized on.
+ */
+ static final Object[] sDataLock = new Object[0];
+
+ /** Also supply a global standard file name for everyone to use */
+ static final String DATA_FILE_NAME = "saved_data";
+
+ /** The various bits of UI that the user can manipulate */
+ RadioGroup mFillingGroup;
+ CheckBox mAddMayoCheckbox;
+ CheckBox mAddTomatoCheckbox;
+
+ /** Cache a reference to our persistent data file */
+ File mDataFile;
+
+ /** Also cache a reference to the Backup Manager */
+ BackupManager mBackupManager;
+
+ /** Set up the activity and populate its UI from the persistent data. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ /** Establish the activity's UI */
+ setContentView(R.layout.backup_restore);
+
+ /** Once the UI has been inflated, cache the controls for later */
+ mFillingGroup = (RadioGroup) findViewById(R.id.filling_group);
+ mAddMayoCheckbox = (CheckBox) findViewById(R.id.mayo);
+ mAddTomatoCheckbox = (CheckBox) findViewById(R.id.tomato);
+
+ /** Set up our file bookkeeping */
+ mDataFile = new File(getFilesDir(), HugeBackupActivity.DATA_FILE_NAME);
+
+ /** It is handy to keep a BackupManager cached */
+ mBackupManager = new BackupManager(this);
+
+ /**
+ * Finally, build the UI from the persistent store
+ */
+ populateUI();
+ }
+
+ /**
+ * Configure the UI based on our persistent data, creating the
+ * data file and establishing defaults if necessary.
+ */
+ void populateUI() {
+ RandomAccessFile file;
+
+ // Default values in case there's no data file yet
+ int whichFilling = R.id.pastrami;
+ boolean addMayo = false;
+ boolean addTomato = false;
+
+ /** Hold the data-access lock around access to the file */
+ synchronized (HugeBackupActivity.sDataLock) {
+ boolean exists = mDataFile.exists();
+ try {
+ file = new RandomAccessFile(mDataFile, "rw");
+ if (exists) {
+ Log.v(TAG, "datafile exists");
+ whichFilling = file.readInt();
+ addMayo = file.readBoolean();
+ addTomato = file.readBoolean();
+ Log.v(TAG, " mayo=" + addMayo
+ + " tomato=" + addTomato
+ + " filling=" + whichFilling);
+ } else {
+ // The default values were configured above: write them
+ // to the newly-created file.
+ Log.v(TAG, "creating default datafile");
+ writeDataToFileLocked(file,
+ addMayo, addTomato, whichFilling);
+
+ // We also need to perform an initial backup; ask for one
+ mBackupManager.dataChanged();
+ }
+ } catch (IOException ioe) {
+ }
+ }
+
+ /** Now that we've processed the file, build the UI outside the lock */
+ mFillingGroup.check(whichFilling);
+ mAddMayoCheckbox.setChecked(addMayo);
+ mAddTomatoCheckbox.setChecked(addTomato);
+
+ /**
+ * We also want to record the new state when the user makes changes,
+ * so install simple observers that do this
+ */
+ mFillingGroup.setOnCheckedChangeListener(
+ new RadioGroup.OnCheckedChangeListener() {
+ public void onCheckedChanged(RadioGroup group,
+ int checkedId) {
+ // As with the checkbox listeners, rewrite the
+ // entire state file
+ Log.v(TAG, "New radio item selected: " + checkedId);
+ recordNewUIState();
+ }
+ });
+
+ CompoundButton.OnCheckedChangeListener checkListener
+ = new CompoundButton.OnCheckedChangeListener() {
+ public void onCheckedChanged(CompoundButton buttonView,
+ boolean isChecked) {
+ // Whichever one is altered, we rewrite the entire UI state
+ Log.v(TAG, "Checkbox toggled: " + buttonView);
+ recordNewUIState();
+ }
+ };
+ mAddMayoCheckbox.setOnCheckedChangeListener(checkListener);
+ mAddTomatoCheckbox.setOnCheckedChangeListener(checkListener);
+ }
+
+ /**
+ * Handy helper routine to write the UI data to a file.
+ */
+ void writeDataToFileLocked(RandomAccessFile file,
+ boolean addMayo, boolean addTomato, int whichFilling)
+ throws IOException {
+ file.setLength(0L);
+ file.writeInt(whichFilling);
+ file.writeBoolean(addMayo);
+ file.writeBoolean(addTomato);
+ Log.v(TAG, "NEW STATE: mayo=" + addMayo
+ + " tomato=" + addTomato
+ + " filling=" + whichFilling);
+ }
+
+ /**
+ * Another helper; this one reads the current UI state and writes that
+ * to the persistent store, then tells the backup manager that we need
+ * a backup.
+ */
+ void recordNewUIState() {
+ boolean addMayo = mAddMayoCheckbox.isChecked();
+ boolean addTomato = mAddTomatoCheckbox.isChecked();
+ int whichFilling = mFillingGroup.getCheckedRadioButtonId();
+ try {
+ synchronized (HugeBackupActivity.sDataLock) {
+ RandomAccessFile file = new RandomAccessFile(mDataFile, "rw");
+ writeDataToFileLocked(file, addMayo, addTomato, whichFilling);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to record new UI state");
+ }
+
+ mBackupManager.dataChanged();
+ }
+
+ /**
+ * Click handler, designated in the layout, that runs a restore of the app's
+ * most recent data when the button is pressed.
+ */
+ public void onRestoreButtonClick(View v) {
+ Log.v(TAG, "Requesting restore of our most recent data");
+ mBackupManager.requestRestore(
+ new RestoreObserver() {
+ public void restoreFinished(int error) {
+ /** Done with the restore! Now draw the new state of our data */
+ Log.v(TAG, "Restore finished, error = " + error);
+ populateUI();
+ }
+ }
+ );
+ }
+}