Merge "Partial fix for issue 3515250: video chat and SCO" 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/drm/drmserver/DrmManager.cpp b/drm/drmserver/DrmManager.cpp
index 305bafc..1eee5f2 100644
--- a/drm/drmserver/DrmManager.cpp
+++ b/drm/drmserver/DrmManager.cpp
@@ -119,7 +119,7 @@
status_t DrmManager::setDrmServiceListener(
int uniqueId, const sp<IDrmServiceListener>& drmServiceListener) {
- Mutex::Autolock _l(mLock);
+ Mutex::Autolock _l(mListenerLock);
if (NULL != drmServiceListener.get()) {
mServiceListeners.add(uniqueId, drmServiceListener);
} else {
@@ -573,7 +573,7 @@
}
void DrmManager::onInfo(const DrmInfoEvent& event) {
- Mutex::Autolock _l(mLock);
+ Mutex::Autolock _l(mListenerLock);
for (unsigned int index = 0; index < mServiceListeners.size(); index++) {
int uniqueId = mServiceListeners.keyAt(index);
diff --git a/drm/libdrmframework/DrmManagerClient.cpp b/drm/libdrmframework/DrmManagerClient.cpp
index 7b51822..1d1e258 100644
--- a/drm/libdrmframework/DrmManagerClient.cpp
+++ b/drm/libdrmframework/DrmManagerClient.cpp
@@ -78,7 +78,6 @@
}
status_t DrmManagerClient::consumeRights(DecryptHandle* decryptHandle, int action, bool reserve) {
- Mutex::Autolock _l(mDecryptLock);
return mDrmManagerClientImpl->consumeRights(mUniqueId, decryptHandle, action, reserve);
}
@@ -131,7 +130,6 @@
status_t DrmManagerClient::initializeDecryptUnit(
DecryptHandle* decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo) {
- Mutex::Autolock _l(mDecryptLock);
return mDrmManagerClientImpl->initializeDecryptUnit(
mUniqueId, decryptHandle, decryptUnitId, headerInfo);
}
@@ -139,19 +137,16 @@
status_t DrmManagerClient::decrypt(
DecryptHandle* decryptHandle, int decryptUnitId,
const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) {
- Mutex::Autolock _l(mDecryptLock);
return mDrmManagerClientImpl->decrypt(
mUniqueId, decryptHandle, decryptUnitId, encBuffer, decBuffer, IV);
}
status_t DrmManagerClient::finalizeDecryptUnit(DecryptHandle* decryptHandle, int decryptUnitId) {
- Mutex::Autolock _l(mDecryptLock);
return mDrmManagerClientImpl->finalizeDecryptUnit(mUniqueId, decryptHandle, decryptUnitId);
}
ssize_t DrmManagerClient::pread(
DecryptHandle* decryptHandle, void* buffer, ssize_t numBytes, off64_t offset) {
- Mutex::Autolock _l(mDecryptLock);
return mDrmManagerClientImpl->pread(mUniqueId, decryptHandle, buffer, numBytes, offset);
}
diff --git a/drm/libdrmframework/include/DrmManager.h b/drm/libdrmframework/include/DrmManager.h
index e05366d..c7276f9 100644
--- a/drm/libdrmframework/include/DrmManager.h
+++ b/drm/libdrmframework/include/DrmManager.h
@@ -147,6 +147,7 @@
int mDecryptSessionId;
int mConvertId;
Mutex mLock;
+ Mutex mListenerLock;
Mutex mDecryptLock;
Mutex mConvertLock;
TPlugInManager<IDrmEngine> mPlugInManager;
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/include/drm/DrmManagerClient.h b/include/drm/DrmManagerClient.h
index 085ebf1..12142bc 100644
--- a/include/drm/DrmManagerClient.h
+++ b/include/drm/DrmManagerClient.h
@@ -365,7 +365,6 @@
private:
int mUniqueId;
- Mutex mDecryptLock;
DrmManagerClientImpl* mDrmManagerClientImpl;
};
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/media/mtp/MtpDevice.cpp b/media/mtp/MtpDevice.cpp
index 4ea8849..2e86159 100644
--- a/media/mtp/MtpDevice.cpp
+++ b/media/mtp/MtpDevice.cpp
@@ -819,6 +819,10 @@
return mResponse.getResponseCode();
}
int ret = mResponse.read(mRequestIn1);
+ // handle zero length packets, which might occur if the data transfer
+ // ends on a packet boundary
+ if (ret == 0)
+ ret = mResponse.read(mRequestIn1);
if (ret >= MTP_CONTAINER_HEADER_SIZE) {
mResponse.dump();
return mResponse.getResponseCode();
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();
+ }
+ }
+ );
+ }
+}