Merge "dont use sqlite_stmt_journals dir"
diff --git a/Android.mk b/Android.mk
index 568352e..5f9bea4 100644
--- a/Android.mk
+++ b/Android.mk
@@ -136,6 +136,7 @@
core/java/android/speech/tts/ITtsCallback.aidl \
core/java/com/android/internal/app/IBatteryStats.aidl \
core/java/com/android/internal/app/IUsageStats.aidl \
+ core/java/com/android/internal/app/IMediaContainerService.aidl \
core/java/com/android/internal/appwidget/IAppWidgetService.aidl \
core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \
core/java/com/android/internal/backup/IBackupTransport.aidl \
diff --git a/api/current.xml b/api/current.xml
index 04bfe98..e8a8481 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -3184,7 +3184,7 @@
value="16843395"
static="true"
final="true"
- deprecated="deprecated"
+ deprecated="not deprecated"
visibility="public"
>
</field>
@@ -3261,7 +3261,7 @@
value="16843362"
static="true"
final="true"
- deprecated="deprecated"
+ deprecated="not deprecated"
visibility="public"
>
</field>
@@ -5131,7 +5131,7 @@
value="16842997"
static="true"
final="true"
- deprecated="deprecated"
+ deprecated="not deprecated"
visibility="public"
>
</field>
@@ -5252,7 +5252,7 @@
value="16842996"
static="true"
final="true"
- deprecated="deprecated"
+ deprecated="not deprecated"
visibility="public"
>
</field>
@@ -20148,6 +20148,34 @@
visibility="public"
>
</method>
+<method name="getTagForPolicy"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="policyIdent" type="int">
+</parameter>
+</method>
+<method name="loadDescription"
+ return="java.lang.CharSequence"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="pm" type="android.content.pm.PackageManager">
+</parameter>
+<exception name="Resources.NotFoundException" type="android.content.res.Resources.NotFoundException">
+</exception>
+</method>
<method name="loadIcon"
return="android.graphics.drawable.Drawable"
abstract="false"
@@ -20174,6 +20202,19 @@
<parameter name="pm" type="android.content.pm.PackageManager">
</parameter>
</method>
+<method name="usesPolicy"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="policyIdent" type="int">
+</parameter>
+</method>
<method name="writeToParcel"
return="void"
abstract="false"
@@ -20199,6 +20240,72 @@
visibility="public"
>
</field>
+<field name="USES_POLICY_FORCE_LOCK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="USES_POLICY_LIMIT_PASSWORD"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="USES_POLICY_LIMIT_UNLOCK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="USES_POLICY_RESET_PASSWORD"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="USES_POLICY_WATCH_LOGIN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="USES_POLICY_WIPE_DATA"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
</class>
<class name="DevicePolicyManager"
extends="java.lang.Object"
@@ -41567,6 +41674,16 @@
visibility="public"
>
</field>
+<field name="descriptionRes"
+ type="int"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="enabled"
type="boolean"
transient="false"
@@ -130694,6 +130811,17 @@
visibility="public"
>
</field>
+<field name="ACTION_DEVICE_INFO_SETTINGS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.settings.DEVICE_INFO_SETTINGS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="ACTION_DISPLAY_SETTINGS"
type="java.lang.String"
transient="false"
@@ -131136,6 +131264,21 @@
<parameter name="name" type="java.lang.String">
</parameter>
</method>
+<method name="isLocationProviderEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="provider" type="java.lang.String">
+</parameter>
+</method>
<method name="putFloat"
return="boolean"
abstract="false"
@@ -131204,6 +131347,23 @@
<parameter name="value" type="java.lang.String">
</parameter>
</method>
+<method name="setLocationProviderEnabled"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="provider" type="java.lang.String">
+</parameter>
+<parameter name="enabled" type="boolean">
+</parameter>
+</method>
<field name="ACCESSIBILITY_ENABLED"
type="java.lang.String"
transient="false"
@@ -132536,6 +132696,39 @@
visibility="public"
>
</field>
+<field name="SCREEN_BRIGHTNESS_MODE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""screen_brightness_mode""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SCREEN_BRIGHTNESS_MODE_AUTOMATIC"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SCREEN_BRIGHTNESS_MODE_MANUAL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="SCREEN_OFF_TIMEOUT"
type="java.lang.String"
transient="false"
@@ -134157,6 +134350,320 @@
</package>
<package name="android.speech"
>
+<interface name="RecognitionListener"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onBeginningOfSpeech"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onBufferReceived"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="buffer" type="byte[]">
+</parameter>
+</method>
+<method name="onEndOfSpeech"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onError"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="int">
+</parameter>
+</method>
+<method name="onInit"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onPartialResults"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="partialResults" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onReadyForSpeech"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="params" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onResults"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="results" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onRmsChanged"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="rmsdB" type="float">
+</parameter>
+</method>
+</interface>
+<class name="RecognitionManager"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="cancel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="createRecognitionManager"
+ return="android.speech.RecognitionManager"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="listener" type="android.speech.RecognitionListener">
+</parameter>
+<parameter name="recognizerIntent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="destroy"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isRecognitionAvailable"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</method>
+<method name="startListening"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="recognizerIntent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="stopListening"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<field name="AUDIO_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CLIENT_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="MANAGER_NOT_INITIALIZED_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="9"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NETWORK_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NETWORK_TIMEOUT_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NO_MATCH_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="7"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="RECOGNITION_RESULTS_STRING_ARRAY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""recognition_results_string_array""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SERVER_BUSY_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SERVER_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SPEECH_TIMEOUT_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
<class name="RecognizerIntent"
extends="java.lang.Object"
abstract="false"
@@ -134275,6 +134782,39 @@
visibility="public"
>
</field>
+<field name="EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.speech.extras.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.speech.extras.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.speech.extras.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="LANGUAGE_MODEL_FREE_FORM"
type="java.lang.String"
transient="false"
diff --git a/camera/libcameraservice/CameraService.cpp b/camera/libcameraservice/CameraService.cpp
index a8e217e..81d60dc 100644
--- a/camera/libcameraservice/CameraService.cpp
+++ b/camera/libcameraservice/CameraService.cpp
@@ -1191,14 +1191,6 @@
CameraParameters p(params);
- // The orientation parameter is actually for CameraService, not for the camera driver.
- if (p.getOrientation() == CameraParameters::CAMERA_ORIENTATION_PORTRAIT) {
- LOGV("portrait mode");
- mOrientation = ISurface::BufferHeap::ROT_90;
- } else {
- mOrientation = 0;
- }
-
return mHardware->setParameters(p);
}
@@ -1224,6 +1216,30 @@
status_t result = checkPid();
if (result != NO_ERROR) return result;
+ if (cmd == CAMERA_CMD_SET_DISPLAY_ORIENTATION) {
+ // The orientation cannot be set during preview.
+ if (mHardware->previewEnabled()) {
+ return INVALID_OPERATION;
+ }
+ switch (arg1) {
+ case 0:
+ mOrientation = ISurface::BufferHeap::ROT_0;
+ break;
+ case 90:
+ mOrientation = ISurface::BufferHeap::ROT_90;
+ break;
+ case 180:
+ mOrientation = ISurface::BufferHeap::ROT_180;
+ break;
+ case 270:
+ mOrientation = ISurface::BufferHeap::ROT_270;
+ break;
+ default:
+ return BAD_VALUE;
+ }
+ return OK;
+ }
+
if (mHardware == 0) {
LOGE("mHardware is NULL, returning.");
return INVALID_OPERATION;
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index 9300915..78f90a1 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -129,7 +129,7 @@
dump_file("LAST PANIC CONSOLE", "/data/dontpanic/apanic_console");
dump_file("LAST PANIC THREADS", "/data/dontpanic/apanic_threads");
- printf("----- BACKLIGHTS -----\n");
+ printf("------ BACKLIGHTS ------\n");
printf("LCD brightness=");
dump_file(NULL, "/sys/class/leds/lcd-backlight/brightness");
printf("Button brightness=");
diff --git a/cmds/dumpstate/utils.c b/cmds/dumpstate/utils.c
index fda618c..39e14e4 100644
--- a/cmds/dumpstate/utils.c
+++ b/cmds/dumpstate/utils.c
@@ -43,13 +43,13 @@
int fd = open(path, O_RDONLY);
if (fd < 0) {
int err = errno;
- if (title) printf("----- %s (%s) -----\n", title, path);
+ if (title) printf("------ %s (%s) ------\n", title, path);
printf("*** %s: %s\n", path, strerror(err));
if (title) printf("\n");
return -1;
}
- if (title) printf("----- %s (%s", title, path);
+ if (title) printf("------ %s (%s", title, path);
if (title) {
struct stat st;
@@ -59,7 +59,7 @@
strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S", localtime(&mtime));
printf(": %s", stamp);
}
- printf(") -----\n");
+ printf(") ------\n");
}
int newline = 0;
@@ -97,13 +97,13 @@
va_list ap;
va_start(ap, command);
- if (title) printf("----- %s (%s", title, command);
+ if (title) printf("------ %s (%s", title, command);
for (arg = 1; arg < sizeof(args) / sizeof(args[0]); ++arg) {
args[arg] = va_arg(ap, const char *);
if (args[arg] == NULL) break;
if (title) printf(" %s", args[arg]);
}
- if (title) printf(") -----\n");
+ if (title) printf(") ------\n");
fflush(stdout);
execvp(command, (char**) args);
@@ -159,7 +159,7 @@
property_list(print_prop, NULL);
qsort(&props, num_props, sizeof(props[0]), compare_prop);
- printf("----- SYSTEM PROPERTIES -----\n");
+ printf("------ SYSTEM PROPERTIES ------\n");
for (i = 0; i < num_props; ++i) {
fputs(props[i], stdout);
free(props[i]);
diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c
index 5db5545..555c19e 100644
--- a/cmds/installd/utils.c
+++ b/cmds/installd/utils.c
@@ -33,6 +33,7 @@
}
x = pkgname;
+ int alpha = -1;
while (*x) {
if (isalnum(*x) || (*x == '_')) {
/* alphanumeric or underscore are fine */
@@ -42,13 +43,28 @@
LOGE("invalid package name '%s'\n", pkgname);
return -1;
}
- } else {
+ } else if (*x == '-') {
+ /* Suffix -X is fine to let versioning of packages.
+ But whatever follows should be alphanumeric.*/
+ alpha = 1;
+ }else {
/* anything not A-Z, a-z, 0-9, _, or . is invalid */
LOGE("invalid package name '%s'\n", pkgname);
return -1;
}
x++;
}
+ if (alpha == 1) {
+ // Skip current character
+ x++;
+ while (*x) {
+ if (!isalnum(*x)) {
+ LOGE("invalid package name '%s' should include only numbers after -\n", pkgname);
+ return -1;
+ }
+ x++;
+ }
+ }
sprintf(path, "%s%s%s", prefix, pkgname, postfix);
return 0;
diff --git a/core/java/android/app/DeviceAdmin.java b/core/java/android/app/DeviceAdmin.java
index 16832db..9750d6e 100644
--- a/core/java/android/app/DeviceAdmin.java
+++ b/core/java/android/app/DeviceAdmin.java
@@ -82,6 +82,10 @@
* {@link DevicePolicyManager#getMinimumPasswordLength()
* DevicePolicyManager.getMinimumPasswordLength()}. You will generally
* handle this in {@link DeviceAdmin#onPasswordChanged(Context, Intent)}.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to receive
+ * this broadcast.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PASSWORD_CHANGED
@@ -94,6 +98,10 @@
* {@link DevicePolicyManager#getCurrentFailedPasswordAttempts()
* DevicePolicyManager.getCurrentFailedPasswordAttempts()}. You will generally
* handle this in {@link DeviceAdmin#onPasswordFailed(Context, Intent)}.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive
+ * this broadcast.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PASSWORD_FAILED
@@ -102,6 +110,10 @@
/**
* Action sent to a device administrator when the user has successfully
* entered their password, after failing one or more times.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive
+ * this broadcast.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PASSWORD_SUCCEEDED
diff --git a/core/java/android/app/DeviceAdminInfo.java b/core/java/android/app/DeviceAdminInfo.java
index eac6e46..92fdbc8 100644
--- a/core/java/android/app/DeviceAdminInfo.java
+++ b/core/java/android/app/DeviceAdminInfo.java
@@ -19,21 +19,28 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.R;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.content.res.Resources.NotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.Printer;
+import android.util.SparseArray;
import android.util.Xml;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
/**
* This class is used to specify meta information of a device administrator
@@ -43,11 +50,123 @@
static final String TAG = "DeviceAdminInfo";
/**
+ * A type of policy that this device admin can use: limit the passwords
+ * that the user can select, via {@link DevicePolicyManager#setPasswordMode}
+ * and {@link DevicePolicyManager#setMinimumPasswordLength}.
+ *
+ * <p>To control this policy, the device admin must have a "limit-password"
+ * tag in the "uses-policies" section of its meta-data.
+ */
+ public static final int USES_POLICY_LIMIT_PASSWORD = 0;
+
+ /**
+ * A type of policy that this device admin can use: able to watch login
+ * attempts from the user, via {@link DeviceAdmin#ACTION_PASSWORD_FAILED},
+ * {@link DeviceAdmin#ACTION_PASSWORD_SUCCEEDED}, and
+ * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts}.
+ *
+ * <p>To control this policy, the device admin must have a "watch-login"
+ * tag in the "uses-policies" section of its meta-data.
+ */
+ public static final int USES_POLICY_WATCH_LOGIN = 1;
+
+ /**
+ * A type of policy that this device admin can use: able to reset the
+ * user's password via
+ * {@link DevicePolicyManager#resetPassword}.
+ *
+ * <p>To control this policy, the device admin must have a "reset-password"
+ * tag in the "uses-policies" section of its meta-data.
+ */
+ public static final int USES_POLICY_RESET_PASSWORD = 2;
+
+ /**
+ * A type of policy that this device admin can use: able to limit the
+ * maximum lock timeout for the device via
+ * {@link DevicePolicyManager#setMaximumTimeToLock}.
+ *
+ * <p>To control this policy, the device admin must have a "limit-unlock"
+ * tag in the "uses-policies" section of its meta-data.
+ */
+ public static final int USES_POLICY_LIMIT_UNLOCK = 3;
+
+ /**
+ * A type of policy that this device admin can use: able to force the device
+ * to lock via{@link DevicePolicyManager#lockNow}.
+ *
+ * <p>To control this policy, the device admin must have a "force-lock"
+ * tag in the "uses-policies" section of its meta-data.
+ */
+ public static final int USES_POLICY_FORCE_LOCK = 4;
+
+ /**
+ * A type of policy that this device admin can use: able to factory
+ * reset the device, erasing all of the user's data, via
+ * {@link DevicePolicyManager#wipeData}.
+ *
+ * <p>To control this policy, the device admin must have a "wipe-data"
+ * tag in the "uses-policies" section of its meta-data.
+ */
+ public static final int USES_POLICY_WIPE_DATA = 5;
+
+ /** @hide */
+ public static class PolicyInfo {
+ final public String tag;
+ final public int label;
+ final public int description;
+
+ public PolicyInfo(String tagIn, int labelIn, int descriptionIn) {
+ tag = tagIn;
+ label = labelIn;
+ description = descriptionIn;
+ }
+ }
+
+ static HashMap<String, Integer> sKnownPolicies = new HashMap<String, Integer>();
+ static SparseArray<PolicyInfo> sRevKnownPolicies = new SparseArray<PolicyInfo>();
+
+ static {
+ sRevKnownPolicies.put(USES_POLICY_LIMIT_PASSWORD,
+ new PolicyInfo("limit-password",
+ com.android.internal.R.string.policylab_limitPassword,
+ com.android.internal.R.string.policydesc_limitPassword));
+ sRevKnownPolicies.put(USES_POLICY_WATCH_LOGIN,
+ new PolicyInfo("watch-login",
+ com.android.internal.R.string.policylab_watchLogin,
+ com.android.internal.R.string.policydesc_watchLogin));
+ sRevKnownPolicies.put(USES_POLICY_RESET_PASSWORD,
+ new PolicyInfo("reset-password",
+ com.android.internal.R.string.policylab_resetPassword,
+ com.android.internal.R.string.policydesc_resetPassword));
+ sRevKnownPolicies.put(USES_POLICY_LIMIT_UNLOCK,
+ new PolicyInfo("limit-unlock",
+ com.android.internal.R.string.policylab_limitUnlock,
+ com.android.internal.R.string.policydesc_limitUnlock));
+ sRevKnownPolicies.put(USES_POLICY_FORCE_LOCK,
+ new PolicyInfo("force-lock",
+ com.android.internal.R.string.policylab_forceLock,
+ com.android.internal.R.string.policydesc_forceLock));
+ sRevKnownPolicies.put(USES_POLICY_WIPE_DATA,
+ new PolicyInfo("wipe-data",
+ com.android.internal.R.string.policylab_wipeData,
+ com.android.internal.R.string.policydesc_wipeData));
+ for (int i=0; i<sRevKnownPolicies.size(); i++) {
+ sKnownPolicies.put(sRevKnownPolicies.valueAt(i).tag,
+ sRevKnownPolicies.keyAt(i));
+ }
+ }
+
+ /**
* The BroadcastReceiver that implements this device admin component.
*/
final ResolveInfo mReceiver;
/**
+ * The policies this administrator needs access to.
+ */
+ int mUsesPolicies;
+
+ /**
* Constructor.
*
* @param context The Context in which we are parsing the device admin.
@@ -86,6 +205,32 @@
com.android.internal.R.styleable.Wallpaper);
sa.recycle();
+
+ int outerDepth = parser.getDepth();
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals("uses-policies")) {
+ int innerDepth = parser.getDepth();
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String policyName = parser.getName();
+ Integer val = sKnownPolicies.get(policyName);
+ if (val != null) {
+ mUsesPolicies |= 1 << val.intValue();
+ } else {
+ Log.w(TAG, "Unknown tag under uses-policies of "
+ + getComponent() + ": " + policyName);
+ }
+ }
+ }
+ }
} finally {
if (parser != null) parser.close();
}
@@ -93,6 +238,7 @@
DeviceAdminInfo(Parcel source) {
mReceiver = ResolveInfo.CREATOR.createFromParcel(source);
+ mUsesPolicies = source.readInt();
}
/**
@@ -137,6 +283,26 @@
}
/**
+ * Load user-visible description associated with this device admin.
+ *
+ * @param pm Supply a PackageManager used to load the device admin's
+ * resources.
+ */
+ public CharSequence loadDescription(PackageManager pm) throws NotFoundException {
+ if (mReceiver.activityInfo.descriptionRes != 0) {
+ String packageName = mReceiver.resolvePackageName;
+ ApplicationInfo applicationInfo = null;
+ if (packageName == null) {
+ packageName = mReceiver.activityInfo.packageName;
+ applicationInfo = mReceiver.activityInfo.applicationInfo;
+ }
+ return pm.getText(packageName,
+ mReceiver.activityInfo.descriptionRes, applicationInfo);
+ }
+ throw new NotFoundException();
+ }
+
+ /**
* Load the user-displayed icon for this device admin.
*
* @param pm Supply a PackageManager used to load the device admin's
@@ -146,6 +312,38 @@
return mReceiver.loadIcon(pm);
}
+ /**
+ * Return true if the device admin has requested that it be able to use
+ * the given policy control. The possible policy identifier inputs are:
+ * {@link #USES_POLICY_LIMIT_PASSWORD}, {@link #USES_POLICY_WATCH_LOGIN},
+ * {@link #USES_POLICY_RESET_PASSWORD}, {@link #USES_POLICY_LIMIT_UNLOCK},
+ * {@link #USES_POLICY_FORCE_LOCK}, {@link #USES_POLICY_WIPE_DATA}.
+ */
+ public boolean usesPolicy(int policyIdent) {
+ return (mUsesPolicies & (1<<policyIdent)) != 0;
+ }
+
+ /**
+ * Return the XML tag name for the given policy identifier. Valid identifiers
+ * are as per {@link #usesPolicy(int)}. If the given identifier is not
+ * known, null is returned.
+ */
+ public String getTagForPolicy(int policyIdent) {
+ return sRevKnownPolicies.get(policyIdent).tag;
+ }
+
+ /** @hide */
+ public ArrayList<PolicyInfo> getUsedPolicies() {
+ ArrayList<PolicyInfo> res = new ArrayList<PolicyInfo>();
+ for (int i=0; i<sRevKnownPolicies.size(); i++) {
+ int ident = sRevKnownPolicies.keyAt(i);
+ if (usesPolicy(ident)) {
+ res.add(sRevKnownPolicies.valueAt(i));
+ }
+ }
+ return res;
+ }
+
public void dump(Printer pw, String prefix) {
pw.println(prefix + "Receiver:");
mReceiver.dump(pw, prefix + " ");
@@ -164,6 +362,7 @@
*/
public void writeToParcel(Parcel dest, int flags) {
mReceiver.writeToParcel(dest, flags);
+ dest.writeInt(mUsesPolicies);
}
/**
diff --git a/core/java/android/app/DevicePolicyManager.java b/core/java/android/app/DevicePolicyManager.java
index 538ba5b..25e3230 100644
--- a/core/java/android/app/DevicePolicyManager.java
+++ b/core/java/android/app/DevicePolicyManager.java
@@ -163,6 +163,10 @@
* the user's preference, and any other considerations) is the one that
* is in effect.
*
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
+ * this method; if it has not, a security exception will be thrown.
+ *
* @param admin Which {@link DeviceAdmin} this request is associated with.
* @param mode The new desired mode. One of
* {@link #PASSWORD_MODE_UNSPECIFIED}, {@link #PASSWORD_MODE_SOMETHING},
@@ -205,6 +209,10 @@
* {@link #PASSWORD_MODE_NUMERIC} or {@link #PASSWORD_MODE_ALPHANUMERIC}
* with {@link #setPasswordMode}.
*
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
+ * this method; if it has not, a security exception will be thrown.
+ *
* @param admin Which {@link DeviceAdmin} this request is associated with.
* @param length The new desired minimum password length. A value of 0
* means there is no restriction.
@@ -239,6 +247,10 @@
* to meet the policy requirements (mode, minimum length) that have been
* requested.
*
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
+ * this method; if it has not, a security exception will be thrown.
+ *
* @return Returns true if the password meets the current requirements,
* else false.
*/
@@ -256,6 +268,10 @@
/**
* Retrieve the number of times the user has failed at entering a
* password since that last successful password entry.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to be able to call
+ * this method; if it has not, a security exception will be thrown.
*/
public int getCurrentFailedPasswordAttempts() {
if (mService != null) {
@@ -277,6 +293,10 @@
* if it contains only digits, that is still an acceptable alphanumeric
* password.)
*
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} to be able to call
+ * this method; if it has not, a security exception will be thrown.
+ *
* @param password The new password for the user.
* @return Returns true if the password was applied, or false if it is
* not acceptable for the current constraints.
@@ -297,6 +317,10 @@
* maximum time for user activity until the device will lock. This limits
* the length that the user can set. It takes effect immediately.
*
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_LIMIT_UNLOCK} to be able to call
+ * this method; if it has not, a security exception will be thrown.
+ *
* @param admin Which {@link DeviceAdmin} this request is associated with.
* @param timeMs The new desired maximum time to lock in milliseconds.
* A value of 0 means there is no restriction.
@@ -329,6 +353,10 @@
/**
* Make the device lock immediately, as if the lock screen timeout has
* expired at the point of this call.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} to be able to call
+ * this method; if it has not, a security exception will be thrown.
*/
public void lockNow() {
if (mService != null) {
@@ -345,6 +373,10 @@
* erasing all user data while next booting up. External storage such
* as SD cards will not be erased.
*
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to be able to call
+ * this method; if it has not, a security exception will be thrown.
+ *
* @param flags Bit mask of additional options: currently must be 0.
*/
public void wipeData(int flags) {
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index 1612ac9..5ca3fb54 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -21,6 +21,7 @@
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -204,7 +205,7 @@
return pm.getDrawable(mService.serviceInfo.packageName,
mThumbnailResource,
- null);
+ mService.serviceInfo.applicationInfo);
}
/**
@@ -212,25 +213,33 @@
*/
public CharSequence loadAuthor(PackageManager pm) throws NotFoundException {
if (mAuthorResource <= 0) throw new NotFoundException();
- return pm.getText(
- (mService.resolvePackageName != null)
- ? mService.resolvePackageName
- : getPackageName(),
- mAuthorResource,
- null);
+ String packageName = mService.resolvePackageName;
+ ApplicationInfo applicationInfo = null;
+ if (packageName == null) {
+ packageName = mService.serviceInfo.packageName;
+ applicationInfo = mService.serviceInfo.applicationInfo;
+ }
+ return pm.getText(packageName, mAuthorResource, applicationInfo);
}
/**
* Return a brief summary of this wallpaper's behavior.
*/
public CharSequence loadDescription(PackageManager pm) throws NotFoundException {
+ String packageName = mService.resolvePackageName;
+ ApplicationInfo applicationInfo = null;
+ if (packageName == null) {
+ packageName = mService.serviceInfo.packageName;
+ applicationInfo = mService.serviceInfo.applicationInfo;
+ }
+ if (mService.serviceInfo.descriptionRes != 0) {
+ return pm.getText(packageName, mService.serviceInfo.descriptionRes,
+ applicationInfo);
+
+ }
if (mDescriptionResource <= 0) throw new NotFoundException();
- return pm.getText(
- (mService.resolvePackageName != null)
- ? mService.resolvePackageName
- : getPackageName(),
- mDescriptionResource,
- null);
+ return pm.getText(packageName, mDescriptionResource,
+ mService.serviceInfo.applicationInfo);
}
/**
diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java
index da1647a..0b27117 100644
--- a/core/java/android/backup/BackupManager.java
+++ b/core/java/android/backup/BackupManager.java
@@ -16,6 +16,7 @@
package android.backup;
+import android.backup.RestoreSession;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -115,19 +116,21 @@
*
* {@hide}
*/
- public IRestoreSession beginRestoreSession(String transport) {
+ public RestoreSession beginRestoreSession() {
if (!EVEN_THINK_ABOUT_DOING_RESTORE) {
return null;
}
- IRestoreSession binder = null;
+ RestoreSession session = null;
checkServiceBinder();
if (sService != null) {
try {
- binder = sService.beginRestoreSession(transport);
+ String transport = sService.getCurrentTransport();
+ IRestoreSession binder = sService.beginRestoreSession(transport);
+ session = new RestoreSession(mContext, binder);
} catch (RemoteException e) {
Log.d(TAG, "beginRestoreSession() couldn't connect");
}
}
- return binder;
+ return session;
}
}
diff --git a/core/java/android/backup/RestoreObserver.java b/core/java/android/backup/RestoreObserver.java
new file mode 100644
index 0000000..3be8c08
--- /dev/null
+++ b/core/java/android/backup/RestoreObserver.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 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 android.backup;
+
+/**
+ * Callback class for receiving progress reports during a restore operation. These
+ * methods will all be called on your application's main thread.
+ * @hide
+ */
+public abstract class RestoreObserver {
+ /**
+ * The restore operation has begun.
+ *
+ * @param numPackages The total number of packages being processed in
+ * this restore operation.
+ */
+ void restoreStarting(int numPackages) {
+ }
+
+ /**
+ * An indication of which package is being restored currently, out of the
+ * total number provided in the restoreStarting() callback. This method
+ * is not guaranteed to be called.
+ *
+ * @param nowBeingRestored The index, between 1 and the numPackages parameter
+ * to the restoreStarting() callback, of the package now being restored.
+ */
+ void onUpdate(int nowBeingRestored) {
+ }
+
+ /**
+ * The restore operation has completed.
+ *
+ * @param error Zero on success; a nonzero error code if the restore operation
+ * as a whole failed.
+ */
+ void restoreFinished(int error) {
+ }
+}
diff --git a/core/java/android/backup/RestoreSession.java b/core/java/android/backup/RestoreSession.java
new file mode 100644
index 0000000..119fc52
--- /dev/null
+++ b/core/java/android/backup/RestoreSession.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2010 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 android.backup;
+
+import android.backup.IRestoreSession;
+import android.backup.RestoreObserver;
+import android.backup.RestoreSet;
+import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Interface for applications to use when managing a restore session.
+ * @hide
+ */
+public class RestoreSession {
+ static final String TAG = "RestoreSession";
+
+ final Context mContext;
+ IRestoreSession mBinder;
+ RestoreObserverWrapper mObserver = null;
+
+ /**
+ * Ask the current transport what the available restore sets are.
+ *
+ * @return A bundle containing two elements: an int array under the key
+ * "tokens" whose entries are a transport-private identifier for each backup set;
+ * and a String array under the key "names" whose entries are the user-meaningful
+ * text corresponding to the backup sets at each index in the tokens array.
+ * On error, returns null.
+ */
+ public RestoreSet[] getAvailableRestoreSets() {
+ try {
+ return mBinder.getAvailableRestoreSets();
+ } catch (RemoteException e) {
+ Log.d(TAG, "Can't contact server to get available sets");
+ return null;
+ }
+ }
+
+ /**
+ * Restore the given set onto the device, replacing the current data of any app
+ * contained in the restore set with the data previously backed up.
+ *
+ * @return Zero on success; nonzero on error. The observer will only receive
+ * progress callbacks if this method returned zero.
+ * @param token The token from {@link #getAvailableRestoreSets()} corresponding to
+ * the restore set that should be used.
+ * @param observer If non-null, this argument points to an object that will receive
+ * progress callbacks during the restore operation. These callbacks will occur
+ * on the main thread of the application.
+ */
+ public int performRestore(long token, RestoreObserver observer) {
+ int err = -1;
+ if (mObserver != null) {
+ Log.d(TAG, "performRestore() called during active restore");
+ return -1;
+ }
+ mObserver = new RestoreObserverWrapper(mContext, observer);
+ try {
+ err = mBinder.performRestore(token, mObserver);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Can't contact server to perform restore");
+ }
+ return err;
+ }
+
+ /**
+ * End this restore session. After this method is called, the RestoreSession
+ * object is no longer valid.
+ *
+ * <p><b>Note:</b> The caller <i>must</i> invoke this method to end the restore session,
+ * even if {@link #getAvailableRestoreSets()} or
+ * {@link #performRestore(long, RestoreObserver)} failed.
+ */
+ public void endRestoreSession() {
+ try {
+ mBinder.endRestoreSession();
+ } catch (RemoteException e) {
+ Log.d(TAG, "Can't contact server to get available sets");
+ } finally {
+ mBinder = null;
+ }
+ }
+
+ /*
+ * Nonpublic implementation here
+ */
+
+ RestoreSession(Context context, IRestoreSession binder) {
+ mContext = context;
+ mBinder = binder;
+ }
+
+ /*
+ * We wrap incoming binder calls with a private class implementation that
+ * redirects them into main-thread actions. This accomplishes two things:
+ * first, it ensures that the app's code is run on their own main thread,
+ * never with system Binder identity; and second, it serializes the restore
+ * progress callbacks nicely within the usual main-thread lifecycle pattern.
+ */
+ private class RestoreObserverWrapper extends IRestoreObserver.Stub {
+ final Handler mHandler;
+ final RestoreObserver mAppObserver;
+
+ RestoreObserverWrapper(Context context, RestoreObserver appObserver) {
+ mHandler = new Handler(context.getMainLooper());
+ mAppObserver = appObserver;
+ }
+
+ // Wrap the IRestoreObserver -> RestoreObserver callthrough in Runnables
+ // posted to the app's main thread looper.
+ class RestoreStartingRunnable implements Runnable {
+ int mNumPackages;
+
+ RestoreStartingRunnable(int numPackages) {
+ mNumPackages = numPackages;
+ }
+
+ public void run() {
+ mAppObserver.restoreStarting(mNumPackages);
+ }
+ }
+
+ class OnUpdateRunnable implements Runnable {
+ int mNowRestoring;
+
+ OnUpdateRunnable(int nowRestoring) {
+ mNowRestoring = nowRestoring;
+ }
+
+ public void run() {
+ mAppObserver.onUpdate(mNowRestoring);
+ }
+ }
+
+ class RestoreFinishedRunnable implements Runnable {
+ int mError;
+
+ RestoreFinishedRunnable(int error) {
+ mError = error;
+ }
+
+ public void run() {
+ mAppObserver.restoreFinished(mError);
+ }
+ }
+
+ // The actual redirection code is quite simple using just the
+ // above Runnable subclasses
+ public void restoreStarting(int numPackages) {
+ mHandler.post(new RestoreStartingRunnable(numPackages));
+ }
+
+ public void onUpdate(int nowBeingRestored) {
+ mHandler.post(new OnUpdateRunnable(nowBeingRestored));
+ }
+
+ public void restoreFinished(int error) {
+ mHandler.post(new RestoreFinishedRunnable(error));
+ }
+ }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d76b616..321ba5c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1278,6 +1278,17 @@
/**
* Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.NetworkManagementService} for handling management of
+ * system network services
+ *
+ * @hide
+ * @see #getSystemService
+ * @see android.net.NetworkManagementService
+ */
+ public static final String NETWORKMANAGEMENT_SERVICE = "network_management";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
* android.net.wifi.WifiManager} for handling management of
* Wi-Fi access.
*
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index a7ea507..808c839b 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -234,6 +234,14 @@
public static final int FLAG_ON_SDCARD = 1<<19;
/**
+ * Value for {@link #flags}: Set to true if the application is
+ * native-debuggable, i.e. embeds a gdbserver binary in its .apk
+ *
+ * {@hide}
+ */
+ public static final int FLAG_NATIVE_DEBUGGABLE = 1<<20;
+
+ /**
* Flags associated with the application. Any combination of
* {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE},
* {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 73c9244..338c62b6 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 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 android.content.pm;
import android.graphics.drawable.Drawable;
@@ -27,6 +43,13 @@
public String processName;
/**
+ * A string resource identifier (in the package's resources) containing
+ * a user-readable description of the component. From the "description"
+ * attribute or, if not set, 0.
+ */
+ public int descriptionRes;
+
+ /**
* Indicates whether or not this component may be instantiated. Note that this value can be
* overriden by the one in its parent {@link ApplicationInfo}.
*/
@@ -47,6 +70,7 @@
super(orig);
applicationInfo = orig.applicationInfo;
processName = orig.processName;
+ descriptionRes = orig.descriptionRes;
enabled = orig.enabled;
exported = orig.exported;
}
@@ -108,6 +132,9 @@
super.dumpFront(pw, prefix);
pw.println(prefix + "enabled=" + enabled + " exported=" + exported
+ " processName=" + processName);
+ if (descriptionRes != 0) {
+ pw.println(prefix + "description=" + descriptionRes);
+ }
}
protected void dumpBack(Printer pw, String prefix) {
@@ -124,6 +151,7 @@
super.writeToParcel(dest, parcelableFlags);
applicationInfo.writeToParcel(dest, parcelableFlags);
dest.writeString(processName);
+ dest.writeInt(descriptionRes);
dest.writeInt(enabled ? 1 : 0);
dest.writeInt(exported ? 1 : 0);
}
@@ -132,6 +160,7 @@
super(source);
applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
processName = source.readString();
+ descriptionRes = source.readInt();
enabled = (source.readInt() != 0);
exported = (source.readInt() != 0);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 8a5df32..bbde0a6 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -119,15 +119,18 @@
static class ParseComponentArgs extends ParsePackageItemArgs {
final String[] sepProcesses;
final int processRes;
+ final int descriptionRes;
final int enabledRes;
int flags;
ParseComponentArgs(Package _owner, String[] _outError,
int _nameRes, int _labelRes, int _iconRes,
- String[] _sepProcesses, int _processRes,int _enabledRes) {
+ String[] _sepProcesses, int _processRes,
+ int _descriptionRes, int _enabledRes) {
super(_owner, _outError, _nameRes, _labelRes, _iconRes);
sepProcesses = _sepProcesses;
processRes = _processRes;
+ descriptionRes = _descriptionRes;
enabledRes = _enabledRes;
}
}
@@ -1602,6 +1605,7 @@
com.android.internal.R.styleable.AndroidManifestActivity_icon,
mSeparateProcesses,
com.android.internal.R.styleable.AndroidManifestActivity_process,
+ com.android.internal.R.styleable.AndroidManifestActivity_description,
com.android.internal.R.styleable.AndroidManifestActivity_enabled);
}
@@ -1803,6 +1807,7 @@
com.android.internal.R.styleable.AndroidManifestActivityAlias_icon,
mSeparateProcesses,
0,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_description,
com.android.internal.R.styleable.AndroidManifestActivityAlias_enabled);
mParseActivityAliasArgs.tag = "<activity-alias>";
}
@@ -1837,6 +1842,9 @@
info.nonLocalizedLabel = target.info.nonLocalizedLabel;
info.launchMode = target.info.launchMode;
info.processName = target.info.processName;
+ if (info.descriptionRes == 0) {
+ info.descriptionRes = target.info.descriptionRes;
+ }
info.screenOrientation = target.info.screenOrientation;
info.taskAffinity = target.info.taskAffinity;
info.theme = target.info.theme;
@@ -1926,6 +1934,7 @@
com.android.internal.R.styleable.AndroidManifestProvider_icon,
mSeparateProcesses,
com.android.internal.R.styleable.AndroidManifestProvider_process,
+ com.android.internal.R.styleable.AndroidManifestProvider_description,
com.android.internal.R.styleable.AndroidManifestProvider_enabled);
mParseProviderArgs.tag = "<provider>";
}
@@ -2188,6 +2197,7 @@
com.android.internal.R.styleable.AndroidManifestService_icon,
mSeparateProcesses,
com.android.internal.R.styleable.AndroidManifestService_process,
+ com.android.internal.R.styleable.AndroidManifestService_description,
com.android.internal.R.styleable.AndroidManifestService_enabled);
mParseServiceArgs.tag = "<service>";
}
@@ -2640,6 +2650,11 @@
owner.applicationInfo.processName, args.sa.getNonResourceString(args.processRes),
args.flags, args.sepProcesses, args.outError);
}
+
+ if (args.descriptionRes != 0) {
+ outInfo.descriptionRes = args.sa.getResourceId(args.descriptionRes, 0);
+ }
+
outInfo.enabled = args.sa.getBoolean(args.enabledRes, true);
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index d90536c..4c4455a 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -543,6 +543,21 @@
public native final void stopSmoothZoom();
/**
+ * Set the display orientation. This affects the preview frames and the
+ * picture displayed after snapshot. This method is useful for portrait
+ * mode applications.
+ *
+ * This does not affect the order of byte array passed in
+ * {@link PreviewCallback#onPreviewFrame}. This method is not allowed to
+ * be called during preview.
+ *
+ * @param degrees the angle that the picture will be rotated clockwise.
+ * Valid values are 0, 90, 180, and 270.
+ * @hide
+ */
+ public native final void setDisplayOrientation(int degrees);
+
+ /**
* Handles the zoom callback.
*
* @hide
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl
index c0c2d03..2124e85 100644
--- a/core/java/android/os/IMountService.aidl
+++ b/core/java/android/os/IMountService.aidl
@@ -99,6 +99,11 @@
void unmountSecureContainer(String id);
/*
+ * Rename an unmounted secure container.
+ */
+ void renameSecureContainer(String oldId, String newId);
+
+ /*
* Returns the filesystem path of a mounted secure container.
*/
String getSecureContainerPath(String id);
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 6bf09b5..08ab166 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -517,6 +517,8 @@
/**
* This download couldn't be completed because of a storage issue.
* Typically, that's because the filesystem is missing or full.
+ * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR}
+ * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate.
* @hide
*/
public static final int STATUS_FILE_ERROR = 492;
@@ -558,6 +560,21 @@
public static final int STATUS_TOO_MANY_REDIRECTS = 497;
/**
+ * This download couldn't be completed due to insufficient storage
+ * space. Typically, this is because the SD card is full.
+ * @hide
+ */
+ public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
+
+ /**
+ * This download couldn't be completed because no external storage
+ * device was found. Typically, this is because the SD card is not
+ * mounted.
+ * @hide
+ */
+ public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
+
+ /**
* This download is visible but only shows in the notifications
* while it's in progress.
* @hide
@@ -1019,6 +1036,8 @@
/**
* This download couldn't be completed because of a storage issue.
* Typically, that's because the filesystem is missing or full.
+ * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR}
+ * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate.
*/
public static final int STATUS_FILE_ERROR = 492;
@@ -1054,6 +1073,19 @@
public static final int STATUS_TOO_MANY_REDIRECTS = 497;
/**
+ * This download couldn't be completed due to insufficient storage
+ * space. Typically, this is because the SD card is full.
+ */
+ public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
+
+ /**
+ * This download couldn't be completed because no external storage
+ * device was found. Typically, this is because the SD card is not
+ * mounted.
+ */
+ public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
+
+ /**
* This download is visible but only shows in the notifications
* while it's in progress.
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dc40113..23a9f49 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -450,6 +450,21 @@
public static final String ACTION_SEARCH_SETTINGS =
"android.search.action.SEARCH_SETTINGS";
+ /**
+ * Activity Action: Show general device information settings (serial
+ * number, software version, phone number, etc.).
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_DEVICE_INFO_SETTINGS =
+ "android.settings.DEVICE_INFO_SETTINGS";
+
// End of Intent actions for Settings
private static final String JID_RESOURCE_PREFIX = "android";
@@ -1115,19 +1130,16 @@
/**
* Control whether to enable automatic brightness mode.
- * @hide
*/
public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode";
/**
* SCREEN_BRIGHTNESS_MODE value for manual mode.
- * @hide
*/
public static final int SCREEN_BRIGHTNESS_MODE_MANUAL = 0;
/**
* SCREEN_BRIGHTNESS_MODE value for manual mode.
- * @hide
*/
public static final int SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1;
@@ -2021,10 +2033,10 @@
public static final String ALLOW_MOCK_LOCATION = "mock_location";
/**
- * The Android ID (a unique 64-bit value) as a hex string.
- * Identical to that obtained by calling
- * GoogleLoginService.getAndroidId(); it is also placed here
- * so you can get it without binding to a service.
+ * A 64-bit number (as a hex string) that is randomly
+ * generated on the device's first boot and should remain
+ * constant for the lifetime of the device. (The value may
+ * change if a factory reset is performed on the device.)
*/
public static final String ANDROID_ID = "android_id";
@@ -2962,8 +2974,6 @@
* @param cr the content resolver to use
* @param provider the location provider to query
* @return true if the provider is enabled
- *
- * @hide
*/
public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) {
String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED);
@@ -2981,8 +2991,6 @@
* @param cr the content resolver to use
* @param provider the location provider to enable or disable
* @param enabled true if the provider should be enabled
- *
- * @hide
*/
public static final void setLocationProviderEnabled(ContentResolver cr,
String provider, boolean enabled) {
diff --git a/core/java/android/speech/IRecognitionListener.aidl b/core/java/android/speech/IRecognitionListener.aidl
index 2da2258..5b48bd2 100644
--- a/core/java/android/speech/IRecognitionListener.aidl
+++ b/core/java/android/speech/IRecognitionListener.aidl
@@ -17,7 +17,6 @@
package android.speech;
import android.os.Bundle;
-import android.speech.RecognitionResult;
/**
* Listener for speech recognition events, used with RecognitionService.
@@ -26,35 +25,55 @@
* {@hide}
*/
interface IRecognitionListener {
- /** Called when the endpointer is ready for the user to start speaking. */
- void onReadyForSpeech(in Bundle noiseParams);
+ /**
+ * Called when the endpointer is ready for the user to start speaking.
+ *
+ * @param params parameters set by the recognition service. Reserved for future use.
+ */
+ void onReadyForSpeech(in Bundle params);
- /** The user has started to speak. */
+ /**
+ * The user has started to speak.
+ */
void onBeginningOfSpeech();
- /** The sound level in the audio stream has changed. */
+ /**
+ * The sound level in the audio stream has changed.
+ *
+ * @param rmsdB the new RMS dB value
+ */
void onRmsChanged(in float rmsdB);
/**
- * More sound has been received. Buffer is a byte buffer containing
- * a sequence of 16-bit shorts.
+ * More sound has been received.
+ *
+ * @param buffer the byte buffer containing a sequence of 16-bit shorts.
*/
void onBufferReceived(in byte[] buffer);
- /** Called after the user stops speaking. */
+ /**
+ * Called after the user stops speaking.
+ */
void onEndOfSpeech();
/**
- * A network or recognition error occurred. The code is defined in
- * {@link android.speech.RecognitionResult}
+ * A network or recognition error occurred.
+ *
+ * @param error code is defined in {@link RecognitionManager}
*/
void onError(in int error);
- /**
+ /**
* Called when recognition results are ready.
- * @param results: an ordered list of the most likely results (N-best list).
- * @param key: a key associated with the results. The same results can
- * be retrieved asynchronously later using the key, if available.
+ *
+ * @param results a Bundle containing the most likely results (N-best list).
*/
- void onResults(in List<RecognitionResult> results, long key);
+ void onResults(in Bundle results);
+
+ /**
+ * Called when recognition partial results are ready.
+ *
+ * @param results a Bundle containing the current most likely result.
+ */
+ void onPartialResults(in Bundle results);
}
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index a18c380..ca9af15 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -16,22 +16,41 @@
package android.speech;
+import android.os.Bundle;
import android.content.Intent;
import android.speech.IRecognitionListener;
-import android.speech.RecognitionResult;
-// A Service interface to speech recognition. Call startListening when
-// you want to begin capturing audio; RecognitionService will automatically
-// determine when the user has finished speaking, stream the audio to the
-// recognition servers, and notify you when results are ready.
-/** {@hide} */
+/**
+* A Service interface to speech recognition. Call startListening when
+* you want to begin capturing audio; RecognitionService will automatically
+* determine when the user has finished speaking, stream the audio to the
+* recognition servers, and notify you when results are ready. In most of the cases,
+* this class should not be used directly, instead use {@link RecognitionManager} for
+* accessing recognition service.
+* {@hide}
+*/
interface IRecognitionService {
- // Start listening for speech. Can only call this from one thread at once.
- // see RecognizerIntent.java for constants used to specify the intent.
- void startListening(in Intent recognizerIntent,
- in IRecognitionListener listener);
-
- List<RecognitionResult> getRecognitionResults(in long key);
+ /**
+ * Starts listening for speech. Please note that the recognition service supports
+ * one listener only, therefore, if this function is called from two different threads,
+ * only the latest one will get the notifications
+ *
+ * @param recognizerIntent the intent from which the invocation occurred. Additionally,
+ * this intent can contain extra parameters to manipulate the behavior of the recognition
+ * client. For more information see {@link RecognizerIntent}.
+ * @param listener to receive callbacks
+ */
+ void startListening(in Intent recognizerIntent, in IRecognitionListener listener);
+ /**
+ * Stops listening for speech. Speech captured so far will be recognized as
+ * if the user had stopped speaking at this point. The function has no effect unless it
+ * is called during the speech capturing.
+ */
+ void stopListening();
+
+ /**
+ * Cancels the speech recognition.
+ */
void cancel();
}
diff --git a/core/java/android/speech/RecognitionListener.java b/core/java/android/speech/RecognitionListener.java
new file mode 100644
index 0000000..eab3f40
--- /dev/null
+++ b/core/java/android/speech/RecognitionListener.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2010 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 android.speech;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Used for receiving notifications from the RecognitionManager when the
+ * recognition related events occur. All the callbacks are executed on the
+ * Application main thread.
+ */
+public interface RecognitionListener {
+
+ /**
+ * Called when RecognitionManager is successfully initialized
+ */
+ void onInit();
+
+ /**
+ * Called when the endpointer is ready for the user to start speaking.
+ *
+ * @param params parameters set by the recognition service. Reserved for future use.
+ */
+ void onReadyForSpeech(Bundle params);
+
+ /**
+ * The user has started to speak.
+ */
+ void onBeginningOfSpeech();
+
+ /**
+ * The sound level in the audio stream has changed. There is no guarantee that this method will
+ * be called.
+ *
+ * @param rmsdB the new RMS dB value
+ */
+ void onRmsChanged(float rmsdB);
+
+ /**
+ * More sound has been received. The purpose of this function is to allow giving feedback to the
+ * user regarding the captured audio. There is no guarantee that this method will be called.
+ *
+ * @param buffer a buffer containing a sequence of big-endian 16-bit integers representing a
+ * single channel audio stream. The sample rate is implementation dependent.
+ */
+ void onBufferReceived(byte[] buffer);
+
+ /**
+ * Called after the user stops speaking.
+ */
+ void onEndOfSpeech();
+
+ /**
+ * A network or recognition error occurred.
+ *
+ * @param error code is defined in {@link RecognitionManager}
+ */
+ void onError(int error);
+
+ /**
+ * Called when recognition results are ready.
+ *
+ * @param results the recognition results. To retrieve the results in {@code
+ * ArrayList<String>} format use {@link Bundle#getStringArrayList(String)} with
+ * {@link RecognitionManager#RECOGNITION_RESULTS_STRING_ARRAY} as a parameter
+ */
+ void onResults(Bundle results);
+
+ /**
+ * Called when partial recognition results are available. The callback might be called at any
+ * time between {@link #onBeginningOfSpeech()} and {@link #onResults(Bundle)} when partial
+ * results are ready. This method may be called zero, one or multiple times for each call to
+ * {@link RecognitionManager#startListening(Intent)}, depending on the speech recognition
+ * service implementation.
+ *
+ * @param partialResults the returned results. To retrieve the results in
+ * ArrayList<String> format use {@link Bundle#getStringArrayList(String)} with
+ * {@link RecognitionManager#RECOGNITION_RESULTS_STRING_ARRAY} as a parameter
+ */
+ void onPartialResults(Bundle partialResults);
+
+}
diff --git a/core/java/android/speech/RecognitionManager.java b/core/java/android/speech/RecognitionManager.java
new file mode 100644
index 0000000..79ae480
--- /dev/null
+++ b/core/java/android/speech/RecognitionManager.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2010 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 android.speech;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * This class provides access to the speech recognition service. This service allows access to the
+ * speech recognizer. Do not instantiate this class directly, instead, call
+ * {@link RecognitionManager#createRecognitionManager(Context, RecognitionListener, Intent)}. This
+ * class is not thread safe and must be synchronized externally if accessed from multiple threads.
+ */
+public class RecognitionManager {
+ /** DEBUG value to enable verbose debug prints */
+ private final static boolean DBG = false;
+
+ /** Log messages identifier */
+ private static final String TAG = "RecognitionManager";
+
+ /**
+ * Used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the
+ * {@link RecognitionListener#onResults(Bundle)} and
+ * {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible
+ * recognition results, where the first element is the most likely candidate.
+ */
+ public static final String RECOGNITION_RESULTS_STRING_ARRAY =
+ "recognition_results_string_array";
+
+ /** The actual RecognitionService endpoint */
+ private IRecognitionService mService;
+
+ /** The connection to the actual service */
+ private Connection mConnection;
+
+ /** Context with which the manager was created */
+ private final Context mContext;
+
+ /** Listener that will receive all the callbacks */
+ private final RecognitionListener mListener;
+
+ /** Helper class wrapping the IRecognitionListener */
+ private final InternalRecognitionListener mInternalRecognitionListener;
+
+ /** Network operation timed out. */
+ public static final int NETWORK_TIMEOUT_ERROR = 1;
+
+ /** Other network related errors. */
+ public static final int NETWORK_ERROR = 2;
+
+ /** Audio recording error. */
+ public static final int AUDIO_ERROR = 3;
+
+ /** Server sends error status. */
+ public static final int SERVER_ERROR = 4;
+
+ /** Other client side errors. */
+ public static final int CLIENT_ERROR = 5;
+
+ /** No speech input */
+ public static final int SPEECH_TIMEOUT_ERROR = 6;
+
+ /** No recognition result matched. */
+ public static final int NO_MATCH_ERROR = 7;
+
+ /** RecognitionService busy. */
+ public static final int SERVER_BUSY_ERROR = 8;
+
+ /**
+ * RecognitionManager was not initialized yet, most probably because
+ * {@link RecognitionListener#onInit()} was not called yet.
+ */
+ public static final int MANAGER_NOT_INITIALIZED_ERROR = 9;
+
+ /**
+ * The right way to create a RecognitionManager is by using
+ * {@link #createRecognitionManager} static factory method
+ */
+ private RecognitionManager(final RecognitionListener listener, final Context context) {
+ mInternalRecognitionListener = new InternalRecognitionListener();
+ mContext = context;
+ mListener = listener;
+ }
+
+ /**
+ * Basic ServiceConnection which just records mService variable.
+ */
+ private class Connection implements ServiceConnection {
+
+ public synchronized void onServiceConnected(final ComponentName name,
+ final IBinder service) {
+ mService = IRecognitionService.Stub.asInterface(service);
+ if (mListener != null) {
+ mListener.onInit();
+ }
+ if (DBG) Log.d(TAG, "onServiceConnected - Success");
+ }
+
+ public void onServiceDisconnected(final ComponentName name) {
+ mService = null;
+ mConnection = null;
+ if (DBG) Log.d(TAG, "onServiceDisconnected - Success");
+ }
+ }
+
+ /**
+ * Checks whether a speech recognition service is available on the system. If this method
+ * returns {@code false},
+ * {@link RecognitionManager#createRecognitionManager(Context, RecognitionListener, Intent)}
+ * will fail.
+ *
+ * @param context with which RecognitionManager will be created
+ * @return {@code true} if recognition is available, {@code false} otherwise
+ */
+ public static boolean isRecognitionAvailable(final Context context) {
+ final List<ResolveInfo> list = context.getPackageManager().queryIntentServices(
+ new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
+ return list != null && list.size() != 0;
+ }
+
+ /**
+ * Factory method to create a new RecognitionManager
+ *
+ * @param context in which to create RecognitionManager
+ * @param listener that will receive all the callbacks from the created
+ * {@link RecognitionManager}
+ * @param recognizerIntent contains initialization parameters for the speech recognizer. The
+ * intent action should be {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH}. Future
+ * versions of this API may add startup parameters for speech recognizer.
+ * @return null if a recognition service implementation is not installed or if speech
+ * recognition is not supported by the device, otherwise a new RecognitionManager is
+ * returned. The created RecognitionManager can only be used after the
+ * {@link RecognitionListener#onInit()} method has been called.
+ */
+ public static RecognitionManager createRecognitionManager(final Context context,
+ final RecognitionListener listener, final Intent recognizerIntent) {
+ if (context == null || recognizerIntent == null) {
+ throw new IllegalArgumentException(
+ "Context and recognizerListener argument cannot be null)");
+ }
+ RecognitionManager manager = new RecognitionManager(listener, context);
+ manager.mConnection = manager.new Connection();
+ if (!context.bindService(recognizerIntent, manager.mConnection, Context.BIND_AUTO_CREATE)) {
+ Log.e(TAG, "bind to recognition service failed");
+ listener.onError(CLIENT_ERROR);
+ return null;
+ }
+ return manager;
+ }
+
+ /**
+ * Checks whether the service is connected
+ *
+ * @param functionName from which the call originated
+ * @return {@code true} if the service was successfully initialized, {@code false} otherwise
+ */
+ private boolean connectToService(final String functionName) {
+ if (mService != null) {
+ return true;
+ }
+ if (mConnection == null) {
+ if (DBG) Log.d(TAG, "restarting connection to the recognition service");
+ mConnection = new Connection();
+ mContext.bindService(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), mConnection,
+ Context.BIND_AUTO_CREATE);
+ }
+ mInternalRecognitionListener.onError(MANAGER_NOT_INITIALIZED_ERROR);
+ Log.e(TAG, functionName + " was called before service connection was initialized");
+ return false;
+ }
+
+ /**
+ * Starts listening for speech.
+ *
+ * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+ * action should be {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH}. The intent may also
+ * contain optional extras, see {@link RecognizerIntent}. If these values are not set
+ * explicitly, default values will be used by the recognizer.
+ */
+ public void startListening(Intent recognizerIntent) {
+ if (recognizerIntent == null) {
+ throw new IllegalArgumentException("recognizerIntent argument cannot be null");
+ }
+ if (!connectToService("startListening")) {
+ return; // service is not connected yet, reconnect in progress
+ }
+ try {
+ mService.startListening(recognizerIntent, mInternalRecognitionListener);
+ if (DBG) Log.d(TAG, "service start listening command succeded");
+ } catch (final RemoteException e) {
+ Log.e(TAG, "startListening() failed", e);
+ mInternalRecognitionListener.onError(CLIENT_ERROR);
+ }
+ }
+
+ /**
+ * Stops listening for speech. Speech captured so far will be recognized as if the user had
+ * stopped speaking at this point. Note that in the default case, this does not need to be
+ * called, as the speech endpointer will automatically stop the recognizer listening when it
+ * determines speech has completed. However, you can manipulate endpointer parameters directly
+ * using the intent extras defined in {@link RecognizerIntent}, in which case you may sometimes
+ * want to manually call this method to stop listening sooner.
+ */
+ public void stopListening() {
+ if (mService == null) {
+ return; // service is not connected, but no need to reconnect at this point
+ }
+ try {
+ mService.stopListening();
+ if (DBG) Log.d(TAG, "service stop listening command succeded");
+ } catch (final RemoteException e) {
+ Log.e(TAG, "stopListening() failed", e);
+ mInternalRecognitionListener.onError(CLIENT_ERROR);
+ }
+ }
+
+ /**
+ * Cancels the speech recognition.
+ */
+ public void cancel() {
+ if (mService == null) {
+ return; // service is not connected, but no need to reconnect at this point
+ }
+ try {
+ mService.cancel();
+ if (DBG) Log.d(TAG, "service cancel command succeded");
+ } catch (final RemoteException e) {
+ Log.e(TAG, "cancel() failed", e);
+ mInternalRecognitionListener.onError(CLIENT_ERROR);
+ }
+ }
+
+ /**
+ * Destroys the RecognitionManager object. Note that after calling this method all method calls
+ * on this object will fail, triggering {@link RecognitionListener#onError}.
+ */
+ public void destroy() {
+ if (mConnection != null) {
+ mContext.unbindService(mConnection);
+ }
+ mService = null;
+ }
+
+ /**
+ * Internal wrapper of IRecognitionListener which will propagate the results
+ * to RecognitionListener
+ */
+ private class InternalRecognitionListener extends IRecognitionListener.Stub {
+
+ public void onBeginningOfSpeech() {
+ if (mListener != null) {
+ mListener.onBeginningOfSpeech();
+ }
+ }
+
+ public void onBufferReceived(final byte[] buffer) {
+ if (mListener != null) {
+ mListener.onBufferReceived(buffer);
+ }
+ }
+
+ public void onEndOfSpeech() {
+ if (mListener != null) {
+ mListener.onEndOfSpeech();
+ }
+ }
+
+ public void onError(final int error) {
+ if (mListener != null) {
+ mListener.onError(error);
+ }
+ }
+
+ public void onReadyForSpeech(final Bundle noiseParams) {
+ if (mListener != null) {
+ mListener.onReadyForSpeech(noiseParams);
+ }
+ }
+
+ public void onResults(final Bundle results) {
+ if (mListener != null) {
+ mListener.onResults(results);
+ }
+ }
+
+ public void onPartialResults(final Bundle results) {
+ if (mListener != null) {
+ mListener.onPartialResults(results);
+ }
+ }
+
+ public void onRmsChanged(final float rmsdB) {
+ if (mListener != null) {
+ mListener.onRmsChanged(rmsdB);
+ }
+ }
+ }
+}
diff --git a/core/java/android/speech/RecognitionResult.aidl b/core/java/android/speech/RecognitionResult.aidl
deleted file mode 100644
index 59e53ab..0000000
--- a/core/java/android/speech/RecognitionResult.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.speech;
-
-parcelable RecognitionResult;
diff --git a/core/java/android/speech/RecognitionResult.java b/core/java/android/speech/RecognitionResult.java
deleted file mode 100644
index 95715ee..0000000
--- a/core/java/android/speech/RecognitionResult.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.speech;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * RecognitionResult is a passive object that stores a single recognized query
- * and its search result.
- *
- * TODO: Revisit and improve this class, reconciling the different types of actions and
- * the different ways they are represented. Maybe we should have a separate result object
- * for each type, and put them (type/value) in bundle?
- * {@hide}
- */
-public class RecognitionResult implements Parcelable {
- /**
- * Status of the recognize request.
- */
- public static final int NETWORK_TIMEOUT = 1; // Network operation timed out.
-
- public static final int NETWORK_ERROR = 2; // Other network related errors.
-
- public static final int AUDIO_ERROR = 3; // Audio recording error.
-
- public static final int SERVER_ERROR = 4; // Server sends error status.
-
- public static final int CLIENT_ERROR = 5; // Other client side errors.
-
- public static final int SPEECH_TIMEOUT = 6; // No speech input
-
- public static final int NO_MATCH = 7; // No recognition result matched.
-
- public static final int SERVICE_BUSY = 8; // RecognitionService busy.
-
- /**
- * Type of the recognition results.
- */
- public static final int RAW_RECOGNITION_RESULT = 0;
-
- public static final int WEB_SEARCH_RESULT = 1;
-
- public static final int CONTACT_RESULT = 2;
-
- public static final int ACTION_RESULT = 3;
-
- /**
- * A factory method to create a raw RecognitionResult
- *
- * @param sentence the recognized text.
- */
- public static RecognitionResult newRawRecognitionResult(String sentence) {
- return new RecognitionResult(RAW_RECOGNITION_RESULT, sentence, null, null);
- }
-
- /**
- * A factory method to create a RecognitionResult for contacts.
- *
- * @param contact the contact name.
- * @param phoneType the phone type.
- * @param callAction whether this result included a command to "call", or
- * just the contact name.
- */
- public static RecognitionResult newContactResult(String contact, int phoneType,
- boolean callAction) {
- return new RecognitionResult(CONTACT_RESULT, contact, phoneType, callAction);
- }
-
- /**
- * A factory method to create a RecognitionResult for a web search query.
- *
- * @param query the query string.
- * @param html the html page of the search result.
- * @param url the url that performs the search with the query.
- */
- public static RecognitionResult newWebResult(String query, String html, String url) {
- return new RecognitionResult(WEB_SEARCH_RESULT, query, html, url);
- }
-
- /**
- * A factory method to create a RecognitionResult for an action.
- *
- * @param action the action type
- * @param query the query string associated with that action.
- */
- public static RecognitionResult newActionResult(int action, String query) {
- return new RecognitionResult(ACTION_RESULT, action, query);
- }
-
- public static final Parcelable.Creator<RecognitionResult> CREATOR =
- new Parcelable.Creator<RecognitionResult>() {
-
- public RecognitionResult createFromParcel(Parcel in) {
- return new RecognitionResult(in);
- }
-
- public RecognitionResult[] newArray(int size) {
- return new RecognitionResult[size];
- }
- };
-
- /**
- * Result type.
- */
- public final int mResultType;
-
- /**
- * The recognized string when mResultType is WEB_SEARCH_RESULT. The name of
- * the contact when mResultType is CONTACT_RESULT. The relevant query when
- * mResultType is ACTION_RESULT.
- */
- public final String mText;
-
- /**
- * The HTML result page for the query. If this is null, then the application
- * must use the url field to get the HTML result page.
- */
- public final String mHtml;
-
- /**
- * The url to get the result page for the query string. The application must
- * use this url instead of performing the search with the query.
- */
- public final String mUrl;
-
- /**
- * Phone number type. This is valid only when mResultType == CONTACT_RESULT.
- */
- public final int mPhoneType;
-
- /**
- * Action type. This is valid only when mResultType == ACTION_RESULT.
- */
- public final int mAction;
-
- /**
- * Whether a contact recognition result included a command to "call". This
- * is valid only when mResultType == CONTACT_RESULT.
- */
- public final boolean mCallAction;
-
- private RecognitionResult(int type, int action, String query) {
- mResultType = type;
- mAction = action;
- mText = query;
- mHtml = null;
- mUrl = null;
- mPhoneType = -1;
- mCallAction = false;
- }
-
- private RecognitionResult(int type, String query, String html, String url) {
- mResultType = type;
- mText = query;
- mHtml = html;
- mUrl = url;
- mPhoneType = -1;
- mAction = -1;
- mCallAction = false;
- }
-
- private RecognitionResult(int type, String query, int phoneType, boolean callAction) {
- mResultType = type;
- mText = query;
- mPhoneType = phoneType;
- mHtml = null;
- mUrl = null;
- mAction = -1;
- mCallAction = callAction;
- }
-
- private RecognitionResult(Parcel in) {
- mResultType = in.readInt();
- mText = in.readString();
- mHtml = in.readString();
- mUrl = in.readString();
- mPhoneType = in.readInt();
- mAction = in.readInt();
- mCallAction = (in.readInt() == 1);
- }
-
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(mResultType);
- out.writeString(mText);
- out.writeString(mHtml);
- out.writeString(mUrl);
- out.writeInt(mPhoneType);
- out.writeInt(mAction);
- out.writeInt(mCallAction ? 1 : 0);
- }
-
- @Override
- public String toString() {
- String resultType[] = {
- "RAW", "WEB", "CONTACT", "ACTION"
- };
- return "[type=" + resultType[mResultType] + ", text=" + mText + ", mUrl=" + mUrl
- + ", html=" + mHtml + ", mAction=" + mAction + ", mCallAction=" + mCallAction + "]";
- }
-
- public int describeContents() {
- // no special description
- return 0;
- }
-}
diff --git a/core/java/android/speech/RecognitionServiceUtil.java b/core/java/android/speech/RecognitionServiceUtil.java
deleted file mode 100644
index 4207543..0000000
--- a/core/java/android/speech/RecognitionServiceUtil.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.speech;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.speech.RecognitionResult;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Utils for Google's network-based speech recognizer, which lets you perform
- * speech-to-text translation through RecognitionService. IRecognitionService
- * and IRecognitionListener are the core interfaces; you begin recognition
- * through IRecognitionService and subscribe to callbacks about when the user
- * stopped speaking, results come in, errors, etc. through IRecognitionListener.
- * RecognitionServiceUtil includes default IRecognitionListener and
- * ServiceConnection implementations to reduce the amount of boilerplate.
- *
- * The Service provides no user interface. See RecognitionActivity if you
- * want the standard voice search UI.
- *
- * Below is a small skeleton of how to use the recognizer:
- *
- * ServiceConnection conn = new RecognitionServiceUtil.Connection();
- * mContext.bindService(RecognitionServiceUtil.sDefaultIntent,
- * conn, Context.BIND_AUTO_CREATE);
- * IRecognitionListener listener = new RecognitionServiceWrapper.NullListener() {
- * public void onResults(List<String> results) {
- * // Do something with recognition transcripts
- * }
- * }
- *
- * // Must wait for conn.mService to be populated, then call below
- * conn.mService.startListening(null, listener);
- *
- * {@hide}
- */
-public class RecognitionServiceUtil {
- public static final Intent sDefaultIntent = new Intent(
- RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
-
- // Recognize request parameters
- public static final String USE_LOCATION = "useLocation";
- public static final String CONTACT_AUTH_TOKEN = "contactAuthToken";
-
- // Bundles
- public static final String NOISE_LEVEL = "NoiseLevel";
- public static final String SIGNAL_NOISE_RATIO = "SignalNoiseRatio";
-
- private RecognitionServiceUtil() {}
-
- /**
- * IRecognitionListener which does nothing in response to recognition
- * callbacks. You can subclass from this and override only the methods
- * whose events you want to respond to.
- */
- public static class NullListener extends IRecognitionListener.Stub {
- public void onReadyForSpeech(Bundle bundle) {}
- public void onBeginningOfSpeech() {}
- public void onRmsChanged(float rmsdB) {}
- public void onBufferReceived(byte[] buf) {}
- public void onEndOfSpeech() {}
- public void onError(int error) {}
- public void onResults(List<RecognitionResult> results, long key) {}
- }
-
- /**
- * Basic ServiceConnection which just records mService variable.
- */
- public static class Connection implements ServiceConnection {
- public IRecognitionService mService;
-
- public synchronized void onServiceConnected(ComponentName name, IBinder service) {
- mService = IRecognitionService.Stub.asInterface(service);
- }
-
- public void onServiceDisconnected(ComponentName name) {
- mService = null;
- }
- }
-}
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index ba06744..49991bd 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -84,6 +84,42 @@
public static final String ACTION_WEB_SEARCH = "android.speech.action.WEB_SEARCH";
/**
+ * The minimum length of an utterance. We will not stop recording before this amount of time.
+ *
+ * Note that it is extremely rare you'd want to specify this value in an intent. If you don't
+ * have a very good reason to change these, you should leave them as they are. Note also that
+ * certain values may cause undesired or unexpected results - use judiciously! Additionally,
+ * depending on the recognizer implementation, these values may have no effect.
+ */
+ public static final String EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS =
+ "android.speech.extras.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS";
+
+ /**
+ * The amount of time that it should take after we stop hearing speech to consider the input
+ * complete.
+ *
+ * Note that it is extremely rare you'd want to specify this value in an intent. If
+ * you don't have a very good reason to change these, you should leave them as they are. Note
+ * also that certain values may cause undesired or unexpected results - use judiciously!
+ * Additionally, depending on the recognizer implementation, these values may have no effect.
+ */
+ public static final String EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS =
+ "android.speech.extras.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS";
+
+ /**
+ * The amount of time that it should take after we stop hearing speech to consider the input
+ * possibly complete. This is used to prevent the endpointer cutting off during very short
+ * mid-speech pauses.
+ *
+ * Note that it is extremely rare you'd want to specify this value in an intent. If
+ * you don't have a very good reason to change these, you should leave them as they are. Note
+ * also that certain values may cause undesired or unexpected results - use judiciously!
+ * Additionally, depending on the recognizer implementation, these values may have no effect.
+ */
+ public static final String EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS =
+ "android.speech.extras.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS";
+
+ /**
* Informs the recognizer which speech model to prefer when performing
* {@link #ACTION_RECOGNIZE_SPEECH}. The recognizer uses this
* information to fine tune the results. This extra is required. Activities implementing
@@ -111,8 +147,9 @@
public static final String EXTRA_PROMPT = "android.speech.extra.PROMPT";
/**
- * Optional language override to inform the recognizer that it should expect speech in
- * a language different than the one set in the {@link java.util.Locale#getDefault()}.
+ * Optional IETF language tag (as defined by BCP 47), for example "en-US". This tag informs the
+ * recognizer to perform speech recognition in a language different than the one set in the
+ * {@link java.util.Locale#getDefault()}.
*/
public static final String EXTRA_LANGUAGE = "android.speech.extra.LANGUAGE";
@@ -121,7 +158,7 @@
* will choose how many results to return. Must be an integer.
*/
public static final String EXTRA_MAX_RESULTS = "android.speech.extra.MAX_RESULTS";
-
+
/**
* When the intent is {@link #ACTION_RECOGNIZE_SPEECH}, the speech input activity will
* return results to you via the activity results mechanism. Alternatively, if you use this
diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java
index 90ed65d..d99c2c0 100644
--- a/core/java/android/webkit/SslErrorHandler.java
+++ b/core/java/android/webkit/SslErrorHandler.java
@@ -51,6 +51,11 @@
*/
private Bundle mSslPrefTable;
+ /**
+ * Flag indicating that a client reponse is pending.
+ */
+ private boolean mResponsePending;
+
// Message id for handling the response
private static final int HANDLE_RESPONSE = 100;
@@ -191,6 +196,7 @@
// if we do not have information on record, ask
// the user (display a dialog)
CallbackProxy proxy = loader.getFrame().getCallbackProxy();
+ mResponsePending = true;
proxy.onReceivedSslError(this, error);
}
@@ -202,7 +208,11 @@
* Proceed with the SSL certificate.
*/
public void proceed() {
- sendMessage(obtainMessage(HANDLE_RESPONSE, 1, 0, mLoaderQueue.poll()));
+ if (mResponsePending) {
+ mResponsePending = false;
+ sendMessage(obtainMessage(HANDLE_RESPONSE, 1, 0,
+ mLoaderQueue.poll()));
+ }
}
/**
@@ -210,7 +220,11 @@
* the error.
*/
public void cancel() {
- sendMessage(obtainMessage(HANDLE_RESPONSE, 0, 0, mLoaderQueue.poll()));
+ if (mResponsePending) {
+ mResponsePending = false;
+ sendMessage(obtainMessage(HANDLE_RESPONSE, 0, 0,
+ mLoaderQueue.poll()));
+ }
}
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 56650a6..93e72ff 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -4607,14 +4607,11 @@
break;
}
} else {
- if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
- // if mPreventDrag is not confirmed, treat it as
- // no so that it won't block tap or double tap.
- mPreventDrag = PREVENT_DRAG_NO;
- mPreventLongPress = false;
- mPreventDoubleTap = false;
- }
- if (mPreventDrag == PREVENT_DRAG_NO) {
+ // mPreventDrag can be PREVENT_DRAG_MAYBE_YES in
+ // TOUCH_INIT_MODE. To give WebCoreThread a little
+ // more time to send PREVENT_TOUCH_ID, we check
+ // again in responding RELEASE_SINGLE_TAP.
+ if (mPreventDrag != PREVENT_DRAG_YES) {
if (mTouchMode == TOUCH_INIT_MODE) {
mPrivateHandler.sendMessageDelayed(
mPrivateHandler.obtainMessage(
@@ -5631,6 +5628,13 @@
break;
}
case RELEASE_SINGLE_TAP: {
+ if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
+ // if mPreventDrag is not confirmed, treat it as
+ // no so that it won't block tap.
+ mPreventDrag = PREVENT_DRAG_NO;
+ mPreventLongPress = false;
+ mPreventDoubleTap = false;
+ }
if (mPreventDrag == PREVENT_DRAG_NO) {
mTouchMode = TOUCH_DONE_MODE;
doShortPress();
@@ -5863,7 +5867,7 @@
// updates is a C++ pointer to a Vector of
// AnimationValues that we apply to the layers.
// The Vector is deallocated in nativeUpdateLayers().
- nativeUpdateLayers(mRootLayer, updates);
+ nativeUpdateLayers(updates);
}
invalidate();
break;
@@ -5985,10 +5989,22 @@
}
mFullScreenHolder = new PluginFullScreenHolder(
WebView.this, data.mNpp);
+ // as we are sharing the View between full screen and
+ // embedded mode, we have to remove the
+ // AbsoluteLayout.LayoutParams set by embedded mode to
+ // ViewGroup.LayoutParams before adding it to the dialog
+ data.mView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT));
mFullScreenHolder.setContentView(data.mView);
mFullScreenHolder.setCancelable(false);
mFullScreenHolder.setCanceledOnTouchOutside(false);
mFullScreenHolder.show();
+ } else if (mFullScreenHolder == null) {
+ // this may happen if user dismisses the fullscreen and
+ // then the WebCore re-position message finally reached
+ // the UI thread.
+ break;
}
// move the matching embedded view fully into the view so
// that touch will be valid instead of rejected due to out
@@ -6602,7 +6618,7 @@
private native void nativeDestroyLayer(int layer);
private native int nativeEvaluateLayersAnimations(int layer);
private native boolean nativeLayersHaveAnimations(int layer);
- private native void nativeUpdateLayers(int layer, int updates);
+ private native void nativeUpdateLayers(int updates);
private native void nativeDrawLayers(int layer,
int scrollX, int scrollY,
int width, int height,
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index a09f23c..aa14c81 100755
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -146,6 +146,19 @@
}
}
+ /**
+ * Utility to retrieve a view displaying a single permission.
+ */
+ public static View getPermissionItemView(Context context,
+ CharSequence grpName, CharSequence description, boolean dangerous) {
+ LayoutInflater inflater = (LayoutInflater)context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ Drawable icon = context.getResources().getDrawable(dangerous
+ ? R.drawable.ic_bullet_key_permission : R.drawable.ic_text_dot);
+ return getPermissionItemView(context, inflater, grpName,
+ description, dangerous, icon);
+ }
+
private void getAllUsedPermissions(int sharedUid, Set<PermissionInfo> permSet) {
String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
if(sharedPkgList == null || (sharedPkgList.length == 0)) {
@@ -304,15 +317,20 @@
mNoPermsView.setVisibility(View.VISIBLE);
}
- private View getPermissionItemView(CharSequence grpName, String permList,
+ private View getPermissionItemView(CharSequence grpName, CharSequence permList,
boolean dangerous) {
- View permView = mInflater.inflate(R.layout.app_permission_item, null);
- Drawable icon = dangerous ? mDangerousIcon : mNormalIcon;
+ return getPermissionItemView(mContext, mInflater, grpName, permList,
+ dangerous, dangerous ? mDangerousIcon : mNormalIcon);
+ }
+
+ private static View getPermissionItemView(Context context, LayoutInflater inflater,
+ CharSequence grpName, CharSequence permList, boolean dangerous, Drawable icon) {
+ View permView = inflater.inflate(R.layout.app_permission_item, null);
TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group);
TextView permDescView = (TextView) permView.findViewById(R.id.permission_list);
if (dangerous) {
- final Resources resources = mContext.getResources();
+ final Resources resources = context.getResources();
permGrpView.setTextColor(resources.getColor(R.color.perms_dangerous_grp_color));
permDescView.setTextColor(resources.getColor(R.color.perms_dangerous_perm_color));
}
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
new file mode 100755
index 0000000..726e28f
--- /dev/null
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2008 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.internal.app;
+
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+
+interface IMediaContainerService {
+ String copyResourceToContainer(in Uri packageURI,
+ String containerId,
+ String key, String resFileName);
+ boolean copyResource(in Uri packageURI,
+ in ParcelFileDescriptor outStream);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/util/HanziToPinyin.java b/core/java/com/android/internal/util/HanziToPinyin.java
index 4368e98..6a4adaa 100644
--- a/core/java/com/android/internal/util/HanziToPinyin.java
+++ b/core/java/com/android/internal/util/HanziToPinyin.java
@@ -16,8 +16,6 @@
package com.android.internal.util;
-import com.google.android.util.AbstractMessageParser.Token;
-
import android.text.TextUtils;
import android.util.Log;
@@ -298,8 +296,10 @@
};
/** First and last Chinese character with known Pinyin according to zh collation */
- private static final String FIRST_UNIHAN = "\u5416";
- private static final String LAST_UNIHAN = "\u5497";
+ private static final String FIRST_PINYIN_UNIHAN = "\u5416";
+ private static final String LAST_PINYIN_UNIHAN = "\u5497";
+ /** The first Chinese character in Unicode block */
+ private static final char FIRST_UNIHAN = '\u3400';
private static final Collator COLLATOR = Collator.getInstance(Locale.CHINA);
private static HanziToPinyin sInstance;
@@ -311,10 +311,18 @@
*/
public static final String SEPARATOR = " ";
- public static final int ASCII = 1;
+ public static final int LATIN = 1;
public static final int PINYIN = 2;
public static final int UNKNOWN = 3;
+ public Token() {
+ }
+
+ public Token(int type, String source, String target) {
+ this.type = type;
+ this.source = source;
+ this.target = target;
+ }
/**
* Type of this token, ASCII, PINYIN or UNKNOWN.
*/
@@ -347,6 +355,7 @@
return sInstance;
}
}
+ Log.w(TAG, "There is no Chinese collator, HanziToPinyin is disabled");
sInstance = new HanziToPinyin(false);
return sInstance;
}
@@ -359,11 +368,15 @@
int offset = -1;
int cmp;
if (character < 256) {
- token.type = Token.ASCII;
+ token.type = Token.LATIN;
+ token.target = letter;
+ return token;
+ } else if (character < FIRST_UNIHAN) {
+ token.type = Token.UNKNOWN;
token.target = letter;
return token;
} else {
- cmp = COLLATOR.compare(letter, FIRST_UNIHAN);
+ cmp = COLLATOR.compare(letter, FIRST_PINYIN_UNIHAN);
if (cmp < 0) {
token.type = Token.UNKNOWN;
token.target = letter;
@@ -372,7 +385,7 @@
token.type = Token.PINYIN;
offset = 0;
} else {
- cmp = COLLATOR.compare(letter, LAST_UNIHAN);
+ cmp = COLLATOR.compare(letter, LAST_PINYIN_UNIHAN);
if (cmp > 0) {
token.type = Token.UNKNOWN;
token.target = letter;
@@ -412,44 +425,71 @@
return token;
}
+ /**
+ * Convert the input to a array of tokens. The sequence of ASCII or Unknown
+ * characters without space will be put into a Token, One Hanzi character
+ * which has pinyin will be treated as a Token.
+ * If these is no China collator, the empty token array is returned.
+ */
public ArrayList<Token> get(final String input) {
- if (!mHasChinaCollator || TextUtils.isEmpty(input)) {
- return null;
- }
-
ArrayList<Token> tokens = new ArrayList<Token>();
- Token currentToken;
-
+ if (!mHasChinaCollator || TextUtils.isEmpty(input)) {
+ // return empty tokens.
+ return tokens;
+ }
final int inputLength = input.length();
-
- currentToken = getToken(input.charAt(0));
-
- for (int i = 1; i < inputLength; i++) {
+ final StringBuilder sb = new StringBuilder();
+ int tokenType = Token.LATIN;
+ // Go through the input, create a new token when
+ // a. Token type changed
+ // b. Get the Pinyin of current charater.
+ // c. current character is space.
+ for (int i = 0; i < inputLength; i++) {
final char character = input.charAt(i);
- Token token = getToken(character);
-
- if (token.type != currentToken.type) {
- currentToken.target = currentToken.target.trim();
- tokens.add(currentToken);
- currentToken = token;
+ if (character == ' ') {
+ if (sb.length() > 0) {
+ addToken(sb, tokens, tokenType);
+ }
+ } else if (character < 256) {
+ if (tokenType != Token.LATIN && sb.length() > 0) {
+ addToken(sb, tokens, tokenType);
+ }
+ tokenType = Token.LATIN;
+ sb.append(character);
+ } else if (character < FIRST_UNIHAN) {
+ if (tokenType != Token.UNKNOWN && sb.length() > 0) {
+ addToken(sb, tokens, tokenType);
+ }
+ tokenType = Token.UNKNOWN;
+ sb.append(character);
} else {
- switch (token.type) {
- case Token.ASCII:
- case Token.UNKNOWN:
- currentToken.source += token.source;
- currentToken.target += token.target;
- break;
- case Token.PINYIN:
- currentToken.source += token.source;
- currentToken.target += " " + token.target;
- break;
+ Token t = getToken(character);
+ if (t.type == Token.PINYIN) {
+ if (sb.length() > 0) {
+ addToken(sb, tokens, tokenType);
+ }
+ tokens.add(t);
+ tokenType = Token.PINYIN;
+ } else {
+ if (tokenType != t.type && sb.length() > 0) {
+ addToken(sb, tokens, tokenType);
+ }
+ tokenType = t.type;
+ sb.append(character);
}
}
}
-
- currentToken.target = currentToken.target.trim();
- tokens.add(currentToken);
-
+ if (sb.length() > 0) {
+ addToken(sb, tokens, tokenType);
+ }
return tokens;
}
+
+ private void addToken(final StringBuilder sb, final ArrayList<Token> tokens,
+ final int tokenType) {
+ String str = sb.toString();
+ tokens.add(new Token(tokenType, str, str));
+ sb.setLength(0);
+ }
+
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 67a0bda..7fd58e8 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -99,6 +99,7 @@
android/graphics/Shader.cpp \
android/graphics/Typeface.cpp \
android/graphics/Xfermode.cpp \
+ android/graphics/YuvToJpegEncoder.cpp \
android_media_AudioRecord.cpp \
android_media_AudioSystem.cpp \
android_media_AudioTrack.cpp \
@@ -148,6 +149,7 @@
external/tremor/Tremor \
external/icu4c/i18n \
external/icu4c/common \
+ external/jpeg \
frameworks/opt/emoji
LOCAL_SHARED_LIBRARIES := \
@@ -175,7 +177,8 @@
libicui18n \
libicudata \
libmedia \
- libwpa_client
+ libwpa_client \
+ libjpeg
ifeq ($(BOARD_HAVE_BLUETOOTH),true)
LOCAL_C_INCLUDES += \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 8364838..fa1ee0d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -64,6 +64,7 @@
extern int register_android_graphics_Region(JNIEnv* env);
extern int register_android_graphics_Shader(JNIEnv* env);
extern int register_android_graphics_Typeface(JNIEnv* env);
+extern int register_android_graphics_YuvImage(JNIEnv* env);
extern int register_com_google_android_gles_jni_EGLImpl(JNIEnv* env);
extern int register_com_google_android_gles_jni_GLImpl(JNIEnv* env);
@@ -1215,6 +1216,7 @@
REG_JNI(register_android_graphics_Shader),
REG_JNI(register_android_graphics_Typeface),
REG_JNI(register_android_graphics_Xfermode),
+ REG_JNI(register_android_graphics_YuvImage),
REG_JNI(register_com_android_internal_graphics_NativeUtils),
REG_JNI(register_android_database_CursorWindow),
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 65f6845..9965fe5 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -645,6 +645,23 @@
return chunkObject;
}
+static void nativeSetDefaultConfig(JNIEnv* env, jobject, int nativeConfig) {
+ SkBitmap::Config config = static_cast<SkBitmap::Config>(nativeConfig);
+
+ // these are the only default configs that make sense for codecs right now
+ static const SkBitmap::Config gValidDefConfig[] = {
+ SkBitmap::kRGB_565_Config,
+ SkBitmap::kARGB_8888_Config,
+ };
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gValidDefConfig); i++) {
+ if (config == gValidDefConfig[i]) {
+ SkImageDecoder::SetDeviceConfig(config);
+ break;
+ }
+ }
+}
+
///////////////////////////////////////////////////////////////////////////////
static JNINativeMethod gMethods[] = {
@@ -671,8 +688,9 @@
{ "nativeScaleNinePatch",
"([BFLandroid/graphics/Rect;)[B",
(void*)nativeScaleNinePatch
- }
+ },
+ { "nativeSetDefaultConfig", "(I)V", (void*)nativeSetDefaultConfig },
};
static JNINativeMethod gOptionsMethods[] = {
diff --git a/core/jni/android/graphics/YuvToJpegEncoder.cpp b/core/jni/android/graphics/YuvToJpegEncoder.cpp
new file mode 100644
index 0000000..ef5c9ae
--- /dev/null
+++ b/core/jni/android/graphics/YuvToJpegEncoder.cpp
@@ -0,0 +1,252 @@
+#include "CreateJavaOutputStreamAdaptor.h"
+#include "SkJpegUtility.h"
+#include "YuvToJpegEncoder.h"
+#include "ui/PixelFormat.h"
+
+#include <jni.h>
+
+YuvToJpegEncoder* YuvToJpegEncoder::create(int format, int* strides) {
+ // Only PIXEL_FORMAT_YCbCr_420_SP and PIXEl_FOMAT_YCbCr_422_I are supported
+ // for now.
+ if (format == android::PIXEL_FORMAT_YCbCr_420_SP) {
+ return new Yuv420SpToJpegEncoder(strides);
+ } else if (format == android::PIXEL_FORMAT_YCbCr_422_I) {
+ return new Yuv422IToJpegEncoder(strides);
+ } else {
+ return NULL;
+ }
+}
+
+YuvToJpegEncoder::YuvToJpegEncoder(int* strides) : fStrides(strides) {
+}
+
+bool YuvToJpegEncoder::encode(SkWStream* stream, void* inYuv, int width,
+ int height, int* offsets, int jpegQuality) {
+ jpeg_compress_struct cinfo;
+ skjpeg_error_mgr sk_err;
+ skjpeg_destination_mgr sk_wstream(stream);
+
+ cinfo.err = jpeg_std_error(&sk_err);
+ sk_err.error_exit = skjpeg_error_exit;
+ if (setjmp(sk_err.fJmpBuf)) {
+ return false;
+ }
+ jpeg_create_compress(&cinfo);
+
+ cinfo.dest = &sk_wstream;
+
+ setJpegCompressStruct(&cinfo, width, height, jpegQuality);
+
+ jpeg_start_compress(&cinfo, TRUE);
+
+ compress(&cinfo, (uint8_t*) inYuv, offsets);
+
+ jpeg_finish_compress(&cinfo);
+
+ return true;
+}
+
+void YuvToJpegEncoder::setJpegCompressStruct(jpeg_compress_struct* cinfo,
+ int width, int height, int quality) {
+ jpeg_set_quality(cinfo, quality, TRUE);
+
+ cinfo->image_width = width;
+ cinfo->image_height = height;
+
+ cinfo->input_components = 3;
+ cinfo->in_color_space = JCS_YCbCr;
+ jpeg_set_defaults(cinfo);
+ jpeg_set_colorspace(cinfo, JCS_YCbCr);
+ cinfo->raw_data_in = TRUE;
+
+ cinfo->dct_method = JDCT_IFAST;
+
+ configSamplingFactors(cinfo);
+}
+
+///////////////////////////////////////////////////////////////////
+Yuv420SpToJpegEncoder::Yuv420SpToJpegEncoder(int* strides) :
+ YuvToJpegEncoder(strides) {
+ fNumPlanes = 2;
+}
+
+void Yuv420SpToJpegEncoder::compress(jpeg_compress_struct* cinfo,
+ uint8_t* yuv, int* offsets) {
+ SkDebugf("onFlyCompress");
+ JSAMPROW y[16];
+ JSAMPROW cb[8];
+ JSAMPROW cr[8];
+ JSAMPARRAY planes[3];
+ planes[0] = y;
+ planes[1] = cb;
+ planes[2] = cr;
+
+ int width = cinfo->image_width;
+ int height = cinfo->image_height;
+ uint8_t* yPlanar = yuv + offsets[0];
+ uint8_t* vuPlanar = yuv + offsets[1]; //width * height;
+ uint8_t* uRows = new uint8_t [8 * (width >> 1)];
+ uint8_t* vRows = new uint8_t [8 * (width >> 1)];
+
+
+ // process 16 lines of Y and 8 lines of U/V each time.
+ while (cinfo->next_scanline < cinfo->image_height) {
+ //deitnerleave u and v
+ deinterleave(vuPlanar, uRows, vRows, cinfo->next_scanline, width);
+
+ for (int i = 0; i < 16; i++) {
+ // y row
+ y[i] = yPlanar + (cinfo->next_scanline + i) * fStrides[0];
+
+ // construct u row and v row
+ if ((i & 1) == 0) {
+ // height and width are both halved because of downsampling
+ int offset = (i >> 1) * (width >> 1);
+ cb[i/2] = uRows + offset;
+ cr[i/2] = vRows + offset;
+ }
+ }
+ jpeg_write_raw_data(cinfo, planes, 16);
+ }
+ delete [] uRows;
+ delete [] vRows;
+
+}
+
+void Yuv420SpToJpegEncoder::deinterleave(uint8_t* vuPlanar, uint8_t* uRows,
+ uint8_t* vRows, int rowIndex, int width) {
+ for (int row = 0; row < 8; ++row) {
+ int offset = ((rowIndex >> 1) + row) * fStrides[1];
+ uint8_t* vu = vuPlanar + offset;
+ for (int i = 0; i < (width >> 1); ++i) {
+ int index = row * (width >> 1) + i;
+ uRows[index] = vu[1];
+ vRows[index] = vu[0];
+ vu += 2;
+ }
+ }
+}
+
+void Yuv420SpToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) {
+ // cb and cr are horizontally downsampled and vertically downsampled as well.
+ cinfo->comp_info[0].h_samp_factor = 2;
+ cinfo->comp_info[0].v_samp_factor = 2;
+ cinfo->comp_info[1].h_samp_factor = 1;
+ cinfo->comp_info[1].v_samp_factor = 1;
+ cinfo->comp_info[2].h_samp_factor = 1;
+ cinfo->comp_info[2].v_samp_factor = 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+Yuv422IToJpegEncoder::Yuv422IToJpegEncoder(int* strides) :
+ YuvToJpegEncoder(strides) {
+ fNumPlanes = 1;
+}
+
+void Yuv422IToJpegEncoder::compress(jpeg_compress_struct* cinfo,
+ uint8_t* yuv, int* offsets) {
+ SkDebugf("onFlyCompress_422");
+ JSAMPROW y[16];
+ JSAMPROW cb[16];
+ JSAMPROW cr[16];
+ JSAMPARRAY planes[3];
+ planes[0] = y;
+ planes[1] = cb;
+ planes[2] = cr;
+
+ int width = cinfo->image_width;
+ int height = cinfo->image_height;
+ uint8_t* yRows = new uint8_t [16 * width];
+ uint8_t* uRows = new uint8_t [16 * (width >> 1)];
+ uint8_t* vRows = new uint8_t [16 * (width >> 1)];
+
+ uint8_t* yuvOffset = yuv + offsets[0];
+
+ // process 16 lines of Y and 16 lines of U/V each time.
+ while (cinfo->next_scanline < cinfo->image_height) {
+ deinterleave(yuvOffset, yRows, uRows, vRows, cinfo->next_scanline, width, height);
+
+ for (int i = 0; i < 16; i++) {
+ // y row
+ y[i] = yRows + i * width;
+
+ // construct u row and v row
+ // width is halved because of downsampling
+ int offset = i * (width >> 1);
+ cb[i] = uRows + offset;
+ cr[i] = vRows + offset;
+ }
+
+ jpeg_write_raw_data(cinfo, planes, 16);
+ }
+ delete [] yRows;
+ delete [] uRows;
+ delete [] vRows;
+}
+
+
+void Yuv422IToJpegEncoder::deinterleave(uint8_t* yuv, uint8_t* yRows, uint8_t* uRows,
+ uint8_t* vRows, int rowIndex, int width, int height) {
+ for (int row = 0; row < 16; ++row) {
+ uint8_t* yuvSeg = yuv + (rowIndex + row) * fStrides[0];
+ for (int i = 0; i < (width >> 1); ++i) {
+ int indexY = row * width + (i << 1);
+ int indexU = row * (width >> 1) + i;
+ yRows[indexY] = yuvSeg[0];
+ yRows[indexY + 1] = yuvSeg[2];
+ uRows[indexU] = yuvSeg[1];
+ vRows[indexU] = yuvSeg[3];
+ yuvSeg += 4;
+ }
+ }
+}
+
+void Yuv422IToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) {
+ // cb and cr are horizontally downsampled and vertically downsampled as well.
+ cinfo->comp_info[0].h_samp_factor = 2;
+ cinfo->comp_info[0].v_samp_factor = 2;
+ cinfo->comp_info[1].h_samp_factor = 1;
+ cinfo->comp_info[1].v_samp_factor = 2;
+ cinfo->comp_info[2].h_samp_factor = 1;
+ cinfo->comp_info[2].v_samp_factor = 2;
+}
+///////////////////////////////////////////////////////////////////////////////
+
+static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv,
+ int format, int width, int height, jintArray offsets,
+ jintArray strides, int jpegQuality, jobject jstream,
+ jbyteArray jstorage) {
+ jbyte* yuv = env->GetByteArrayElements(inYuv, NULL);
+ SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
+
+ jint* imgOffsets = env->GetIntArrayElements(offsets, NULL);
+ jint* imgStrides = env->GetIntArrayElements(strides, NULL);
+ YuvToJpegEncoder* encoder = YuvToJpegEncoder::create(format, imgStrides);
+ if (encoder == NULL) {
+ return false;
+ }
+ encoder->encode(strm, yuv, width, height, imgOffsets, jpegQuality);
+
+ delete encoder;
+ env->ReleaseByteArrayElements(inYuv, yuv, 0);
+ env->ReleaseIntArrayElements(offsets, imgOffsets, 0);
+ env->ReleaseIntArrayElements(strides, imgStrides, 0);
+ return true;
+}
+///////////////////////////////////////////////////////////////////////////////
+
+#include <android_runtime/AndroidRuntime.h>
+
+static JNINativeMethod gYuvImageMethods[] = {
+ { "nativeCompressToJpeg", "([BIII[I[IILjava/io/OutputStream;[B)Z",
+ (void*)YuvImage_compressToJpeg }
+};
+
+#define kClassPathName "android/graphics/YuvImage"
+
+int register_android_graphics_YuvImage(JNIEnv* env);
+int register_android_graphics_YuvImage(JNIEnv* env)
+{
+ return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
+ gYuvImageMethods, SK_ARRAY_COUNT(gYuvImageMethods));
+}
diff --git a/core/jni/android/graphics/YuvToJpegEncoder.h b/core/jni/android/graphics/YuvToJpegEncoder.h
new file mode 100644
index 0000000..97106ce
--- /dev/null
+++ b/core/jni/android/graphics/YuvToJpegEncoder.h
@@ -0,0 +1,74 @@
+#ifndef YuvToJpegEncoder_DEFINED
+#define YuvToJpegEncoder_DEFINED
+
+#include "SkTypes.h"
+#include "SkStream.h"
+extern "C" {
+ #include "jpeglib.h"
+ #include "jerror.h"
+}
+
+class YuvToJpegEncoder {
+public:
+ /** Create an encoder based on the YUV format.
+ *
+ * @param pixelFormat The yuv pixel format as defined in ui/PixelFormat.h.
+ * @param strides The number of row bytes in each image plane.
+ * @return an encoder based on the pixelFormat.
+ */
+ static YuvToJpegEncoder* create(int pixelFormat, int* strides);
+
+ YuvToJpegEncoder(int* strides);
+
+ /** Encode YUV data to jpeg, which is output to a stream.
+ *
+ * @param stream The jpeg output stream.
+ * @param inYuv The input yuv data.
+ * @param width Width of the the Yuv data in terms of pixels.
+ * @param height Height of the Yuv data in terms of pixels.
+ * @param offsets The offsets in each image plane with respect to inYuv.
+ * @param jpegQuality Picture quality in [0, 100].
+ * @return true if successfully compressed the stream.
+ */
+ bool encode(SkWStream* stream, void* inYuv, int width,
+ int height, int* offsets, int jpegQuality);
+
+ virtual ~YuvToJpegEncoder() {}
+
+protected:
+ int fNumPlanes;
+ int* fStrides;
+ void setJpegCompressStruct(jpeg_compress_struct* cinfo, int width,
+ int height, int quality);
+ virtual void configSamplingFactors(jpeg_compress_struct* cinfo) = 0;
+ virtual void compress(jpeg_compress_struct* cinfo,
+ uint8_t* yuv, int* offsets) = 0;
+};
+
+class Yuv420SpToJpegEncoder : public YuvToJpegEncoder {
+public:
+ Yuv420SpToJpegEncoder(int* strides);
+ virtual ~Yuv420SpToJpegEncoder() {}
+
+private:
+ void configSamplingFactors(jpeg_compress_struct* cinfo);
+ void deinterleaveYuv(uint8_t* yuv, int width, int height,
+ uint8_t*& yPlanar, uint8_t*& uPlanar, uint8_t*& vPlanar);
+ void deinterleave(uint8_t* vuPlanar, uint8_t* uRows, uint8_t* vRows,
+ int rowIndex, int width);
+ void compress(jpeg_compress_struct* cinfo, uint8_t* yuv, int* offsets);
+};
+
+class Yuv422IToJpegEncoder : public YuvToJpegEncoder {
+public:
+ Yuv422IToJpegEncoder(int* strides);
+ virtual ~Yuv422IToJpegEncoder() {}
+
+private:
+ void configSamplingFactors(jpeg_compress_struct* cinfo);
+ void compress(jpeg_compress_struct* cinfo, uint8_t* yuv, int* offsets);
+ void deinterleave(uint8_t* yuv, uint8_t* yRows, uint8_t* uRows,
+ uint8_t* vRows, int rowIndex, int width, int height);
+};
+
+#endif
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 64ee4f0..1a5987c 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -546,6 +546,18 @@
}
}
+static void android_hardware_Camera_setDisplayOrientation(JNIEnv *env, jobject thiz,
+ jint value)
+{
+ LOGV("setDisplayOrientation");
+ sp<Camera> camera = get_native_camera(env, thiz, NULL);
+ if (camera == 0) return;
+
+ if (camera->sendCommand(CAMERA_CMD_SET_DISPLAY_ORIENTATION, value, 0) != NO_ERROR) {
+ jniThrowException(env, "java/lang/RuntimeException", "set display orientation failed");
+ }
+}
+
//-------------------------------------------------
static JNINativeMethod camMethods[] = {
@@ -603,6 +615,9 @@
{ "stopSmoothZoom",
"()V",
(void *)android_hardware_Camera_stopSmoothZoom },
+ { "setDisplayOrientation",
+ "(I)V",
+ (void *)android_hardware_Camera_setDisplayOrientation },
};
struct field {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d81476a..54e15a5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -700,6 +700,46 @@
android:label="@string/permlab_mount_format_filesystems"
android:description="@string/permdesc_mount_format_filesystems" />
+ <!-- Allows access to ASEC non-destructive API calls
+ @hide -->
+ <permission android:name="android.permission.ASEC_ACCESS"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="dangerous"
+ android:label="@string/permlab_asec_access"
+ android:description="@string/permdesc_asec_access" />
+
+ <!-- Allows creation of ASEC volumes
+ @hide -->
+ <permission android:name="android.permission.ASEC_CREATE"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="dangerous"
+ android:label="@string/permlab_asec_create"
+ android:description="@string/permdesc_asec_create" />
+
+ <!-- Allows destruction of ASEC volumes
+ @hide -->
+ <permission android:name="android.permission.ASEC_DESTROY"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="dangerous"
+ android:label="@string/permlab_asec_destroy"
+ android:description="@string/permdesc_asec_destroy" />
+
+ <!-- Allows mount / unmount of ASEC volumes
+ @hide -->
+ <permission android:name="android.permission.ASEC_MOUNT_UNMOUNT"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="dangerous"
+ android:label="@string/permlab_asec_mount_unmount"
+ android:description="@string/permdesc_asec_mount_unmount" />
+
+ <!-- Allows rename of ASEC volumes
+ @hide -->
+ <permission android:name="android.permission.ASEC_RENAME"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="dangerous"
+ android:label="@string/permlab_asec_rename"
+ android:description="@string/permdesc_asec_rename" />
+
<!-- Allows applications to disable the keyguard -->
<permission android:name="android.permission.DISABLE_KEYGUARD"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
@@ -1162,6 +1202,16 @@
android:description="@string/permdesc_cache_filesystem"
android:protectionLevel="signatureOrSystem" />
+ <!-- Must be required by default container service so that only
+ the system can bind to it and use it to copy
+ protected data to secure containers or files
+ accessible to the system.
+ @hide -->
+ <permission android:name="android.permission.COPY_PROTECTED_DATA"
+ android:label="@string/permlab_copyProtectedData"
+ android:description="@string/permlab_copyProtectedData"
+ android:protectionLevel="signature" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 287e3a0..794a9f0 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1394,7 +1394,7 @@
be a dimension (such as "12dip") for a constant width or one of
the special constants. -->
<attr name="layout_width" format="dimension">
- <!-- {@deprecated Use match_parent instead.} -->
+ <!-- <strong>Deprecated.</strong> Use {@code match_parent} instead. -->
<enum name="fill_parent" value="-1" />
<!-- The view should be as big as its parent (minus padding). -->
<enum name="match_parent" value="-1" />
@@ -1407,7 +1407,7 @@
be a dimension (such as "12dip") for a constant height or one of
the special constants. -->
<attr name="layout_height" format="dimension">
- <!-- {@deprecated Use match_parent instead.} -->
+ <!-- <strong>Deprecated.</strong> Use {@code match_parent} instead. -->
<enum name="fill_parent" value="-1" />
<!-- The view should be as big as its parent (minus padding). -->
<enum name="match_parent" value="-1" />
@@ -2122,7 +2122,7 @@
to match the width of the screen, or wrap_content to match the width
of the anchored view. -->
<attr name="dropDownWidth" format="dimension">
- <!-- {@deprecated Use match_parent instead.} -->
+ <!-- <strong>Deprecated.</strong> Use {@code match_parent} instead. -->
<enum name="fill_parent" value="-1" />
<!-- The dropdown should fit the width of the screen. -->
<enum name="match_parent" value="-1" />
@@ -2134,7 +2134,7 @@
to fill the width of the screen, or wrap_content to match the height of
the content of the drop down. -->
<attr name="dropDownHeight" format="dimension">
- <!-- {@deprecated Use match_parent instead.} -->
+ <!-- <strong>Deprecated.</strong> Use {@code match_parent} instead. -->
<enum name="fill_parent" value="-1" />
<!-- The dropdown should fill the width of the screen. -->
<enum name="match_parent" value="-1" />
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c6ef3a0..7728c50 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -938,6 +938,7 @@
is a period then it is appended to your package name. -->
<attr name="name" />
<attr name="label" />
+ <attr name="description" />
<attr name="icon" />
<attr name="process" />
<attr name="authorities" />
@@ -1016,6 +1017,7 @@
is a period then it is appended to your package name. -->
<attr name="name" />
<attr name="label" />
+ <attr name="description" />
<attr name="icon" />
<attr name="permission" />
<attr name="process" />
@@ -1047,6 +1049,7 @@
is a period then it is appended to your package name. -->
<attr name="name" />
<attr name="label" />
+ <attr name="description" />
<attr name="icon" />
<attr name="permission" />
<attr name="process" />
@@ -1078,6 +1081,7 @@
<attr name="name" />
<attr name="theme" />
<attr name="label" />
+ <attr name="description" />
<attr name="icon" />
<attr name="launchMode" />
<attr name="screenOrientation" />
@@ -1130,6 +1134,7 @@
"com.mycompany.MyName". -->
<attr name="targetActivity" format="string" />
<attr name="label" />
+ <attr name="description" />
<attr name="icon" />
<attr name="permission" />
<!-- Specify whether the activity-alias is enabled or not (that is, can be instantiated by the system).
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 265dacd..31f71d3 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -877,6 +877,31 @@
<string name="permdesc_mount_format_filesystems">Allows the application to format removable storage.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_asec_access">get information on secure storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_asec_access">Allows the application to get information on secure storage.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_asec_create">create secure storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_asec_create">Allows the application to create secure storage.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_asec_destroy">destroy secure storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_asec_destroy">Allows the application to destroy secure storage.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_asec_mount_unmount">mount / unmount secure storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_asec_mount_unmount">Allows the application to mount / unmount secure storage.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_asec_rename">rename secure storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_asec_rename">Allows the application to rename secure storage.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_vibrate">control vibrator</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_vibrate">Allows the application to control
@@ -1157,6 +1182,40 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_cache_filesystem">Allows an application to read and write the cache filesystem.</string>
+ <!-- Policy administration -->
+
+ <!-- Title of policy access to limiting the user's password choices -->
+ <string name="policylab_limitPassword">Limit password</string>
+ <!-- Description of policy access to limiting the user's password choices -->
+ <string name="policydesc_limitPassword">Restrict the types of passwords you
+ are allowed to use.</string>
+ <!-- Title of policy access to watch user login attempts -->
+ <string name="policylab_watchLogin">Watch login attempts</string>
+ <!-- Description of policy access to watch user login attempts -->
+ <string name="policydesc_watchLogin">Monitor attempts to login to
+ the device, in particular to respond to failed login attempts.</string>
+ <!-- Title of policy access to reset user's password -->
+ <string name="policylab_resetPassword">Reset your password</string>
+ <!-- Description of policy access to reset user's password -->
+ <string name="policydesc_resetPassword">Force your password
+ to a new value, requiring the administrator give it to you
+ before you can log in.</string>
+ <!-- Title of policy access to limiting the user's unlock timeout -->
+ <string name="policylab_limitUnlock">Limit lock timeout</string>
+ <!-- Description of policy access to limiting the user's unlock timeout -->
+ <string name="policydesc_limitUnlock">Restrict the unlock timeout
+ durations you can select.</string>
+ <!-- Title of policy access to force lock the device -->
+ <string name="policylab_forceLock">Force lock</string>
+ <!-- Description of policy access to limiting the user's password choices -->
+ <string name="policydesc_forceLock">Force the device to immediately lock,
+ requiring that its password is re-entered.</string>
+ <!-- Title of policy access to wipe the user's data -->
+ <string name="policylab_wipeData">Erase all data</string>
+ <!-- Description of policy access to wipe the user's data -->
+ <string name="policydesc_wipeData">Perform a factory reset, deleting
+ all of your data without any confirmation from you.</string>
+
<!-- The order of these is important, don't reorder without changing Contacts.java --> <skip />
<!-- Phone number types from android.provider.Contacts. This could be used when adding a new phone number for a contact, for example. -->
<string-array name="phoneTypes">
@@ -2043,6 +2102,12 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_pkgUsageStats">Allows the modification of collected component usage statistics. Not for use by normal applications.</string>
+ <!-- permission attributes related to default container service -->
+ <!-- Title of an application permission that lets an application use default container service. -->
+ <string name="permlab_copyProtectedData">Allows to invoke default container service to copy content. Not for use by normal applications.</string>
+ <!-- Description of an application permission, used to invoke default container service to copy content. -->
+ <string name="permdesc_copyProtectedData">Allows to invoke default container service to copy content. Not for use by normal applications.</string>
+
<!-- Shown in the tutorial for tap twice for zoom control. -->
<string name="tutorial_double_tap_to_zoom_message_short">Tap twice for zoom control</string>
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 3c03eed..2313f4c 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -558,6 +558,30 @@
return decodeFileDescriptor(fd, null, null);
}
+ /**
+ * Set the default config used for decoding bitmaps. This config is
+ * presented to the codec if the caller did not specify a preferred config
+ * in their call to decode...
+ *
+ * The default value is chosen by the system to best match the device's
+ * screen and memory constraints.
+ *
+ * @param config The preferred config for decoding bitmaps. If null, then
+ * a suitable default is chosen by the system.
+ *
+ * @hide - only called by the browser at the moment, but should be stable
+ * enough to expose if needed
+ */
+ public static void setDefaultConfig(Bitmap.Config config) {
+ if (config == null) {
+ // pick this for now, as historically it was our default.
+ // However, if we have a smarter algorithm, we can change this.
+ config = Bitmap.Config.RGB_565;
+ }
+ nativeSetDefaultConfig(config.nativeInt);
+ }
+
+ private static native void nativeSetDefaultConfig(int nativeConfig);
private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
Rect padding, Options opts);
private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java
new file mode 100644
index 0000000..4a3bd47
--- /dev/null
+++ b/graphics/java/android/graphics/YuvImage.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import java.io.OutputStream;
+
+/**
+ * @hide pending API council approval
+ *
+ * YuvImage contains YUV data and provides a method that compresses a region of
+ * the YUV data to a Jpeg. The YUV data should be provided as a single byte
+ * array irrespective of the number of image planes in it. The stride of each
+ * image plane should be provided as well.
+ *
+ * To compress a rectangle region in the YUV data, users have to specify a
+ * region by width, height and offsets, where each image plane has a
+ * corresponding offset. All offsets are measured as a displacement in bytes
+ * from yuv[0], where yuv[0] is the beginning of the yuv data.
+ */
+public class YuvImage {
+
+ /**
+ * Number of bytes of temp storage we use for communicating between the
+ * native compressor and the java OutputStream.
+ */
+ private final static int WORKING_COMPRESS_STORAGE = 4096;
+
+ /**
+ * The YUV format as defined in {@link PixelFormat}.
+ */
+ private int mFormat;
+
+ /**
+ * The raw YUV data.
+ * In the case of more than one image plane, the image planes must be
+ * concatenated into a single byte array.
+ */
+ private byte[] mData;
+
+ /**
+ * The number of row bytes in each image plane.
+ */
+ private int[] mStrides;
+
+ /**
+ * Construct an YuvImage.
+ *
+ * @param yuv The YUV data. In the case of more than one image plane, all the planes must be
+ * concatenated into a single byte array.
+ * @param format The YUV data format as defined in {@link PixelFormat}.
+ * @param strides Row bytes of each image plane.
+ */
+ public YuvImage(byte[] yuv, int format, int[] strides) {
+ if ((yuv == null) || (strides == null)) {
+ throw new IllegalArgumentException(
+ "yuv or strides cannot be null");
+ }
+ mData = yuv;
+ mFormat = format;
+ mStrides = strides;
+ }
+
+ /**
+ * Compress a rectangle region in the YuvImage to a jpeg.
+ * Only PixelFormat.YCbCr_420_SP and PixelFormat.YCbCr_422_I
+ * are supported for now.
+ *
+ * @param width The width of the rectangle region.
+ * @param height The height of the rectangle region.
+ * @param offsets The offsets of the rectangle region in each image plane.
+ * The offsets are measured as a displacement in bytes from
+ * yuv[0], where yuv[0] is the beginning of the yuv data.
+ * @param quality Hint to the compressor, 0-100. 0 meaning compress for
+ * small size, 100 meaning compress for max quality.
+ * @param stream The outputstream to write the compressed data.
+ *
+ * @return true if successfully compressed to the specified stream.
+ *
+ */
+ public boolean compressToJpeg(int width, int height, int[] offsets, int quality,
+ OutputStream stream) {
+ if (!validate(mFormat, width, height, offsets)) {
+ return false;
+ }
+
+ if (quality < 0 || quality > 100) {
+ throw new IllegalArgumentException("quality must be 0..100");
+ }
+
+ if (stream == null) {
+ throw new NullPointerException();
+ }
+
+ return nativeCompressToJpeg(mData, mFormat, width, height, offsets,
+ mStrides, quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
+ }
+
+ /**
+ * @return the YUV data.
+ */
+ public byte[] getYuvData() {
+ return mData;
+ }
+
+ /**
+ * @return the YUV format as defined in {@link PixelFormat}.
+ */
+ public int getYuvFormat() {
+ return mFormat;
+ }
+
+ /**
+ * @return the number of row bytes in each image plane.
+ */
+ public int[] getStrides() {
+ return mStrides;
+ }
+
+ protected boolean validate(int format, int width, int height, int[] offsets) {
+ if (format != PixelFormat.YCbCr_420_SP &&
+ format != PixelFormat.YCbCr_422_I) {
+ throw new IllegalArgumentException(
+ "only support PixelFormat.YCbCr_420_SP " +
+ "and PixelFormat.YCbCr_422_I for now");
+ }
+
+ if (offsets.length != mStrides.length) {
+ throw new IllegalArgumentException(
+ "the number of image planes are mismatched");
+ }
+
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException(
+ "width and height must large than 0");
+ }
+
+ int requiredSize;
+ if (format == PixelFormat.YCbCr_420_SP) {
+ requiredSize = height * mStrides[0] +(height >> 1) * mStrides[1];
+ } else {
+ requiredSize = height * mStrides[0];
+ }
+
+ if (requiredSize > mData.length) {
+ throw new IllegalArgumentException(
+ "width or/and height is larger than the yuv data");
+ }
+
+ return true;
+ }
+
+ //////////// native methods
+
+ private static native boolean nativeCompressToJpeg(byte[] oriYuv,
+ int format, int width, int height, int[] offsets, int[] strides,
+ int quality, OutputStream stream, byte[] tempStorage);
+}
diff --git a/include/media/stagefright/AMRWriter.h b/include/media/stagefright/AMRWriter.h
index 6ee9869..372909a 100644
--- a/include/media/stagefright/AMRWriter.h
+++ b/include/media/stagefright/AMRWriter.h
@@ -20,23 +20,23 @@
#include <stdio.h>
-#include <utils/RefBase.h>
+#include <media/stagefright/MediaWriter.h>
#include <utils/threads.h>
namespace android {
struct MediaSource;
-struct AMRWriter : public RefBase {
+struct AMRWriter : public MediaWriter {
AMRWriter(const char *filename);
AMRWriter(int fd);
status_t initCheck() const;
- status_t addSource(const sp<MediaSource> &source);
-
- status_t start();
- void stop();
+ virtual status_t addSource(const sp<MediaSource> &source);
+ virtual bool reachedEOS();
+ virtual status_t start();
+ virtual void stop();
protected:
virtual ~AMRWriter();
@@ -49,6 +49,7 @@
sp<MediaSource> mSource;
bool mStarted;
volatile bool mDone;
+ bool mReachedEOS;
pthread_t mThread;
static void *ThreadWrapper(void *);
diff --git a/include/media/stagefright/CachingDataSource.h b/include/media/stagefright/CachingDataSource.h
index b0fc4b2..30b7ad9 100644
--- a/include/media/stagefright/CachingDataSource.h
+++ b/include/media/stagefright/CachingDataSource.h
@@ -33,6 +33,8 @@
virtual ssize_t readAt(off_t offset, void *data, size_t size);
+ virtual uint32_t flags();
+
protected:
virtual ~CachingDataSource();
diff --git a/include/media/stagefright/DataSource.h b/include/media/stagefright/DataSource.h
index f88666a..0c0ace0 100644
--- a/include/media/stagefright/DataSource.h
+++ b/include/media/stagefright/DataSource.h
@@ -31,6 +31,10 @@
class DataSource : public RefBase {
public:
+ enum Flags {
+ kWantsPrefetching = 1,
+ };
+
static sp<DataSource> CreateFromURI(const char *uri);
DataSource() {}
@@ -45,6 +49,10 @@
// May return ERROR_UNSUPPORTED.
virtual status_t getSize(off_t *size);
+ virtual uint32_t flags() {
+ return 0;
+ }
+
////////////////////////////////////////////////////////////////////////////
bool sniff(String8 *mimeType, float *confidence);
diff --git a/include/media/stagefright/HTTPDataSource.h b/include/media/stagefright/HTTPDataSource.h
index d5dc9e6..3075f1c 100644
--- a/include/media/stagefright/HTTPDataSource.h
+++ b/include/media/stagefright/HTTPDataSource.h
@@ -33,6 +33,10 @@
virtual ssize_t readAt(off_t offset, void *data, size_t size);
+ virtual uint32_t flags() {
+ return kWantsPrefetching;
+ }
+
protected:
virtual ~HTTPDataSource();
@@ -52,6 +56,8 @@
status_t mInitCheck;
+ ssize_t sendRangeRequest(size_t offset);
+
HTTPDataSource(const HTTPDataSource &);
HTTPDataSource &operator=(const HTTPDataSource &);
};
diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h
index 2ca04fa..6b93f19 100644
--- a/include/media/stagefright/MPEG4Writer.h
+++ b/include/media/stagefright/MPEG4Writer.h
@@ -20,8 +20,8 @@
#include <stdio.h>
+#include <media/stagefright/MediaWriter.h>
#include <utils/List.h>
-#include <utils/RefBase.h>
#include <utils/threads.h>
namespace android {
@@ -30,15 +30,15 @@
class MediaSource;
class MetaData;
-class MPEG4Writer : public RefBase {
+class MPEG4Writer : public MediaWriter {
public:
MPEG4Writer(const char *filename);
MPEG4Writer(int fd);
- void addSource(const sp<MediaSource> &source);
- status_t start();
- bool reachedEOS();
- void stop();
+ virtual status_t addSource(const sp<MediaSource> &source);
+ virtual status_t start();
+ virtual bool reachedEOS();
+ virtual void stop();
void beginBox(const char *fourcc);
void writeInt8(int8_t x);
diff --git a/include/media/stagefright/MediaWriter.h b/include/media/stagefright/MediaWriter.h
new file mode 100644
index 0000000..b8232c6
--- /dev/null
+++ b/include/media/stagefright/MediaWriter.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 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 MEDIA_WRITER_H_
+
+#define MEDIA_WRITER_H_
+
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct MediaSource;
+
+struct MediaWriter : public RefBase {
+ MediaWriter() {}
+
+ virtual status_t addSource(const sp<MediaSource> &source) = 0;
+ virtual bool reachedEOS() = 0;
+ virtual status_t start() = 0;
+ virtual void stop() = 0;
+
+protected:
+ virtual ~MediaWriter() {}
+
+private:
+ MediaWriter(const MediaWriter &);
+ MediaWriter &operator=(const MediaWriter &);
+};
+
+} // namespace android
+
+#endif // MEDIA_WRITER_H_
diff --git a/include/ui/Camera.h b/include/ui/Camera.h
index 5219772..c506fb8 100644
--- a/include/ui/Camera.h
+++ b/include/ui/Camera.h
@@ -82,6 +82,7 @@
enum {
CAMERA_CMD_START_SMOOTH_ZOOM = 1,
CAMERA_CMD_STOP_SMOOTH_ZOOM = 2,
+ CAMERA_CMD_SET_DISPLAY_ORIENTATION = 3,
};
// camera fatal errors
@@ -209,4 +210,3 @@
}; // namespace android
#endif
-
diff --git a/include/ui/CameraParameters.h b/include/ui/CameraParameters.h
index a5ea133..cae0676 100644
--- a/include/ui/CameraParameters.h
+++ b/include/ui/CameraParameters.h
@@ -29,12 +29,6 @@
CameraParameters(const String8 ¶ms) { unflatten(params); }
~CameraParameters();
- enum {
- CAMERA_ORIENTATION_UNKNOWN = 0,
- CAMERA_ORIENTATION_PORTRAIT = 1,
- CAMERA_ORIENTATION_LANDSCAPE = 2,
- };
-
String8 flatten() const;
void unflatten(const String8 ¶ms);
@@ -63,9 +57,6 @@
void setPictureFormat(const char *format);
const char *getPictureFormat() const;
- int getOrientation() const;
- void setOrientation(int orientation);
-
void dump() const;
status_t dump(int fd, const Vector<String16>& args) const;
diff --git a/libs/rs/rsUtils.h b/libs/rs/rsUtils.h
index 3c6da68..07f8933 100644
--- a/libs/rs/rsUtils.h
+++ b/libs/rs/rsUtils.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_RS_UTILS_H
#define ANDROID_RS_UTILS_H
+#define LOG_NDEBUG 0
#define LOG_TAG "RenderScript"
#include <utils/Log.h>
#include <utils/Vector.h>
diff --git a/libs/ui/CameraParameters.cpp b/libs/ui/CameraParameters.cpp
index 2e0409b..a94f6b9 100644
--- a/libs/ui/CameraParameters.cpp
+++ b/libs/ui/CameraParameters.cpp
@@ -121,9 +121,6 @@
const char CameraParameters::FOCUS_MODE_MACRO[] = "macro";
const char CameraParameters::FOCUS_MODE_FIXED[] = "fixed";
-static const char* portrait = "portrait";
-static const char* landscape = "landscape";
-
CameraParameters::CameraParameters()
: mMap()
{
@@ -282,23 +279,6 @@
set(KEY_PREVIEW_FORMAT, format);
}
-int CameraParameters::getOrientation() const
-{
- const char* orientation = get("orientation");
- if (orientation && !strcmp(orientation, portrait))
- return CAMERA_ORIENTATION_PORTRAIT;
- return CAMERA_ORIENTATION_LANDSCAPE;
-}
-
-void CameraParameters::setOrientation(int orientation)
-{
- if (orientation == CAMERA_ORIENTATION_PORTRAIT) {
- set("orientation", portrait);
- } else {
- set("orientation", landscape);
- }
-}
-
const char *CameraParameters::getPreviewFormat() const
{
return get(KEY_PREVIEW_FORMAT);
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index 4aac455..c4d4f99 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -489,7 +489,6 @@
{
int version;
int fd;
- int attempt;
struct pollfd *new_mFDs;
device_t **new_devices;
char **new_device_names;
@@ -502,16 +501,11 @@
AutoMutex _l(mLock);
- for (attempt = 0; attempt < 10; attempt++) {
- fd = open(deviceName, O_RDWR);
- if (fd >= 0) break;
- usleep(100);
- }
+ fd = open(deviceName, O_RDWR);
if(fd < 0) {
LOGE("could not open %s, %s\n", deviceName, strerror(errno));
return -1;
}
- LOGV("Opened device: %s (%d failures)", deviceName, attempt);
if(ioctl(fd, EVIOCGVERSION, &version)) {
LOGE("could not get driver version for %s, %s\n", deviceName, strerror(errno));
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index a55273d..6383f0c 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -20,6 +20,8 @@
#include "StagefrightRecorder.h"
+#include <media/stagefright/AudioSource.h>
+#include <media/stagefright/AMRWriter.h>
#include <media/stagefright/CameraSource.h>
#include <media/stagefright/MPEG4Writer.h>
#include <media/stagefright/MediaDebug.h>
@@ -146,7 +148,90 @@
return UNKNOWN_ERROR;
}
- if (mVideoSource == VIDEO_SOURCE_CAMERA) {
+ switch (mOutputFormat) {
+ case OUTPUT_FORMAT_DEFAULT:
+ case OUTPUT_FORMAT_THREE_GPP:
+ case OUTPUT_FORMAT_MPEG_4:
+ return startMPEG4Recording();
+
+ case OUTPUT_FORMAT_AMR_NB:
+ case OUTPUT_FORMAT_AMR_WB:
+ return startAMRRecording();
+
+ default:
+ return UNKNOWN_ERROR;
+ }
+}
+
+sp<MediaSource> StagefrightRecorder::createAMRAudioSource() {
+ uint32_t sampleRate =
+ mAudioEncoder == AUDIO_ENCODER_AMR_NB ? 8000 : 16000;
+
+ sp<AudioSource> audioSource =
+ new AudioSource(
+ mAudioSource,
+ sampleRate,
+ AudioSystem::CHANNEL_IN_MONO);
+
+ status_t err = audioSource->initCheck();
+
+ if (err != OK) {
+ return NULL;
+ }
+
+ sp<MetaData> encMeta = new MetaData;
+ encMeta->setCString(
+ kKeyMIMEType,
+ mAudioEncoder == AUDIO_ENCODER_AMR_NB
+ ? MEDIA_MIMETYPE_AUDIO_AMR_NB : MEDIA_MIMETYPE_AUDIO_AMR_WB);
+
+ encMeta->setInt32(kKeyChannelCount, 1);
+ encMeta->setInt32(kKeySampleRate, sampleRate);
+
+ OMXClient client;
+ CHECK_EQ(client.connect(), OK);
+
+ sp<MediaSource> audioEncoder =
+ OMXCodec::Create(client.interface(), encMeta,
+ true /* createEncoder */, audioSource);
+
+ return audioEncoder;
+}
+
+status_t StagefrightRecorder::startAMRRecording() {
+ if (mAudioSource == AUDIO_SOURCE_LIST_END
+ || mVideoSource != VIDEO_SOURCE_LIST_END) {
+ return UNKNOWN_ERROR;
+ }
+
+ if (mOutputFormat == OUTPUT_FORMAT_AMR_NB
+ && mAudioEncoder != AUDIO_ENCODER_DEFAULT
+ && mAudioEncoder != AUDIO_ENCODER_AMR_NB) {
+ return UNKNOWN_ERROR;
+ } else if (mOutputFormat == OUTPUT_FORMAT_AMR_WB
+ && mAudioEncoder != AUDIO_ENCODER_AMR_WB) {
+ return UNKNOWN_ERROR;
+ }
+
+ sp<MediaSource> audioEncoder = createAMRAudioSource();
+
+ if (audioEncoder == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ CHECK(mOutputFd >= 0);
+ mWriter = new AMRWriter(dup(mOutputFd));
+ mWriter->addSource(audioEncoder);
+ mWriter->start();
+
+ return OK;
+}
+
+status_t StagefrightRecorder::startMPEG4Recording() {
+ mWriter = new MPEG4Writer(dup(mOutputFd));
+
+ if (mVideoSource == VIDEO_SOURCE_DEFAULT
+ || mVideoSource == VIDEO_SOURCE_CAMERA) {
CHECK(mCamera != NULL);
sp<CameraSource> cameraSource =
@@ -193,11 +278,20 @@
true /* createEncoder */, cameraSource);
CHECK(mOutputFd >= 0);
- mWriter = new MPEG4Writer(dup(mOutputFd));
mWriter->addSource(encoder);
- mWriter->start();
}
+ if (mAudioSource != AUDIO_SOURCE_LIST_END) {
+ sp<MediaSource> audioEncoder = createAMRAudioSource();
+
+ if (audioEncoder == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ mWriter->addSource(audioEncoder);
+ }
+
+ mWriter->start();
return OK;
}
@@ -235,7 +329,9 @@
}
status_t StagefrightRecorder::getMaxAmplitude(int *max) {
- return UNKNOWN_ERROR;
+ *max = 0;
+
+ return OK;
}
} // namespace android
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 56c4e0e..7ec412d 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -23,7 +23,8 @@
namespace android {
-class MPEG4Writer;
+struct MediaSource;
+struct MediaWriter;
struct StagefrightRecorder : public MediaRecorderBase {
StagefrightRecorder();
@@ -54,7 +55,7 @@
sp<ICamera> mCamera;
sp<ISurface> mPreviewSurface;
sp<IMediaPlayerClient> mListener;
- sp<MPEG4Writer> mWriter;
+ sp<MediaWriter> mWriter;
audio_source mAudioSource;
video_source mVideoSource;
@@ -66,6 +67,10 @@
String8 mParams;
int mOutputFd;
+ status_t startMPEG4Recording();
+ status_t startAMRRecording();
+ sp<MediaSource> createAMRAudioSource();
+
StagefrightRecorder(const StagefrightRecorder &);
StagefrightRecorder &operator=(const StagefrightRecorder &);
};
diff --git a/media/libstagefright/AMRWriter.cpp b/media/libstagefright/AMRWriter.cpp
index 7b681f1..caff452 100644
--- a/media/libstagefright/AMRWriter.cpp
+++ b/media/libstagefright/AMRWriter.cpp
@@ -115,6 +115,7 @@
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+ mReachedEOS = false;
mDone = false;
pthread_create(&mThread, &attr, ThreadWrapper, this);
@@ -179,6 +180,14 @@
break;
}
}
+
+ Mutex::Autolock autoLock(mLock);
+ mReachedEOS = true;
+}
+
+bool AMRWriter::reachedEOS() {
+ Mutex::Autolock autoLock(mLock);
+ return mReachedEOS;
}
} // namespace android
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 3813907..dbb52c6 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -31,6 +31,7 @@
MPEG4Extractor.cpp \
MPEG4Writer.cpp \
MediaExtractor.cpp \
+ Prefetcher.cpp \
SampleIterator.cpp \
SampleTable.cpp \
ShoutcastSource.cpp \
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index f6cd46a..42b9acc 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -19,6 +19,7 @@
#include <utils/Log.h>
#include "include/AwesomePlayer.h"
+#include "include/Prefetcher.h"
#include "include/SoftwareRenderer.h"
#include <binder/IPCThreadState.h>
@@ -118,6 +119,8 @@
mVideoEventPending = false;
mStreamDoneEvent = new AwesomeEvent(this, 1);
mStreamDoneEventPending = false;
+ mBufferingEvent = new AwesomeEvent(this, 2);
+ mBufferingEventPending = false;
mQueue.start();
@@ -132,11 +135,16 @@
mClient.disconnect();
}
-void AwesomePlayer::cancelPlayerEvents() {
+void AwesomePlayer::cancelPlayerEvents(bool keepBufferingGoing) {
mQueue.cancelEvent(mVideoEvent->eventID());
mVideoEventPending = false;
mQueue.cancelEvent(mStreamDoneEvent->eventID());
mStreamDoneEventPending = false;
+
+ if (!keepBufferingGoing) {
+ mQueue.cancelEvent(mBufferingEvent->eventID());
+ mBufferingEventPending = false;
+ }
}
void AwesomePlayer::setListener(const wp<MediaPlayerBase> &listener) {
@@ -149,12 +157,22 @@
reset_l();
- sp<MediaExtractor> extractor = MediaExtractor::CreateFromURI(uri);
+ sp<DataSource> dataSource = DataSource::CreateFromURI(uri);
+
+ if (dataSource == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
if (extractor == NULL) {
return UNKNOWN_ERROR;
}
+ if (dataSource->flags() & DataSource::kWantsPrefetching) {
+ mPrefetcher = new Prefetcher;
+ }
+
return setDataSource_l(extractor);
}
@@ -182,8 +200,6 @@
}
status_t AwesomePlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {
- reset_l();
-
bool haveAudio = false;
bool haveVideo = false;
for (size_t i = 0; i < extractor->countTracks(); ++i) {
@@ -253,6 +269,8 @@
mSeeking = false;
mSeekTimeUs = 0;
+
+ mPrefetcher.clear();
}
// static
@@ -278,16 +296,38 @@
}
}
-void AwesomePlayer::notifyListener_l(int msg) {
+void AwesomePlayer::notifyListener_l(int msg, int ext1) {
if (mListener != NULL) {
sp<MediaPlayerBase> listener = mListener.promote();
if (listener != NULL) {
- listener->sendEvent(msg);
+ listener->sendEvent(msg, ext1);
}
}
}
+void AwesomePlayer::onBufferingUpdate() {
+ Mutex::Autolock autoLock(mLock);
+ mBufferingEventPending = false;
+
+ if (mDurationUs >= 0) {
+ int64_t cachedDurationUs = mPrefetcher->getCachedDurationUs();
+ int64_t positionUs = 0;
+ if (mVideoRenderer != NULL) {
+ positionUs = mVideoTimeUs;
+ } else if (mAudioPlayer != NULL) {
+ positionUs = mAudioPlayer->getMediaTimeUs();
+ }
+
+ cachedDurationUs += positionUs;
+
+ double percentage = (double)cachedDurationUs / mDurationUs;
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, percentage * 100.0);
+
+ postBufferingEvent_l();
+ }
+}
+
void AwesomePlayer::onStreamDone() {
// Posted whenever any stream finishes playing.
@@ -361,6 +401,8 @@
seekAudioIfNecessary_l();
}
+ postBufferingEvent_l();
+
return OK;
}
@@ -414,7 +456,7 @@
return OK;
}
- cancelPlayerEvents();
+ cancelPlayerEvents(true /* keepBufferingGoing */);
if (mAudioPlayer != NULL) {
mAudioPlayer->pause();
@@ -518,11 +560,15 @@
return OK;
}
-status_t AwesomePlayer::setAudioSource(const sp<MediaSource> &source) {
+status_t AwesomePlayer::setAudioSource(sp<MediaSource> source) {
if (source == NULL) {
return UNKNOWN_ERROR;
}
+ if (mPrefetcher != NULL) {
+ source = mPrefetcher->addSource(source);
+ }
+
sp<MetaData> meta = source->getFormat();
const char *mime;
@@ -549,11 +595,15 @@
return mAudioSource != NULL ? OK : UNKNOWN_ERROR;
}
-status_t AwesomePlayer::setVideoSource(const sp<MediaSource> &source) {
+status_t AwesomePlayer::setVideoSource(sp<MediaSource> source) {
if (source == NULL) {
return UNKNOWN_ERROR;
}
+ if (mPrefetcher != NULL) {
+ source = mPrefetcher->addSource(source);
+ }
+
mVideoSource = OMXCodec::Create(
mClient.interface(), source->getFormat(),
false, // createEncoder
@@ -580,9 +630,13 @@
if (code == 1) {
onStreamDone();
return;
+ } else if (code == 2) {
+ onBufferingUpdate();
+ return;
}
Mutex::Autolock autoLock(mLock);
+
mVideoEventPending = false;
if (mSeeking) {
@@ -718,5 +772,17 @@
mQueue.postEvent(mStreamDoneEvent);
}
+void AwesomePlayer::postBufferingEvent_l() {
+ if (mPrefetcher == NULL) {
+ return;
+ }
+
+ if (mBufferingEventPending) {
+ return;
+ }
+ mBufferingEventPending = true;
+ mQueue.postEventWithDelay(mBufferingEvent, 1000000ll);
+}
+
} // namespace android
diff --git a/media/libstagefright/CachingDataSource.cpp b/media/libstagefright/CachingDataSource.cpp
index 23f4897..8d04ead 100644
--- a/media/libstagefright/CachingDataSource.cpp
+++ b/media/libstagefright/CachingDataSource.cpp
@@ -65,6 +65,10 @@
return mSource->initCheck();
}
+uint32_t CachingDataSource::flags() {
+ return mSource->flags();
+}
+
ssize_t CachingDataSource::readAt(off_t offset, void *data, size_t size) {
Mutex::Autolock autoLock(mLock);
diff --git a/media/libstagefright/HTTPDataSource.cpp b/media/libstagefright/HTTPDataSource.cpp
index 7e8bbc6..135a044 100644
--- a/media/libstagefright/HTTPDataSource.cpp
+++ b/media/libstagefright/HTTPDataSource.cpp
@@ -190,30 +190,12 @@
mHttp = NULL;
}
-ssize_t HTTPDataSource::readAt(off_t offset, void *data, size_t size) {
- if (offset >= mBufferOffset
- && offset < (off_t)(mBufferOffset + mBufferLength)) {
- size_t num_bytes_available = mBufferLength - (offset - mBufferOffset);
-
- size_t copy = num_bytes_available;
- if (copy > size) {
- copy = size;
- }
-
- memcpy(data, (const char *)mBuffer + (offset - mBufferOffset), copy);
-
- return copy;
- }
-
- mBufferOffset = offset;
- mBufferLength = 0;
-
+ssize_t HTTPDataSource::sendRangeRequest(size_t offset) {
char host[128];
sprintf(host, "Host: %s\r\n", mHost);
char range[128];
- sprintf(range, "Range: bytes=%ld-%ld\r\n\r\n",
- mBufferOffset, mBufferOffset + kBufferSize - 1);
+ sprintf(range, "Range: bytes=%d-\r\n\r\n", offset);
int http_status;
@@ -251,12 +233,44 @@
char *end;
unsigned long contentLength = strtoul(value.c_str(), &end, 10);
- ssize_t num_bytes_received = mHttp->receive(mBuffer, contentLength);
+ return contentLength;
+}
- if (num_bytes_received <= 0) {
- return num_bytes_received;
+ssize_t HTTPDataSource::readAt(off_t offset, void *data, size_t size) {
+ if (offset >= mBufferOffset
+ && offset < (off_t)(mBufferOffset + mBufferLength)) {
+ size_t num_bytes_available = mBufferLength - (offset - mBufferOffset);
+
+ size_t copy = num_bytes_available;
+ if (copy > size) {
+ copy = size;
+ }
+
+ memcpy(data, (const char *)mBuffer + (offset - mBufferOffset), copy);
+
+ return copy;
}
+ ssize_t contentLength = 0;
+ if (mBufferLength <= 0 || offset != mBufferOffset + mBufferLength) {
+ mHttp->disconnect();
+ contentLength = sendRangeRequest(offset);
+
+ if (contentLength > kBufferSize) {
+ contentLength = kBufferSize;
+ }
+ } else {
+ contentLength = kBufferSize;
+ }
+
+ mBufferOffset = offset;
+
+ if (contentLength <= 0) {
+ return contentLength;
+ }
+
+ ssize_t num_bytes_received = mHttp->receive(mBuffer, contentLength);
+
mBufferLength = (size_t)num_bytes_received;
size_t copy = mBufferLength;
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 367459f..aee4d15 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -92,9 +92,11 @@
mTracks.clear();
}
-void MPEG4Writer::addSource(const sp<MediaSource> &source) {
+status_t MPEG4Writer::addSource(const sp<MediaSource> &source) {
Track *track = new Track(this, source);
mTracks.push_back(track);
+
+ return OK;
}
status_t MPEG4Writer::start() {
diff --git a/media/libstagefright/Prefetcher.cpp b/media/libstagefright/Prefetcher.cpp
new file mode 100644
index 0000000..93e3fdc
--- /dev/null
+++ b/media/libstagefright/Prefetcher.cpp
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#define LOG_TAG "Prefetcher"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include "include/Prefetcher.h"
+
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/List.h>
+
+namespace android {
+
+struct PrefetchedSource : public MediaSource {
+ PrefetchedSource(
+ const sp<Prefetcher> &prefetcher,
+ size_t index,
+ const sp<MediaSource> &source);
+
+ virtual status_t start(MetaData *params);
+ virtual status_t stop();
+
+ virtual status_t read(
+ MediaBuffer **buffer, const ReadOptions *options);
+
+ virtual sp<MetaData> getFormat();
+
+protected:
+ virtual ~PrefetchedSource();
+
+private:
+ friend struct Prefetcher;
+
+ Mutex mLock;
+ Condition mCondition;
+
+ sp<Prefetcher> mPrefetcher;
+ sp<MediaSource> mSource;
+ size_t mIndex;
+ bool mStarted;
+ bool mReachedEOS;
+ int64_t mSeekTimeUs;
+ int64_t mCacheDurationUs;
+
+ List<MediaBuffer *> mCachedBuffers;
+
+ // Returns true iff source is currently caching.
+ bool getCacheDurationUs(int64_t *durationUs);
+
+ void updateCacheDuration_l();
+ void clearCache_l();
+
+ void cacheMore();
+
+ PrefetchedSource(const PrefetchedSource &);
+ PrefetchedSource &operator=(const PrefetchedSource &);
+};
+
+Prefetcher::Prefetcher()
+ : mDone(false),
+ mThreadExited(false) {
+ startThread();
+}
+
+Prefetcher::~Prefetcher() {
+ stopThread();
+}
+
+sp<MediaSource> Prefetcher::addSource(const sp<MediaSource> &source) {
+ Mutex::Autolock autoLock(mLock);
+
+ sp<PrefetchedSource> psource =
+ new PrefetchedSource(this, mSources.size(), source);
+
+ mSources.add(psource);
+
+ return psource;
+}
+
+void Prefetcher::startThread() {
+ mThreadExited = false;
+ mDone = false;
+
+ int res = androidCreateThreadEtc(
+ ThreadWrapper, this, "Prefetcher",
+ ANDROID_PRIORITY_DEFAULT, 0, &mThread);
+
+ CHECK_EQ(res, 1);
+}
+
+void Prefetcher::stopThread() {
+ Mutex::Autolock autoLock(mLock);
+
+ while (!mThreadExited) {
+ mDone = true;
+ mCondition.signal();
+ mCondition.wait(mLock);
+ }
+}
+
+// static
+int Prefetcher::ThreadWrapper(void *me) {
+ static_cast<Prefetcher *>(me)->threadFunc();
+
+ return 0;
+}
+
+// Cache about 10secs for each source.
+static int64_t kMaxCacheDurationUs = 10000000ll;
+
+void Prefetcher::threadFunc() {
+ for (;;) {
+ Mutex::Autolock autoLock(mLock);
+ if (mDone) {
+ mThreadExited = true;
+ mCondition.signal();
+ break;
+ }
+ mCondition.waitRelative(mLock, 10000000ll);
+
+ int64_t minCacheDurationUs = -1;
+ ssize_t minIndex = -1;
+ for (size_t i = 0; i < mSources.size(); ++i) {
+ sp<PrefetchedSource> source = mSources[i].promote();
+
+ if (source == NULL) {
+ continue;
+ }
+
+ int64_t cacheDurationUs;
+ if (!source->getCacheDurationUs(&cacheDurationUs)) {
+ continue;
+ }
+
+ if (cacheDurationUs >= kMaxCacheDurationUs) {
+ continue;
+ }
+
+ if (minIndex < 0 || cacheDurationUs < minCacheDurationUs) {
+ minCacheDurationUs = cacheDurationUs;
+ minIndex = i;
+ }
+ }
+
+ if (minIndex < 0) {
+ continue;
+ }
+
+ sp<PrefetchedSource> source = mSources[minIndex].promote();
+ if (source != NULL) {
+ source->cacheMore();
+ }
+ }
+}
+
+int64_t Prefetcher::getCachedDurationUs() {
+ Mutex::Autolock autoLock(mLock);
+
+ int64_t minCacheDurationUs = -1;
+ ssize_t minIndex = -1;
+ for (size_t i = 0; i < mSources.size(); ++i) {
+ int64_t cacheDurationUs;
+ sp<PrefetchedSource> source = mSources[i].promote();
+ if (source == NULL) {
+ continue;
+ }
+
+ if (!source->getCacheDurationUs(&cacheDurationUs)) {
+ continue;
+ }
+
+ if (cacheDurationUs >= kMaxCacheDurationUs) {
+ continue;
+ }
+
+ if (minIndex < 0 || cacheDurationUs < minCacheDurationUs) {
+ minCacheDurationUs = cacheDurationUs;
+ minIndex = i;
+ }
+ }
+
+ return minCacheDurationUs < 0 ? 0 : minCacheDurationUs;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+PrefetchedSource::PrefetchedSource(
+ const sp<Prefetcher> &prefetcher,
+ size_t index,
+ const sp<MediaSource> &source)
+ : mPrefetcher(prefetcher),
+ mSource(source),
+ mIndex(index),
+ mStarted(false),
+ mReachedEOS(false),
+ mSeekTimeUs(0),
+ mCacheDurationUs(0) {
+}
+
+PrefetchedSource::~PrefetchedSource() {
+ if (mStarted) {
+ stop();
+ }
+}
+
+status_t PrefetchedSource::start(MetaData *params) {
+ Mutex::Autolock autoLock(mLock);
+
+ status_t err = mSource->start(params);
+
+ if (err != OK) {
+ return err;
+ }
+
+ mStarted = true;
+
+ for (;;) {
+ // Buffer 2 secs on startup.
+ if (mReachedEOS || mCacheDurationUs > 2000000) {
+ break;
+ }
+
+ mCondition.wait(mLock);
+ }
+
+ return OK;
+}
+
+status_t PrefetchedSource::stop() {
+ Mutex::Autolock autoLock(mLock);
+
+ clearCache_l();
+
+ status_t err = mSource->stop();
+
+ mStarted = false;
+
+ return err;
+}
+
+status_t PrefetchedSource::read(
+ MediaBuffer **out, const ReadOptions *options) {
+ *out = NULL;
+
+ Mutex::Autolock autoLock(mLock);
+
+ CHECK(mStarted);
+
+ int64_t seekTimeUs;
+ if (options && options->getSeekTo(&seekTimeUs)) {
+ CHECK(seekTimeUs >= 0);
+
+ clearCache_l();
+
+ mReachedEOS = false;
+ mSeekTimeUs = seekTimeUs;
+ }
+
+ while (!mReachedEOS && mCachedBuffers.empty()) {
+ mCondition.wait(mLock);
+ }
+
+ if (mCachedBuffers.empty()) {
+ return ERROR_END_OF_STREAM;
+ }
+
+ *out = *mCachedBuffers.begin();
+ mCachedBuffers.erase(mCachedBuffers.begin());
+ updateCacheDuration_l();
+
+ return OK;
+}
+
+sp<MetaData> PrefetchedSource::getFormat() {
+ return mSource->getFormat();
+}
+
+bool PrefetchedSource::getCacheDurationUs(int64_t *durationUs) {
+ Mutex::Autolock autoLock(mLock);
+
+ if (!mStarted || mReachedEOS) {
+ *durationUs = 0;
+
+ return false;
+ }
+
+ *durationUs = mCacheDurationUs;
+
+ return true;
+}
+
+void PrefetchedSource::cacheMore() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (!mStarted) {
+ return;
+ }
+
+ MediaBuffer *buffer;
+ MediaSource::ReadOptions options;
+ if (mSeekTimeUs >= 0) {
+ options.setSeekTo(mSeekTimeUs);
+ mSeekTimeUs = -1;
+ }
+
+ status_t err = mSource->read(&buffer, &options);
+
+ if (err != OK) {
+ mReachedEOS = true;
+ mCondition.signal();
+
+ return;
+ }
+
+ CHECK(buffer != NULL);
+
+ MediaBuffer *copy = new MediaBuffer(buffer->range_length());
+ memcpy(copy->data(),
+ (const uint8_t *)buffer->data() + buffer->range_offset(),
+ buffer->range_length());
+
+ sp<MetaData> from = buffer->meta_data();
+ sp<MetaData> to = copy->meta_data();
+
+ int64_t timeUs;
+ if (from->findInt64(kKeyTime, &timeUs)) {
+ to->setInt64(kKeyTime, timeUs);
+ }
+
+ buffer->release();
+ buffer = NULL;
+
+ mCachedBuffers.push_back(copy);
+ updateCacheDuration_l();
+ mCondition.signal();
+}
+
+void PrefetchedSource::updateCacheDuration_l() {
+ if (mCachedBuffers.size() < 2) {
+ mCacheDurationUs = 0;
+ } else {
+ int64_t firstTimeUs, lastTimeUs;
+ CHECK((*mCachedBuffers.begin())->meta_data()->findInt64(
+ kKeyTime, &firstTimeUs));
+ CHECK((*--mCachedBuffers.end())->meta_data()->findInt64(
+ kKeyTime, &lastTimeUs));
+
+ mCacheDurationUs = lastTimeUs - firstTimeUs;
+ }
+}
+
+void PrefetchedSource::clearCache_l() {
+ List<MediaBuffer *>::iterator it = mCachedBuffers.begin();
+ while (it != mCachedBuffers.end()) {
+ (*it)->release();
+
+ it = mCachedBuffers.erase(it);
+ }
+
+ updateCacheDuration_l();
+}
+
+} // namespace android
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index b28a12c..c2e46c0 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -26,10 +26,11 @@
namespace android {
+struct AudioPlayer;
struct MediaBuffer;
struct MediaExtractor;
struct MediaSource;
-struct AudioPlayer;
+struct Prefetcher;
struct TimeSource;
struct AwesomeRenderer : public RefBase {
@@ -109,13 +110,18 @@
bool mVideoEventPending;
sp<TimedEventQueue::Event> mStreamDoneEvent;
bool mStreamDoneEventPending;
+ sp<TimedEventQueue::Event> mBufferingEvent;
+ bool mBufferingEventPending;
void postVideoEvent_l(int64_t delayUs = -1);
+ void postBufferingEvent_l();
void postStreamDoneEvent_l();
MediaBuffer *mLastVideoBuffer;
MediaBuffer *mVideoBuffer;
+ sp<Prefetcher> mPrefetcher;
+
status_t setDataSource_l(const sp<MediaExtractor> &extractor);
void reset_l();
status_t seekTo_l(int64_t timeUs);
@@ -123,17 +129,19 @@
void initRenderer_l();
void seekAudioIfNecessary_l();
- void cancelPlayerEvents();
+ void cancelPlayerEvents(bool keepBufferingGoing = false);
- status_t setAudioSource(const sp<MediaSource> &source);
- status_t setVideoSource(const sp<MediaSource> &source);
+ status_t setAudioSource(sp<MediaSource> source);
+ status_t setVideoSource(sp<MediaSource> source);
void onEvent(int32_t code);
static void AudioNotify(void *me, int what);
void onStreamDone();
- void notifyListener_l(int msg);
+ void notifyListener_l(int msg, int ext1 = 0);
+
+ void onBufferingUpdate();
AwesomePlayer(const AwesomePlayer &);
AwesomePlayer &operator=(const AwesomePlayer &);
diff --git a/media/libstagefright/include/Prefetcher.h b/media/libstagefright/include/Prefetcher.h
new file mode 100644
index 0000000..7a97785
--- /dev/null
+++ b/media/libstagefright/include/Prefetcher.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 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 PREFETCHER_H_
+
+#define PREFETCHER_H_
+
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+namespace android {
+
+struct MediaSource;
+struct PrefetchedSource;
+
+struct Prefetcher : public RefBase {
+ Prefetcher();
+
+ // Given an existing MediaSource returns a new MediaSource
+ // that will benefit from prefetching/caching the original one.
+ sp<MediaSource> addSource(const sp<MediaSource> &source);
+
+ int64_t getCachedDurationUs();
+
+protected:
+ virtual ~Prefetcher();
+
+private:
+ Mutex mLock;
+ Condition mCondition;
+
+ Vector<wp<PrefetchedSource> > mSources;
+ android_thread_id_t mThread;
+ bool mDone;
+ bool mThreadExited;
+
+ void startThread();
+ void stopThread();
+
+ static int ThreadWrapper(void *me);
+ void threadFunc();
+
+ Prefetcher(const Prefetcher &);
+ Prefetcher &operator=(const Prefetcher &);
+};
+
+} // namespace android
+
+#endif // PREFETCHER_H_
diff --git a/media/sdutils/sdutil.cpp b/media/sdutils/sdutil.cpp
index 322f743..a61cccb 100644
--- a/media/sdutils/sdutil.cpp
+++ b/media/sdutils/sdutil.cpp
@@ -134,6 +134,12 @@
gMountService->unmountSecureContainer(sId);
}
+static void asec_rename(const char *oldId, const char *newId) {
+ String16 sOldId(oldId);
+ String16 sNewId(newId);
+ gMountService->renameSecureContainer(sOldId, sNewId);
+}
+
static int asec_path(const char *id) {
String16 sId(id);
gMountService->getSecureContainerPath(sId);
@@ -212,7 +218,13 @@
} else if (!strcmp(argument, "destroy")) {
return android::asec_destroy(id);
} else if (!strcmp(argument, "mount")) {
- return android::asec_mount(id, argv[4], atoi(argv[5]));
+ if (argc == 6)
+ return android::asec_mount(id, argv[4], atoi(argv[5]));
+ } else if (!strcmp(argument, "rename")) {
+ if (argc == 5) {
+ android::asec_rename(id, argv[4]);
+ return 0;
+ }
} else if (!strcmp(argument, "unmount")) {
android::asec_unmount(id);
return 0;
@@ -233,6 +245,7 @@
" sdutil asec destroy <id>\n"
" sdutil asec mount <id> <key> <ownerUid>\n"
" sdutil asec unmount <id>\n"
+ " sdutil asec rename <oldId, newId>\n"
" sdutil asec path <id>\n"
);
return -1;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
index 5127255..6087268 100755
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
@@ -274,6 +274,7 @@
//video thumbnail
public static final String THUMBNAIL_OUTPUT = "/sdcard/media_api/videoThumbnail.png";
public static final String GOLDEN_THUMBNAIL_OUTPUT = "/sdcard/media_api/goldenThumbnail.png";
+ public static final String GOLDEN_THUMBNAIL_OUTPUT_2 = "/sdcard/media_api/goldenThumbnail2.png";
//Metadata Utility
public static final String[] THUMBNAIL_CAPTURE_TEST_FILES = {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java
index 4e30f91..ee6067ab 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java
@@ -420,7 +420,7 @@
@LargeTest
public void testGetThumbnail() throws Exception {
- boolean getThumbnail = CodecTest.getThumbnail(MediaNames.VIDEO_H264_AAC, MediaNames.GOLDEN_THUMBNAIL_OUTPUT);
+ boolean getThumbnail = CodecTest.getThumbnail(MediaNames.VIDEO_H264_AAC, MediaNames.GOLDEN_THUMBNAIL_OUTPUT_2);
assertTrue("Get Thumbnail", getThumbnail);
}
diff --git a/packages/DefaultContainerService/Android.mk b/packages/DefaultContainerService/Android.mk
new file mode 100755
index 0000000..2f1d6ab
--- /dev/null
+++ b/packages/DefaultContainerService/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := DefaultContainerService
+
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/packages/DefaultContainerService/AndroidManifest.xml b/packages/DefaultContainerService/AndroidManifest.xml
new file mode 100755
index 0000000..fd77148
--- /dev/null
+++ b/packages/DefaultContainerService/AndroidManifest.xml
@@ -0,0 +1,14 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.defcontainer">
+ <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER"/>
+
+ <application android:process="def.container.service"
+ android:label="@string/service_name">
+
+ <service android:name=".DefaultContainerService"
+ android:enabled="true"
+ android:exported="true"
+ android:permission="android.permission.COPY_PROTECTED_DATA"/>
+ </application>
+
+</manifest>
diff --git a/packages/DefaultContainerService/res/values/strings.xml b/packages/DefaultContainerService/res/values/strings.xml
new file mode 100644
index 0000000..2897f34
--- /dev/null
+++ b/packages/DefaultContainerService/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, 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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- service name -->
+ <string name="service_name">Media Container Service</string>
+</resources>
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
new file mode 100644
index 0000000..d36207b
--- /dev/null
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -0,0 +1,305 @@
+package com.android.defcontainer;
+
+import com.android.internal.app.IMediaContainerService;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Debug;
+import android.os.IBinder;
+import android.os.IMountService;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.app.Service;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import android.os.FileUtils;
+
+
+/*
+ * This service copies a downloaded apk to a file passed in as
+ * a ParcelFileDescriptor or to a newly created container specified
+ * by parameters. The DownloadManager gives access to this process
+ * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER
+ * permission to access apks downloaded via the download manager.
+ */
+public class DefaultContainerService extends Service {
+ private static final String TAG = "DefContainer";
+ private static final boolean localLOGV = false;
+
+ private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
+ /*
+ * Creates a new container and copies resource there.
+ * @param paackageURI the uri of resource to be copied. Can be either
+ * a content uri or a file uri
+ * @param containerId the id of the secure container that should
+ * be used for creating a secure container into which the resource
+ * will be copied.
+ * @param key Refers to key used for encrypting the secure container
+ * @param resFileName Name of the target resource file(relative to newly
+ * created secure container)
+ * @return Returns the new cache path where the resource has been copied into
+ *
+ */
+ public String copyResourceToContainer(final Uri packageURI,
+ final String containerId,
+ final String key, final String resFileName) {
+ if (packageURI == null || containerId == null) {
+ return null;
+ }
+ return copyResourceInner(packageURI, containerId, key, resFileName);
+ }
+
+ /*
+ * Copy specified resource to output stream
+ * @param packageURI the uri of resource to be copied. Should be a
+ * file uri
+ * @param outStream Remote file descriptor to be used for copying
+ * @return Returns true if copy succeded or false otherwise.
+ */
+ public boolean copyResource(final Uri packageURI,
+ ParcelFileDescriptor outStream) {
+ if (packageURI == null || outStream == null) {
+ return false;
+ }
+ ParcelFileDescriptor.AutoCloseOutputStream
+ autoOut = new ParcelFileDescriptor.AutoCloseOutputStream(outStream);
+ return copyFile(packageURI, autoOut);
+ }
+ };
+
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ private IMountService getMountService() {
+ return IMountService.Stub.asInterface(ServiceManager.getService("mount"));
+ }
+
+ private String copyResourceInner(Uri packageURI, String newCacheId, String key, String resFileName) {
+ // Create new container at newCachePath
+ String codePath = packageURI.getPath();
+ String newCachePath = null;
+ final int CREATE_FAILED = 1;
+ final int COPY_FAILED = 2;
+ final int FINALIZE_FAILED = 3;
+ final int PASS = 4;
+ int errCode = CREATE_FAILED;
+ // Create new container
+ if ((newCachePath = createSdDir(packageURI, newCacheId, key)) != null) {
+ File resFile = new File(newCachePath, resFileName);
+ errCode = COPY_FAILED;
+ if (localLOGV) Log.i(TAG, "Trying to copy " + codePath + " to " + resFile);
+ // Copy file from codePath
+ if (FileUtils.copyFile(new File(codePath), resFile)) {
+ errCode = FINALIZE_FAILED;
+ if (finalizeSdDir(newCacheId)) {
+ errCode = PASS;
+ }
+ }
+ }
+ // Print error based on errCode
+ String errMsg = "";
+ switch (errCode) {
+ case CREATE_FAILED:
+ errMsg = "CREATE_FAILED";
+ break;
+ case COPY_FAILED:
+ errMsg = "COPY_FAILED";
+ destroySdDir(newCacheId);
+ break;
+ case FINALIZE_FAILED:
+ errMsg = "FINALIZE_FAILED";
+ destroySdDir(newCacheId);
+ break;
+ default:
+ errMsg = "PASS";
+ unMountSdDir(newCacheId);
+ break;
+ }
+ Log.i(TAG, "Status: " + errMsg);
+ if (errCode != PASS) {
+ return null;
+ }
+ return newCachePath;
+ }
+
+ private String createSdDir(final Uri packageURI,
+ String containerId, String sdEncKey) {
+ File tmpPackageFile = new File(packageURI.getPath());
+ // Create mount point via MountService
+ IMountService mountService = getMountService();
+ long len = tmpPackageFile.length();
+ int mbLen = (int) (len/(1024*1024));
+ if ((len - (mbLen * 1024 * 1024)) > 0) {
+ mbLen++;
+ }
+ if (localLOGV) Log.i(TAG, "mbLen="+mbLen);
+ String cachePath = null;
+ int ownerUid = Process.myUid();
+ try {
+ cachePath = mountService.createSecureContainer(containerId,
+ mbLen,
+ "vfat", sdEncKey, ownerUid);
+ if (localLOGV) Log.i(TAG, "Trying to create secure container for "
+ + containerId + ", cachePath =" + cachePath);
+ return cachePath;
+ } catch(IllegalStateException e) {
+ Log.e(TAG, "Failed to create storage on sdcard with exception: " + e);
+ } catch(RemoteException e) {
+ Log.e(TAG, "MounteService not running?");
+ return null;
+ }
+ // TODO just fail here and let the user delete later on.
+ try {
+ mountService.destroySecureContainer(containerId);
+ if (localLOGV) Log.i(TAG, "Destroying cache for " + containerId
+ + ", cachePath =" + cachePath);
+ } catch(IllegalStateException e) {
+ Log.e(TAG, "Failed to destroy existing cache: " + e);
+ return null;
+ } catch(RemoteException e) {
+ Log.e(TAG, "MounteService not running?");
+ return null;
+ }
+ try {
+ cachePath = mountService.createSecureContainer(containerId,
+ mbLen,
+ "vfat", sdEncKey, ownerUid);
+ if (localLOGV) Log.i(TAG, "Trying to install again " + containerId
+ + ", cachePath =" + cachePath);
+ return cachePath;
+ } catch(IllegalStateException e) {
+ Log.e(TAG, "Failed to create storage on sdcard with exception: " + e);
+ } catch(RemoteException e) {
+ Log.e(TAG, "MounteService not running?");
+ }
+ return null;
+ }
+
+ private boolean destroySdDir(String containerId) {
+ try {
+ // We need to destroy right away
+ getMountService().destroySecureContainer(containerId);
+ return true;
+ } catch (IllegalStateException e) {
+ Log.i(TAG, "Failed to destroy container : " + containerId);
+ } catch(RemoteException e) {
+ Log.e(TAG, "MounteService not running?");
+ }
+ return false;
+ }
+
+ private boolean finalizeSdDir(String containerId){
+ try {
+ getMountService().finalizeSecureContainer(containerId);
+ return true;
+ } catch (IllegalStateException e) {
+ Log.i(TAG, "Failed to finalize container for pkg : " + containerId);
+ } catch(RemoteException e) {
+ Log.e(TAG, "MounteService not running?");
+ }
+ return false;
+ }
+
+ private boolean unMountSdDir(String containerId) {
+ try {
+ getMountService().unmountSecureContainer(containerId);
+ return true;
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to unmount id: " + containerId + " with exception " + e);
+ } catch(RemoteException e) {
+ Log.e(TAG, "MounteService not running?");
+ }
+ return false;
+ }
+
+ private String mountSdDir(String containerId, String key) {
+ try {
+ return getMountService().mountSecureContainer(containerId, key, Process.myUid());
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to mount id: " +
+ containerId + " with exception " + e);
+ } catch(RemoteException e) {
+ Log.e(TAG, "MounteService not running?");
+ }
+ return null;
+ }
+
+ public static boolean copyToFile(InputStream inputStream, FileOutputStream out) {
+ try {
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer)) >= 0) {
+ out.write(buffer, 0, bytesRead);
+ }
+ return true;
+ } catch (IOException e) {
+ Log.i(TAG, "Exception : " + e + " when copying file");
+ return false;
+ }
+ }
+
+ public static boolean copyToFile(File srcFile, FileOutputStream out) {
+ InputStream inputStream = null;
+ try {
+ inputStream = new FileInputStream(srcFile);
+ return copyToFile(inputStream, out);
+ } catch (IOException e) {
+ return false;
+ } finally {
+ try { if (inputStream != null) inputStream.close(); } catch (IOException e) {}
+ }
+ }
+
+ private boolean copyFile(Uri pPackageURI, FileOutputStream outStream) {
+ if (pPackageURI.getScheme().equals("file")) {
+ final File srcPackageFile = new File(pPackageURI.getPath());
+ // We copy the source package file to a temp file and then rename it to the
+ // destination file in order to eliminate a window where the package directory
+ // scanner notices the new package file but it's not completely copied yet.
+ if (!copyToFile(srcPackageFile, outStream)) {
+ Log.e(TAG, "Couldn't copy file: " + srcPackageFile);
+ return false;
+ }
+ } else if (pPackageURI.getScheme().equals("content")) {
+ ParcelFileDescriptor fd = null;
+ try {
+ fd = getContentResolver().openFileDescriptor(pPackageURI, "r");
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e);
+ return false;
+ }
+ if (fd == null) {
+ Log.e(TAG, "Couldn't open file descriptor from download service (null).");
+ return false;
+ } else {
+ if (localLOGV) {
+ Log.v(TAG, "Opened file descriptor from download service.");
+ }
+ ParcelFileDescriptor.AutoCloseInputStream
+ dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+ // We copy the source package file to a temp file and then rename it to the
+ // destination file in order to eliminate a window where the package directory
+ // scanner notices the new package file but it's not completely copied yet.
+ if (!copyToFile(dlStream, outStream)) {
+ Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file.");
+ return false;
+ }
+ }
+ } else {
+ Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI);
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/preloaded-classes b/preloaded-classes
index 90bbb37..63aca99 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -695,8 +695,6 @@
com.ibm.icu4jni.charset.CharsetICU
com.ibm.icu4jni.text.CollationAttribute
com.ibm.icu4jni.text.DecimalFormat
-com.ibm.icu4jni.text.DecimalFormatSymbols
-com.ibm.icu4jni.text.NativeDecimalFormat$UNumberFormatAttribute
com.ibm.icu4jni.text.RuleBasedCollator
com.ibm.icu4jni.util.Resources$DefaultTimeZones
dalvik.system.DexFile
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 2e45512..72e26f8 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -161,7 +161,7 @@
= new HashMap<String,IBackupTransport>();
String mCurrentTransport;
IBackupTransport mLocalTransport, mGoogleTransport;
- RestoreSession mActiveRestoreSession;
+ ActiveRestoreSession mActiveRestoreSession;
class RestoreParams {
public IBackupTransport transport;
@@ -2068,20 +2068,20 @@
Log.d(TAG, "Restore session requested but one already active");
return null;
}
- mActiveRestoreSession = new RestoreSession(transport);
+ mActiveRestoreSession = new ActiveRestoreSession(transport);
}
return mActiveRestoreSession;
}
// ----- Restore session -----
- class RestoreSession extends IRestoreSession.Stub {
+ class ActiveRestoreSession extends IRestoreSession.Stub {
private static final String TAG = "RestoreSession";
private IBackupTransport mRestoreTransport = null;
RestoreSet[] mRestoreSets = null;
- RestoreSession(String transport) {
+ ActiveRestoreSession(String transport) {
mRestoreTransport = getTransport(transport);
}
diff --git a/services/java/com/android/server/BootReceiver.java b/services/java/com/android/server/BootReceiver.java
index debbbb4..fbb4411 100644
--- a/services/java/com/android/server/BootReceiver.java
+++ b/services/java/com/android/server/BootReceiver.java
@@ -89,14 +89,14 @@
}
ContentResolver cr = context.getContentResolver();
- logBootFile(cr, db, "/cache/recovery/log", "SYSTEM_RECOVERY_LOG");
- logBootFile(cr, db, "/proc/last_kmsg", "SYSTEM_LAST_KMSG");
- logBootFile(cr, db, "/data/dontpanic/apanic_console", "APANIC_CONSOLE");
- logBootFile(cr, db, "/data/dontpanic/apanic_threads", "APANIC_THREADS");
+ logBootFile(cr, db, props, "/cache/recovery/log", "SYSTEM_RECOVERY_LOG");
+ logBootFile(cr, db, props, "/proc/last_kmsg", "SYSTEM_LAST_KMSG");
+ logBootFile(cr, db, props, "/data/dontpanic/apanic_console", "APANIC_CONSOLE");
+ logBootFile(cr, db, props, "/data/dontpanic/apanic_threads", "APANIC_THREADS");
}
- private void logBootFile(ContentResolver cr, DropBoxManager db, String filename, String tag)
- throws IOException {
+ private void logBootFile(ContentResolver cr, DropBoxManager db,
+ CharSequence headers, String filename, String tag) throws IOException {
if (cr == null || db == null || !db.isTagEnabled(tag)) return; // Logging disabled
File file = new File(filename);
@@ -108,10 +108,8 @@
if (lastTime == fileTime) return; // Already logged this particular file
Settings.Secure.putLong(cr, setting, fileTime);
- StringBuilder report = new StringBuilder();
- report.append("Build: ").append(Build.FINGERPRINT).append("\n");
- report.append("Kernel: ");
- report.append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n"));
+ StringBuilder report = new StringBuilder(headers);
+ report.append("\n");
report.append(FileUtils.readTextFile(new File(filename), LOG_SIZE, "[[TRUNCATED]]\n"));
db.addText(tag, report.toString());
}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 5a8d35f..4417d7b 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -828,6 +828,28 @@
info.getExtraInfo());
}
+ NetworkStateTracker newNet = tryFailover(prevNetType);
+ if (newNet != null) {
+ NetworkInfo switchTo = newNet.getNetworkInfo();
+ intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
+ } else {
+ intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
+ }
+ // do this before we broadcast the change
+ handleConnectivityChange();
+
+ sendStickyBroadcast(intent);
+ /*
+ * If the failover network is already connected, then immediately send
+ * out a followup broadcast indicating successful failover
+ */
+ if (newNet != null && newNet.getNetworkInfo().isConnected()) {
+ sendConnectedBroadcast(newNet.getNetworkInfo());
+ }
+ }
+
+ // returns -1 if no failover available
+ private NetworkStateTracker tryFailover(int prevNetType) {
/*
* If this is a default network, check if other defaults are available
* or active
@@ -840,8 +862,7 @@
int newType = -1;
int newPriority = -1;
- for (int checkType=0; checkType <=
- ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
+ for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
if (checkType == prevNetType) continue;
if (mNetAttributes[checkType] == null) continue;
if (mNetAttributes[checkType].isDefault()) {
@@ -884,29 +905,13 @@
switchTo.getTypeName());
}
}
- intent.putExtra(ConnectivityManager.
- EXTRA_OTHER_NETWORK_INFO, switchTo);
} else {
- intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY,
- true);
newNet.reconnect();
}
- } else {
- intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY,
- true);
}
}
- // do this before we broadcast the change
- handleConnectivityChange();
-
- sendStickyBroadcast(intent);
- /*
- * If the failover network is already connected, then immediately send
- * out a followup broadcast indicating successful failover
- */
- if (newNet != null && newNet.getNetworkInfo().isConnected())
- sendConnectedBroadcast(newNet.getNetworkInfo());
+ return newNet;
}
private void sendConnectedBroadcast(NetworkInfo info) {
@@ -964,7 +969,25 @@
intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
info.setFailover(false);
}
+
+ NetworkStateTracker newNet = tryFailover(info.getType());
+ if (newNet != null) {
+ NetworkInfo switchTo = newNet.getNetworkInfo();
+ intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
+ } else {
+ intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
+ }
+ // do this before we broadcast the change
+ handleConnectivityChange();
+
sendStickyBroadcast(intent);
+ /*
+ * If the failover network is already connected, then immediately send
+ * out a followup broadcast indicating successful failover
+ */
+ if (newNet != null && newNet.getNetworkInfo().isConnected()) {
+ sendConnectedBroadcast(newNet.getNetworkInfo());
+ }
}
private void sendStickyBroadcast(Intent intent) {
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 36da4eb..fbd5317 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -91,7 +91,8 @@
return mIPowerManager;
}
- ActiveAdmin getActiveAdminForCallerLocked(ComponentName who) throws SecurityException {
+ ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy)
+ throws SecurityException {
if (mActiveAdmin != null && mActiveAdmin.getUid() == Binder.getCallingUid()) {
if (who != null) {
if (!who.getPackageName().equals(mActiveAdmin.info.getActivityInfo().packageName)
@@ -99,20 +100,27 @@
throw new SecurityException("Current admin is not " + who);
}
}
+ if (!mActiveAdmin.info.usesPolicy(reqPolicy)) {
+ throw new SecurityException("Admin " + mActiveAdmin.info.getComponent()
+ + " did not specify uses-policy for: "
+ + mActiveAdmin.info.getTagForPolicy(reqPolicy));
+ }
return mActiveAdmin;
}
throw new SecurityException("Current admin is not owned by uid " + Binder.getCallingUid());
}
-
- void sendAdminCommandLocked(ActiveAdmin policy, String action) {
+ void sendAdminCommandLocked(ActiveAdmin admin, String action) {
Intent intent = new Intent(action);
- intent.setComponent(policy.info.getComponent());
+ intent.setComponent(admin.info.getComponent());
mContext.sendBroadcast(intent);
}
- void sendAdminCommandLocked(String action) {
+ void sendAdminCommandLocked(String action, int reqPolicy) {
if (mActiveAdmin != null) {
+ if (mActiveAdmin.info.usesPolicy(reqPolicy)) {
+ return;
+ }
sendAdminCommandLocked(mActiveAdmin, action);
}
}
@@ -353,7 +361,8 @@
if (who == null) {
throw new NullPointerException("ComponentName is null");
}
- ActiveAdmin ap = getActiveAdminForCallerLocked(who);
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (ap.passwordMode != mode) {
ap.passwordMode = mode;
saveSettingsLocked();
@@ -373,7 +382,8 @@
if (who == null) {
throw new NullPointerException("ComponentName is null");
}
- ActiveAdmin ap = getActiveAdminForCallerLocked(who);
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (ap.minimumPasswordLength != length) {
ap.minimumPasswordLength = length;
saveSettingsLocked();
@@ -391,7 +401,8 @@
synchronized (this) {
// This API can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(null);
+ getActiveAdminForCallerLocked(null,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
return mActivePasswordMode >= getPasswordMode()
&& mActivePasswordLength >= getMinimumPasswordLength();
}
@@ -401,7 +412,8 @@
synchronized (this) {
// This API can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(null);
+ getActiveAdminForCallerLocked(null,
+ DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
return mFailedPasswordAttempts;
}
}
@@ -411,7 +423,8 @@
synchronized (this) {
// This API can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(null);
+ getActiveAdminForCallerLocked(null,
+ DeviceAdminInfo.USES_POLICY_RESET_PASSWORD);
mode = getPasswordMode();
if (password.length() < getMinimumPasswordLength()) {
return false;
@@ -436,7 +449,8 @@
if (who == null) {
throw new NullPointerException("ComponentName is null");
}
- ActiveAdmin ap = getActiveAdminForCallerLocked(who);
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_LIMIT_UNLOCK);
if (ap.maximumTimeToUnlock != timeMs) {
ap.maximumTimeToUnlock = timeMs;
@@ -468,7 +482,8 @@
synchronized (this) {
// This API can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(null);
+ getActiveAdminForCallerLocked(null,
+ DeviceAdminInfo.USES_POLICY_FORCE_LOCK);
// STOPSHIP need to implement.
}
}
@@ -477,7 +492,8 @@
synchronized (this) {
// This API can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(null);
+ getActiveAdminForCallerLocked(null,
+ DeviceAdminInfo.USES_POLICY_WIPE_DATA);
}
long ident = Binder.clearCallingIdentity();
try {
@@ -501,7 +517,8 @@
mActivePasswordMode = mode;
mActivePasswordLength = length;
mFailedPasswordAttempts = 0;
- sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_CHANGED);
+ sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_CHANGED,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -517,7 +534,8 @@
long ident = Binder.clearCallingIdentity();
try {
mFailedPasswordAttempts++;
- sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_FAILED);
+ sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_FAILED,
+ DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -533,7 +551,8 @@
long ident = Binder.clearCallingIdentity();
try {
mFailedPasswordAttempts = 0;
- sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_SUCCEEDED);
+ sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_SUCCEEDED,
+ DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/java/com/android/server/Installer.java b/services/java/com/android/server/Installer.java
index 6a7d432..88b1f02 100644
--- a/services/java/com/android/server/Installer.java
+++ b/services/java/com/android/server/Installer.java
@@ -263,10 +263,15 @@
return execute(builder.toString());
}
- public int setForwardLockPerm(String packageName, int gid) {
+ /*
+ * @param packagePathSuffix The name of the path relative to install
+ * directory. Say if the path name is /data/app/com.test-1.apk,
+ * the package suffix path will be com.test-1
+ */
+ public int setForwardLockPerm(String packagePathSuffix, int gid) {
StringBuilder builder = new StringBuilder("protect");
builder.append(' ');
- builder.append(packageName);
+ builder.append(packagePathSuffix);
builder.append(' ');
builder.append(gid);
return execute(builder.toString());
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 0cee31d..0b7cfae 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -526,21 +526,55 @@
* Callback from NativeDaemonConnector
*/
public void onDaemonConnected() {
+ /*
+ * Since we'll be calling back into the NativeDaemonConnector,
+ * we need to do our work in a new thread.
+ */
new Thread() {
public void run() {
+ /**
+ * Determine media state and UMS detection status
+ */
+ String path = Environment.getExternalStorageDirectory().getPath();
+ String state = Environment.MEDIA_REMOVED;
+
try {
- if (!getVolumeState(Environment.getExternalStorageDirectory().getPath())
- .equals(Environment.MEDIA_MOUNTED)) {
- try {
- mountVolume(Environment.getExternalStorageDirectory().getPath());
- } catch (Exception ex) {
- Log.w(TAG, "Connection-mount failed");
+ String[] vols = mConnector.doListCommand(
+ "list_volumes", VoldResponseCode.VolumeListResult);
+ for (String volstr : vols) {
+ String[] tok = volstr.split(" ");
+ // FMT: <label> <mountpoint> <state>
+ if (!tok[1].equals(path)) {
+ Log.w(TAG, String.format(
+ "Skipping unknown volume '%s'",tok[1]));
+ continue;
}
- } else {
- Log.d(TAG, "Skipping connection-mount; already mounted");
+ int st = Integer.parseInt(tok[2]);
+ if (st == VolumeState.NoMedia) {
+ state = Environment.MEDIA_REMOVED;
+ } else if (st == VolumeState.Idle) {
+ state = null;
+ try {
+ mountVolume(path);
+ } catch (Exception ex) {
+ Log.e(TAG, "Connection-mount failed", ex);
+ }
+ } else if (st == VolumeState.Mounted) {
+ state = Environment.MEDIA_MOUNTED;
+ Log.i(TAG, "Media already mounted on daemon connection");
+ } else if (st == VolumeState.Shared) {
+ state = Environment.MEDIA_SHARED;
+ Log.i(TAG, "Media shared on daemon connection");
+ } else {
+ throw new Exception(String.format("Unexpected state %d", st));
+ }
}
- } catch (IllegalStateException rex) {
- Log.e(TAG, "Exception while handling connection mount ", rex);
+ if (state != null) {
+ updatePublicVolumeState(path, state);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error processing initial volume state", e);
+ updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
}
try {
@@ -1024,11 +1058,21 @@
}
public String[] getSecureContainerList() throws IllegalStateException {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.ASEC_ACCESS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ASEC_ACCESS permission");
+ }
return mConnector.doListCommand("list_asec", VoldResponseCode.AsecListResult);
}
public String createSecureContainer(String id, int sizeMb, String fstype,
String key, int ownerUid) throws IllegalStateException {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.ASEC_CREATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ASEC_CREATE permission");
+ }
String cmd = String.format("create_asec %s %d %s %s %d",
id, sizeMb, fstype, key, ownerUid);
mConnector.doCommand(cmd);
@@ -1036,15 +1080,31 @@
}
public void finalizeSecureContainer(String id) throws IllegalStateException {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.ASEC_CREATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ASEC_CREATE permission");
+ }
mConnector.doCommand(String.format("finalize_asec %s", id));
}
public void destroySecureContainer(String id) throws IllegalStateException {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.ASEC_DESTROY)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ASEC_DESTROY permission");
+ }
mConnector.doCommand(String.format("destroy_asec %s", id));
}
public String mountSecureContainer(String id, String key,
int ownerUid) throws IllegalStateException {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.ASEC_MOUNT_UNMOUNT)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ASEC_MOUNT_UNMOUNT permission");
+ }
+ mConnector.doCommand(String.format("destroy_asec %s", id));
String cmd = String.format("mount_asec %s %s %d",
id, key, ownerUid);
mConnector.doCommand(cmd);
@@ -1052,11 +1112,31 @@
}
public void unmountSecureContainer(String id) throws IllegalStateException {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.ASEC_MOUNT_UNMOUNT)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ASEC_MOUNT_UNMOUNT permission");
+ }
String cmd = String.format("unmount_asec %s", id);
mConnector.doCommand(cmd);
}
+ public void renameSecureContainer(String oldId, String newId) throws IllegalStateException {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.ASEC_RENAME)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ASEC_RENAME permission");
+ }
+ String cmd = String.format("rename_asec %s %s", oldId, newId);
+ mConnector.doCommand(cmd);
+ }
+
public String getSecureContainerPath(String id) throws IllegalStateException {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.ASEC_ACCESS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ASEC_ACCESS permission");
+ }
ArrayList<String> rsp = mConnector.doCommand("asec_path " + id);
for (String line : rsp) {
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index 98e00dc..92ba5f8 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -255,11 +255,7 @@
String[] tok = line.split(" ");
int code = Integer.parseInt(tok[0]);
if (code == expectedResponseCode) {
- if (tok.length !=2) {
- throw new IllegalStateException(
- String.format("Malformatted list entry '%s'", line));
- }
- rdata[idx++] = tok[1];
+ rdata[idx++] = line.substring(tok[0].length() + 1);
} else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
return rdata;
} else {
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 8594e44..b34b50a 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -78,7 +78,7 @@
*
* @param context Binder context for this service
*/
- private NetworkManagementService(Context context) {
+ public NetworkManagementService(Context context) {
mContext = context;
mObservers = new ArrayList<INetworkManagementEventObserver>();
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index cafc804..1cf3bad 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import com.android.internal.app.IMediaContainerService;
import com.android.internal.app.ResolverActivity;
import com.android.common.FastXmlSerializer;
import com.android.common.XmlUtils;
@@ -31,6 +32,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.content.ServiceConnection;
import android.content.IntentSender.SendIntentException;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -62,6 +64,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
@@ -283,26 +286,79 @@
final HashMap<String, ArrayList<String>> mPendingBroadcasts
= new HashMap<String, ArrayList<String>>();
static final int SEND_PENDING_BROADCAST = 1;
- static final int DESTROY_SD_CONTAINER = 2;
+ static final int MCS_BOUND = 3;
+ static final int END_COPY = 4;
+ static final int INIT_COPY = 5;
+ static final int MCS_UNBIND = 6;
// Delay time in millisecs
static final int BROADCAST_DELAY = 10 * 1000;
- static final int DESTROY_SD_CONTAINER_DELAY = 30 * 1000;
+ private ServiceConnection mDefContainerConn = new ServiceConnection() {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ IMediaContainerService imcs =
+ IMediaContainerService.Stub.asInterface(service);
+ Message msg = mHandler.obtainMessage(MCS_BOUND, imcs);
+ mHandler.sendMessage(msg);
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ }
+ };
class PackageHandler extends Handler {
+ final ArrayList<InstallArgs> mPendingInstalls =
+ new ArrayList<InstallArgs>();
+ // Service Connection to remote media container service to copy
+ // package uri's from external media onto secure containers
+ // or internal storage.
+ private IMediaContainerService mContainerService = null;
+
PackageHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
switch (msg.what) {
- case DESTROY_SD_CONTAINER:
- String pkgName = (String) msg.obj;
- if (pkgName != null) {
- // Too bad we cannot handle the errors from destroying the containers.
- if (!destroySdDir(pkgName)) {
- Log.e(TAG, "Failed to destroy container for pkg : " + pkgName);
+ case INIT_COPY: {
+ InstallArgs args = (InstallArgs) msg.obj;
+ args.createCopyFile();
+ Intent service = new Intent().setComponent(new ComponentName(
+ "com.android.defcontainer",
+ "com.android.defcontainer.DefaultContainerService"));
+ if (mContainerService != null) {
+ // No need to add to pending list. Use remote stub directly
+ handleStartCopy(args);
+ } else {
+ if (mContext.bindService(service, mDefContainerConn,
+ Context.BIND_AUTO_CREATE)) {
+ mPendingInstalls.add(args);
+ } else {
+ Log.e(TAG, "Failed to bind to media container service");
+ // Indicate install failure TODO add new error code
+ processPendingInstall(args,
+ PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE);
}
}
break;
+ }
+ case MCS_BOUND: {
+ // Initialize mContainerService if needed.
+ if (msg.obj != null) {
+ mContainerService = (IMediaContainerService) msg.obj;
+ }
+ if (mPendingInstalls.size() > 0) {
+ InstallArgs args = mPendingInstalls.remove(0);
+ if (args != null) {
+ handleStartCopy(args);
+ }
+ }
+ break;
+ }
+ case MCS_UNBIND : {
+ if (mPendingInstalls.size() == 0) {
+ mContext.unbindService(mDefContainerConn);
+ mContainerService = null;
+ }
+ break;
+ }
case SEND_PENDING_BROADCAST : {
String packages[];
ArrayList components[];
@@ -343,7 +399,37 @@
}
}
}
+
+ // Utility method to initiate copying apk via media
+ // container service.
+ private void handleStartCopy(InstallArgs args) {
+ int ret = PackageManager.INSTALL_SUCCEEDED;
+ if (mContainerService == null) {
+ // Install error
+ ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ } else {
+ ret = args.copyApk(mContainerService);
+ }
+ mHandler.sendEmptyMessage(MCS_UNBIND);
+ processPendingInstall(args, ret);
+ }
}
+
+ static boolean installOnSd(int flags) {
+ if (((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) ||
+ ((flags & PackageManager.INSTALL_ON_SDCARD) == 0)) {
+ return false;
+ }
+ return true;
+ }
+
+ static boolean isFwdLocked(int flags) {
+ if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) {
+ return true;
+ }
+ return false;
+ }
+
public static final IPackageManager main(Context context, boolean factoryTest) {
PackageManagerService m = new PackageManagerService(context, factoryTest);
ServiceManager.addService("package", m);
@@ -2022,11 +2108,12 @@
int parseFlags,
int scanMode) {
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
+ String scanPath = scanFile.getPath();
parseFlags |= mDefParseFlags;
- PackageParser pp = new PackageParser(scanFile.getPath());
+ PackageParser pp = new PackageParser(scanPath);
pp.setSeparateProcesses(mSeparateProcesses);
final PackageParser.Package pkg = pp.parsePackage(scanFile,
- scanFile.getPath(),
+ scanPath,
mMetrics, parseFlags);
if (pkg == null) {
mLastScanError = pp.getParseError();
@@ -2066,7 +2153,8 @@
// Just remove the loaded entries from package lists.
mPackages.remove(ps.name);
}
- deletePackageResourcesLI(ps.name, ps.codePathString, ps.resourcePathString);
+ InstallArgs args = new FileInstallArgs(ps.codePathString, ps.resourcePathString);
+ args.cleanUpResourcesLI();
mSettings.enableSystemPackageLP(ps.name);
}
}
@@ -2077,6 +2165,18 @@
if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
}
+
+ if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0) {
+ if (ps != null && ps.resourcePathString != null) {
+ pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
+ } else {
+ // Should not happen at all. Just log an error.
+ Log.e(TAG, "Resource path not set for pkg : " + pkg.packageName);
+ }
+ } else {
+ pkg.applicationInfo.publicSourceDir = pkg.mScanPath;
+ }
+ pkg.applicationInfo.sourceDir = pkg.mScanPath;
// Note that we invoke the following method only if we are about to unpack an application
return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE);
}
@@ -2230,41 +2330,8 @@
}
// Initialize package source and resource directories
- File destResourceFile = null;
- File destCodeFile = null;
- if ((scanMode & SCAN_NO_PATHS) == 0) {
- boolean fwdLocked = (parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0;
- final String pkgFileName = pkgName + ".apk";
- File destDir = null;
-
- if (fwdLocked) {
- destDir = mDrmAppPrivateInstallDir;
- destResourceFile = new File(mAppInstallDir, pkgName + ".zip");
- } else {
- boolean onSd = (parseFlags & PackageParser.PARSE_ON_SDCARD) != 0;
- if (!onSd) {
- destDir = mAppInstallDir;
- } else {
- String cachePath = getSdDir(pkgName);
- if (cachePath == null) {
- Log.e(TAG, "Secure container path for pkg: " + pkgName + " at location: " + cachePath +
- " not found");
- mLastScanError = PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
- return null;
- }
- destDir = new File(cachePath);
- }
- destResourceFile = new File(destDir, pkgFileName);
- }
- destCodeFile = new File(destDir, pkgFileName);
- pkg.mPath = destCodeFile.getAbsolutePath();
- } else {
- pkg.mPath = pkg.mScanPath;
- destCodeFile = new File(pkg.mScanPath);
- destResourceFile = new File(pkg.mScanPath);
- }
- pkg.applicationInfo.sourceDir = destCodeFile.getAbsolutePath();
- pkg.applicationInfo.publicSourceDir = destResourceFile.getAbsolutePath();
+ File destCodeFile = new File(pkg.applicationInfo.sourceDir);
+ File destResourceFile = new File(pkg.applicationInfo.publicSourceDir);
SharedUserSetting suid = null;
PackageSetting pkgSetting = null;
@@ -2568,21 +2635,17 @@
// We don't expect installation to fail beyond this point,
if ((scanMode&SCAN_MONITOR) != 0) {
- pkg.mPath = destCodeFile.getAbsolutePath();
mAppDirs.put(pkg.mPath, pkg);
}
// Request the ActivityManager to kill the process(only for existing packages)
// so that we do not end up in a confused state while the user is still using the older
// version of the application while the new one gets installed.
- IActivityManager am = ActivityManagerNative.getDefault();
- if ((am != null) && ((parseFlags & PackageManager.INSTALL_REPLACE_EXISTING ) != 0)) {
- try {
- am.killApplicationWithUid(pkg.applicationInfo.packageName,
+ if ((parseFlags & PackageManager.INSTALL_REPLACE_EXISTING ) != 0) {
+ killApplication(pkg.applicationInfo.packageName,
pkg.applicationInfo.uid);
- } catch (RemoteException e) {
- }
}
+
synchronized (mPackages) {
// Add the new setting to mSettings
mSettings.insertPackageSettingLP(pkgSetting, pkg);
@@ -2835,6 +2898,19 @@
return pkg;
}
+ private void killApplication(String pkgName, int uid) {
+ // Request the ActivityManager to kill the process(only for existing packages)
+ // so that we do not end up in a confused state while the user is still using the older
+ // version of the application while the new one gets installed.
+ IActivityManager am = ActivityManagerNative.getDefault();
+ if (am != null) {
+ try {
+ am.killApplicationWithUid(pkgName, uid);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
// The following constants are returned by cachePackageSharedLibsForAbiLI
// to indicate if native shared libraries were found in the package.
// Values are:
@@ -2855,9 +2931,8 @@
// room left on the data partition, or a ZipException if the package
// file is malformed.
//
- private int cachePackageSharedLibsForAbiLI( PackageParser.Package pkg,
- File dataPath, File scanFile, String cpuAbi)
- throws IOException, ZipException {
+ private int cachePackageSharedLibsForAbiLI(PackageParser.Package pkg,
+ File dataPath, File scanFile, String cpuAbi) throws IOException, ZipException {
File sharedLibraryDir = new File(dataPath.getPath() + "/lib");
final String apkLib = "lib/";
final int apkLibLen = apkLib.length();
@@ -2935,7 +3010,7 @@
if (mInstaller == null) {
sharedLibraryDir.mkdir();
}
- cacheSharedLibLI(pkg, zipFile, entry, sharedLibraryDir,
+ cacheNativeBinaryLI(pkg, zipFile, entry, sharedLibraryDir,
sharedLibraryFile);
}
}
@@ -2948,6 +3023,54 @@
return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
}
+ // Find the gdbserver executable program in a package at
+ // lib/<cpuAbi>/gdbserver and copy it to /data/data/<name>/lib/gdbserver
+ //
+ // Returns PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES on success,
+ // or PACKAGE_INSTALL_NATIVE_NO_LIBRARIES otherwise.
+ //
+ private int cachePackageGdbServerLI(PackageParser.Package pkg,
+ File dataPath, File scanFile, String cpuAbi) throws IOException, ZipException {
+ File installGdbServerDir = new File(dataPath.getPath() + "/lib");
+ final String GDBSERVER = "gdbserver";
+ final String apkGdbServerPath = "lib/" + cpuAbi + "/" + GDBSERVER;
+
+ ZipFile zipFile = new ZipFile(scanFile);
+ Enumeration<ZipEntry> entries =
+ (Enumeration<ZipEntry>) zipFile.entries();
+
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ // skip directories
+ if (entry.isDirectory()) {
+ continue;
+ }
+ String entryName = entry.getName();
+
+ if (!entryName.equals(apkGdbServerPath)) {
+ continue;
+ }
+
+ String installGdbServerPath = installGdbServerDir.getPath() +
+ "/" + GDBSERVER;
+ File installGdbServerFile = new File(installGdbServerPath);
+ if (! installGdbServerFile.exists() ||
+ installGdbServerFile.length() != entry.getSize() ||
+ installGdbServerFile.lastModified() != entry.getTime()) {
+ if (Config.LOGD) {
+ Log.d(TAG, "Caching gdbserver " + entry.getName());
+ }
+ if (mInstaller == null) {
+ installGdbServerDir.mkdir();
+ }
+ cacheNativeBinaryLI(pkg, zipFile, entry, installGdbServerDir,
+ installGdbServerFile);
+ }
+ return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
+ }
+ return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES;
+ }
+
// extract shared libraries stored in the APK as lib/<cpuAbi>/lib<name>.so
// and copy them to /data/data/<appname>/lib.
//
@@ -2957,7 +3080,7 @@
//
private int cachePackageSharedLibsLI(PackageParser.Package pkg,
File dataPath, File scanFile) {
- final String cpuAbi = Build.CPU_ABI;
+ String cpuAbi = Build.CPU_ABI;
try {
int result = cachePackageSharedLibsForAbiLI(pkg, dataPath, scanFile, cpuAbi);
@@ -2968,7 +3091,7 @@
//
// only scan the package twice in case of ABI mismatch
if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) {
- String cpuAbi2 = SystemProperties.get("ro.product.cpu.abi2",null);
+ final String cpuAbi2 = SystemProperties.get("ro.product.cpu.abi2",null);
if (cpuAbi2 != null) {
result = cachePackageSharedLibsForAbiLI(pkg, dataPath, scanFile, cpuAbi2);
}
@@ -2977,6 +3100,20 @@
Log.w(TAG,"Native ABI mismatch from package file");
return PackageManager.INSTALL_FAILED_INVALID_APK;
}
+
+ if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
+ cpuAbi = cpuAbi2;
+ }
+ }
+
+ // for debuggable packages, also extract gdbserver from lib/<abi>
+ // into /data/data/<appname>/lib too.
+ if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES &&
+ (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ int result2 = cachePackageGdbServerLI(pkg, dataPath, scanFile, cpuAbi);
+ if (result2 == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_NATIVE_DEBUGGABLE;
+ }
}
} catch (ZipException e) {
Log.w(TAG, "Failed to extract data from package file", e);
@@ -2988,26 +3125,27 @@
return PackageManager.INSTALL_SUCCEEDED;
}
- private void cacheSharedLibLI(PackageParser.Package pkg,
+ private void cacheNativeBinaryLI(PackageParser.Package pkg,
ZipFile zipFile, ZipEntry entry,
- File sharedLibraryDir,
- File sharedLibraryFile) throws IOException {
+ File binaryDir,
+ File binaryFile) throws IOException {
InputStream inputStream = zipFile.getInputStream(entry);
try {
- File tempFile = File.createTempFile("tmp", "tmp", sharedLibraryDir);
+ File tempFile = File.createTempFile("tmp", "tmp", binaryDir);
String tempFilePath = tempFile.getPath();
- // XXX package manager can't change owner, so the lib files for
+ // XXX package manager can't change owner, so the executable files for
// now need to be left as world readable and owned by the system.
if (! FileUtils.copyToFile(inputStream, tempFile) ||
! tempFile.setLastModified(entry.getTime()) ||
FileUtils.setPermissions(tempFilePath,
FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP
+ |FileUtils.S_IXUSR|FileUtils.S_IXGRP|FileUtils.S_IXOTH
|FileUtils.S_IROTH, -1, -1) != 0 ||
- ! tempFile.renameTo(sharedLibraryFile)) {
+ ! tempFile.renameTo(binaryFile)) {
// Failed to properly write file.
tempFile.delete();
- throw new IOException("Couldn't create cached shared lib "
- + sharedLibraryFile + " in " + sharedLibraryDir);
+ throw new IOException("Couldn't create cached binary "
+ + binaryFile + " in " + binaryDir);
}
} finally {
inputStream.close();
@@ -3804,26 +3942,51 @@
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INSTALL_PACKAGES, null);
+ Message msg = mHandler.obtainMessage(INIT_COPY);
+ msg.obj = createInstallArgs(packageURI, observer, flags, installerPackageName);
+ mHandler.sendMessage(msg);
+ }
+
+ private InstallArgs createInstallArgs(Uri packageURI, IPackageInstallObserver observer,
+ int flags, String installerPackageName) {
+ if (installOnSd(flags)) {
+ return new SdInstallArgs(packageURI, observer, flags,
+ installerPackageName);
+ } else {
+ return new FileInstallArgs(packageURI, observer, flags,
+ installerPackageName);
+ }
+ }
+
+ private InstallArgs createInstallArgs(int flags, String fullCodePath, String fullResourcePath) {
+ if (installOnSd(flags)) {
+ return new SdInstallArgs(fullCodePath, fullResourcePath);
+ } else {
+ return new FileInstallArgs(fullCodePath, fullResourcePath);
+ }
+ }
+
+ private void processPendingInstall(final InstallArgs args, final int currentStatus) {
// Queue up an async operation since the package installation may take a little while.
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
// Result object to be returned
PackageInstalledInfo res = new PackageInstalledInfo();
- res.returnCode = PackageManager.INSTALL_SUCCEEDED;
+ res.returnCode = currentStatus;
res.uid = -1;
res.pkg = null;
res.removedInfo = new PackageRemovedInfo();
- // Make a temporary copy of file from given packageURI
- File tmpPackageFile = copyTempInstallFile(packageURI, res);
- if (tmpPackageFile != null) {
+ args.doPreInstall(res.returnCode);
+ if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
synchronized (mInstallLock) {
- installPackageLI(packageURI, flags, true, installerPackageName, tmpPackageFile, res);
+ installPackageLI(args, true, res);
}
+ args.doPostInstall(res.returnCode);
}
- if (observer != null) {
+ if (args.observer != null) {
try {
- observer.packageInstalled(res.name, res.returnCode);
+ args.observer.packageInstalled(res.name, res.returnCode);
} catch (RemoteException e) {
Log.i(TAG, "Observer no longer exists.");
}
@@ -3846,12 +4009,403 @@
res.pkg.applicationInfo.packageName,
extras);
}
+ if (res.removedInfo.args != null) {
+ // Remove the replaced package's older resources safely now
+ synchronized (mInstallLock) {
+ res.removedInfo.args.cleanUpResourcesLI();
+ }
+ }
}
Runtime.getRuntime().gc();
}
});
}
+ static abstract class InstallArgs {
+ final IPackageInstallObserver observer;
+ final int flags;
+ final Uri packageURI;
+ final String installerPackageName;
+
+ InstallArgs(Uri packageURI,
+ IPackageInstallObserver observer, int flags,
+ String installerPackageName) {
+ this.packageURI = packageURI;
+ this.flags = flags;
+ this.observer = observer;
+ this.installerPackageName = installerPackageName;
+ }
+
+ abstract void createCopyFile();
+ abstract int copyApk(IMediaContainerService imcs);
+ abstract void doPreInstall(int status);
+ abstract boolean doRename(int status, String pkgName, String oldCodePath);
+ abstract void doPostInstall(int status);
+ abstract String getCodePath();
+ abstract String getResourcePath();
+ // Need installer lock especially for dex file removal.
+ abstract void cleanUpResourcesLI();
+ }
+
+ class FileInstallArgs extends InstallArgs {
+ File installDir;
+ String codeFileName;
+ String resourceFileName;
+
+ FileInstallArgs(Uri packageURI,
+ IPackageInstallObserver observer, int flags,
+ String installerPackageName) {
+ super(packageURI, observer, flags, installerPackageName);
+ }
+
+ FileInstallArgs(String fullCodePath, String fullResourcePath) {
+ super(null, null, 0, null);
+ File codeFile = new File(fullCodePath);
+ installDir = codeFile.getParentFile();
+ codeFileName = fullCodePath;
+ resourceFileName = fullResourcePath;
+ }
+
+ void createCopyFile() {
+ boolean fwdLocked = isFwdLocked(flags);
+ installDir = fwdLocked ? mDrmAppPrivateInstallDir : mAppInstallDir;
+ codeFileName = createTempPackageFile(installDir).getPath();
+ resourceFileName = getResourcePathFromCodePath();
+ }
+
+ String getCodePath() {
+ return codeFileName;
+ }
+
+ int copyApk(IMediaContainerService imcs) {
+ // Get a ParcelFileDescriptor to write to the output file
+ File codeFile = new File(codeFileName);
+ ParcelFileDescriptor out = null;
+ try {
+ out = ParcelFileDescriptor.open(codeFile,
+ ParcelFileDescriptor.MODE_READ_WRITE);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Failed to create file descritpor for : " + codeFileName);
+ return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ }
+ // Copy the resource now
+ int ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ try {
+ if (imcs.copyResource(packageURI, out)) {
+ ret = PackageManager.INSTALL_SUCCEEDED;
+ }
+ } catch (RemoteException e) {
+ } finally {
+ try { if (out != null) out.close(); } catch (IOException e) {}
+ }
+ return ret;
+ }
+
+ void doPreInstall(int status) {
+ if (status != PackageManager.INSTALL_SUCCEEDED) {
+ cleanUp();
+ }
+ }
+
+ boolean doRename(int status, final String pkgName, String oldCodePath) {
+ if (status != PackageManager.INSTALL_SUCCEEDED) {
+ cleanUp();
+ return false;
+ } else {
+ // Rename based on packageName
+ File codeFile = new File(getCodePath());
+ String apkName = getNextCodePath(oldCodePath, pkgName, ".apk");
+ File desFile = new File(installDir, apkName + ".apk");
+ if (!codeFile.renameTo(desFile)) {
+ return false;
+ }
+ // Reset paths since the file has been renamed.
+ codeFileName = desFile.getPath();
+ resourceFileName = getResourcePathFromCodePath();
+ // Set permissions
+ if (!setPermissions(pkgName)) {
+ // Failed setting permissions.
+ return false;
+ }
+ return true;
+ }
+ }
+
+ void doPostInstall(int status) {
+ if (status != PackageManager.INSTALL_SUCCEEDED) {
+ cleanUp();
+ }
+ }
+
+ String getResourcePath() {
+ return resourceFileName;
+ }
+
+ String getResourcePathFromCodePath() {
+ String codePath = getCodePath();
+ if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) {
+ String apkNameOnly = getApkName(codePath);
+ return mAppInstallDir.getPath() + "/" + apkNameOnly + ".zip";
+ } else {
+ return codePath;
+ }
+ }
+
+ private boolean cleanUp() {
+ boolean ret = true;
+ String sourceDir = getCodePath();
+ String publicSourceDir = getResourcePath();
+ if (sourceDir != null) {
+ File sourceFile = new File(sourceDir);
+ if (!sourceFile.exists()) {
+ Log.w(TAG, "Package source " + sourceDir + " does not exist.");
+ ret = false;
+ }
+ // Delete application's code and resources
+ sourceFile.delete();
+ }
+ if (publicSourceDir != null && !publicSourceDir.equals(sourceDir)) {
+ final File publicSourceFile = new File(publicSourceDir);
+ if (!publicSourceFile.exists()) {
+ Log.w(TAG, "Package public source " + publicSourceFile + " does not exist.");
+ }
+ if (publicSourceFile.exists()) {
+ publicSourceFile.delete();
+ }
+ }
+ return ret;
+ }
+
+ void cleanUpResourcesLI() {
+ String sourceDir = getCodePath();
+ if (cleanUp() && mInstaller != null) {
+ int retCode = mInstaller.rmdex(sourceDir);
+ if (retCode < 0) {
+ Log.w(TAG, "Couldn't remove dex file for package: "
+ + " at location "
+ + sourceDir + ", retcode=" + retCode);
+ // we don't consider this to be a failure of the core package deletion
+ }
+ }
+ }
+
+ private boolean setPermissions(String pkgName) {
+ // TODO Do this in a more elegant way later on. for now just a hack
+ if (!isFwdLocked(flags)) {
+ final int filePermissions =
+ FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP
+ |FileUtils.S_IROTH;
+ int retCode = FileUtils.setPermissions(getCodePath(), filePermissions, -1, -1);
+ if (retCode != 0) {
+ Log.e(TAG, "Couldn't set new package file permissions for " +
+ getCodePath()
+ + ". The return code was: " + retCode);
+ // TODO Define new internal error
+ return false;
+ }
+ return true;
+ }
+ return true;
+ }
+ }
+
+ class SdInstallArgs extends InstallArgs {
+ String cid;
+ String cachePath;
+ static final String RES_FILE_NAME = "pkg.apk";
+
+ SdInstallArgs(Uri packageURI,
+ IPackageInstallObserver observer, int flags,
+ String installerPackageName) {
+ super(packageURI, observer, flags, installerPackageName);
+ }
+
+ SdInstallArgs(String fullCodePath, String fullResourcePath) {
+ super(null, null, 0, null);
+ // Extract cid from fullCodePath
+ int eidx = fullCodePath.lastIndexOf("/");
+ String subStr1 = fullCodePath.substring(0, eidx);
+ int sidx = subStr1.lastIndexOf("/");
+ cid = subStr1.substring(sidx+1, eidx);
+ cachePath = subStr1;
+ }
+
+ void createCopyFile() {
+ cid = getTempContainerId();
+ }
+
+ int copyApk(IMediaContainerService imcs) {
+ try {
+ cachePath = imcs.copyResourceToContainer(
+ packageURI, cid,
+ getEncryptKey(), RES_FILE_NAME);
+ } catch (RemoteException e) {
+ }
+
+ if (cachePath != null) {
+ // Mount container once its created with system_uid
+ cachePath = mountSdDir(cid, Process.SYSTEM_UID);
+ }
+ if (cachePath == null) {
+ return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
+ } else {
+ return PackageManager.INSTALL_SUCCEEDED;
+ }
+ }
+
+ @Override
+ String getCodePath() {
+ return cachePath + "/" + RES_FILE_NAME;
+ }
+
+ @Override
+ String getResourcePath() {
+ return cachePath + "/" + RES_FILE_NAME;
+ }
+
+ void doPreInstall(int status) {
+ if (status != PackageManager.INSTALL_SUCCEEDED) {
+ // Destroy container
+ destroySdDir(cid);
+ }
+ }
+
+ boolean doRename(int status, final String pkgName,
+ String oldCodePath) {
+ String newCacheId = getNextCodePath(oldCodePath, pkgName, "/" + RES_FILE_NAME);
+ // STOPSHIP TEMPORARY HACK FOR RENAME
+ // Create new container at newCachePath
+ String codePath = getCodePath();
+ String newCachePath = null;
+ final int CREATE_FAILED = 1;
+ final int COPY_FAILED = 3;
+ final int FINALIZE_FAILED = 5;
+ final int PASS = 7;
+ int errCode = CREATE_FAILED;
+ if ((newCachePath = createSdDir(new File(codePath), newCacheId)) != null) {
+ errCode = COPY_FAILED;
+ // Copy file from codePath
+ if (FileUtils.copyFile(new File(codePath), new File(newCachePath, RES_FILE_NAME))) {
+ errCode = FINALIZE_FAILED;
+ if (finalizeSdDir(newCacheId)) {
+ errCode = PASS;
+ }
+ }
+ }
+ // Print error based on errCode
+ String errMsg = "";
+ switch (errCode) {
+ case CREATE_FAILED:
+ errMsg = "CREATE_FAILED";
+ break;
+ case COPY_FAILED:
+ errMsg = "COPY_FAILED";
+ destroySdDir(newCacheId);
+ break;
+ case FINALIZE_FAILED:
+ errMsg = "FINALIZE_FAILED";
+ destroySdDir(newCacheId);
+ break;
+ default:
+ errMsg = "PASS";
+ break;
+ }
+ // Destroy the temporary container
+ destroySdDir(cid);
+ Log.i(TAG, "Status: " + errMsg);
+ if (errCode != PASS) {
+ return false;
+ }
+ cid = newCacheId;
+ cachePath = newCachePath;
+
+ return true;
+ }
+
+ void doPostInstall(int status) {
+ if (status != PackageManager.INSTALL_SUCCEEDED) {
+ cleanUp();
+ } else {
+ // Unmount container
+ // Rename and remount based on package name and new uid
+ }
+ }
+
+ private void cleanUp() {
+ // Destroy secure container
+ destroySdDir(cid);
+ }
+
+ void cleanUpResourcesLI() {
+ String sourceFile = getCodePath();
+ // Remove dex file
+ if (mInstaller != null) {
+ int retCode = mInstaller.rmdex(sourceFile.toString());
+ if (retCode < 0) {
+ Log.w(TAG, "Couldn't remove dex file for package: "
+ + " at location "
+ + sourceFile.toString() + ", retcode=" + retCode);
+ // we don't consider this to be a failure of the core package deletion
+ }
+ }
+ cleanUp();
+ }
+ };
+
+ // Utility method used to create code paths based on package name and available index.
+ private static String getNextCodePath(String oldCodePath, String prefix, String suffix) {
+ String idxStr = "";
+ int idx = 1;
+ if (oldCodePath != null) {
+ int eidx = -1;
+ if (suffix != null) {
+ eidx = oldCodePath.indexOf(suffix);
+ }
+ if (eidx == -1) {
+ eidx = oldCodePath.length();
+ }
+ int sidx = oldCodePath.indexOf(prefix);
+ if (sidx == -1) {
+ sidx = 0;
+ }
+ String subStr = oldCodePath.substring(sidx + prefix.length(), eidx);
+ if (subStr != null) {
+ if (subStr.startsWith("-")) {
+ subStr = subStr.substring(1);
+ }
+ try {
+ idx = Integer.parseInt(subStr);
+ if (idx <= 1) {
+ idx++;
+ } else {
+ idx--;
+ }
+ } catch(NumberFormatException e) {
+ }
+ }
+ }
+ idxStr = "-" + Integer.toString(idx);
+ return prefix + idxStr;
+ }
+
+ // Utility method that returns the relative package path with respect
+ // to the installation directory. Like say for /data/data/com.test-1.apk
+ // string com.test-1 is returned.
+ static String getApkName(String codePath) {
+ if (codePath == null) {
+ return null;
+ }
+ int sidx = codePath.lastIndexOf("/");
+ int eidx = codePath.lastIndexOf(".");
+ if (eidx == -1) {
+ eidx = codePath.length();
+ } else if (eidx == 0) {
+ Log.w(TAG, " Invalid code path, "+ codePath + " Not a valid apk name");
+ return null;
+ }
+ return codePath.substring(sidx+1, eidx);
+ }
+
class PackageInstalledInfo {
String name;
int uid;
@@ -3870,7 +4424,6 @@
// Remember this for later, in case we need to rollback this install
boolean dataDirExists;
String pkgName = pkg.packageName;
- boolean onSd = (parseFlags & PackageParser.PARSE_ON_SDCARD) != 0;
if (useEncryptedFilesystemForPackage(pkg)) {
dataDirExists = (new File(mSecureAppDataDir, pkgName)).exists();
@@ -3888,14 +4441,6 @@
}
}
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
- if (onSd) {
- // Create secure container mount point for package
- String cPath = createSdDir(new File(pkg.mScanPath), pkgName);
- if (cPath == null) {
- mLastScanError = res.returnCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
- return;
- }
- }
PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode);
if (newPackage == null) {
Log.w(TAG, "Package couldn't be installed in " + pkg.mPath);
@@ -3903,21 +4448,9 @@
res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
}
} else {
- File destPackageFile = new File(pkg.mPath);
- if (destPackageFile.exists()) {
- // It's safe to do this because we know (from the above check) that the file
- // isn't currently used for an installed package.
- destPackageFile.delete();
- }
updateSettingsLI(newPackage,
installerPackageName,
res);
- if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
- // Check if container can be finalized
- if(onSd && !finalizeSdDir(pkgName)) {
- res.returnCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
- }
- }
// delete the partially installed application. the data directory will have to be
// restored if it was already existing
if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
@@ -3926,15 +4459,11 @@
// scanPackageLocked, unless those directories existed before we even tried to
// install.
deletePackageLI(
- pkgName, true,
+ pkgName, false,
dataDirExists ? PackageManager.DONT_DELETE_DATA : 0,
res.removedInfo);
}
}
- if (onSd && res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
- // Destroy cache
- destroySdDir(pkgName);
- }
}
private void replacePackageLI(PackageParser.Package pkg,
@@ -3977,7 +4506,7 @@
parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
// First delete the existing package while retaining the data directory
- if (!deletePackageLI(pkgName, false, PackageManager.DONT_DELETE_DATA,
+ if (!deletePackageLI(pkgName, true, PackageManager.DONT_DELETE_DATA,
res.removedInfo)) {
// If the existing package was'nt successfully deleted
res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
@@ -4001,17 +4530,10 @@
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
// If we deleted an exisiting package, the old source and resource files that we
- // were keeping around in case we needed them (see below) can now be deleted
- final ApplicationInfo deletedPackageAppInfo = deletedPackage.applicationInfo;
- final ApplicationInfo installedPackageAppInfo =
- newPackage.applicationInfo;
- deletePackageResourcesLI(pkgName,
- !deletedPackageAppInfo.sourceDir
- .equals(installedPackageAppInfo.sourceDir)
- ? deletedPackageAppInfo.sourceDir : null,
- !deletedPackageAppInfo.publicSourceDir
- .equals(installedPackageAppInfo.publicSourceDir)
- ? deletedPackageAppInfo.publicSourceDir : null);
+ // were keeping around in case we needed them (see below) can now be deleted.
+ // This info will be set on the res.removedInfo to clean up later on as post
+ // install action.
+
//update signature on the new package setting
//this should always succeed, since we checked the
//signature earlier.
@@ -4038,22 +4560,15 @@
Log.e(TAG, "Failed allocating storage when restoring pkg : " + pkgName);
return;
}
- File restoreTmpFile = createTempPackageFile();
- if (restoreTmpFile == null) {
- Log.e(TAG, "Failed creating temp file when restoring pkg : " + pkgName);
- return;
- }
- if (!FileUtils.copyFile(restoreFile, restoreTmpFile)) {
- Log.e(TAG, "Failed copying temp file when restoring pkg : " + pkgName);
- return;
- }
PackageInstalledInfo restoreRes = new PackageInstalledInfo();
restoreRes.removedInfo = new PackageRemovedInfo();
- installPackageLI(
- Uri.fromFile(restoreFile),
- isForwardLocked(deletedPackage)
- ? PackageManager.INSTALL_FORWARD_LOCK
- : 0, false, oldInstallerPackageName, restoreTmpFile, restoreRes);
+ // Parse old package
+ parseFlags |= ~PackageManager.INSTALL_REPLACE_EXISTING;
+ scanPackageLI(restoreFile, parseFlags, scanMode);
+ synchronized (mPackages) {
+ grantPermissionsLP(deletedPackage, false);
+ mSettings.writeLP();
+ }
if (restoreRes.returnCode != PackageManager.INSTALL_SUCCEEDED) {
Log.e(TAG, "Failed restoring pkg : " + pkgName + " after failed upgrade");
}
@@ -4158,24 +4673,11 @@
return;
}
}
- // XXX There are probably some big issues here: upon doing
- // the rename, we have reached the point of no return (the
- // original .apk is gone!), so we can't fail. Yet... we can.
- File scanFile = new File(newPackage.mScanPath);
- if (!scanFile.renameTo(new File(newPackage.mPath))) {
- Log.e(TAG, "Couldn't move package file: " + newPackage.mScanPath + " to: " + newPackage.mPath);
- // TODO rename should work. Workaround
- if (!FileUtils.copyFile(scanFile, new File(newPackage.mPath))) {
- Log.e(TAG, "Couldn't move package file to: " + newPackage.mPath);
- res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- }
+ res.returnCode = setPermissionsLI(newPackage);
+ if(res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
+ return;
} else {
- res.returnCode = setPermissionsLI(newPackage);
- if(res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
- return;
- } else {
- Log.d(TAG, "New package installed in " + newPackage.mPath);
- }
+ Log.d(TAG, "New package installed in " + newPackage.mPath);
}
if(res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
if (mInstaller != null) {
@@ -4196,67 +4698,11 @@
}
}
- private File copyTempInstallFile(Uri pPackageURI,
- PackageInstalledInfo res) {
- File tmpPackageFile = createTempPackageFile();
- int retCode = PackageManager.INSTALL_SUCCEEDED;
- if (tmpPackageFile == null) {
- res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- return null;
- }
-
- if (pPackageURI.getScheme().equals("file")) {
- final File srcPackageFile = new File(pPackageURI.getPath());
- // We copy the source package file to a temp file and then rename it to the
- // destination file in order to eliminate a window where the package directory
- // scanner notices the new package file but it's not completely copied yet.
- if (!FileUtils.copyFile(srcPackageFile, tmpPackageFile)) {
- Log.e(TAG, "Couldn't copy package file to temp file.");
- retCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- }
- } else if (pPackageURI.getScheme().equals("content")) {
- ParcelFileDescriptor fd = null;
- try {
- fd = mContext.getContentResolver().openFileDescriptor(pPackageURI, "r");
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e);
- retCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- }
- if (fd == null) {
- Log.e(TAG, "Couldn't open file descriptor from download service (null).");
- retCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- } else {
- if (Config.LOGV) {
- Log.v(TAG, "Opened file descriptor from download service.");
- }
- ParcelFileDescriptor.AutoCloseInputStream
- dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd);
- // We copy the source package file to a temp file and then rename it to the
- // destination file in order to eliminate a window where the package directory
- // scanner notices the new package file but it's not completely copied yet.
- if (!FileUtils.copyToFile(dlStream, tmpPackageFile)) {
- Log.e(TAG, "Couldn't copy package stream to temp file.");
- retCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- }
- }
- } else {
- Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI);
- retCode = PackageManager.INSTALL_FAILED_INVALID_URI;
- }
-
- res.returnCode = retCode;
- if (retCode != PackageManager.INSTALL_SUCCEEDED) {
- if (tmpPackageFile != null && tmpPackageFile.exists()) {
- tmpPackageFile.delete();
- }
- return null;
- }
- return tmpPackageFile;
- }
-
- private void installPackageLI(Uri pPackageURI,
- int pFlags, boolean newInstall, String installerPackageName,
- File tmpPackageFile, PackageInstalledInfo res) {
+ private void installPackageLI(InstallArgs args,
+ boolean newInstall, PackageInstalledInfo res) {
+ int pFlags = args.flags;
+ String installerPackageName = args.installerPackageName;
+ File tmpPackageFile = new File(args.getCodePath());
boolean forwardLocked = ((pFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
boolean onSd = ((pFlags & PackageManager.INSTALL_ON_SDCARD) != 0);
boolean replacingExistingPackage = false;
@@ -4291,14 +4737,25 @@
break main_flow;
}
+ // Get rid of all references to package scan path via parser.
+ pp = null;
+ String oldCodePath = null;
synchronized (mPackages) {
//check if installing already existing package
if ((pFlags&PackageManager.INSTALL_REPLACE_EXISTING) != 0
&& mPackages.containsKey(pkgName)) {
replacingExistingPackage = true;
+ oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
}
}
+ if (!args.doRename(res.returnCode, pkgName, oldCodePath)) {
+ res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ break main_flow;
+ }
+ // TODO rename pkg.mScanPath In scanPackageLI let it just set values based on mScanPath
+ pkg.applicationInfo.sourceDir = pkg.mScanPath= pkg.mPath = args.getCodePath();
+ pkg.applicationInfo.publicSourceDir = args.getResourcePath();
if(replacingExistingPackage) {
replacePackageLI(pkg, parseFlags, scanMode,
installerPackageName, res);
@@ -4307,15 +4764,16 @@
installerPackageName,res);
}
} finally {
- if (tmpPackageFile != null && tmpPackageFile.exists()) {
- tmpPackageFile.delete();
+ if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
}
}
}
private int setPermissionsLI(PackageParser.Package newPackage) {
String pkgName = newPackage.packageName;
- int retCode;
+ int retCode = 0;
+ // TODO Gross hack but fix later. Ideally move this to be a post installation
+ // check after alloting uid.
if ((newPackage.applicationInfo.flags
& ApplicationInfo.FLAG_FORWARD_LOCK) != 0) {
File destResourceFile = new File(newPackage.applicationInfo.publicSourceDir);
@@ -4329,7 +4787,7 @@
//TODO clean up the extracted public files
}
if (mInstaller != null) {
- retCode = mInstaller.setForwardLockPerm(pkgName,
+ retCode = mInstaller.setForwardLockPerm(getApkName(newPackage.mPath),
newPackage.applicationInfo.uid);
} else {
final int filePermissions =
@@ -4338,15 +4796,16 @@
newPackage.applicationInfo.uid);
}
} else {
- final int filePermissions =
- FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP
- |FileUtils.S_IROTH;
- retCode = FileUtils.setPermissions(newPackage.mPath, filePermissions, -1, -1);
+ // The permissions on the resource file was set when it was copied for
+ // non forward locked apps and apps on sdcard
}
+
if (retCode != 0) {
Log.e(TAG, "Couldn't set new package file permissions for " +
newPackage.mPath
+ ". The return code was: " + retCode);
+ // TODO Define new internal error
+ return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
return PackageManager.INSTALL_SUCCEEDED;
}
@@ -4429,10 +4888,10 @@
}
}
- private File createTempPackageFile() {
+ private File createTempPackageFile(File installDir) {
File tmpPackageFile;
try {
- tmpPackageFile = File.createTempFile("vmdl", ".tmp", mAppInstallDir);
+ tmpPackageFile = File.createTempFile("vmdl", ".tmp", installDir);
} catch (IOException e) {
Log.e(TAG, "Couldn't create temp file for downloaded package file.");
return null;
@@ -4508,6 +4967,13 @@
sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, extras);
}
}
+ // Delete the resources here after sending the broadcast to let
+ // other processes clean up before deleting resources.
+ synchronized (mInstallLock) {
+ if (info.args != null) {
+ info.args.cleanUpResourcesLI();
+ }
+ }
return res;
}
@@ -4516,6 +4982,8 @@
int uid = -1;
int removedUid = -1;
boolean isRemovedPackageSystemUpdate = false;
+ // Clean up resources deleted packages.
+ InstallArgs args = null;
void sendBroadcast(boolean fullRemove, boolean replacing) {
Bundle extras = new Bundle(1);
@@ -4645,36 +5113,6 @@
return true;
}
- private void deletePackageResourcesLI(String packageName,
- String sourceDir, String publicSourceDir) {
- if (sourceDir != null) {
- File sourceFile = new File(sourceDir);
- if (!sourceFile.exists()) {
- Log.w(TAG, "Package source " + sourceDir + " does not exist.");
- }
- // Delete application's code and resources
- sourceFile.delete();
- if (mInstaller != null) {
- int retCode = mInstaller.rmdex(sourceFile.toString());
- if (retCode < 0) {
- Log.w(TAG, "Couldn't remove dex file for package: "
- + packageName + " at location "
- + sourceFile.toString() + ", retcode=" + retCode);
- // we don't consider this to be a failure of the core package deletion
- }
- }
- }
- if (publicSourceDir != null && !publicSourceDir.equals(sourceDir)) {
- final File publicSourceFile = new File(publicSourceDir);
- if (!publicSourceFile.exists()) {
- Log.w(TAG, "Package public source " + publicSourceFile + " does not exist.");
- }
- if (publicSourceFile.exists()) {
- publicSourceFile.delete();
- }
- }
- }
-
private boolean deleteInstalledPackageLI(PackageParser.Package p,
boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo) {
ApplicationInfo applicationInfo = p.applicationInfo;
@@ -4691,7 +5129,12 @@
// Delete application code and resources
if (deleteCodeAndResources) {
- deletePackageResourcesLI(applicationInfo.packageName,
+ // TODO can pick up from PackageSettings as well
+ int installFlags = ((p.applicationInfo.flags & ApplicationInfo.FLAG_ON_SDCARD)!=0) ?
+ PackageManager.INSTALL_ON_SDCARD : 0;
+ installFlags |= ((p.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK)!=0) ?
+ PackageManager.INSTALL_FORWARD_LOCK : 0;
+ outInfo.args = createInstallArgs(installFlags,
applicationInfo.sourceDir, applicationInfo.publicSourceDir);
}
return true;
@@ -4736,12 +5179,6 @@
Log.w(TAG, "Package " + p.packageName + " has no applicationInfo.");
return false;
}
- boolean onSd = (p.applicationInfo.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0;
- // Mount sd container if needed
- if (onSd) {
- // TODO Better error handling from MountService api later
- mountSdDir(p.packageName, Process.SYSTEM_UID) ;
- }
boolean ret = false;
if ( (p.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
Log.i(TAG, "Removing system package:"+p.packageName);
@@ -4750,22 +5187,10 @@
ret = deleteSystemPackageLI(p, flags, outInfo);
} else {
Log.i(TAG, "Removing non-system package:"+p.packageName);
+ // Kill application pre-emptively especially for apps on sd.
+ killApplication(packageName, p.applicationInfo.uid);
ret = deleteInstalledPackageLI (p, deleteCodeAndResources, flags, outInfo);
}
- if (ret && onSd) {
- if (deleteCodeAndResources) {
- // Post a delayed destroy on the container since there might
- // be active processes holding open file handles to package
- // resources which will get killed by the process killer when
- // destroying the container. This might even kill the current
- // process and crash the system. Delay the destroy a bit so
- // that the active processes get to handle the uninstall broadcasts.
- sendDelayedDestroySdDir(packageName);
- } else {
- // Just unmount the directory
- unMountSdDir(packageName);
- }
- }
return ret;
}
@@ -7458,11 +7883,31 @@
final private String mSdEncryptKey = "AppsOnSD";
final private String mSdEncryptAlg = "AES";
private boolean mMediaMounted = false;
+ private static final int MAX_CONTAINERS = 250;
- private MountService getMountService() {
+
+ static MountService getMountService() {
return (MountService) ServiceManager.getService("mount");
}
+ private String getEncryptKey() {
+ try {
+ String sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString(mSdEncryptKey);
+ if (sdEncKey == null) {
+ sdEncKey = SystemKeyStore.getInstance().
+ generateNewKeyHexString(128, mSdEncryptAlg, mSdEncryptKey);
+ if (sdEncKey == null) {
+ Log.e(TAG, "Failed to create encryption keys");
+ return null;
+ }
+ }
+ return sdEncKey;
+ } catch (NoSuchAlgorithmException nsae) {
+ Log.e(TAG, "Failed to create encryption keys with exception: " + nsae);
+ return null;
+ }
+ }
+
private String createSdDir(File tmpPackageFile, String pkgName) {
// Create mount point via MountService
MountService mountService = getMountService();
@@ -7473,8 +7918,6 @@
}
if (DEBUG_SD_INSTALL) Log.i(TAG, "mbLen="+mbLen);
String cachePath = null;
- // Remove any pending destroy messages
- mHandler.removeMessages(DESTROY_SD_CONTAINER, pkgName);
String sdEncKey;
try {
sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString(mSdEncryptKey);
@@ -7535,7 +7978,13 @@
private boolean unMountSdDir(String pkgName) {
// STOPSHIP unmount directory
- return true;
+ try {
+ getMountService().unmountSecureContainer(pkgName);
+ return true;
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to unmount : " + pkgName + " with exception " + e);
+ }
+ return false;
}
private String getSdDir(String pkgName) {
@@ -7560,10 +8009,6 @@
private boolean destroySdDir(String pkgName) {
try {
- if (mHandler.hasMessages(DESTROY_SD_CONTAINER, pkgName)) {
- // Don't have to send message again
- mHandler.removeMessages(DESTROY_SD_CONTAINER, pkgName);
- }
// We need to destroy right away
getMountService().destroySecureContainer(pkgName);
return true;
@@ -7573,7 +8018,7 @@
}
}
- private String[] getSecureContainerList() {
+ static String[] getSecureContainerList() {
try {
return getMountService().getSecureContainerList();
} catch (IllegalStateException e) {
@@ -7582,13 +8027,47 @@
return null;
}
- private void sendDelayedDestroySdDir(String pkgName) {
- if (mHandler.hasMessages(DESTROY_SD_CONTAINER, pkgName)) {
- // Don't have to send message again
- return;
+ static String getTempContainerId() {
+ String prefix = "smdl1tmp";
+ int tmpIdx = 1;
+ String list[] = getSecureContainerList();
+ if (list != null) {
+ int idx = 0;
+ int idList[] = new int[MAX_CONTAINERS];
+ boolean neverFound = true;
+ for (String name : list) {
+ // Ignore null entries
+ if (name == null) {
+ continue;
+ }
+ int sidx = name.indexOf(prefix);
+ if (sidx == -1) {
+ // Not a temp file. just ignore
+ continue;
+ }
+ String subStr = name.substring(sidx + prefix.length());
+ idList[idx] = -1;
+ if (subStr != null) {
+ try {
+ int cid = Integer.parseInt(subStr);
+ idList[idx++] = cid;
+ neverFound = false;
+ } catch (NumberFormatException e) {
+ }
+ }
+ }
+ if (!neverFound) {
+ // Sort idList
+ Arrays.sort(idList);
+ for (int j = 1; j <= idList.length; j++) {
+ if (idList[j-1] != j) {
+ tmpIdx = j;
+ break;
+ }
+ }
+ }
}
- Message msg = mHandler.obtainMessage(DESTROY_SD_CONTAINER, pkgName);
- mHandler.sendMessageDelayed(msg, DESTROY_SD_CONTAINER_DELAY);
+ return prefix + tmpIdx;
}
public void updateExternalMediaStatus(final boolean mediaStatus) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 6b3f433..22447ed 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -249,6 +249,14 @@
}
try {
+ Log.i(TAG, "NetworkManagement Service");
+ ServiceManager.addService(
+ Context.NETWORKMANAGEMENT_SERVICE, new NetworkManagementService(context));
+ } catch (Throwable e) {
+ Log.e(TAG, "Failure starting NetworkManagement Service", e);
+ }
+
+ try {
Log.i(TAG, "Connectivity Service");
connectivity = ConnectivityService.getInstance(context);
ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 44b6624..c2c6e76 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -61,6 +61,7 @@
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
@@ -8845,7 +8846,26 @@
sb.append("Process: system_server\n");
} else {
sb.append("Process: ").append(process.processName).append("\n");
- sb.append("Flags: 0x").append(Integer.toString(process.info.flags, 16)).append("\n");
+ }
+ if (process != null) {
+ int flags = process.info.flags;
+ IPackageManager pm = ActivityThread.getPackageManager();
+ sb.append("Flags: 0x").append(Integer.toString(flags, 16)).append("\n");
+ for (String pkg : process.pkgList) {
+ sb.append("Package: ").append(pkg);
+ try {
+ PackageInfo pi = pm.getPackageInfo(pkg, 0);
+ if (pi != null) {
+ sb.append(" v").append(pi.versionCode);
+ if (pi.versionName != null) {
+ sb.append(" (").append(pi.versionName).append(")");
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error getting package info: " + pkg, e);
+ }
+ sb.append("\n");
+ }
}
if (activity != null) {
sb.append("Activity: ").append(activity.shortComponentName).append("\n");
diff --git a/telephony/java/com/android/internal/telephony/MccTable.java b/telephony/java/com/android/internal/telephony/MccTable.java
index e25eb7b..7f62169 100644
--- a/telephony/java/com/android/internal/telephony/MccTable.java
+++ b/telephony/java/com/android/internal/telephony/MccTable.java
@@ -590,7 +590,7 @@
if (mcc != 0) {
setTimezoneFromMccIfNeeded(phone, mcc);
setLocaleFromMccIfNeeded(phone, mcc);
- setWifiChannelsFromMccIfNeeded(phone, mcc);
+ setWifiChannelsFromMcc(phone, mcc);
}
try {
Configuration config = ActivityManagerNative.getDefault().getConfiguration();
@@ -646,20 +646,14 @@
* @param phone PhoneBase to act on (get context from).
* @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
*/
- private static void setWifiChannelsFromMccIfNeeded(PhoneBase phone, int mcc) {
+ private static void setWifiChannelsFromMcc(PhoneBase phone, int mcc) {
int wifiChannels = MccTable.wifiChannelsForMcc(mcc);
if (wifiChannels != 0) {
Context context = phone.getContext();
- // only set to this default if the user hasn't manually set it
- try {
- Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS);
- } catch (Settings.SettingNotFoundException e) {
- Log.d(LOG_TAG, "WIFI_NUM_ALLOWED_CHANNESL set to " + wifiChannels);
- WifiManager wM = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- // don't persist
- wM.setNumAllowedChannels(wifiChannels, false);
- }
+ Log.d(LOG_TAG, "WIFI_NUM_ALLOWED_CHANNELS set to " + wifiChannels);
+ WifiManager wM = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ //persist
+ wM.setNumAllowedChannels(wifiChannels, true);
}
}
}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/internal/util/HanziToPinyinTest.java b/tests/AndroidTests/src/com/android/unit_tests/internal/util/HanziToPinyinTest.java
new file mode 100644
index 0000000..8e1ff0b
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/internal/util/HanziToPinyinTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 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.unit_tests.internal.util;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import com.android.internal.util.HanziToPinyin;
+import com.android.internal.util.HanziToPinyin.Token;
+
+import junit.framework.TestCase;
+
+public class HanziToPinyinTest extends TestCase {
+ private final static String ONE_HANZI = "\u675C";
+ private final static String TWO_HANZI = "\u675C\u9D51";
+ private final static String ASSIC = "test";
+ private final static String ONE_UNKNOWN = "\uFF71";
+ private final static String MISC = "test\u675C Test with space\uFF71\uFF71\u675C";
+
+ @SmallTest
+ public void testGetToken() throws Exception {
+ ArrayList<Token> tokens = HanziToPinyin.getInstance().get(ONE_HANZI);
+ assertEquals(tokens.size(), 1);
+ assertEquals(tokens.get(0).type, Token.PINYIN);
+ assertTrue(tokens.get(0).target.equalsIgnoreCase("DU"));
+
+ tokens = HanziToPinyin.getInstance().get(TWO_HANZI);
+ assertEquals(tokens.size(), 2);
+ assertEquals(tokens.get(0).type, Token.PINYIN);
+ assertEquals(tokens.get(1).type, Token.PINYIN);
+ assertTrue(tokens.get(0).target.equalsIgnoreCase("DU"));
+ assertTrue(tokens.get(1).target.equalsIgnoreCase("JUAN"));
+
+ tokens = HanziToPinyin.getInstance().get(ASSIC);
+ assertEquals(tokens.size(), 1);
+ assertEquals(tokens.get(0).type, Token.LATIN);
+
+ tokens = HanziToPinyin.getInstance().get(ONE_UNKNOWN);
+ assertEquals(tokens.size(), 1);
+ assertEquals(tokens.get(0).type, Token.UNKNOWN);
+
+ tokens = HanziToPinyin.getInstance().get(MISC);
+ assertEquals(tokens.size(), 7);
+ assertEquals(tokens.get(0).type, Token.LATIN);
+ assertEquals(tokens.get(1).type, Token.PINYIN);
+ assertEquals(tokens.get(2).type, Token.LATIN);
+ assertEquals(tokens.get(3).type, Token.LATIN);
+ assertEquals(tokens.get(4).type, Token.LATIN);
+ assertEquals(tokens.get(5).type, Token.UNKNOWN);
+ assertEquals(tokens.get(6).type, Token.PINYIN);
+ }
+}