Merge "Fix bug 2452941 Add a flag to maintain the intended direct playback of TTS data independently from the associated AudioTrack state. Only start the track when it's been stopped but is supposed to be playing."
diff --git a/Android.mk b/Android.mk
index a48ef45..06db5822 100644
--- a/Android.mk
+++ b/Android.mk
@@ -111,6 +111,7 @@
core/java/android/content/pm/IPackageDeleteObserver.aidl \
core/java/android/content/pm/IPackageInstallObserver.aidl \
core/java/android/content/pm/IPackageManager.aidl \
+ core/java/android/content/pm/IPackageMoveObserver.aidl \
core/java/android/content/pm/IPackageStatsObserver.aidl \
core/java/android/database/IContentObserver.aidl \
core/java/android/hardware/ISensorService.aidl \
@@ -275,7 +276,7 @@
# Search through the base framework dirs for these packages.
# The result will be relative to frameworks/base.
fwbase_dirs_to_document := \
- test-runner \
+ test-runner/src \
$(patsubst $(LOCAL_PATH)/%,%, \
$(wildcard \
$(foreach dir, $(FRAMEWORKS_BASE_JAVA_SRC_DIRS), \
diff --git a/api/current.xml b/api/current.xml
index 444b002..031506b 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -716,6 +716,17 @@
visibility="public"
>
</field>
+<field name="MOVE_PACKAGE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.permission.MOVE_PACKAGE""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="PERSISTENT_ACTIVITY"
type="java.lang.String"
transient="false"
@@ -6774,6 +6785,17 @@
visibility="public"
>
</field>
+<field name="restoreAnyVersion"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843451"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="restoreNeedsApplication"
type="int"
transient="false"
@@ -13833,7 +13855,7 @@
>
<parameter name="parcel" type="android.os.Parcel">
</parameter>
-<parameter name="flags" type="int">
+<parameter name="flagz" type="int">
</parameter>
</method>
<field name="CREATOR"
@@ -14619,7 +14641,7 @@
</parameter>
<parameter name="features" type="java.lang.String[]">
</parameter>
-<parameter name="activityForPrompting" type="android.app.Activity">
+<parameter name="activity" type="android.app.Activity">
</parameter>
<parameter name="addAccountOptions" type="android.os.Bundle">
</parameter>
@@ -26266,6 +26288,83 @@
</parameter>
</method>
</interface>
+<class name="UiModeManager"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="disableCarMode"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getNightMode"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setNightMode"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="mode" type="int">
+</parameter>
+</method>
+<field name="MODE_AUTO"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="MODE_NIGHT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="MODE_NOTNIGHT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
<class name="WallpaperInfo"
extends="java.lang.Object"
abstract="false"
@@ -34538,6 +34637,17 @@
visibility="public"
>
</field>
+<field name="UI_MODE_SERVICE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""uimode""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="VIBRATOR_SERVICE"
type="java.lang.String"
transient="false"
@@ -38899,6 +39009,17 @@
visibility="public"
>
</field>
+<field name="EXTRA_PHYSICAL_DOCK_STATE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.intent.extra.PHYSICAL_DOCK_STATE""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="EXTRA_REMOTE_INTENT_TOKEN"
type="java.lang.String"
transient="false"
@@ -42617,6 +42738,16 @@
visibility="public"
>
</field>
+<field name="backupAgentName"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="className"
type="java.lang.String"
transient="false"
@@ -60401,6 +60532,102 @@
</parameter>
</constructor>
</class>
+<class name="ImageFormat"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="ImageFormat"
+ type="android.graphics.ImageFormat"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="getBitsPerPixel"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="format" type="int">
+</parameter>
+</method>
+<field name="JPEG"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="256"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NV16"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NV21"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="17"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="RGB_565"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="UNKNOWN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="YUY2"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="20"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
<class name="Interpolator"
extends="java.lang.Object"
abstract="false"
@@ -64462,7 +64689,7 @@
value="256"
static="true"
final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</field>
@@ -64616,7 +64843,7 @@
value="17"
static="true"
final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</field>
@@ -64627,7 +64854,7 @@
value="20"
static="true"
final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</field>
@@ -64638,7 +64865,7 @@
value="16"
static="true"
final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</field>
@@ -112967,6 +113194,28 @@
visibility="public"
>
</method>
+<method name="getGlobalClassInitCount"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getGlobalClassInitTime"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="getGlobalExternalAllocCount"
return="int"
abstract="false"
@@ -113213,6 +113462,28 @@
visibility="public"
>
</method>
+<method name="resetGlobalClassInitCount"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="resetGlobalClassInitTime"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="resetGlobalExternalAllocCount"
return="void"
abstract="false"
@@ -119450,6 +119721,17 @@
visibility="public"
>
</field>
+<field name="OperationFailedStorageBusy"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-7"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="OperationFailedStorageMounted"
type="int"
transient="false"
@@ -130604,6 +130886,17 @@
visibility="public"
>
</field>
+<field name="INTENT_ACTION_MUSIC_PLAYER"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.intent.action.MUSIC_PLAYER""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="INTENT_ACTION_STILL_IMAGE_CAMERA"
type="java.lang.String"
transient="false"
@@ -133227,6 +133520,17 @@
visibility="public"
>
</field>
+<field name="ACTION_ADD_ACCOUNT"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.settings.ADD_ACCOUNT_SETTINGS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="ACTION_AIRPLANE_MODE_SETTINGS"
type="java.lang.String"
transient="false"
@@ -133535,6 +133839,17 @@
visibility="public"
>
</field>
+<field name="EXTRA_AUTHORITIES"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""authorities""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
</class>
<class name="Settings.NameValueTable"
extends="java.lang.Object"
@@ -136998,6 +137313,21 @@
<parameter name="context" type="android.content.Context">
</parameter>
</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="serviceComponent" type="android.content.ComponentName">
+</parameter>
+</method>
<method name="destroy"
return="void"
abstract="false"
@@ -137396,6 +137726,30 @@
deprecated="not deprecated"
visibility="public"
>
+<method name="getVoiceDetailsIntent"
+ return="android.content.Intent"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</method>
+<field name="ACTION_GET_LANGUAGE_DETAILS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.speech.action.GET_LANGUAGE_DETAILS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="ACTION_RECOGNIZE_SPEECH"
type="java.lang.String"
transient="false"
@@ -137418,6 +137772,17 @@
visibility="public"
>
</field>
+<field name="DETAILS_META_DATA"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.speech.DETAILS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="EXTRA_LANGUAGE"
type="java.lang.String"
transient="false"
@@ -137440,6 +137805,17 @@
visibility="public"
>
</field>
+<field name="EXTRA_LANGUAGE_PREFERENCE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.speech.extra.LANGUAGE_PREFERENCE""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="EXTRA_MAX_RESULTS"
type="java.lang.String"
transient="false"
@@ -137539,6 +137915,17 @@
visibility="public"
>
</field>
+<field name="EXTRA_SUPPORTED_LANGUAGES"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.speech.extra.SUPPORTED_LANGUAGES""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="LANGUAGE_MODEL_FREE_FORM"
type="java.lang.String"
transient="false"
@@ -172890,6 +173277,19 @@
visibility="public"
>
</method>
+<method name="dispatchConfigurationChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="newConfig" type="android.content.res.Configuration">
+</parameter>
+</method>
<method name="dispatchDisplayHint"
return="void"
abstract="false"
@@ -174581,6 +174981,19 @@
visibility="public"
>
</method>
+<method name="onConfigurationChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="newConfig" type="android.content.res.Configuration">
+</parameter>
+</method>
<method name="onCreateContextMenu"
return="void"
abstract="false"
@@ -190965,6 +191378,17 @@
visibility="public"
>
</method>
+<method name="getBlockNetworkLoads"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="getBuiltInZoomControls"
return="boolean"
abstract="false"
@@ -191393,6 +191817,19 @@
<parameter name="flag" type="boolean">
</parameter>
</method>
+<method name="setBlockNetworkLoads"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="flag" type="boolean">
+</parameter>
+</method>
<method name="setBuiltInZoomControls"
return="void"
abstract="false"
@@ -211379,6 +211816,17 @@
<parameter name="measureSpec" type="int">
</parameter>
</method>
+<method name="resume"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="seekTo"
return="void"
abstract="false"
@@ -211492,6 +211940,17 @@
visibility="public"
>
</method>
+<method name="suspend"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
</class>
<class name="ViewAnimator"
extends="android.widget.FrameLayout"
@@ -215443,7 +215902,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -215526,7 +215985,7 @@
value=""/sdcard/dmtrace.trace""
static="true"
final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</field>
@@ -215563,6 +216022,28 @@
visibility="public"
>
</field>
+<field name="KIND_GLOBAL_CLASS_INIT_COUNT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="32"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KIND_GLOBAL_CLASS_INIT_TIME"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="64"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="KIND_GLOBAL_EXT_ALLOCATED_BYTES"
type="int"
transient="false"
@@ -215662,6 +216143,28 @@
visibility="public"
>
</field>
+<field name="KIND_THREAD_CLASS_INIT_COUNT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2097152"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KIND_THREAD_CLASS_INIT_TIME"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4194304"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="KIND_THREAD_EXT_ALLOCATED_BYTES"
type="int"
transient="false"
diff --git a/camera/libcameraservice/FakeCamera.cpp b/camera/libcameraservice/FakeCamera.cpp
index 3daf47d..6749899 100644
--- a/camera/libcameraservice/FakeCamera.cpp
+++ b/camera/libcameraservice/FakeCamera.cpp
@@ -234,7 +234,7 @@
uint8_t y0, y1, u, v;
pixels = inputRGB[i];
- temp = (ALPHA*(pixels & 0x001F) + BETA*(pixels>>11) );
+ temp = (BETA*(pixels & 0x001F) + ALPHA*(pixels>>11) );
y0 = y_tab[(temp>>SHIFT1) + ((pixels>>3) & 0x00FC)];
G_ds += (pixels>>1) & 0x03E0;
@@ -242,7 +242,7 @@
R_ds += (pixels>>6) & 0x03E0;
pixels = inputRGB[i+1];
- temp = (ALPHA*(pixels & 0x001F) + BETA*(pixels>>11) );
+ temp = (BETA*(pixels & 0x001F) + ALPHA*(pixels>>11) );
y1 = y_tab[(temp>>SHIFT1) + ((pixels>>3) & 0x00FC)];
G_ds += (pixels>>1) & 0x03E0;
@@ -255,8 +255,8 @@
tmp = R_ds - B_ds;
- u = cb_tab[(((R_ds-G_ds)<<SHIFT2) + DELTA*tmp)>>(SHIFT2+2)];
- v = cr_tab[(((B_ds-G_ds)<<SHIFT2) - GAMMA*tmp)>>(SHIFT2+2)];
+ u = cb_tab[(((B_ds-G_ds)<<SHIFT2) - GAMMA*tmp)>>(SHIFT2+2)];
+ v = cr_tab[(((R_ds-G_ds)<<SHIFT2) + DELTA*tmp)>>(SHIFT2+2)];
tempY[0] = y0;
tempY[1] = y1;
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index d640de1..b6c9de4 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -88,6 +88,8 @@
if (op.equals("start")) {
runStart();
+ } else if (op.equals("startservice")) {
+ runStartService();
} else if (op.equals("instrument")) {
runInstrument();
} else if (op.equals("broadcast")) {
@@ -183,6 +185,15 @@
return intent;
}
+ private void runStartService() throws Exception {
+ Intent intent = makeIntent();
+ System.out.println("Starting service: " + intent);
+ ComponentName cn = mAm.startService(null, intent, intent.getType());
+ if (cn == null) {
+ System.err.println("Error: Not found; no service started.");
+ }
+ }
+
private void runStart() throws Exception {
Intent intent = makeIntent();
System.out.println("Starting: " + intent);
@@ -496,6 +507,8 @@
" start an Activity: am start [-D] <INTENT>\n" +
" -D: enable debugging\n" +
"\n" +
+ " start a Service: am startservice <INTENT>\n" +
+ "\n" +
" send a broadcast Intent: am broadcast <INTENT>\n" +
"\n" +
" start an Instrumentation: am instrument [flags] <COMPONENT>\n" +
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index 78f90a1..adec5a4 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -96,6 +96,7 @@
run_command("NETWORK INTERFACES", 10, "netcfg", NULL);
dump_file("NETWORK ROUTES", "/proc/net/route");
+ dump_file("ARP CACHE", "/proc/net/arp");
#ifdef FWDUMP_bcm4329
run_command("DUMP WIFI FIRMWARE LOG", 60,
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index 92ae310..66f3676 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -68,7 +68,7 @@
/* other handy constants */
#define PROTECTED_DIR_PREFIX "/data/app-private/"
-#define SDCARD_DIR_PREFIX "/asec/"
+#define SDCARD_DIR_PREFIX "/mnt/asec/"
#define DALVIK_CACHE_PREFIX "/data/dalvik-cache/"
#define DALVIK_CACHE_POSTFIX "/classes.dex"
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index ff16c6e..fc5707d 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -600,6 +600,7 @@
} else if (opt.equals("-t")) {
installFlags |= PackageManager.INSTALL_ALLOW_TEST;
} else if (opt.equals("-s")) {
+ // Override if -s option is specified.
installFlags |= PackageManager.INSTALL_EXTERNAL;
} else {
System.err.println("Error: Unknown option: " + opt);
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk
index 68d8bb0..52f767e 100644
--- a/cmds/stagefright/Android.mk
+++ b/cmds/stagefright/Android.mk
@@ -17,6 +17,8 @@
LOCAL_CFLAGS += -Wno-multichar
+LOCAL_MODULE_TAGS := debug
+
LOCAL_MODULE:= stagefright
include $(BUILD_EXECUTABLE)
@@ -39,6 +41,8 @@
LOCAL_CFLAGS += -Wno-multichar
+LOCAL_MODULE_TAGS := debug
+
LOCAL_MODULE:= record
include $(BUILD_EXECUTABLE)
@@ -61,6 +65,8 @@
LOCAL_CFLAGS += -Wno-multichar
+LOCAL_MODULE_TAGS := debug
+
LOCAL_MODULE:= audioloop
include $(BUILD_EXECUTABLE)
diff --git a/common/java/com/android/common/OperationScheduler.java b/common/java/com/android/common/OperationScheduler.java
index c7b12d3..0c7ca83 100644
--- a/common/java/com/android/common/OperationScheduler.java
+++ b/common/java/com/android/common/OperationScheduler.java
@@ -158,12 +158,35 @@
time = Math.max(time, moratoriumTimeMillis);
}
time = Math.max(time, lastSuccessTimeMillis + options.minTriggerMillis);
- time = Math.max(time, lastErrorTimeMillis + options.backoffFixedMillis +
- options.backoffIncrementalMillis * errorCount);
+ if (errorCount > 0) {
+ time = Math.max(time, lastErrorTimeMillis + options.backoffFixedMillis +
+ options.backoffIncrementalMillis * errorCount);
+ }
return time;
}
/**
+ * Return the last time the operation completed. Does not modify any state.
+ *
+ * @return the wall clock time when {@link #onSuccess()} was last called.
+ */
+ public long getLastSuccessTimeMillis() {
+ return mStorage.getLong(PREFIX + "lastSuccessTimeMillis", 0);
+ }
+
+ /**
+ * Return the last time the operation was attempted. Does not modify any state.
+ *
+ * @return the wall clock time when {@link #onSuccess()} or {@link
+ * #onTransientError()} was last called.
+ */
+ public long getLastAttemptTimeMillis() {
+ return Math.max(
+ mStorage.getLong(PREFIX + "lastSuccessTimeMillis", 0),
+ mStorage.getLong(PREFIX + "lastErrorTimeMillis", 0));
+ }
+
+ /**
* Fetch a {@link SharedPreferences} property, but force it to be before
* a certain time, updating the value if necessary. This is to recover
* gracefully from clock rollbacks which could otherwise strand our timers.
@@ -273,9 +296,7 @@
* where there is reason to hope things might start working better.
*/
public void resetTransientError() {
- mStorage.edit()
- .remove(PREFIX + "lastErrorTimeMillis")
- .remove(PREFIX + "errorCount").commit();
+ mStorage.edit().remove(PREFIX + "errorCount").commit();
}
/**
diff --git a/common/tests/src/com/android/common/OperationSchedulerTest.java b/common/tests/src/com/android/common/OperationSchedulerTest.java
index 28178b5..f728eea 100644
--- a/common/tests/src/com/android/common/OperationSchedulerTest.java
+++ b/common/tests/src/com/android/common/OperationSchedulerTest.java
@@ -28,6 +28,8 @@
OperationScheduler scheduler = new OperationScheduler(storage);
OperationScheduler.Options options = new OperationScheduler.Options();
assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options));
+ assertEquals(0, scheduler.getLastSuccessTimeMillis());
+ assertEquals(0, scheduler.getLastAttemptTimeMillis());
long beforeTrigger = System.currentTimeMillis();
scheduler.setTriggerTimeMillis(beforeTrigger + 1000000);
@@ -49,6 +51,9 @@
long beforeError = System.currentTimeMillis();
scheduler.onTransientError();
long afterError = System.currentTimeMillis();
+ assertEquals(0, scheduler.getLastSuccessTimeMillis());
+ assertTrue(beforeError <= scheduler.getLastAttemptTimeMillis());
+ assertTrue(afterError >= scheduler.getLastAttemptTimeMillis());
assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options));
options.backoffFixedMillis = 1000000;
options.backoffIncrementalMillis = 500000;
@@ -59,9 +64,18 @@
beforeError = System.currentTimeMillis();
scheduler.onTransientError();
afterError = System.currentTimeMillis();
+ assertTrue(beforeError <= scheduler.getLastAttemptTimeMillis());
+ assertTrue(afterError >= scheduler.getLastAttemptTimeMillis());
assertTrue(beforeError + 2000000 <= scheduler.getNextTimeMillis(options));
assertTrue(afterError + 2000000 >= scheduler.getNextTimeMillis(options));
+ // Reset transient error: no backoff interval
+ scheduler.resetTransientError();
+ assertEquals(0, scheduler.getLastSuccessTimeMillis());
+ assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options));
+ assertTrue(beforeError <= scheduler.getLastAttemptTimeMillis());
+ assertTrue(afterError >= scheduler.getLastAttemptTimeMillis());
+
// Permanent error holds true even if transient errors are reset
// However, we remember that the transient error was reset...
scheduler.onPermanentError();
@@ -75,6 +89,10 @@
long beforeSuccess = System.currentTimeMillis();
scheduler.onSuccess();
long afterSuccess = System.currentTimeMillis();
+ assertTrue(beforeSuccess <= scheduler.getLastAttemptTimeMillis());
+ assertTrue(afterSuccess >= scheduler.getLastAttemptTimeMillis());
+ assertTrue(beforeSuccess <= scheduler.getLastSuccessTimeMillis());
+ assertTrue(afterSuccess >= scheduler.getLastSuccessTimeMillis());
assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options));
// The moratorium is not reset by success!
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 4761f98..bf9e07d 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -66,10 +66,10 @@
* The event types an {@link AccessibilityService} is interested in.
*
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_LONG_CLICKED
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED
- * @see android.view.accessibility.AccessibilityEvent#TYPE_ACTIVITY_STARTED
* @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
* @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED
*/
@@ -115,7 +115,7 @@
return 0;
}
- public void writeToParcel(Parcel parcel, int flags) {
+ public void writeToParcel(Parcel parcel, int flagz) {
parcel.writeInt(eventTypes);
parcel.writeStringArray(packageNames);
parcel.writeInt(feedbackType);
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index be2bdbe..8bc7428 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -296,8 +296,7 @@
* <ul>
* <li> {@link AccountManager#KEY_INTENT}, or
* <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of
- * the account that was added, plus {@link AccountManager#KEY_AUTHTOKEN} if an authTokenType
- * was supplied, or
+ * the account that was added, or
* <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
* indicate an error
* </ul>
@@ -368,8 +367,7 @@
* <ul>
* <li> {@link AccountManager#KEY_INTENT}, or
* <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of
- * the account that was added, plus {@link AccountManager#KEY_AUTHTOKEN} if an authTokenType
- * was supplied, or
+ * the account that was added, or
* <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
* indicate an error
* </ul>
@@ -378,7 +376,7 @@
*/
public abstract Bundle updateCredentials(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle options) throws NetworkErrorException;
-
+
/**
* Checks if the account supports all the specified authenticator specific features.
* @param response to send the result back to the AccountManager, will never be null
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index be15ac9..e2263fc 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -43,34 +43,92 @@
import com.google.android.collect.Maps;
/**
- * A class that helps with interactions with the AccountManager Service. It provides
- * methods to allow for account, password, and authtoken management for all accounts on the
- * device. One accesses the {@link AccountManager} by calling:
- * <pre>
- * AccountManager accountManager = AccountManager.get(context);
- * </pre>
+ * This class provides access to a centralized registry of the user's
+ * online accounts. With this service, users only need to enter their
+ * credentials (username and password) once for any account, granting
+ * applications access to online resources with "one-click" approval.
*
- * <p>
- * The AccountManager Service provides storage for the accounts known to the system,
- * provides methods to manage them, and allows the registration of authenticators to
- * which operations such as addAccount and getAuthToken are delegated.
- * <p>
- * Many of the calls take an {@link AccountManagerCallback} and {@link Handler} as parameters.
- * These calls return immediately but run asynchronously. If a callback is provided then
- * {@link AccountManagerCallback#run} will be invoked wen the request completes, successfully
- * or not. An {@link AccountManagerFuture} is returned by these requests and also passed into the
- * callback. The result if retrieved by calling {@link AccountManagerFuture#getResult()} which
- * either returns the result or throws an exception as appropriate.
- * <p>
- * The asynchronous request can be made blocking by not providing a callback and instead
- * calling {@link AccountManagerFuture#getResult()} on the future that is returned. This will
- * cause the running thread to block until the result is returned. Keep in mind that one
- * should not block the main thread in this way. Instead one should either use a callback,
- * thus making the call asynchronous, or make the blocking call on a separate thread.
- * <p>
- * If one wants to ensure that the callback is invoked from a specific handler then they should
- * pass the handler to the request. This makes it easier to ensure thread-safety by running
- * all of one's logic from a single handler.
+ * <p>Different online services have different ways of handling accounts and
+ * authentication, so the account manager uses pluggable <em>authenticator</em>
+ * modules for different <em>account types</em>. The authenticators (which
+ * may be written by third parties) handle the actual details of validating
+ * account credentials and storing account information. For example, Google,
+ * Facebook, and Microsoft Exchange each have their own authenticator.
+ *
+ * <p>Many servers support some notion of an <em>authentication token</em>,
+ * which can be used to authenticate a request to the server without sending
+ * the user's actual password. (Auth tokens are normally created with a
+ * separate request which does include the user's credentials.) AccountManager
+ * can generate these auth tokens for applications, so the application doesn't
+ * need to handle passwords directly. Auth tokens are normally reusable, and
+ * cached by AccountManager, but must be refreshed periodically. It's the
+ * responsibility of applications to <em>invalidate</em> auth tokens when they
+ * stop working so the AccountManager knows it needs to regenerate them.
+ *
+ * <p>Applications accessing a server normally go through these steps:
+ *
+ * <ul>
+ * <li>Get an instance of AccountManager using {@link #get(Context)}.
+ *
+ * <li>List the available accounts using {@link #getAccountsByType} or
+ * {@link #getAccountsByTypeAndFeatures}. Normally applications will only
+ * be interested in accounts with one particular <em>type</em>, which
+ * identifies the authenticator. Account <em>features</em> are used to
+ * identify particular account subtypes and capabilities. Both the account
+ * type and features are authenticator-specific strings, and must be known by
+ * the application in coordination with its preferred authenticators.
+ *
+ * <li>Select one or more of the available accounts, possibly by asking the
+ * user for their preference. If no suitable accounts are available,
+ * {@link #addAccount} may be called to prompt the user to create an
+ * account of the appropriate type.
+ *
+ * <li>Request an auth token for the selected account(s) using one of the
+ * {@link #getAuthToken} methods or related helpers. Refer to the description
+ * of each method for exact usage and error handling details.
+ *
+ * <li>Make the request using the auth token. The form of the auth token,
+ * the format of the request, and the protocol used are all specific to the
+ * service you are accessing. The application makes the request itself, using
+ * whatever network and protocol libraries are useful.
+ *
+ * <li><b>Important:</b> If the request fails with an authentication error,
+ * it could be that a cached auth token is stale and no longer honored by
+ * the server. The application must call {@link #invalidateAuthToken} to remove
+ * the token from the cache, otherwise requests will continue failing! After
+ * invalidating the auth token, immediately go back to the "Request an auth
+ * token" step above. If the process fails the second time, then it can be
+ * treated as a "genuine" authentication failure and the user notified or other
+ * appropriate actions taken.
+ * </ul>
+ *
+ * <p>Some AccountManager methods may require interaction with the user to
+ * prompt for credentials, present options, or ask the user to add an account.
+ * The caller may choose whether to allow AccountManager to directly launch the
+ * necessary user interface and wait for the user, or to return an Intent which
+ * the caller may use to launch the interface, or (in some cases) to install a
+ * notification which the user can select at any time to launch the interface.
+ * To have AccountManager launch the interface directly, the caller must supply
+ * the current foreground {@link Activity} context.
+ *
+ * <p>Many AccountManager methods take {@link AccountManagerCallback} and
+ * {@link Handler} as parameters. These methods return immediately but
+ * run asynchronously. If a callback is provided then
+ * {@link AccountManagerCallback#run} will be invoked on the Handler's
+ * thread when the request completes, successfully or not.
+ * An {@link AccountManagerFuture} is returned by these requests and also
+ * supplied to the callback (if any). The result is retrieved by calling
+ * {@link AccountManagerFuture#getResult()} which waits for the operation
+ * to complete (if necessary) and either returns the result or throws an
+ * exception if an error occurred during the operation.
+ * To make the request synchronously, call
+ * {@link AccountManagerFuture#getResult()} immediately on receiving the
+ * future from the method. No callback need be supplied.
+ *
+ * <p>Requests which may block, including
+ * {@link AccountManagerFuture#getResult()}, must never be called on
+ * the application's main event thread. These operations throw
+ * {@link IllegalStateException} if they are used on the main thread.
*/
public class AccountManager {
private static final String TAG = "AccountManager";
@@ -83,34 +141,65 @@
public static final int ERROR_CODE_BAD_ARGUMENTS = 7;
public static final int ERROR_CODE_BAD_REQUEST = 8;
- public static final String KEY_ACCOUNTS = "accounts";
- public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
- public static final String KEY_USERDATA = "userdata";
- public static final String KEY_AUTHTOKEN = "authtoken";
- public static final String KEY_PASSWORD = "password";
+ /**
+ * The Bundle key used for the {@link String} account name in results
+ * from methods which return information about a particular account.
+ */
public static final String KEY_ACCOUNT_NAME = "authAccount";
+
+ /**
+ * The Bundle key used for the {@link String} account type in results
+ * from methods which return information about a particular account.
+ */
public static final String KEY_ACCOUNT_TYPE = "accountType";
- public static final String KEY_ERROR_CODE = "errorCode";
- public static final String KEY_ERROR_MESSAGE = "errorMessage";
+
+ /**
+ * The Bundle key used for the auth token value in results
+ * from {@link #getAuthToken} and friends.
+ */
+ public static final String KEY_AUTHTOKEN = "authtoken";
+
+ /**
+ * The Bundle key used for an {@link Intent} in results from methods that
+ * may require the caller to interact with the user. The Intent can
+ * be used to start the corresponding user interface activity.
+ */
public static final String KEY_INTENT = "intent";
- public static final String KEY_BOOLEAN_RESULT = "booleanResult";
+
+ /**
+ * The Bundle key used to supply the password directly in options to
+ * {@link #confirmCredentials}, rather than prompting the user with
+ * the standard password prompt.
+ */
+ public static final String KEY_PASSWORD = "password";
+
+ public static final String KEY_ACCOUNTS = "accounts";
public static final String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
public static final String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
+ public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
public static final String KEY_AUTH_FAILED_MESSAGE = "authFailedMessage";
public static final String KEY_AUTH_TOKEN_LABEL = "authTokenLabelKey";
+ public static final String KEY_BOOLEAN_RESULT = "booleanResult";
+ public static final String KEY_ERROR_CODE = "errorCode";
+ public static final String KEY_ERROR_MESSAGE = "errorMessage";
+ public static final String KEY_USERDATA = "userdata";
+
public static final String ACTION_AUTHENTICATOR_INTENT =
"android.accounts.AccountAuthenticator";
public static final String AUTHENTICATOR_META_DATA_NAME =
- "android.accounts.AccountAuthenticator";
+ "android.accounts.AccountAuthenticator";
public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
private final Context mContext;
private final IAccountManager mService;
private final Handler mMainHandler;
+
/**
* Action sent as a broadcast Intent by the AccountsService
- * when accounts are added to and/or removed from the device's
- * database.
+ * when accounts are added, accounts are removed, or an
+ * account's credentials (saved password, etc) are changed.
+ *
+ * @see #addOnAccountsUpdatedListener
*/
public static final String LOGIN_ACCOUNTS_CHANGED_ACTION =
"android.accounts.LOGIN_ACCOUNTS_CHANGED";
@@ -134,24 +223,36 @@
}
/**
- * Retrieve an AccountManager instance that is associated with the context that is passed in.
- * Certain calls such as {@link #addOnAccountsUpdatedListener} use this context internally,
- * so the caller must take care to use a {@link Context} whose lifetime is associated with
- * the listener registration.
+ * Gets an AccountManager instance associated with a Context.
+ * The {@link Context} will be used as long as the AccountManager is
+ * active, so make sure to use a {@link Context} whose lifetime is
+ * commensurate with any listeners registered to
+ * {@link #addOnAccountsUpdatedListener} or similar methods.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
* @param context The {@link Context} to use when necessary
- * @return an {@link AccountManager} instance that is associated with context
+ * @return An {@link AccountManager} instance
*/
public static AccountManager get(Context context) {
return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
}
/**
- * Get the password that is associated with the account. Returns null if the account does
- * not exist.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
+ * Gets the saved password associated with the account.
+ * This is intended for authenticators and related code; applications
+ * should get an auth token instead.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and to have the same UID as the account's authenticator.
+ *
+ * @param account The account to query for a password
+ * @return The account's password, null if none or if the account doesn't exist
*/
public String getPassword(final Account account) {
try {
@@ -163,12 +264,19 @@
}
/**
- * Get the user data named by "key" that is associated with the account.
- * Returns null if the account does not exist or if it does not have a value for key.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
+ * Gets the user data named by "key" associated with the account.
+ * This is intended for authenticators and related code to store
+ * arbitrary metadata along with accounts. The meaning of the keys
+ * and values is up to the authenticator for the account.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and to have the same UID as the account's authenticator.
+ *
+ * @param account The account to query for user data
+ * @return The user data, null if the account or key doesn't exist
*/
public String getUserData(final Account account, final String key) {
try {
@@ -180,12 +288,15 @@
}
/**
- * Query the AccountManager Service for an array that contains a
- * {@link AuthenticatorDescription} for each registered authenticator.
- * @return an array that contains all the authenticators known to the AccountManager service.
- * This array will be empty if there are no authenticators and will never return null.
- * <p>
- * No permission is required to make this call.
+ * Lists the currently registered authenticators.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
+ * @return An array of {@link AuthenticatorDescription} for every
+ * authenticator known to the AccountManager service. Empty (never
+ * null) if no authenticators are known.
*/
public AuthenticatorDescription[] getAuthenticatorTypes() {
try {
@@ -197,11 +308,16 @@
}
/**
- * Query the AccountManager Service for all accounts.
- * @return an array that contains all the accounts known to the AccountManager service.
- * This array will be empty if there are no accounts and will never return null.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}
+ * Lists all accounts of any type registered on the device.
+ * Equivalent to getAccountsByType(null).
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#GET_ACCOUNTS}.
+ *
+ * @return An array of {@link Account}, one for each account. Empty
+ * (never null) if no accounts have been added.
*/
public Account[] getAccounts() {
try {
@@ -213,13 +329,20 @@
}
/**
- * Query the AccountManager for the set of accounts that have a given type. If null
- * is passed as the type than all accounts are returned.
- * @param type the account type by which to filter, or null to get all accounts
- * @return an array that contains the accounts that match the specified type. This array
- * will be empty if no accounts match. It will never return null.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}
+ * Lists all accounts of a particular type. The account type is a
+ * string token corresponding to the authenticator and useful domain
+ * of the account. For example, there are types corresponding to Google
+ * and Facebook. The exact string token to use will be published somewhere
+ * associated with the authenticator in question.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#GET_ACCOUNTS}.
+ *
+ * @param type The type of accounts to return, null to retrieve all accounts
+ * @return An array of {@link Account}, one per matching account. Empty
+ * (never null) if no accounts of the specified type have been added.
*/
public Account[] getAccountsByType(String type) {
try {
@@ -231,29 +354,27 @@
}
/**
- * Tests that the given account has the specified features. If this account does not exist
- * then this call returns false.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}.
+ * Finds out whether a particular account has all the specified features.
+ * Account features are authenticator-specific string tokens identifying
+ * boolean account properties. For example, features are used to tell
+ * whether Google accounts have a particular service (such as Google
+ * Calendar or Google Talk) enabled. The feature names and their meanings
+ * are published somewhere associated with the authenticator in question.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#GET_ACCOUNTS}.
*
* @param account The {@link Account} to test
- * @param features the features for which to test
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Boolean} that is true if the account exists and has the
- * specified features.
+ * @param features An array of the account features to check
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean,
+ * true if the account exists and has all of the specified features.
*/
public AccountManagerFuture<Boolean> hasFeatures(final Account account,
final String[] features,
@@ -272,401 +393,32 @@
}
/**
- * Add an account to the AccountManager's set of known accounts.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
- * @param account The account to add
- * @param password The password to associate with the account. May be null.
- * @param userdata A bundle of key/value pairs to set as the account's userdata. May be null.
- * @return true if the account was sucessfully added, false otherwise, for example,
- * if the account already exists or if the account is null
- */
- public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
- try {
- return mService.addAccount(account, password, userdata);
- } catch (RemoteException e) {
- // won't ever happen
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Removes the given account. If this account does not exist then this call has no effect.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ * Lists all accounts of a type which have certain features. The account
+ * type identifies the authenticator (see {@link #getAccountsByType}).
+ * Account features are authenticator-specific string tokens identifying
+ * boolean account properties (see {@link #hasFeatures}).
*
- * @param account The {@link Account} to remove
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Boolean} that is true if the account is successfully removed
- * or false if the authenticator refuses to remove the account.
- */
- public AccountManagerFuture<Boolean> removeAccount(final Account account,
- AccountManagerCallback<Boolean> callback, Handler handler) {
- return new Future2Task<Boolean>(handler, callback) {
- public void doWork() throws RemoteException {
- mService.removeAccount(mResponse, account);
- }
- public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
- if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
- throw new AuthenticatorException("no result in response");
- }
- return bundle.getBoolean(KEY_BOOLEAN_RESULT);
- }
- }.start();
- }
-
- /**
- * Removes the given authtoken. If this authtoken does not exist for the given account type
- * then this call has no effect.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
- * @param accountType the account type of the authtoken to invalidate
- * @param authToken the authtoken to invalidate
- */
- public void invalidateAuthToken(final String accountType, final String authToken) {
- try {
- mService.invalidateAuthToken(accountType, authToken);
- } catch (RemoteException e) {
- // won't ever happen
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Gets the authtoken named by "authTokenType" for the specified account if it is cached
- * by the AccountManager. If no authtoken is cached then null is returned rather than
- * asking the authenticaticor to generate one. If the account or the
- * authtoken do not exist then null is returned.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
- * @param account the account whose authtoken is to be retrieved, must not be null
- * @param authTokenType the type of authtoken to retrieve
- * @return an authtoken for the given account and authTokenType, if one is cached by the
- * AccountManager, null otherwise.
- */
- public String peekAuthToken(final Account account, final String authTokenType) {
- if (account == null) {
- Log.e(TAG, "peekAuthToken: the account must not be null");
- return null;
- }
- if (authTokenType == null) {
- return null;
- }
- try {
- return mService.peekAuthToken(account, authTokenType);
- } catch (RemoteException e) {
- // won't ever happen
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Sets the password for the account. The password may be null. If the account does not exist
- * then this call has no affect.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
- * @param account the account whose password is to be set. Must not be null.
- * @param password the password to set for the account. May be null.
- */
- public void setPassword(final Account account, final String password) {
- if (account == null) {
- Log.e(TAG, "the account must not be null");
- return;
- }
- try {
- mService.setPassword(account, password);
- } catch (RemoteException e) {
- // won't ever happen
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Sets the password for account to null. If the account does not exist then this call
- * has no effect.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
- * @param account the account whose password is to be cleared. Must not be null.
- */
- public void clearPassword(final Account account) {
- if (account == null) {
- Log.e(TAG, "the account must not be null");
- return;
- }
- try {
- mService.clearPassword(account);
- } catch (RemoteException e) {
- // won't ever happen
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Sets account's userdata named "key" to the specified value. If the account does not
- * exist then this call has no effect.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
- * @param account the account whose userdata is to be set. Must not be null.
- * @param key the key of the userdata to set. Must not be null.
- * @param value the value to set. May be null.
- */
- public void setUserData(final Account account, final String key, final String value) {
- if (account == null) {
- Log.e(TAG, "the account must not be null");
- return;
- }
- if (key == null) {
- Log.e(TAG, "the key must not be null");
- return;
- }
- try {
- mService.setUserData(account, key, value);
- } catch (RemoteException e) {
- // won't ever happen
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Sets the authtoken named by "authTokenType" to the value specified by authToken.
- * If the account does not exist then this call has no effect.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
- * @param account the account whose authtoken is to be set. Must not be null.
- * @param authTokenType the type of the authtoken to set. Must not be null.
- * @param authToken the authToken to set. May be null.
- */
- public void setAuthToken(Account account, final String authTokenType, final String authToken) {
- try {
- mService.setAuthToken(account, authTokenType, authToken);
- } catch (RemoteException e) {
- // won't ever happen
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Convenience method that makes a blocking call to
- * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}
- * then extracts and returns the value of {@link #KEY_AUTHTOKEN} from its result.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
- * @param account the account whose authtoken is to be retrieved, must not be null
- * @param authTokenType the type of authtoken to retrieve
- * @param notifyAuthFailure if true, cause the AccountManager to put up a "sign-on" notification
- * for the account if no authtoken is cached by the AccountManager and the the authenticator
- * does not have valid credentials to get an authtoken.
- * @return an authtoken for the given account and authTokenType, if one is cached by the
- * AccountManager, null otherwise.
- * @throws AuthenticatorException if the authenticator is not present, unreachable or returns
- * an invalid response.
- * @throws OperationCanceledException if the request is canceled for any reason
- * @throws java.io.IOException if the authenticator experiences an IOException while attempting
- * to communicate with its backend server.
- */
- public String blockingGetAuthToken(Account account, String authTokenType,
- boolean notifyAuthFailure)
- throws OperationCanceledException, IOException, AuthenticatorException {
- Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
- null /* handler */).getResult();
- return bundle.getString(KEY_AUTHTOKEN);
- }
-
- /**
- * Request that an authtoken of the specified type be returned for an account.
- * If the Account Manager has a cached authtoken of the requested type then it will
- * service the request itself. Otherwise it will pass the request on to the authenticator.
- * The authenticator can try to service this request with information it already has stored
- * in the AccountManager but may need to launch an activity to prompt the
- * user to enter credentials. If it is able to retrieve the authtoken it will be returned
- * in the result.
- * <p>
- * If the authenticator needs to prompt the user for credentials it will return an intent to
- * the activity that will do the prompting. If an activity is supplied then that activity
- * will be used to launch the intent and the result will come from it. Otherwise a result will
- * be returned that contains the intent.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
+ * <p>Unlike {@link #getAccountsByType}, this method calls the authenticator,
+ * which may contact the server or do other work to check account features,
+ * so the method returns an {@link AccountManagerFuture}.
*
- * @param account The account whose credentials are to be updated.
- * @param authTokenType the auth token to retrieve as part of updating the credentials.
- * May be null.
- * @param options authenticator specific options for the request
- * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
- * the intent will be started with this activity. If activity is null then the result will
- * be returned as-is.
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains:
- * <ul>
- * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN}
- * </ul>
- * If the user presses "back" then the request will be canceled.
- */
- public AccountManagerFuture<Bundle> getAuthToken(
- final Account account, final String authTokenType, final Bundle options,
- final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
- if (activity == null) throw new IllegalArgumentException("activity is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
- return new AmsTask(activity, handler, callback) {
- public void doWork() throws RemoteException {
- mService.getAuthToken(mResponse, account, authTokenType,
- false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
- options);
- }
- }.start();
- }
-
- /**
- * Request that an authtoken of the specified type be returned for an account.
- * If the Account Manager has a cached authtoken of the requested type then it will
- * service the request itself. Otherwise it will pass the request on to the authenticator.
- * The authenticator can try to service this request with information it already has stored
- * in the AccountManager but may need to launch an activity to prompt the
- * user to enter credentials. If it is able to retrieve the authtoken it will be returned
- * in the result.
- * <p>
- * If the authenticator needs to prompt the user for credentials, rather than returning the
- * authtoken it will instead return an intent for
- * an activity that will do the prompting. If an intent is returned and notifyAuthFailure
- * is true then a notification will be created that launches this intent. This intent can be
- * invoked by the caller directly to start the activity that prompts the user for the
- * updated credentials. Otherwise this activity will not be run until the user activates
- * the notification.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
*
- * @param account The account whose credentials are to be updated.
- * @param authTokenType the auth token to retrieve as part of updating the credentials.
- * May be null.
- * @param notifyAuthFailure if true and the authenticator returns a {@link #KEY_INTENT} in the
- * result then a "sign-on needed" notification will be created that will launch this intent.
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains either:
- * <ul>
- * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
- * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN}
- * if the authenticator is able to retrieve the auth token
- * </ul>
- * If the user presses "back" then the request will be canceled.
- */
- public AccountManagerFuture<Bundle> getAuthToken(
- final Account account, final String authTokenType, final boolean notifyAuthFailure,
- AccountManagerCallback<Bundle> callback, Handler handler) {
- if (account == null) throw new IllegalArgumentException("account is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
- return new AmsTask(null, handler, callback) {
- public void doWork() throws RemoteException {
- mService.getAuthToken(mResponse, account, authTokenType,
- notifyAuthFailure, false /* expectActivityLaunch */, null /* options */);
- }
- }.start();
- }
-
- /**
- * Request that an account be added with the given accountType. This request
- * is processed by the authenticator for the account type. If no authenticator is registered
- * in the system then {@link AuthenticatorException} is thrown.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#GET_ACCOUNTS}.
*
- * @param accountType The type of account to add. This must not be null.
- * @param authTokenType The account that is added should be able to service this auth token
- * type. This may be null.
- * @param requiredFeatures The account that is added should support these features.
- * This array may be null or empty.
- * @param addAccountOptions A bundle of authenticator-specific options that is passed on
- * to the authenticator. This may be null.
- * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
- * the intent will be started with this activity. If activity is null then the result will
- * be returned as-is.
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains either:
- * <ul>
- * <li> {@link #KEY_INTENT}, or
- * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE}
- * and {@link #KEY_AUTHTOKEN} (if an authTokenType was specified).
- * </ul>
+ * @param type The type of accounts to return, must not be null
+ * @param features An array of the account features to require,
+ * may be null or empty
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to an array of
+ * {@link Account}, one per account of the specified type which
+ * matches the requested features.
*/
- public AccountManagerFuture<Bundle> addAccount(final String accountType,
- final String authTokenType, final String[] requiredFeatures,
- final Bundle addAccountOptions,
- final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
- return new AmsTask(activity, handler, callback) {
- public void doWork() throws RemoteException {
- if (accountType == null) {
- Log.e(TAG, "the account must not be null");
- // to unblock caller waiting on Future.get()
- set(new Bundle());
- return;
- }
- mService.addAcount(mResponse, accountType, authTokenType,
- requiredFeatures, activity != null, addAccountOptions);
- }
- }.start();
- }
-
public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
final String type, final String[] features,
AccountManagerCallback<Account[]> callback, Handler handler) {
@@ -694,40 +446,535 @@
}
/**
- * Requests that the authenticator checks that the user knows the credentials for the account.
- * This is typically done by returning an intent to an activity that prompts the user to
- * enter the credentials. This request
- * is processed by the authenticator for the account. If no matching authenticator is
- * registered in the system then {@link AuthenticatorException} is thrown.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ * Adds an account directly to the AccountManager. Normally used by sign-up
+ * wizards associated with authenticators, not directly by applications.
*
- * @param account The account whose credentials are to be checked
- * @param options authenticator specific options for the request
- * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
- * the intent will be started with this activity. If activity is null then the result will
- * be returned as-is.
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains either:
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and to have the same UID as the added account's authenticator.
+ *
+ * @param account The {@link Account} to add
+ * @param password The password to associate with the account, null for none
+ * @param userdata String values to use for the account's userdata, null for none
+ * @return Whether the account was successfully added. False if the account
+ * already exists, the account is null, or another error occurs.
+ */
+ public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
+ try {
+ return mService.addAccount(account, password, userdata);
+ } catch (RemoteException e) {
+ // won't ever happen
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Removes an account from the AccountManager. Does nothing if the account
+ * does not exist. Does not delete the account from the server.
+ * The authenticator may have its own policies preventing account
+ * deletion, in which case the account will not be deleted.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ *
+ * @param account The {@link Account} to remove
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean,
+ * true if the account has been successfully removed,
+ * false if the authenticator forbids deleting this account.
+ */
+ public AccountManagerFuture<Boolean> removeAccount(final Account account,
+ AccountManagerCallback<Boolean> callback, Handler handler) {
+ return new Future2Task<Boolean>(handler, callback) {
+ public void doWork() throws RemoteException {
+ mService.removeAccount(mResponse, account);
+ }
+ public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+ }
+ }.start();
+ }
+
+ /**
+ * Removes an auth token from the AccountManager's cache. Does nothing if
+ * the auth token is not currently in the cache. Applications must call this
+ * method when the auth token is found to have expired or otherwise become
+ * invalid for authenticating requests. The AccountManager does not validate
+ * or expire cached auth tokens otherwise.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS} or
+ * {@link android.Manifest.permission#USE_CREDENTIALS}
+ *
+ * @param accountType The account type of the auth token to invalidate
+ * @param authToken The auth token to invalidate
+ */
+ public void invalidateAuthToken(final String accountType, final String authToken) {
+ try {
+ mService.invalidateAuthToken(accountType, authToken);
+ } catch (RemoteException e) {
+ // won't ever happen
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets an auth token from the AccountManager's cache. If no auth
+ * token is cached for this account, null will be returned -- a new
+ * auth token will not be generated, and the server will not be contacted.
+ * Intended for use by the authenticator, not directly by applications.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and to have the same UID as the account's authenticator.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The type of auth token to fetch, see {#getAuthToken}
+ * @return The cached auth token for this account and type, or null if
+ * no auth token is cached or the account does not exist.
+ */
+ public String peekAuthToken(final Account account, final String authTokenType) {
+ if (account == null) {
+ Log.e(TAG, "peekAuthToken: the account must not be null");
+ return null;
+ }
+ if (authTokenType == null) {
+ return null;
+ }
+ try {
+ return mService.peekAuthToken(account, authTokenType);
+ } catch (RemoteException e) {
+ // won't ever happen
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sets or forgets a saved password. This modifies the local copy of the
+ * password used to automatically authenticate the user; it does
+ * not change the user's account password on the server. Intended for use
+ * by the authenticator, not directly by applications.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and have the same UID as the account's authenticator.
+ *
+ * @param account The account to set a password for
+ * @param password The password to set, null to clear the password
+ */
+ public void setPassword(final Account account, final String password) {
+ if (account == null) {
+ Log.e(TAG, "the account must not be null");
+ return;
+ }
+ try {
+ mService.setPassword(account, password);
+ } catch (RemoteException e) {
+ // won't ever happen
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Forgets a saved password. This erases the local copy of the password;
+ * it does not change the user's account password on the server.
+ * Has the same effect as setPassword(account, null) but requires fewer
+ * permissions, and may be used by applications or management interfaces
+ * to "sign out" from an account.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}
+ *
+ * @param account The account whose password to clear
+ */
+ public void clearPassword(final Account account) {
+ if (account == null) {
+ Log.e(TAG, "the account must not be null");
+ return;
+ }
+ try {
+ mService.clearPassword(account);
+ } catch (RemoteException e) {
+ // won't ever happen
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sets one userdata key for an account. Intended by use for the
+ * authenticator to stash state for itself, not directly by applications.
+ * The meaning of the keys and values is up to the authenticator.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and to have the same UID as the account's authenticator.
+ *
+ * @param account The account to set the userdata for
+ * @param key The userdata key to set. Must not be null
+ * @param value The value to set, null to clear this userdata key
+ */
+ public void setUserData(final Account account, final String key, final String value) {
+ if (account == null) {
+ Log.e(TAG, "the account must not be null");
+ return;
+ }
+ if (key == null) {
+ Log.e(TAG, "the key must not be null");
+ return;
+ }
+ try {
+ mService.setUserData(account, key, value);
+ } catch (RemoteException e) {
+ // won't ever happen
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Adds an auth token to the AccountManager cache for an account.
+ * If the account does not exist then this call has no effect.
+ * Replaces any previous auth token for this account and auth token type.
+ * Intended for use by the authenticator, not directly by applications.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and to have the same UID as the account's authenticator.
+ *
+ * @param account The account to set an auth token for
+ * @param authTokenType The type of the auth token, see {#getAuthToken}
+ * @param authToken The auth token to add to the cache
+ */
+ public void setAuthToken(Account account, final String authTokenType, final String authToken) {
+ try {
+ mService.setAuthToken(account, authTokenType, authToken);
+ } catch (RemoteException e) {
+ // won't ever happen
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * This convenience helper synchronously gets an auth token with
+ * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}.
+ *
+ * <p>This method may block while a network request completes, and must
+ * never be made from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#USE_CREDENTIALS}.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, see {#link getAuthToken}
+ * @param notifyAuthFailure If true, display a notification and return null
+ * if authentication fails; if false, prompt and wait for the user to
+ * re-enter correct credentials before returning
+ * @return An auth token of the specified type for this account, or null
+ * if authentication fails or none can be fetched.
+ * @throws AuthenticatorException if the authenticator failed to respond
+ * @throws OperationCanceledException if the request was canceled for any
+ * reason, including the user canceling a credential request
+ * @throws java.io.IOException if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
+ */
+ public String blockingGetAuthToken(Account account, String authTokenType,
+ boolean notifyAuthFailure)
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
+ null /* handler */).getResult();
+ return bundle.getString(KEY_AUTHTOKEN);
+ }
+
+ /**
+ * Gets an auth token of the specified type for a particular account,
+ * prompting the user for credentials if necessary. This method is
+ * intended for applications running in the foreground where it makes
+ * sense to ask the user directly for a password.
+ *
+ * <p>If a previously generated auth token is cached for this account and
+ * type, then it will be returned. Otherwise, if we have a saved password
+ * the server accepts, it will be used to generate a new auth token.
+ * Otherwise, the user will be asked for a password, which will be sent to
+ * the server to generate a new auth token.
+ *
+ * <p>The value of the auth token type depends on the authenticator.
+ * Some services use different tokens to access different functionality --
+ * for example, Google uses different auth tokens to access Gmail and
+ * Google Calendar for the same account.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#USE_CREDENTIALS}.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, an authenticator-dependent
+ * string token, must not be null
+ * @param options Authenticator-specific options for the request,
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user for a password
+ * if necessary; used only to call startActivity(); must not be null.
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * at least the following fields:
* <ul>
- * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
- * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct
- * credentials
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
* </ul>
- * If the user presses "back" then the request will be canceled.
+ *
+ * (Other authenticator-specific values may be returned.) If an auth token
+ * could not be fetched, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation is canceled for
+ * any reason, incluidng the user canceling a credential request
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
+ * </ul>
+ */
+ public AccountManagerFuture<Bundle> getAuthToken(
+ final Account account, final String authTokenType, final Bundle options,
+ final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+ if (activity == null) throw new IllegalArgumentException("activity is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ return new AmsTask(activity, handler, callback) {
+ public void doWork() throws RemoteException {
+ mService.getAuthToken(mResponse, account, authTokenType,
+ false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
+ options);
+ }
+ }.start();
+ }
+
+ /**
+ * Gets an auth token of the specified type for a particular account,
+ * optionally raising a notification if the user must enter credentials.
+ * This method is intended for background tasks and services where the
+ * user should not be immediately interrupted with a password prompt.
+ *
+ * <p>If a previously generated auth token is cached for this account and
+ * type, then it will be returned. Otherwise, if we have saved credentials
+ * the server accepts, it will be used to generate a new auth token.
+ * Otherwise, an Intent will be returned which, when started, will prompt
+ * the user for a password. If the notifyAuthFailure parameter is set,
+ * the same Intent will be associated with a status bar notification,
+ * alerting the user that they need to enter a password at some point.
+ *
+ * <p>If the intent is left in a notification, you will need to wait until
+ * the user gets around to entering a password before trying again,
+ * which could be hours or days or never. When it does happen, the
+ * account manager will broadcast the {@link #LOGIN_ACCOUNTS_CHANGED_ACTION}
+ * {@link Intent}, which applications can use to trigger another attempt
+ * to fetch an auth token.
+ *
+ * <p>If notifications are not enabled, it is the application's
+ * responsibility to launch the returned intent at some point to let
+ * the user enter credentials. In either case, the result from this
+ * call will not wait for user action.
+ *
+ * <p>The value of the auth token type depends on the authenticator.
+ * Some services use different tokens to access different functionality --
+ * for example, Google uses different auth tokens to access Gmail and
+ * Google Calendar for the same account.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#USE_CREDENTIALS}.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, an authenticator-dependent
+ * string token, must not be null
+ * @param notifyAuthFailure True to add a notification to prompt the
+ * user for a password if necessary, false to leave that to the caller
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * at least the following fields on success:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
+ * </ul>
+ *
+ * (Other authenticator-specific values may be returned.) If the user
+ * must enter credentials, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt.
+ *
+ * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation is canceled for
+ * any reason, incluidng the user canceling a credential request
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
+ * </ul>
+ */
+ public AccountManagerFuture<Bundle> getAuthToken(
+ final Account account, final String authTokenType, final boolean notifyAuthFailure,
+ AccountManagerCallback<Bundle> callback, Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ return new AmsTask(null, handler, callback) {
+ public void doWork() throws RemoteException {
+ mService.getAuthToken(mResponse, account, authTokenType,
+ notifyAuthFailure, false /* expectActivityLaunch */, null /* options */);
+ }
+ }.start();
+ }
+
+ /**
+ * Asks the user to add an account of a specified type. The authenticator
+ * for this account type processes this request with the appropriate user
+ * interface. If the user does elect to create a new account, the account
+ * name is returned.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ *
+ * @param accountType The type of account to add; must not be null
+ * @param authTokenType The type of auth token (see {@link #getAuthToken})
+ * this account will need to be able to generate, null for none
+ * @param requiredFeatures The features (see {@link #hasFeatures}) this
+ * account must have, null for none
+ * @param addAccountOptions Authenticator-specific options for the request,
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to create an
+ * account; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the necessary {@link Intent}
+ * will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * these fields if activity was specified and an account was created:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * </ul>
+ *
+ * If no activity was specified, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * actual account creation process.
+ *
+ * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if no authenticator was registered for
+ * this account type or the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the creation process
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new account, usually because of network trouble
+ * </ul>
+ */
+ public AccountManagerFuture<Bundle> addAccount(final String accountType,
+ final String authTokenType, final String[] requiredFeatures,
+ final Bundle addAccountOptions,
+ final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+ return new AmsTask(activity, handler, callback) {
+ public void doWork() throws RemoteException {
+ if (accountType == null) {
+ Log.e(TAG, "the account must not be null");
+ // to unblock caller waiting on Future.get()
+ set(new Bundle());
+ return;
+ }
+ mService.addAcount(mResponse, accountType, authTokenType,
+ requiredFeatures, activity != null, addAccountOptions);
+ }
+ }.start();
+ }
+
+ /**
+ * Confirms that the user knows the password for an account to make extra
+ * sure they are the owner of the account. The user-entered password can
+ * be supplied directly, otherwise the authenticator for this account type
+ * prompts the user with the appropriate interface. This method is
+ * intended for applications which want extra assurance; for example, the
+ * phone lock screen uses this to let the user unlock the phone with an
+ * account password if they forget the lock pattern.
+ *
+ * <p>If the user-entered password matches a saved password for this
+ * account, the request is considered valid; otherwise the authenticator
+ * verifies the password (usually by contacting the server).
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ *
+ * @param account The account to confirm password knowledge for
+ * @param options Authenticator-specific options for the request;
+ * if the {@link #KEY_PASSWORD} string field is present, the
+ * authenticator may use it directly rather than prompting the user;
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to enter a
+ * password; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the necessary {@link Intent}
+ * will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle
+ * with these fields if activity or password was supplied and
+ * the account was successfully verified:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success
+ * </ul>
+ *
+ * If no activity or password was specified, the returned Bundle contains
+ * only {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * password prompt.
+ *
+ * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the password prompt
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * verifying the password, usually because of network trouble
+ * </ul>
*/
public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
final Bundle options,
@@ -742,42 +989,52 @@
}
/**
- * Requests that the authenticator update the the credentials for a user. This is typically
- * done by returning an intent to an activity that will prompt the user to update the stored
- * credentials for the account. This request
- * is processed by the authenticator for the account. If no matching authenticator is
- * registered in the system then {@link AuthenticatorException} is thrown.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ * Asks the user to enter a new password for an account, updating the
+ * saved credentials for the account. Normally this happens automatically
+ * when the server rejects credentials during an auth token fetch, but this
+ * can be invoked directly to ensure we have the correct credentials stored.
*
- * @param account The account whose credentials are to be updated.
- * @param authTokenType the auth token to retrieve as part of updating the credentials.
- * May be null.
- * @param options authenticator specific options for the request
- * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
- * the intent will be started with this activity. If activity is null then the result will
- * be returned as-is.
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains either:
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ *
+ * @param account The account to update credentials for
+ * @param authTokenType The credentials entered must allow an auth token
+ * of this type to be created (but no actual auth token is returned);
+ * may be null
+ * @param options Authenticator-specific options for the request;
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to enter a
+ * password; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the necessary {@link Intent}
+ * will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle
+ * with these fields if an activity was supplied and the account
+ * credentials were successfully updated:
* <ul>
- * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
- * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct
- * credentials, and optionally a {@link #KEY_AUTHTOKEN} if an authTokenType was provided.
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
* </ul>
- * If the user presses "back" then the request will be canceled.
+ *
+ * If no activity was specified, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * password prompt.
+ *
+ * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the password prompt
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * verifying the password, usually because of network trouble
+ * </ul>
*/
public AccountManagerFuture<Bundle> updateCredentials(final Account account,
final String authTokenType,
@@ -793,37 +1050,41 @@
}
/**
- * Request that the properties for an authenticator be updated. This is typically done by
- * returning an intent to an activity that will allow the user to make changes. This request
- * is processed by the authenticator for the account. If no matching authenticator is
- * registered in the system then {@link AuthenticatorException} is thrown.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ * Offers the user an opportunity to change an authenticator's settings.
+ * These properties are for the authenticator in general, not a particular
+ * account. Not all authenticators support this method.
*
- * @param accountType The account type of the authenticator whose properties are to be edited.
- * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
- * the intent will be started with this activity. If activity is null then the result will
- * be returned as-is.
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains either:
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ *
+ * @param accountType The account type associated with the authenticator
+ * to adjust
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to adjust authenticator settings;
+ * used only to call startActivity(); if null, the settings dialog will
+ * not be launched directly, but the necessary {@link Intent} will be
+ * returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle
+ * which is empty if properties were edited successfully, or
+ * if no activity was specified, contains only {@link #KEY_INTENT}
+ * needed to launch the authenticator's settings dialog.
+ *
+ * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
* <ul>
- * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
- * <li> nothing, returned if the edit completes successfully
+ * <li> {@link AuthenticatorException} if no authenticator was registered for
+ * this account type or the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the settings dialog
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * updating settings, usually because of network trouble
* </ul>
- * If the user presses "back" then the request will be canceled.
*/
public AccountManagerFuture<Bundle> editProperties(final String accountType,
final Activity activity, final AccountManagerCallback<Bundle> callback,
@@ -1270,57 +1531,68 @@
}
/**
- * Convenience method that combines the functionality of {@link #getAccountsByTypeAndFeatures},
- * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)},
- * and {@link #addAccount}. It first gets the list of accounts that match accountType and the
- * feature set. If there are none then {@link #addAccount} is invoked with the authTokenType
- * feature set, and addAccountOptions. If there is exactly one then
- * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)} is
- * called with that account. If there are more than one then a chooser activity is launched
- * to prompt the user to select one of them and then the authtoken is retrieved for it,
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ * This convenience helper combines the functionality of
+ * {@link #getAccountsByTypeAndFeatures}, {@link #getAuthToken}, and
+ * {@link #addAccount}.
*
- * @param accountType the accountType to query; this must be non-null
- * @param authTokenType the type of authtoken to retrieve; this must be non-null
- * @param features a filter for the accounts. See {@link #getAccountsByTypeAndFeatures}.
- * @param activityForPrompting The activity used to start any account management
- * activities that are required to fulfill this request. This may be null.
- * @param addAccountOptions authenticator-specific options used if an account needs to be added
- * @param getAuthTokenOptions authenticator-specific options passed to getAuthToken
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains either:
+ * <p>This method gets a list of the accounts matching the
+ * specified type and feature set; if there is exactly one, it is
+ * used; if there are more than one, the user is prompted to pick one;
+ * if there are none, the user is prompted to add one. Finally,
+ * an auth token is acquired for the chosen account.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ *
+ * @param accountType The account type required
+ * (see {@link #getAccountsByType}), must not be null
+ * @param authTokenType The desired auth token type
+ * (see {@link #getAuthToken}), must not be null
+ * @param features Required features for the account
+ * (see {@link #getAccountsByTypeAndFeatures}), may be null or empty
+ * @param activity The {@link Activity} context to use for launching new
+ * sub-Activities to prompt to add an account, select an account,
+ * and/or enter a password, as necessary; used only to call
+ * startActivity(); should not be null
+ * @param addAccountOptions Authenticator-specific options to use for
+ * adding new accounts; may be null or empty
+ * @param getAuthTokenOptions Authenticator-specific options to use for
+ * getting auth tokens; may be null or empty
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * at least the following fields:
* <ul>
- * <li> {@link #KEY_INTENT}, if no activity is supplied yet an activity needs to launched to
- * fulfill the request.
- * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} if the
- * request completes successfully.
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
* </ul>
- * If the user presses "back" then the request will be canceled.
+ *
+ * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if no authenticator was registered for
+ * this account type or the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling any operation
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * updating settings, usually because of network trouble
+ * </ul>
*/
public AccountManagerFuture<Bundle> getAuthTokenByFeatures(
final String accountType, final String authTokenType, final String[] features,
- final Activity activityForPrompting, final Bundle addAccountOptions,
+ final Activity activity, final Bundle addAccountOptions,
final Bundle getAuthTokenOptions,
final AccountManagerCallback<Bundle> callback, final Handler handler) {
if (accountType == null) throw new IllegalArgumentException("account type is null");
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
final GetAuthTokenByTypeAndFeaturesTask task =
new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
- activityForPrompting, addAccountOptions, getAuthTokenOptions, callback, handler);
+ activity, addAccountOptions, getAuthTokenOptions, callback, handler);
task.start();
return task;
}
@@ -1347,18 +1619,26 @@
};
/**
- * Add a {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}.
- * The listener is guaranteed to be invoked on the thread of the Handler that is passed
- * in or the main thread's Handler if handler is null.
- * <p>
- * You must remove this listener before the context that was used to retrieve this
- * {@link AccountManager} instance goes away. This generally means when the Activity
- * or Service you are running is stopped.
- * @param listener the listener to add
- * @param handler the Handler whose thread will be used to invoke the listener. If null
- * the AccountManager context's main thread will be used.
- * @param updateImmediately if true then the listener will be invoked as a result of this
- * call.
+ * Adds an {@link OnAccountsUpdateListener} to this instance of the
+ * {@link AccountManager}. This listener will be notified whenever the
+ * list of accounts on the device changes.
+ *
+ * <p>As long as this listener is present, the AccountManager instance
+ * will not be garbage-collected, and neither will the {@link Context}
+ * used to retrieve it, which may be a large Activity instance. To avoid
+ * memory leaks, you must remove this listener before then. Normally
+ * listeners are added in an Activity or Service's {@link Activity#onCreate}
+ * and removed in {@link Activity#onDestroy}.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
+ * @param listener The listener to send notifications to
+ * @param handler {@link Handler} identifying the thread to use
+ * for notifications, null for the main thread
+ * @param updateImmediately If true, the listener will be invoked
+ * (on the handler thread) right away with the current account list
* @throws IllegalArgumentException if listener is null
* @throws IllegalStateException if listener was already added
*/
@@ -1391,9 +1671,15 @@
}
/**
- * Remove an {@link OnAccountsUpdateListener} that was previously registered with
- * {@link #addOnAccountsUpdatedListener}.
- * @param listener the listener to remove
+ * Removes an {@link OnAccountsUpdateListener} previously registered with
+ * {@link #addOnAccountsUpdatedListener}. The listener will no longer
+ * receive notifications of account changes.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
+ * @param listener The previously added listener to remove
* @throws IllegalArgumentException if listener is null
* @throws IllegalStateException if listener was not already added
*/
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 7850124..2aaf5b0 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -466,7 +466,8 @@
public TestFeaturesSession(IAccountManagerResponse response,
Account account, String[] features) {
- super(response, account.type, false /* expectActivityLaunch */);
+ super(response, account.type, false /* expectActivityLaunch */,
+ true /* stripAuthTokenFromResult */);
mFeatures = features;
mAccount = account;
}
@@ -520,7 +521,8 @@
private class RemoveAccountSession extends Session {
final Account mAccount;
public RemoveAccountSession(IAccountManagerResponse response, Account account) {
- super(response, account.type, false /* expectActivityLaunch */);
+ super(response, account.type, false /* expectActivityLaunch */,
+ true /* stripAuthTokenFromResult */);
mAccount = account;
}
@@ -563,7 +565,7 @@
}
public void invalidateAuthToken(String accountType, String authToken) {
- checkManageAccountsPermission();
+ checkManageAccountsOrUseCredentialsPermissions();
long identityToken = clearCallingIdentity();
try {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
@@ -689,11 +691,21 @@
if (account == null) {
return;
}
- ContentValues values = new ContentValues();
- values.put(ACCOUNTS_PASSWORD, password);
- mOpenHelper.getWritableDatabase().update(TABLE_ACCOUNTS, values,
- ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
- new String[]{account.name, account.type});
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ final ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_PASSWORD, password);
+ final long accountId = getAccountId(db, account);
+ if (accountId >= 0) {
+ final String[] argsAccountId = {String.valueOf(accountId)};
+ db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
+ db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId);
+ db.setTransactionSuccessful();
+ }
+ } finally {
+ db.endTransaction();
+ }
sendAccountsChangedBroadcast();
}
@@ -794,7 +806,8 @@
}
}
- new Session(response, account.type, expectActivityLaunch) {
+ new Session(response, account.type, expectActivityLaunch,
+ false /* stripAuthTokenFromResult */) {
protected String toDebugString(long now) {
if (loginOptions != null) loginOptions.keySet();
return super.toDebugString(now) + ", getAuthToken"
@@ -945,7 +958,8 @@
checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
try {
- new Session(response, accountType, expectActivityLaunch) {
+ new Session(response, accountType, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */) {
public void run() throws RemoteException {
mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
options);
@@ -970,7 +984,8 @@
checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
try {
- new Session(response, account.type, expectActivityLaunch) {
+ new Session(response, account.type, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */) {
public void run() throws RemoteException {
mAuthenticator.confirmCredentials(this, account, options);
}
@@ -990,7 +1005,8 @@
checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
try {
- new Session(response, account.type, expectActivityLaunch) {
+ new Session(response, account.type, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */) {
public void run() throws RemoteException {
mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
}
@@ -1012,7 +1028,8 @@
checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
try {
- new Session(response, accountType, expectActivityLaunch) {
+ new Session(response, accountType, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */) {
public void run() throws RemoteException {
mAuthenticator.editProperties(this, mAccountType);
}
@@ -1034,7 +1051,8 @@
public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response,
String type, String[] features) {
- super(response, type, false /* expectActivityLaunch */);
+ super(response, type, false /* expectActivityLaunch */,
+ true /* stripAuthTokenFromResult */);
mFeatures = features;
}
@@ -1126,7 +1144,10 @@
long identityToken = clearCallingIdentity();
try {
if (features == null || features.length == 0) {
- getAccountsByType(type);
+ Account[] accounts = getAccountsByType(type);
+ Bundle result = new Bundle();
+ result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
+ onResult(response, result);
return;
}
new GetAccountsByTypeAndFeatureSession(response, type, features).bind();
@@ -1176,11 +1197,14 @@
IAccountAuthenticator mAuthenticator = null;
+ private final boolean mStripAuthTokenFromResult;
+
public Session(IAccountManagerResponse response, String accountType,
- boolean expectActivityLaunch) {
+ boolean expectActivityLaunch, boolean stripAuthTokenFromResult) {
super();
if (response == null) throw new IllegalArgumentException("response is null");
if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ mStripAuthTokenFromResult = stripAuthTokenFromResult;
mResponse = response;
mAccountType = accountType;
mExpectActivityLaunch = expectActivityLaunch;
@@ -1319,6 +1343,9 @@
response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
"null bundle returned");
} else {
+ if (mStripAuthTokenFromResult) {
+ result.remove(AccountManager.KEY_AUTHTOKEN);
+ }
response.onResult(result);
}
} catch (RemoteException e) {
@@ -1720,17 +1747,22 @@
}
}
- private void checkBinderPermission(String permission) {
+ /** Succeeds if any of the specified permissions are granted. */
+ private void checkBinderPermission(String... permissions) {
final int uid = Binder.getCallingUid();
- if (mContext.checkCallingOrSelfPermission(permission) !=
- PackageManager.PERMISSION_GRANTED) {
- String msg = "caller uid " + uid + " lacks " + permission;
- Log.w(TAG, msg);
- throw new SecurityException(msg);
+
+ for (String perm : permissions) {
+ if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "caller uid " + uid + " has " + perm);
+ }
+ return;
+ }
}
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "caller uid " + uid + " has " + permission);
- }
+
+ String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions);
+ Log.w(TAG, msg);
+ throw new SecurityException(msg);
}
private boolean inSystemImage(int callerUid) {
@@ -1821,6 +1853,11 @@
checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS);
}
+ private void checkManageAccountsOrUseCredentialsPermissions() {
+ checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS,
+ Manifest.permission.USE_CREDENTIALS);
+ }
+
/**
* Allow callers with the given uid permission to get credentials for account/authTokenType.
* <p>
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 05bbf3b..b38aeda 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1308,14 +1308,16 @@
}
// close any cursors we are managing.
- int numCursors = mManagedCursors.size();
- for (int i = 0; i < numCursors; i++) {
- ManagedCursor c = mManagedCursors.get(i);
- if (c != null) {
- c.mCursor.close();
+ synchronized (mManagedCursors) {
+ int numCursors = mManagedCursors.size();
+ for (int i = 0; i < numCursors; i++) {
+ ManagedCursor c = mManagedCursors.get(i);
+ if (c != null) {
+ c.mCursor.close();
+ }
}
+ mManagedCursors.clear();
}
- mManagedCursors.clear();
}
/**
@@ -3778,13 +3780,15 @@
}
final void performRestart() {
- final int N = mManagedCursors.size();
- for (int i=0; i<N; i++) {
- ManagedCursor mc = mManagedCursors.get(i);
- if (mc.mReleased || mc.mUpdated) {
- mc.mCursor.requery();
- mc.mReleased = false;
- mc.mUpdated = false;
+ synchronized (mManagedCursors) {
+ final int N = mManagedCursors.size();
+ for (int i=0; i<N; i++) {
+ ManagedCursor mc = mManagedCursors.get(i);
+ if (mc.mReleased || mc.mUpdated) {
+ mc.mCursor.requery();
+ mc.mReleased = false;
+ mc.mUpdated = false;
+ }
}
}
@@ -3850,12 +3854,14 @@
" did not call through to super.onStop()");
}
- final int N = mManagedCursors.size();
- for (int i=0; i<N; i++) {
- ManagedCursor mc = mManagedCursors.get(i);
- if (!mc.mReleased) {
- mc.mCursor.deactivate();
- mc.mReleased = true;
+ synchronized (mManagedCursors) {
+ final int N = mManagedCursors.size();
+ for (int i=0; i<N; i++) {
+ ManagedCursor mc = mManagedCursors.get(i);
+ if (!mc.mReleased) {
+ mc.mCursor.deactivate();
+ mc.mReleased = true;
+ }
}
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 56e44c8..0756c71 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -77,9 +77,12 @@
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.net.URL;
import java.util.ArrayList;
+import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -460,6 +463,7 @@
mClassLoader =
ApplicationLoaders.getDefault().getClassLoader(
zip, mDataDir, mBaseClassLoader);
+ initializeJavaContextClassLoader();
} else {
if (mBaseClassLoader == null) {
mClassLoader = ClassLoader.getSystemClassLoader();
@@ -471,6 +475,120 @@
}
}
+ /**
+ * Setup value for Thread.getContextClassLoader(). If the
+ * package will not run in in a VM with other packages, we set
+ * the Java context ClassLoader to the
+ * PackageInfo.getClassLoader value. However, if this VM can
+ * contain multiple packages, we intead set the Java context
+ * ClassLoader to a proxy that will warn about the use of Java
+ * context ClassLoaders and then fall through to use the
+ * system ClassLoader.
+ *
+ * <p> Note that this is similar to but not the same as the
+ * android.content.Context.getClassLoader(). While both
+ * context class loaders are typically set to the
+ * PathClassLoader used to load the package archive in the
+ * single application per VM case, a single Android process
+ * may contain several Contexts executing on one thread with
+ * their own logical ClassLoaders while the Java context
+ * ClassLoader is a thread local. This is why in the case when
+ * we have multiple packages per VM we do not set the Java
+ * context ClassLoader to an arbitrary but instead warn the
+ * user to set their own if we detect that they are using a
+ * Java library that expects it to be set.
+ */
+ private void initializeJavaContextClassLoader() {
+ IPackageManager pm = getPackageManager();
+ android.content.pm.PackageInfo pi;
+ try {
+ pi = pm.getPackageInfo(mPackageName, 0);
+ } catch (RemoteException e) {
+ throw new AssertionError(e);
+ }
+ /*
+ * Two possible indications that this package could be
+ * sharing its virtual machine with other packages:
+ *
+ * 1.) the sharedUserId attribute is set in the manifest,
+ * indicating a request to share a VM with other
+ * packages with the same sharedUserId.
+ *
+ * 2.) the application element of the manifest has an
+ * attribute specifying a non-default process name,
+ * indicating the desire to run in another packages VM.
+ */
+ boolean sharedUserIdSet = (pi.sharedUserId != null);
+ boolean processNameNotDefault =
+ (pi.applicationInfo != null &&
+ !mPackageName.equals(pi.applicationInfo.processName));
+ boolean sharable = (sharedUserIdSet || processNameNotDefault);
+ ClassLoader contextClassLoader =
+ (sharable)
+ ? new WarningContextClassLoader()
+ : mClassLoader;
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+
+ private static class WarningContextClassLoader extends ClassLoader {
+
+ private static boolean warned = false;
+
+ private void warn(String methodName) {
+ if (warned) {
+ return;
+ }
+ warned = true;
+ Thread.currentThread().setContextClassLoader(getParent());
+ Log.w(TAG, "ClassLoader." + methodName + ": " +
+ "The class loader returned by " +
+ "Thread.getContextClassLoader() may fail for processes " +
+ "that host multiple applications. You should explicitly " +
+ "specify a context class loader. For example: " +
+ "Thread.setContextClassLoader(getClass().getClassLoader());");
+ }
+
+ @Override public URL getResource(String resName) {
+ warn("getResource");
+ return getParent().getResource(resName);
+ }
+
+ @Override public Enumeration<URL> getResources(String resName) throws IOException {
+ warn("getResources");
+ return getParent().getResources(resName);
+ }
+
+ @Override public InputStream getResourceAsStream(String resName) {
+ warn("getResourceAsStream");
+ return getParent().getResourceAsStream(resName);
+ }
+
+ @Override public Class<?> loadClass(String className) throws ClassNotFoundException {
+ warn("loadClass");
+ return getParent().loadClass(className);
+ }
+
+ @Override public void setClassAssertionStatus(String cname, boolean enable) {
+ warn("setClassAssertionStatus");
+ getParent().setClassAssertionStatus(cname, enable);
+ }
+
+ @Override public void setPackageAssertionStatus(String pname, boolean enable) {
+ warn("setPackageAssertionStatus");
+ getParent().setPackageAssertionStatus(pname, enable);
+ }
+
+ @Override public void setDefaultAssertionStatus(boolean enable) {
+ warn("setDefaultAssertionStatus");
+ getParent().setDefaultAssertionStatus(enable);
+ }
+
+ @Override public void clearAssertionStatus() {
+ warn("clearAssertionStatus");
+ getParent().clearAssertionStatus();
+ }
+ }
+
public String getAppDir() {
return mAppDir;
}
@@ -1392,7 +1510,7 @@
r.startsNotResumed = notResumed;
r.createdConfig = config;
- synchronized (mRelaunchingActivities) {
+ synchronized (mPackages) {
mRelaunchingActivities.add(r);
}
@@ -1523,8 +1641,11 @@
}
public void scheduleConfigurationChanged(Configuration config) {
- synchronized (mRelaunchingActivities) {
- mPendingConfiguration = config;
+ synchronized (mPackages) {
+ if (mPendingConfiguration == null ||
+ mPendingConfiguration.isOtherSeqNewer(config)) {
+ mPendingConfiguration = config;
+ }
}
queueOrSendMessage(H.CONFIGURATION_CHANGED, config);
}
@@ -2060,6 +2181,7 @@
= new HashMap<IBinder, Service>();
AppBindData mBoundApplication;
Configuration mConfiguration;
+ Configuration mResConfiguration;
Application mInitialApplication;
final ArrayList<Application> mAllApplications
= new ArrayList<Application>();
@@ -2073,14 +2195,6 @@
boolean mSystemThread = false;
boolean mJitEnabled = false;
- /**
- * Activities that are enqueued to be relaunched. This list is accessed
- * by multiple threads, so you must synchronize on it when accessing it.
- */
- final ArrayList<ActivityRecord> mRelaunchingActivities
- = new ArrayList<ActivityRecord>();
- Configuration mPendingConfiguration = null;
-
// These can be accessed by multiple threads; mPackages is the lock.
// XXX For now we keep around information about all packages we have
// seen, not removing entries from this map.
@@ -2092,6 +2206,9 @@
DisplayMetrics mDisplayMetrics = null;
HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources
= new HashMap<ResourcesKey, WeakReference<Resources> >();
+ final ArrayList<ActivityRecord> mRelaunchingActivities
+ = new ArrayList<ActivityRecord>();
+ Configuration mPendingConfiguration = null;
// The lock of mProviderMap protects the following variables.
final HashMap<String, ProviderRecord> mProviderMap
@@ -2496,7 +2613,6 @@
" did not call through to super.onPostCreate()");
}
}
- r.state = null;
}
r.paused = true;
@@ -2527,6 +2643,7 @@
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
+ Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward);
if (!r.activity.mFinished && r.startsNotResumed) {
@@ -2542,6 +2659,9 @@
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
+ // We need to keep around the original state, in case
+ // we need to be created again.
+ r.state = oldState;
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
@@ -3555,7 +3675,7 @@
// First: make sure we have the most recent configuration and most
// recent version of the activity, or skip it if some previous call
// had taken a more recent version.
- synchronized (mRelaunchingActivities) {
+ synchronized (mPackages) {
int N = mRelaunchingActivities.size();
IBinder token = tmp.token;
tmp = null;
@@ -3585,8 +3705,12 @@
// assume that is really what we want regardless of what we
// may have pending.
if (mConfiguration == null
- || mConfiguration.diff(tmp.createdConfig) != 0) {
- changedConfig = tmp.createdConfig;
+ || (tmp.createdConfig.isOtherSeqNewer(mConfiguration)
+ && mConfiguration.diff(tmp.createdConfig) != 0)) {
+ if (changedConfig == null
+ || tmp.createdConfig.isOtherSeqNewer(changedConfig)) {
+ changedConfig = tmp.createdConfig;
+ }
}
}
@@ -3761,62 +3885,81 @@
}
}
- final void handleConfigurationChanged(Configuration config) {
+ final void applyConfigurationToResourcesLocked(Configuration config) {
+ if (mResConfiguration == null) {
+ mResConfiguration = new Configuration();
+ }
+ if (!mResConfiguration.isOtherSeqNewer(config)) {
+ return;
+ }
+ mResConfiguration.updateFrom(config);
+ DisplayMetrics dm = getDisplayMetricsLocked(true);
- synchronized (mRelaunchingActivities) {
- if (mPendingConfiguration != null) {
- config = mPendingConfiguration;
- mPendingConfiguration = null;
- }
+ // set it for java, this also affects newly created Resources
+ if (config.locale != null) {
+ Locale.setDefault(config.locale);
}
- ArrayList<ComponentCallbacks> callbacks
- = new ArrayList<ComponentCallbacks>();
+ Resources.updateSystemConfiguration(config, dm);
- if (DEBUG_CONFIGURATION) Log.v(TAG, "Handle configuration changed: "
- + config);
+ ContextImpl.ApplicationPackageManager.configurationChanged();
+ //Log.i(TAG, "Configuration changed in " + currentPackageName());
- synchronized(mPackages) {
+ Iterator<WeakReference<Resources>> it =
+ mActiveResources.values().iterator();
+ //Iterator<Map.Entry<String, WeakReference<Resources>>> it =
+ // mActiveResources.entrySet().iterator();
+ while (it.hasNext()) {
+ WeakReference<Resources> v = it.next();
+ Resources r = v.get();
+ if (r != null) {
+ r.updateConfiguration(config, dm);
+ //Log.i(TAG, "Updated app resources " + v.getKey()
+ // + " " + r + ": " + r.getConfiguration());
+ } else {
+ //Log.i(TAG, "Removing old resources " + v.getKey());
+ it.remove();
+ }
+ }
+ }
+
+ final void handleConfigurationChanged(Configuration config) {
+
+ ArrayList<ComponentCallbacks> callbacks = null;
+
+ synchronized (mPackages) {
+ if (mPendingConfiguration != null) {
+ if (!mPendingConfiguration.isOtherSeqNewer(config)) {
+ config = mPendingConfiguration;
+ }
+ mPendingConfiguration = null;
+ }
+
+ if (config == null) {
+ return;
+ }
+
+ if (DEBUG_CONFIGURATION) Log.v(TAG, "Handle configuration changed: "
+ + config);
+
+ applyConfigurationToResourcesLocked(config);
+
if (mConfiguration == null) {
mConfiguration = new Configuration();
}
+ if (!mConfiguration.isOtherSeqNewer(config)) {
+ return;
+ }
mConfiguration.updateFrom(config);
- DisplayMetrics dm = getDisplayMetricsLocked(true);
-
- // set it for java, this also affects newly created Resources
- if (config.locale != null) {
- Locale.setDefault(config.locale);
- }
-
- Resources.updateSystemConfiguration(config, dm);
-
- ContextImpl.ApplicationPackageManager.configurationChanged();
- //Log.i(TAG, "Configuration changed in " + currentPackageName());
- {
- Iterator<WeakReference<Resources>> it =
- mActiveResources.values().iterator();
- //Iterator<Map.Entry<String, WeakReference<Resources>>> it =
- // mActiveResources.entrySet().iterator();
- while (it.hasNext()) {
- WeakReference<Resources> v = it.next();
- Resources r = v.get();
- if (r != null) {
- r.updateConfiguration(config, dm);
- //Log.i(TAG, "Updated app resources " + v.getKey()
- // + " " + r + ": " + r.getConfiguration());
- } else {
- //Log.i(TAG, "Removing old resources " + v.getKey());
- it.remove();
- }
- }
- }
callbacks = collectComponentCallbacksLocked(false, config);
}
- final int N = callbacks.size();
- for (int i=0; i<N; i++) {
- performConfigurationChanged(callbacks.get(i), config);
+ if (callbacks != null) {
+ final int N = callbacks.size();
+ for (int i=0; i<N; i++) {
+ performConfigurationChanged(callbacks.get(i), config);
+ }
}
}
@@ -3856,7 +3999,7 @@
ArrayList<ComponentCallbacks> callbacks
= new ArrayList<ComponentCallbacks>();
- synchronized(mPackages) {
+ synchronized (mPackages) {
callbacks = collectComponentCallbacksLocked(true, null);
}
@@ -4348,6 +4491,25 @@
"Unable to instantiate Application():" + e.toString(), e);
}
}
+
+ ViewRoot.addConfigCallback(new ComponentCallbacks() {
+ public void onConfigurationChanged(Configuration newConfig) {
+ synchronized (mPackages) {
+ if (mPendingConfiguration == null ||
+ mPendingConfiguration.isOtherSeqNewer(newConfig)) {
+ mPendingConfiguration = newConfig;
+
+ // We need to apply this change to the resources
+ // immediately, because upon returning the view
+ // hierarchy will be informed about it.
+ applyConfigurationToResourcesLocked(newConfig);
+ }
+ }
+ queueOrSendMessage(H.CONFIGURATION_CHANGED, newConfig);
+ }
+ public void onLowMemory() {
+ }
+ });
}
private final void detach()
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index 832f599..fe81056 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -16,8 +16,16 @@
package android.app;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemProperties;
+import android.provider.Settings;
import android.util.Printer;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -38,6 +46,13 @@
*/
public class ApplicationErrorReport implements Parcelable {
+ // System property defining error report receiver for system apps
+ static final String SYSTEM_APPS_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.system.apps";
+
+ // System property defining default error report receiver
+ static final String DEFAULT_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.default";
+
+
/**
* Uninitialized error report.
*/
@@ -54,8 +69,13 @@
public static final int TYPE_ANR = 2;
/**
+ * An error report about an application that's consuming too much battery.
+ */
+ public static final int TYPE_BATTERY = 3;
+
+ /**
* Type of this report. Can be one of {@link #TYPE_NONE},
- * {@link #TYPE_CRASH} or {@link #TYPE_ANR}.
+ * {@link #TYPE_CRASH}, {@link #TYPE_ANR}, or {@link #TYPE_BATTERY}.
*/
public int type;
@@ -99,6 +119,11 @@
public AnrInfo anrInfo;
/**
+ * Text containing battery usage data.
+ */
+ public String batteryText;
+
+ /**
* Create an uninitialized instance of {@link ApplicationErrorReport}.
*/
public ApplicationErrorReport() {
@@ -112,6 +137,68 @@
readFromParcel(in);
}
+ public static ComponentName getErrorReportReceiver(Context context,
+ String packageName, int appFlags) {
+ // check if error reporting is enabled in secure settings
+ int enabled = Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.SEND_ACTION_APP_ERROR, 0);
+ if (enabled == 0) {
+ return null;
+ }
+
+ PackageManager pm = context.getPackageManager();
+
+ // look for receiver in the installer package
+ String candidate = pm.getInstallerPackageName(packageName);
+ ComponentName result = getErrorReportReceiver(pm, packageName, candidate);
+ if (result != null) {
+ return result;
+ }
+
+ // if the error app is on the system image, look for system apps
+ // error receiver
+ if ((appFlags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+ candidate = SystemProperties.get(SYSTEM_APPS_ERROR_RECEIVER_PROPERTY);
+ result = getErrorReportReceiver(pm, packageName, candidate);
+ if (result != null) {
+ return result;
+ }
+ }
+
+ // if there is a default receiver, try that
+ candidate = SystemProperties.get(DEFAULT_ERROR_RECEIVER_PROPERTY);
+ return getErrorReportReceiver(pm, packageName, candidate);
+ }
+
+ /**
+ * Return activity in receiverPackage that handles ACTION_APP_ERROR.
+ *
+ * @param pm PackageManager isntance
+ * @param errorPackage package which caused the error
+ * @param receiverPackage candidate package to receive the error
+ * @return activity component within receiverPackage which handles
+ * ACTION_APP_ERROR, or null if not found
+ */
+ static ComponentName getErrorReportReceiver(PackageManager pm, String errorPackage,
+ String receiverPackage) {
+ if (receiverPackage == null || receiverPackage.length() == 0) {
+ return null;
+ }
+
+ // break the loop if it's the error report receiver package that crashed
+ if (receiverPackage.equals(errorPackage)) {
+ return null;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_APP_ERROR);
+ intent.setPackage(receiverPackage);
+ ResolveInfo info = pm.resolveActivity(intent, 0);
+ if (info == null || info.activityInfo == null) {
+ return null;
+ }
+ return new ComponentName(receiverPackage, info.activityInfo.name);
+ }
+
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(type);
dest.writeString(packageName);
@@ -127,6 +214,9 @@
case TYPE_ANR:
anrInfo.writeToParcel(dest, flags);
break;
+ case TYPE_BATTERY:
+ dest.writeString(batteryText);
+ break;
}
}
@@ -142,10 +232,17 @@
case TYPE_CRASH:
crashInfo = new CrashInfo(in);
anrInfo = null;
+ batteryText = null;
break;
case TYPE_ANR:
anrInfo = new AnrInfo(in);
crashInfo = null;
+ batteryText = null;
+ break;
+ case TYPE_BATTERY:
+ batteryText = in.readString();
+ anrInfo = null;
+ crashInfo = null;
break;
}
}
@@ -347,6 +444,9 @@
case TYPE_ANR:
anrInfo.dump(pw, prefix);
break;
+ case TYPE_BATTERY:
+ pw.println(batteryText);
+ break;
}
}
}
diff --git a/core/java/android/app/BackupAgent.java b/core/java/android/app/BackupAgent.java
index a2bfc76..4695c21 100644
--- a/core/java/android/app/BackupAgent.java
+++ b/core/java/android/app/BackupAgent.java
@@ -31,10 +31,18 @@
import java.io.IOException;
/**
- * This is the central interface between an application and Android's
- * settings backup mechanism.
- *
- * <p>STOPSHIP write more documentation about the backup process here.
+ * This is the central interface between an application and Android's settings
+ * backup mechanism. Any implementation of a backup agent should perform backup
+ * and restore actions in
+ * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)}
+ * and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor)}
+ * respectively.
+ * <p>
+ * A backup agent based on convenient helper classes is available in
+ * {@link android.backup.BackupHelperAgent} for less complex implementation
+ * requirements.
+ * <p>
+ * STOPSHIP write more documentation about the backup process here.
*/
public abstract class BackupAgent extends ContextWrapper {
private static final String TAG = "BackupAgent";
@@ -51,51 +59,58 @@
}
/**
- * The application is being asked to write any data changed since the
- * last time it performed a backup operation. The state data recorded
- * during the last backup pass is provided in the oldState file descriptor.
- * If oldState is null, no old state is available and the application should perform
- * a full backup. In both cases, a representation of the final backup state after
- * this pass should be written to the file pointed to by the newStateFd file descriptor.
- *
- * @param oldState An open, read-only ParcelFileDescriptor pointing to the last backup
- * state provided by the application. May be null, in which
- * case no prior state is being provided and the application should
- * perform a full backup.
- * @param data A structured wrapper around an open, read/write ParcelFileDescriptor
- * pointing to the backup data destination. Typically the application will use
- * backup helper classes to write to this file.
- * @param newState An open, read/write ParcelFileDescriptor pointing to an empty
- * file. The application should record the final backup state
- * here after writing the requested data to dataFd.
+ * The application is being asked to write any data changed since the last
+ * time it performed a backup operation. The state data recorded during the
+ * last backup pass is provided in the <code>oldState</code> file
+ * descriptor. If <code>oldState</code> is <code>null</code>, no old state
+ * is available and the application should perform a full backup. In both
+ * cases, a representation of the final backup state after this pass should
+ * be written to the file pointed to by the file descriptor wrapped in
+ * <code>newState</code>.
+ *
+ * @param oldState An open, read-only ParcelFileDescriptor pointing to the
+ * last backup state provided by the application. May be
+ * <code>null</code>, in which case no prior state is being
+ * provided and the application should perform a full backup.
+ * @param data A structured wrapper around an open, read/write
+ * ParcelFileDescriptor pointing to the backup data destination.
+ * Typically the application will use backup helper classes to
+ * write to this file.
+ * @param newState An open, read/write ParcelFileDescriptor pointing to an
+ * empty file. The application should record the final backup
+ * state here after writing the requested data to dataFd.
*/
public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException;
-
+
/**
- * The application is being restored from backup, and should replace any
- * existing data with the contents of the backup. The backup data is
- * provided in the file pointed to by the dataFd file descriptor. Once
- * the restore is finished, the application should write a representation
- * of the final state to the newStateFd file descriptor,
- *
- * <p>The application is responsible for properly erasing its old data and
- * replacing it with the data supplied to this method. No "clear user data"
- * operation will be performed automatically by the operating system. The
- * exception to this is in the case of a failed restore attempt: if onRestore()
- * throws an exception, the OS will assume that the application's data may now
- * be in an incoherent state, and will clear it before proceeding.
- *
- * @param data A structured wrapper around an open, read-only ParcelFileDescriptor
- * pointing to a full snapshot of the application's data. Typically the
- * application will use helper classes to read this data.
- * @param appVersionCode The android:versionCode value of the application that backed
- * up this particular data set. This makes it easier for an application's
- * agent to distinguish among several possible older data versions when
- * asked to perform the restore operation.
- * @param newState An open, read/write ParcelFileDescriptor pointing to an empty
- * file. The application should record the final backup state
- * here after restoring its data from dataFd.
+ * The application is being restored from backup and should replace any
+ * existing data with the contents of the backup. The backup data is
+ * provided in the file descriptor pointed to by the
+ * {@link android.backup.BackupDataInput} instance <code>data</code>. Once
+ * the restore is finished, the application should write a representation of
+ * the final state to the <code>newState</code> file descriptor.
+ * <p>
+ * The application is responsible for properly erasing its old data and
+ * replacing it with the data supplied to this method. No "clear user data"
+ * operation will be performed automatically by the operating system. The
+ * exception to this is in the case of a failed restore attempt: if
+ * onRestore() throws an exception, the OS will assume that the
+ * application's data may now be in an incoherent state, and will clear it
+ * before proceeding.
+ *
+ * @param data A structured wrapper around an open, read-only
+ * ParcelFileDescriptor pointing to a full snapshot of the
+ * application's data. Typically the application will use helper
+ * classes to read this data.
+ * @param appVersionCode The android:versionCode value of the application
+ * that backed up this particular data set. This makes it easier
+ * for an application's agent to distinguish among several
+ * possible older data versions when asked to perform the restore
+ * operation.
+ * @param newState An open, read/write ParcelFileDescriptor pointing to an
+ * empty file. The application should record the final backup
+ * state here after restoring its data from dataFd.
*/
public abstract void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState)
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index db6a4bf..50dcdf9 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -42,6 +42,7 @@
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstallObserver;
+import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
@@ -193,6 +194,7 @@
private AccountManager mAccountManager; // protected by mSync
private DropBoxManager mDropBoxManager = null;
private DevicePolicyManager mDevicePolicyManager = null;
+ private UiModeManager mUiModeManager = null;
private final Object mSync = new Object();
@@ -959,6 +961,8 @@
return getDropBoxManager();
} else if (DEVICE_POLICY_SERVICE.equals(name)) {
return getDevicePolicyManager();
+ } else if (UI_MODE_SERVICE.equals(name)) {
+ return getUiModeManager();
}
return null;
@@ -1145,13 +1149,22 @@
private DevicePolicyManager getDevicePolicyManager() {
synchronized (mSync) {
if (mDevicePolicyManager == null) {
- mDevicePolicyManager = new DevicePolicyManager(this,
+ mDevicePolicyManager = DevicePolicyManager.create(this,
mMainThread.getHandler());
}
}
return mDevicePolicyManager;
}
+ private UiModeManager getUiModeManager() {
+ synchronized (mSync) {
+ if (mUiModeManager == null) {
+ mUiModeManager = new UiModeManager();
+ }
+ }
+ return mUiModeManager;
+ }
+
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
@@ -2489,6 +2502,15 @@
}
@Override
+ public void movePackage(String packageName, IPackageMoveObserver observer, int flags) {
+ try {
+ mPM.movePackage(packageName, observer, flags);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
+ @Override
public String getInstallerPackageName(String packageName) {
try {
return mPM.getInstallerPackageName(packageName);
diff --git a/core/java/android/app/DeviceAdminInfo.java b/core/java/android/app/DeviceAdminInfo.java
index bedf4b4..61e1bb339 100644
--- a/core/java/android/app/DeviceAdminInfo.java
+++ b/core/java/android/app/DeviceAdminInfo.java
@@ -18,6 +18,7 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
import android.content.ComponentName;
import android.content.Context;
@@ -331,6 +332,19 @@
return res;
}
+ /** @hide */
+ public void writePoliciesToXml(XmlSerializer out)
+ throws IllegalArgumentException, IllegalStateException, IOException {
+ out.attribute(null, "flags", Integer.toString(mUsesPolicies));
+ }
+
+ /** @hide */
+ public void readPoliciesFromXml(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ mUsesPolicies = Integer.parseInt(
+ parser.getAttributeValue(null, "flags"));
+ }
+
public void dump(Printer pw, String prefix) {
pw.println(prefix + "Receiver:");
mReceiver.dump(pw, prefix + " ");
diff --git a/core/java/android/app/DevicePolicyManager.java b/core/java/android/app/DevicePolicyManager.java
index d611807..0e8c1ab 100644
--- a/core/java/android/app/DevicePolicyManager.java
+++ b/core/java/android/app/DevicePolicyManager.java
@@ -49,13 +49,18 @@
private final Handler mHandler;
- /*package*/ DevicePolicyManager(Context context, Handler handler) {
+ private DevicePolicyManager(Context context, Handler handler) {
mContext = context;
mHandler = handler;
mService = IDevicePolicyManager.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
}
+ /*package*/ static DevicePolicyManager create(Context context, Handler handler) {
+ DevicePolicyManager me = new DevicePolicyManager(context, handler);
+ return me.mService != null ? me : null;
+ }
+
/**
* Activity action: ask the user to add a new device administrator to the system.
* The desired policy is the ComponentName of the policy in the
@@ -133,6 +138,20 @@
}
/**
+ * @hide
+ */
+ public boolean packageHasActiveAdmins(String packageName) {
+ if (mService != null) {
+ try {
+ return mService.packageHasActiveAdmins(packageName);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
+ /**
* Remove a current administration component. This can only be called
* by the application that owns the administration component; if you
* try to remove someone else's component, a security exception will be
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 4598bb5..0ed5eb8 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -822,13 +822,15 @@
final SearchManager searchManager = (SearchManager) mContext
.getSystemService(Context.SEARCH_SERVICE);
- // associate search with owner activity if possible (otherwise it will default to
- // global search).
+ // associate search with owner activity
final ComponentName appName = getAssociatedActivity();
- final boolean globalSearch = (appName == null);
- searchManager.startSearch(null, false, appName, null, globalSearch);
- dismiss();
- return true;
+ if (appName != null) {
+ searchManager.startSearch(null, false, appName, null, false);
+ dismiss();
+ return true;
+ } else {
+ return false;
+ }
}
/**
diff --git a/core/java/android/app/IDevicePolicyManager.aidl b/core/java/android/app/IDevicePolicyManager.aidl
index ae5c4bf..b138720 100644
--- a/core/java/android/app/IDevicePolicyManager.aidl
+++ b/core/java/android/app/IDevicePolicyManager.aidl
@@ -49,6 +49,7 @@
void setActiveAdmin(in ComponentName policyReceiver);
boolean isAdminActive(in ComponentName policyReceiver);
List<ComponentName> getActiveAdmins();
+ boolean packageHasActiveAdmins(String packageName);
void getRemoveWarning(in ComponentName policyReceiver, in RemoteCallback result);
void removeActiveAdmin(in ComponentName policyReceiver);
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
index 9ba7863..cb03d2c 100644
--- a/core/java/android/app/ISearchManager.aidl
+++ b/core/java/android/app/ISearchManager.aidl
@@ -24,9 +24,8 @@
/** @hide */
interface ISearchManager {
- SearchableInfo getSearchableInfo(in ComponentName launchActivity, boolean globalSearch);
+ SearchableInfo getSearchableInfo(in ComponentName launchActivity);
List<SearchableInfo> getSearchablesInGlobalSearch();
- List<SearchableInfo> getSearchablesForWebSearch();
- SearchableInfo getDefaultSearchableForWebSearch();
- void setDefaultWebSearch(in ComponentName component);
+ ComponentName getGlobalSearchActivity();
+ ComponentName getWebSearchActivity();
}
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 581b436..6a02a58 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -274,7 +274,7 @@
SearchManager searchManager = (SearchManager)
mContext.getSystemService(Context.SEARCH_SERVICE);
// Try to get the searchable info for the provided component.
- mSearchable = searchManager.getSearchableInfo(componentName, false);
+ mSearchable = searchManager.getSearchableInfo(componentName);
if (mSearchable == null) {
return false;
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 52cdc74..b54e53d 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -137,21 +137,11 @@
* setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); // search within your activity
* setDefaultKeyMode(DEFAULT_KEYS_SEARCH_GLOBAL); // search using platform global search</pre>
*
- * <p><b>How to enable global search with Quick Search Box.</b> In addition to searching within
+ * <p><b>How to start global search.</b> In addition to searching within
* your activity or application, you can also use the Search Manager to invoke a platform-global
- * search, which uses Quick Search Box to search across the device and the web. There are two ways
- * to do this:
- * <ul><li>You can simply define "search" within your application or activity to mean global search.
- * This is described in more detail in the
- * <a href="#SearchabilityMetadata">Searchability Metadata</a> section. Briefly, you will
- * add a single meta-data entry to your manifest, declaring that the default search
- * for your application is "*". This indicates to the system that no application-specific
- * search activity is provided, and that it should launch web-based search instead.</li>
- * <li>Simply do nothing and the default implementation of
- * {@link android.app.Activity#onSearchRequested} will cause global search to be triggered.
- * (You can also always trigger search via a direct call to {@link android.app.Activity#startSearch}.
- * This is most useful if you wish to provide local searchability <i>and</i> access to global
- * search.)</li></ul>
+ * search, which uses Quick Search Box to search across the device and the web.
+ * Override {@link android.app.Activity#onSearchRequested} and call
+ * {@link android.app.Activity#startSearch} with {@code globalSearch} set to {@code true}.
*
* <p><b>How to disable search from your activity.</b> Search is a system-wide feature and users
* will expect it to be available in all contexts. If your UI design absolutely precludes
@@ -871,12 +861,8 @@
*
* <p>The simplest way to specify this is to add a <i>search reference</i> element to the
* application entry in the <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> file.
- * The value of this reference can be either of:
- * <ul><li>The name of your searchable activity.
- * It is typically prefixed by '.' to indicate that it's in the same package.</li>
- * <li>A "*" indicates that the system may select a default searchable activity, in which
- * case it will typically select web-based search.</li>
- * </ul>
+ * The value of this reference should be the name of your searchable activity.
+ * It is typically prefixed by '.' to indicate that it's in the same package.
*
* <p>Here is a snippet showing the necessary addition to the manifest entry for your
* non-searchable activities.
@@ -1639,6 +1625,7 @@
return;
}
Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(globalSearchActivity);
// TODO: Always pass name of calling package as an extra?
if (appSearchData != null) {
@@ -1661,32 +1648,33 @@
/**
* Gets the name of the global search activity.
*
- * This is currently implemented by returning the first activity that handles
- * the GLOBAL_SEARCH intent and has the GLOBAL_SEARCH permission. If we allow
- * more than one global search acitivity to be installed, this code must be changed.
- *
- * TODO: Doing this every time we start global search is inefficient. Will fix that once
- * we have settled on the right mechanism for finding the global search activity.
- *
* @hide
*/
public ComponentName getGlobalSearchActivity() {
- Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH);
- PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> activities =
- pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
- int count = activities.size();
- for (int i = 0; i < count; i++) {
- ActivityInfo ai = activities.get(i).activityInfo;
- if (pm.checkPermission(Manifest.permission.GLOBAL_SEARCH,
- ai.packageName) == PackageManager.PERMISSION_GRANTED) {
- return new ComponentName(ai.packageName, ai.name);
- } else {
- Log.w(TAG, "Package " + ai.packageName + " wants to handle GLOBAL_SEARCH, "
- + "but does not have the GLOBAL_SEARCH permission.");
- }
+ try {
+ return mService.getGlobalSearchActivity();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getGlobalSearchActivity() failed: " + ex);
+ return null;
}
- return null;
+ }
+
+ /**
+ * Gets the name of the web search activity.
+ *
+ * @return The name of the default activity for web searches. This activity
+ * can be used to get web search suggestions. Returns {@code null} if
+ * there is no default web search activity.
+ *
+ * @hide
+ */
+ public ComponentName getWebSearchActivity() {
+ try {
+ return mService.getWebSearchActivity();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getWebSearchActivity() failed: " + ex);
+ return null;
+ }
}
/**
@@ -1820,27 +1808,7 @@
*/
public SearchableInfo getSearchableInfo(ComponentName componentName) {
try {
- return mService.getSearchableInfo(componentName, false);
- } catch (RemoteException ex) {
- Log.e(TAG, "getSearchableInfo() failed: " + ex);
- return null;
- }
- }
-
- /**
- * Gets information about a searchable activity.
- *
- * @param componentName The activity to get searchable information for.
- * @param globalSearch If <code>false</code>, return information about the given activity.
- * If <code>true</code>, return information about the global search activity.
- * @return Searchable information, or <code>null</code> if the activity is not searchable.
- *
- * @hide because SearchableInfo is not part of the API.
- */
- public SearchableInfo getSearchableInfo(ComponentName componentName,
- boolean globalSearch) {
- try {
- return mService.getSearchableInfo(componentName, globalSearch);
+ return mService.getSearchableInfo(componentName);
} catch (RemoteException ex) {
Log.e(TAG, "getSearchableInfo() failed: " + ex);
return null;
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 8ec5bd4..6767332 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -56,6 +56,8 @@
* <li><a href="#ServiceLifecycle">Service Lifecycle</a>
* <li><a href="#Permissions">Permissions</a>
* <li><a href="#ProcessLifecycle">Process Lifecycle</a>
+ * <li><a href="#LocalServiceSample">Local Service Sample</a>
+ * <li><a href="#RemoteMessengerServiceSample">Remote Messenger Service Sample</a>
* </ol>
*
* <a name="ServiceLifecycle"></a>
@@ -166,6 +168,64 @@
* (such as an {@link android.app.Activity}) can, of course, increase the
* importance of the overall
* process beyond just the importance of the service itself.
+ *
+ * <a name="LocalServiceSample"></a>
+ * <h3>Local Service Sample</h3>
+ *
+ * <p>One of the most common uses of a Service is as a secondary component
+ * running alongside other parts of an application, in the same process as
+ * the rest of the components. All components of an .apk run in the same
+ * process unless explicitly stated otherwise, so this is a typical situation.
+ *
+ * <p>When used in this way, by assuming the
+ * components are in the same process, you can greatly simplify the interaction
+ * between them: clients of the service can simply cast the IBinder they
+ * receive from it to a concrete class published by the service.
+ *
+ * <p>An example of this use of a Service is shown here. First is the Service
+ * itself, publishing a custom class when bound:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LocalService.java
+ * service}
+ *
+ * <p>With that done, one can now write client code that directly accesses the
+ * running service, such as:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LocalServiceActivities.java
+ * bind}
+ *
+ * <a name="RemoteMessengerServiceSample"></a>
+ * <h3>Remote Messenger Service Sample</h3>
+ *
+ * <p>If you need to be able to write a Service that can perform complicated
+ * communication with clients in remote processes (beyond simply the use of
+ * {@link Context#startService(Intent) Context.startService} to send
+ * commands to it), then you can use the {@link android.os.Messenger} class
+ * instead of writing full AIDL files.
+ *
+ * <p>An example of a Service that uses Messenger as its client interface
+ * is shown here. First is the Service itself, publishing a Messenger to
+ * an internal Handler when bound:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/MessengerService.java
+ * service}
+ *
+ * <p>If we want to make this service run in a remote process (instead of the
+ * standard one for its .apk), we can use <code>android:process</code> in its
+ * manifest tag to specify one:
+ *
+ * {@sample development/samples/ApiDemos/AndroidManifest.xml remote_service_declaration}
+ *
+ * <p>Note that the name "remote" chosen here is arbitrary, and you can use
+ * other names if you want additional processes. The ':' prefix appends the
+ * name to your package's standard process name.
+ *
+ * <p>With that done, clients can now bind to the service and send messages
+ * to it. Note that this allows clients to register with it to receive
+ * messages back as well:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/MessengerServiceActivities.java
+ * bind}
*/
public abstract class Service extends ContextWrapper implements ComponentCallbacks {
private static final String TAG = "Service";
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
new file mode 100644
index 0000000..aca8ab4
--- /dev/null
+++ b/core/java/android/app/UiModeManager.java
@@ -0,0 +1,83 @@
+package android.app;
+
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * This class provides access to the system uimode services. These services
+ * allow applications to control UI modes of the device.
+ * It provides functionality to disable the car mode and it gives access to the
+ * night mode settings.
+ *
+ * <p>You do not instantiate this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService
+ * Context.getSystemService(Context.UI_MODE_SERVICE)}.
+ */
+public class UiModeManager {
+ private static final String TAG = "UiModeManager";
+
+ public static final int MODE_NOTNIGHT = 1;
+ public static final int MODE_NIGHT = 2;
+ public static final int MODE_AUTO = 3;
+
+ private IUiModeManager mService;
+
+ /*package*/ UiModeManager() {
+ mService = IUiModeManager.Stub.asInterface(
+ ServiceManager.getService("uimode"));
+ }
+
+ /**
+ * Disables the car mode.
+ */
+ public void disableCarMode() {
+ if (mService != null) {
+ try {
+ mService.disableCarMode();
+ } catch (RemoteException e) {
+ Log.e(TAG, "disableCarMode: RemoteException", e);
+ }
+ }
+ }
+
+ /**
+ * Sets the night mode. Changes to the night mode are only effective when
+ * the car mode is enabled on a device.
+ *
+ * <p>The mode can be one of:
+ * <ul>
+ * <li><em>{@link #MODE_NOTNIGHT}<em> - sets the device into notnight
+ * mode.</li>
+ * <li><em>{@link #MODE_NIGHT}</em> - sets the device into night mode.
+ * </li>
+ * <li><em>{@link #MODE_AUTO}</em> - automatic night/notnight switching
+ * depending on the location and certain other sensors.</li>
+ */
+ public void setNightMode(int mode) {
+ if (mService != null) {
+ try {
+ mService.setNightMode(mode);
+ } catch (RemoteException e) {
+ Log.e(TAG, "setNightMode: RemoteException", e);
+ }
+ }
+ }
+
+ /**
+ * Returns the currently configured night mode.
+ *
+ * @return {@link #MODE_NOTNIGHT}, {@link #MODE_NIGHT} or {@link #MODE_AUTO}
+ * When an error occurred -1 is returned.
+ */
+ public int getNightMode() {
+ if (mService != null) {
+ try {
+ return mService.getNightMode();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getNightMode: RemoteException", e);
+ }
+ }
+ return -1;
+ }
+}
diff --git a/core/java/android/backup/AbsoluteFileBackupHelper.java b/core/java/android/backup/AbsoluteFileBackupHelper.java
index 6bf848f..5a8034b 100644
--- a/core/java/android/backup/AbsoluteFileBackupHelper.java
+++ b/core/java/android/backup/AbsoluteFileBackupHelper.java
@@ -21,7 +21,6 @@
import android.util.Log;
import java.io.File;
-import java.io.FileDescriptor;
/**
* Like FileBackupHelper, but takes absolute paths for the files instead of
diff --git a/core/java/android/backup/BackupDataInput.java b/core/java/android/backup/BackupDataInput.java
index 295dc66..a08ee75 100644
--- a/core/java/android/backup/BackupDataInput.java
+++ b/core/java/android/backup/BackupDataInput.java
@@ -16,8 +16,6 @@
package android.backup;
-import android.content.Context;
-
import java.io.FileDescriptor;
import java.io.IOException;
diff --git a/core/java/android/backup/BackupDataOutput.java b/core/java/android/backup/BackupDataOutput.java
index 672d01f..34879d8 100644
--- a/core/java/android/backup/BackupDataOutput.java
+++ b/core/java/android/backup/BackupDataOutput.java
@@ -16,8 +16,6 @@
package android.backup;
-import android.content.Context;
-
import java.io.FileDescriptor;
import java.io.IOException;
diff --git a/core/java/android/backup/BackupHelper.java b/core/java/android/backup/BackupHelper.java
index fc48cf2..7eedd01 100644
--- a/core/java/android/backup/BackupHelper.java
+++ b/core/java/android/backup/BackupHelper.java
@@ -18,16 +18,19 @@
import android.os.ParcelFileDescriptor;
-import java.io.InputStream;
-
/**
- * STOPSHIP: document!
+ * A convenient interface to be used with the
+ * {@link android.backup.BackupHelperAgent} to implement backup and restore of
+ * arbitrary data types.
+ * <p>
+ * STOPSHOP: document!
*/
public interface BackupHelper {
/**
- * Based on oldState, determine which of the files from the application's data directory
- * need to be backed up, write them to the data stream, and fill in newState with the
- * state as it exists now.
+ * Based on <code>oldState</code>, determine which of the files from the
+ * application's data directory need to be backed up, write them to
+ * <code>data</code>, and fill in <code>newState</code> with the state as it
+ * exists now.
*/
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState);
diff --git a/core/java/android/backup/BackupHelperAgent.java b/core/java/android/backup/BackupHelperAgent.java
index dc17154f..7fb44f4 100644
--- a/core/java/android/backup/BackupHelperAgent.java
+++ b/core/java/android/backup/BackupHelperAgent.java
@@ -17,24 +17,19 @@
package android.backup;
import android.app.BackupAgent;
-import android.backup.BackupHelper;
-import android.backup.BackupHelperDispatcher;
-import android.backup.BackupDataInput;
-import android.backup.BackupDataOutput;
import android.os.ParcelFileDescriptor;
-import android.util.Log;
import java.io.IOException;
/**
- * A convenient BackupAgent wrapper class that automatically manages heterogeneous
- * data sets within the backup data, each identified by a unique key prefix. An
- * application will typically extend this class in their own backup agent. Then,
- * within the agent's onBackup() and onRestore() methods, it will call
- * {@link #addHelper(String, BackupHelper)} one or more times to specify the data
- * sets, then invoke super.onBackup() or super.onRestore() to have the BackupHelperAgent
- * implementation process the data.
- *
+ * A convenient BackupAgent wrapper class that automatically manages
+ * heterogeneous data sets within the backup data, each identified by a unique
+ * key prefix. An application will typically extend this class in their own
+ * backup agent. Then, within the agent's onBackup() and onRestore() methods, it
+ * will call {@link #addHelper(String, BackupHelper)} one or more times to
+ * specify the data sets, then invoke super.onBackup() or super.onRestore() to
+ * have the BackupHelperAgent implementation process the data.
+ * <p>
* STOPSHIP: document!
*/
public class BackupHelperAgent extends BackupAgent {
diff --git a/core/java/android/backup/BackupHelperDispatcher.java b/core/java/android/backup/BackupHelperDispatcher.java
index bf2c44d..68076db 100644
--- a/core/java/android/backup/BackupHelperDispatcher.java
+++ b/core/java/android/backup/BackupHelperDispatcher.java
@@ -19,12 +19,10 @@
import android.os.ParcelFileDescriptor;
import android.util.Log;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
import java.io.FileDescriptor;
-import java.util.TreeMap;
+import java.io.IOException;
import java.util.Map;
+import java.util.TreeMap;
/** @hide */
public class BackupHelperDispatcher {
diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java
index 4bf59eb..2dff975 100644
--- a/core/java/android/backup/BackupManager.java
+++ b/core/java/android/backup/BackupManager.java
@@ -23,33 +23,40 @@
import android.util.Log;
/**
- * BackupManager is the interface to the system's backup service.
- * Applications simply instantiate one, and then use that instance
- * to communicate with the backup infrastructure.
- *
- * <p>When your application has made changes to data it wishes to have
- * backed up, call {@link #dataChanged()} to notify the backup service.
- * The system will then schedule a backup operation to occur in the near
- * future. Repeated calls to {@link #dataChanged()} have no further effect
- * until the backup operation actually occurs.
- *
- * <p>The backup operation itself begins with the system launching the
- * {@link android.app.BackupAgent} subclass declared in your manifest. See the
+ * BackupManager is the interface to the system's backup service. Applications
+ * simply instantiate one, and then use that instance to communicate with the
+ * backup infrastructure.
+ * <p>
+ * When an application has made changes to data which should be backed up, a
+ * call to {@link #dataChanged()} will notify the backup service. The system
+ * will then schedule a backup operation to occur in the near future. Repeated
+ * calls to {@link #dataChanged()} have no further effect until the backup
+ * operation actually occurs.
+ * <p>
+ * The backup operation itself begins with the system launching the
+ * {@link android.app.BackupAgent} subclass declared in your manifest. See the
* documentation for {@link android.app.BackupAgent} for a detailed description
* of how the backup then proceeds.
- *
- * <p>STOPSHIP more documentation here! Include the attributes:
- * android:backupAgent
- * android:allowBackup
- * android:restoreNeedsApplication
- * android:killAfterRestore
+ * <p>
+ * A simple implementation of a BackupAgent useful for backing up Preferences
+ * and files is available by using {@link android.backup.BackupHelperAgent}.
+ * <p>
+ * STOPSHIP: more documentation!
+ * <p>
+ * <b>XML attributes</b>
+ * <p>
+ * See {@link android.R.styleable#AndroidManifestApplication
+ * AndroidManifest.xml's application attributes}
+ *
+ * @attr ref android.R.styleable#AndroidManifestApplication_allowBackup
+ * @attr ref android.R.styleable#AndroidManifestApplication_backupAgent
+ * @attr ref
+ * android.R.styleable#AndroidManifestApplication_restoreNeedsApplication
+ * @attr ref android.R.styleable#AndroidManifestApplication_killAfterRestore
*/
public class BackupManager {
private static final String TAG = "BackupManager";
- /** @hide TODO: REMOVE THIS */
- public static final boolean EVEN_THINK_ABOUT_DOING_RESTORE = true;
-
private Context mContext;
private static IBackupManager sService;
@@ -78,9 +85,6 @@
* {@link android.app.BackupAgent} subclass will be scheduled when you call this method.
*/
public void dataChanged() {
- if (!EVEN_THINK_ABOUT_DOING_RESTORE) {
- return;
- }
checkServiceBinder();
if (sService != null) {
try {
@@ -100,9 +104,6 @@
* permission if the package named in the argument is not the caller's own.
*/
public static void dataChanged(String packageName) {
- if (!EVEN_THINK_ABOUT_DOING_RESTORE) {
- return;
- }
checkServiceBinder();
if (sService != null) {
try {
@@ -118,9 +119,6 @@
* {@link android.backup.RestoreSession} class for documentation on that process.
*/
public RestoreSession beginRestoreSession() {
- if (!EVEN_THINK_ABOUT_DOING_RESTORE) {
- return null;
- }
RestoreSession session = null;
checkServiceBinder();
if (sService != null) {
diff --git a/core/java/android/backup/FileBackupHelper.java b/core/java/android/backup/FileBackupHelper.java
index 68b4d42..cc859e2 100644
--- a/core/java/android/backup/FileBackupHelper.java
+++ b/core/java/android/backup/FileBackupHelper.java
@@ -21,10 +21,23 @@
import android.util.Log;
import java.io.File;
-import java.io.FileDescriptor;
/**
- * STOPSHIP: document! [manages backup of a set of files; restore is totally opaque]
+ * A helper class which can be used in conjunction with
+ * {@link android.backup.BackupHelperAgent} to manage the backup of a set of
+ * files. Whenever backup is performed, all files changed since the last backup
+ * will be saved in their entirety. During the first time the backup happens,
+ * all the files in the list will be backed up. Note that this should only be
+ * used with small configuration files and not with large binary files.
+ * <p>
+ * Any files not present in the list of files during the restore procedure will
+ * be ignored. If files present in a previous version of an application are
+ * removed in subsequent versions, it is the responsibility of the developer to
+ * design a mechanism to remove those files. Otherwise files no longer needed
+ * will linger and consume space on the device.
+ * <p>
+ * STOPSHIP: document! [manages backup of a set of files; restore is totally
+ * opaque]
*/
public class FileBackupHelper extends FileBackupHelperBase implements BackupHelper {
private static final String TAG = "FileBackupHelper";
@@ -50,9 +63,16 @@
}
/**
- * Based on oldState, determine which of the files from the application's data directory
- * need to be backed up, write them to the data stream, and fill in newState with the
- * state as it exists now.
+ * Based on <code>oldState</code>, determine which of the files from the
+ * application's data directory need to be backed up, write them to the data
+ * stream, and fill in <code>newState</code> with the state as it exists
+ * now. When <code>oldState</code> is <code>null</code>, all the files will
+ * be backed up.
+ * <p>
+ * This should be called from {@link android.backup.BackupHelperAgent}
+ * directly. See
+ * {@link android.app.BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)}
+ * for a description of parameter meanings.
*/
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) {
diff --git a/core/java/android/backup/FileBackupHelperBase.java b/core/java/android/backup/FileBackupHelperBase.java
index a0ff38b..7cb1ccc 100644
--- a/core/java/android/backup/FileBackupHelperBase.java
+++ b/core/java/android/backup/FileBackupHelperBase.java
@@ -22,10 +22,12 @@
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
+/**
+ * Base class for the {@link android.backup.FileBackupHelper} implementation.
+ */
class FileBackupHelperBase {
- private static final String TAG = "RestoreHelperBase";
+ private static final String TAG = "FileBackupHelperBase";
int mPtr;
Context mContext;
@@ -45,8 +47,8 @@
}
/**
- * Check the parameters so the native code doens't have to throw all the exceptions
- * since it's easier to do that from java.
+ * Check the parameters so the native code doesn't have to throw all the exceptions
+ * since it's easier to do that from Java.
*/
static void performBackup_checked(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState, String[] files, String[] keys) {
diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl
index bf6c79f..d94b066 100644
--- a/core/java/android/backup/IBackupManager.aidl
+++ b/core/java/android/backup/IBackupManager.aidl
@@ -62,6 +62,12 @@
void agentDisconnected(String packageName);
/**
+ * Notify the Backup Manager Service that an application being installed will
+ * need a data-restore pass. This method is only invoked by the Package Manager.
+ */
+ void restoreAtInstall(String packageName, int token);
+
+ /**
* Enable/disable the backup service entirely. When disabled, no backup
* or restore operations will take place. Data-changed notifications will
* still be observed and collected, however, so that changes made while the
diff --git a/core/java/android/backup/SharedPreferencesBackupHelper.java b/core/java/android/backup/SharedPreferencesBackupHelper.java
index f9c97a3..7ba80db 100644
--- a/core/java/android/backup/SharedPreferencesBackupHelper.java
+++ b/core/java/android/backup/SharedPreferencesBackupHelper.java
@@ -17,13 +17,19 @@
package android.backup;
import android.content.Context;
+import android.content.SharedPreferences;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.File;
-import java.io.FileDescriptor;
/**
+ * A helper class which can be used in conjunction with
+ * {@link android.backup.BackupHelperAgent} to manage the backup of
+ * {@link android.content.SharedPreferences}. Whenever backup is performed it
+ * will back up all named shared preferences which have changed since the last
+ * backup.
+ * <p>
* STOPSHIP: document!
*/
public class SharedPreferencesBackupHelper extends FileBackupHelperBase implements BackupHelper {
diff --git a/core/java/android/backup/package.html b/core/java/android/backup/package.html
new file mode 100644
index 0000000..13c0eb8
--- /dev/null
+++ b/core/java/android/backup/package.html
@@ -0,0 +1,22 @@
+<HTML>
+<BODY>
+<p>Package containing the backup and restore functionality available to
+applications. All backup management is controlled through
+{@link android.backup.BackupManager}. Individual backup functionality is
+implemented through a subclass {@link android.app.BackupAgent} and a
+simple and easy-to-use implementation is provided in
+{@link android.backup.BackupHelperAgent}.</p>
+
+<p>STOPSHIP: add more documenation and remove Dev Guide link if not written!</p>
+
+<p>The backup APIs let applications:</p>
+<ul>
+ <li>Perform backup of arbitrary data</li>
+ <li>Easily perform backup of Preferences and files</li>
+ <li>Handle restore of data</li>
+</ul>
+
+<p>For a detailed guide to using the backup APIs, see the <a
+href="{@docRoot}guide/topics/backup.html">Backup Dev Guide topic</a>.</p>
+</BODY>
+</HTML>
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index cf9c58f..e77e76f 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -664,6 +664,10 @@
* determine which channel to connect to.
* <p>The remote device will be authenticated and communication on this
* socket will be encrypted.
+ * <p>Hint: If you are connecting to a Bluetooth serial board then try
+ * using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB.
+ * However if you are connecting to an Android peer then please generate
+ * your own unique UUID.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
* @param uuid service record uuid to lookup RFCOMM channel
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index b792965..251813e 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -112,11 +112,9 @@
/** Default priority when not set or when the device is unpaired */
public static final int PRIORITY_UNDEFINED = -1;
- /** The voice dialer 'works' but the user experience is poor. The voice
- * recognizer has trouble dealing with the 8kHz SCO signal, and it still
- * requires visual confirmation. Disable for cupcake.
- */
- public static final boolean DISABLE_BT_VOICE_DIALING = true;
+ /** Set this to true to prevent the bluetooth headset from
+ * activating the VoiceDialer. */
+ public static final boolean DISABLE_BT_VOICE_DIALING = false;
/**
* An interface for notifying BluetoothHeadset IPC clients when they have
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index c4ba05d..7ca0f01 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -126,7 +126,7 @@
}
/**
- * The samee as {@link #flattenToString()}, but abbreviates the class
+ * The same as {@link #flattenToString()}, but abbreviates the class
* name if it is a suffix of the package. The result can still be used
* with {@link #unflattenFromString(String)}.
*
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index ca36df2..7945f3f 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -21,6 +21,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
@@ -46,6 +47,8 @@
private final Map<Integer, Integer> mSelectionArgsBackReferences;
private final boolean mYieldAllowed;
+ private final static String TAG = "ContentProviderOperation";
+
/**
* Creates a {@link ContentProviderOperation} by copying the contents of a
* {@link Builder}.
@@ -241,6 +244,7 @@
final String expectedValue = values.getAsString(projection[i]);
if (!TextUtils.equals(cursorValue, expectedValue)) {
// Throw exception when expected values don't match
+ Log.e(TAG, this.toString());
throw new OperationApplicationException("Found value " + cursorValue
+ " when expected " + expectedValue + " for column "
+ projection[i]);
@@ -252,10 +256,12 @@
cursor.close();
}
} else {
+ Log.e(TAG, this.toString());
throw new IllegalStateException("bad type, " + mType);
}
if (mExpectedCount != null && mExpectedCount != numRows) {
+ Log.e(TAG, this.toString());
throw new OperationApplicationException("wrong number of rows: " + numRows);
}
@@ -289,6 +295,7 @@
String key = entry.getKey();
Integer backRefIndex = mValuesBackReferences.getAsInteger(key);
if (backRefIndex == null) {
+ Log.e(TAG, this.toString());
throw new IllegalArgumentException("values backref " + key + " is not an integer");
}
values.put(key, backRefToValue(backRefs, numBackRefs, backRefIndex));
@@ -326,6 +333,17 @@
return newArgs;
}
+ @Override
+ public String toString() {
+ return "mType: " + mType + ", mUri: " + mUri +
+ ", mSelection: " + mSelection +
+ ", mExpectedCount: " + mExpectedCount +
+ ", mYieldAllowed: " + mYieldAllowed +
+ ", mValues: " + mValues +
+ ", mValuesBackReferences: " + mValuesBackReferences +
+ ", mSelectionArgsBackReferences: " + mSelectionArgsBackReferences;
+ }
+
/**
* Return the string representation of the requested back reference.
* @param backRefs an array of results
@@ -335,9 +353,10 @@
* the numBackRefs
* @return the string representation of the requested back reference.
*/
- private static long backRefToValue(ContentProviderResult[] backRefs, int numBackRefs,
+ private long backRefToValue(ContentProviderResult[] backRefs, int numBackRefs,
Integer backRefIndex) {
if (backRefIndex >= numBackRefs) {
+ Log.e(TAG, this.toString());
throw new ArrayIndexOutOfBoundsException("asked for back ref " + backRefIndex
+ " but there are only " + numBackRefs + " back refs");
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 45f361e..29f388a 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -167,9 +167,9 @@
/** @hide */
public static final int SYNC_OBSERVER_TYPE_ALL = 0x7fffffff;
- // Always log queries which take 100ms+; shorter queries are
+ // Always log queries which take 500ms+; shorter queries are
// sampled accordingly.
- private static final int SLOW_THRESHOLD_MILLIS = 100;
+ private static final int SLOW_THRESHOLD_MILLIS = 500;
private final Random mRandom = new Random(); // guarded by itself
public ContentResolver(Context context) {
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 532cc03..c04625d 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -39,14 +39,14 @@
* Creates an empty set of values using the default initial size
*/
public ContentValues() {
- // Choosing a default size of 8 based on analysis of typical
+ // Choosing a default size of 8 based on analysis of typical
// consumption by applications.
mValues = new HashMap<String, Object>(8);
}
/**
* Creates an empty set of values using the given initial size
- *
+ *
* @param size the initial size of the set of values
*/
public ContentValues(int size) {
@@ -55,7 +55,7 @@
/**
* Creates a set of values copied from the given set
- *
+ *
* @param from the values to copy
*/
public ContentValues(ContentValues from) {
@@ -65,7 +65,7 @@
/**
* Creates a set of values copied from the given HashMap. This is used
* by the Parcel unmarshalling code.
- *
+ *
* @param from the values to start with
* {@hide}
*/
@@ -174,7 +174,7 @@
public void put(String key, Boolean value) {
mValues.put(key, value);
}
-
+
/**
* Adds a value to the set.
*
@@ -187,7 +187,7 @@
/**
* Adds a null value to the set.
- *
+ *
* @param key the name of the value to make null
*/
public void putNull(String key) {
@@ -196,7 +196,7 @@
/**
* Returns the number of values.
- *
+ *
* @return the number of values
*/
public int size() {
@@ -223,7 +223,7 @@
* Returns true if this object has the named value.
*
* @param key the value to check for
- * @return {@code true} if the value is present, {@code false} otherwise
+ * @return {@code true} if the value is present, {@code false} otherwise
*/
public boolean containsKey(String key) {
return mValues.containsKey(key);
@@ -242,7 +242,7 @@
/**
* Gets a value and converts it to a String.
- *
+ *
* @param key the value to get
* @return the String for the value
*/
@@ -253,7 +253,7 @@
/**
* Gets a value and converts it to a Long.
- *
+ *
* @param key the value to get
* @return the Long value, or null if the value is missing or cannot be converted
*/
@@ -270,7 +270,7 @@
return null;
}
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Long");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Long: " + value, e);
return null;
}
}
@@ -278,7 +278,7 @@
/**
* Gets a value and converts it to an Integer.
- *
+ *
* @param key the value to get
* @return the Integer value, or null if the value is missing or cannot be converted
*/
@@ -295,7 +295,7 @@
return null;
}
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Integer");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Integer: " + value, e);
return null;
}
}
@@ -303,7 +303,7 @@
/**
* Gets a value and converts it to a Short.
- *
+ *
* @param key the value to get
* @return the Short value, or null if the value is missing or cannot be converted
*/
@@ -320,7 +320,7 @@
return null;
}
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Short");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Short: " + value, e);
return null;
}
}
@@ -328,7 +328,7 @@
/**
* Gets a value and converts it to a Byte.
- *
+ *
* @param key the value to get
* @return the Byte value, or null if the value is missing or cannot be converted
*/
@@ -345,7 +345,7 @@
return null;
}
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Byte");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Byte: " + value, e);
return null;
}
}
@@ -353,7 +353,7 @@
/**
* Gets a value and converts it to a Double.
- *
+ *
* @param key the value to get
* @return the Double value, or null if the value is missing or cannot be converted
*/
@@ -370,7 +370,7 @@
return null;
}
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Double");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Double: " + value, e);
return null;
}
}
@@ -378,7 +378,7 @@
/**
* Gets a value and converts it to a Float.
- *
+ *
* @param key the value to get
* @return the Float value, or null if the value is missing or cannot be converted
*/
@@ -395,7 +395,7 @@
return null;
}
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Float");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Float: " + value, e);
return null;
}
}
@@ -403,7 +403,7 @@
/**
* Gets a value and converts it to a Boolean.
- *
+ *
* @param key the value to get
* @return the Boolean value, or null if the value is missing or cannot be converted
*/
@@ -415,7 +415,7 @@
if (value instanceof CharSequence) {
return Boolean.valueOf(value.toString());
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Boolean");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Boolean: " + value, e);
return null;
}
}
@@ -424,7 +424,7 @@
/**
* Gets a value that is a byte array. Note that this method will not convert
* any other types to byte arrays.
- *
+ *
* @param key the value to get
* @return the byte[] value, or null is the value is missing or not a byte[]
*/
@@ -439,13 +439,13 @@
/**
* Returns a set of all of the keys and values
- *
+ *
* @return a set of all of the keys and values
*/
public Set<Map.Entry<String, Object>> valueSet() {
return mValues.entrySet();
}
-
+
public static final Parcelable.Creator<ContentValues> CREATOR =
new Parcelable.Creator<ContentValues>() {
@SuppressWarnings({"deprecation", "unchecked"})
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 672e5f7..897d702 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1204,6 +1204,8 @@
* <dt> {@link #INPUT_METHOD_SERVICE} ("input_method")
* <dd> An {@link android.view.inputmethod.InputMethodManager InputMethodManager}
* for management of input methods.
+ * <dt> {@link #UI_MODE_SERVICE} ("uimode")
+ * <dd> An {@link android.app.UiModeManager} for controlling UI modes.
* </dl>
*
* <p>Note: System services obtained via this API may be closely associated with
@@ -1249,6 +1251,8 @@
* @see android.telephony.TelephonyManager
* @see #INPUT_METHOD_SERVICE
* @see android.view.inputmethod.InputMethodManager
+ * @see #UI_MODE_SERVICE
+ * @see android.app.UiModeManager
*/
public abstract Object getSystemService(String name);
@@ -1511,6 +1515,14 @@
public static final String DEVICE_POLICY_SERVICE = "device_policy";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.UiModeManager} for controlling UI modes.
+ *
+ * @see #getSystemService
+ */
+ public static final String UI_MODE_SERVICE = "uimode";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index d31b25b..607605d 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1316,6 +1316,21 @@
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
/**
+ * @hide
+ * Broadcast Action: Ask system services if there is any reason to
+ * restart the given package. The data contains the name of the
+ * package.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+ * <li> {@link #EXTRA_PACKAGES} String array of all packages to check.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
+ /**
* Broadcast Action: The user has restarted a package, and all of its
* processes have been killed. All runtime state
* associated with it (processes, alarms, notifications, etc) should
@@ -1815,11 +1830,18 @@
/**
* Broadcast Action: A sticky broadcast indicating the phone was docked
- * or undocked. Includes the extra
- * field {@link #EXTRA_DOCK_STATE}, containing the current dock state. It also
- * includes the boolean extra field {@link #EXTRA_CAR_MODE_ENABLED}, indicating
- * the state of the car mode.
- * This is intended for monitoring the current dock state.
+ * or undocked.
+ *
+ * <p>The intent will have the following extra values:
+ * <ul>
+ * <li><em>{@link #EXTRA_DOCK_STATE}</em> - the current dock
+ * state, which depends on the state of the car mode.</li>
+ * <li><em>{@link #EXTRA_PHYSICAL_DOCK_STATE}</em> - the physical dock
+ * state.</li>
+ * <li><em>{@link #EXTRA_CAR_MODE_ENABLED}</em> - a boolean indicating the
+ * state of the car mode.</li>
+ * </ul>
+ * <p>This is intended for monitoring the current dock state.
* To launch an activity from a dock state change, use {@link #CATEGORY_CAR_DOCK}
* or {@link #CATEGORY_DESK_DOCK} instead.
*/
@@ -2091,6 +2113,7 @@
* number to call in a {@link android.content.Intent#ACTION_CALL}.
*/
public static final String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
+
/**
* Used as an int extra field in {@link android.content.Intent#ACTION_UID_REMOVED}
* intents to supply the uid the package had been assigned. Also an optional
@@ -2101,6 +2124,11 @@
public static final String EXTRA_UID = "android.intent.extra.UID";
/**
+ * @hide String array of package names.
+ */
+ public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
+
+ /**
* Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
* intents to indicate whether this represents a full uninstall (removing
* both the code and its data) or a partial uninstall (leaving its data,
@@ -2154,6 +2182,16 @@
public static final int EXTRA_DOCK_STATE_CAR = 2;
/**
+ * Used as an int extra field in {@link android.content.Intent#ACTION_DOCK_EVENT}
+ * intents to request the physical dock state. Possible values are
+ * {@link android.content.Intent#EXTRA_DOCK_STATE_UNDOCKED},
+ * {@link android.content.Intent#EXTRA_DOCK_STATE_DESK}, or
+ * {@link android.content.Intent#EXTRA_DOCK_STATE_CAR}.
+ */
+ public static final String EXTRA_PHYSICAL_DOCK_STATE =
+ "android.intent.extra.PHYSICAL_DOCK_STATE";
+
+ /**
* Used as an boolean extra field in {@link android.content.Intent#ACTION_DOCK_EVENT}
* intents to indicate that the car mode is enabled or not.
*/
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 4ddb819..317e5a9 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -115,6 +115,11 @@
private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour
/**
+ * How long to wait before retrying a sync that failed due to one already being in progress.
+ */
+ private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10;
+
+ /**
* An error notification is sent if sync of any of the providers has been failing for this long.
*/
private static final long ERROR_NOTIFICATION_DELAY_MS = 1000 * 60 * 10; // 10 minutes
@@ -807,6 +812,14 @@
+ "it achieved some success");
}
scheduleSyncOperation(operation);
+ } else if (syncResult.syncAlreadyInProgress) {
+ if (isLoggable) {
+ Log.d(TAG, "retrying sync operation that failed because there was already a "
+ + "sync in progress: " + operation);
+ }
+ scheduleSyncOperation(new SyncOperation(operation.account, operation.syncSource,
+ operation.authority, operation.extras,
+ DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS));
} else if (syncResult.hasSoftError()) {
if (isLoggable) {
Log.d(TAG, "retrying sync operation because it encountered a soft error: "
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 123d9b7..8773f59 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -68,8 +68,6 @@
* will be null if the application does not specify it in its manifest.
*
* <p>If android:allowBackup is set to false, this attribute is ignored.
- *
- * {@hide}
*/
public String backupAgentName;
@@ -250,6 +248,19 @@
public static final int FLAG_NATIVE_DEBUGGABLE = 1<<21;
/**
+ * Value for {@link #flags}: Set to true if the application's backup
+ * agent claims to be able to handle restore data even "from the future,"
+ * i.e. from versions of the application with a versionCode greater than
+ * the one currently installed on the device.
+ *
+ * <p>If android:allowBackup is set to false or no android:backupAgent
+ * is specified, this flag will be ignored.
+ *
+ * {@hide}
+ */
+ public static final int FLAG_RESTORE_ANY_VERSION = 1<<22;
+
+ /**
* 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/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 47789a5..399a87d 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -26,6 +26,7 @@
import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
@@ -153,6 +154,8 @@
void installPackage(in Uri packageURI, IPackageInstallObserver observer, int flags,
in String installerPackageName);
+ void finishPackageInstall(int token);
+
/**
* Delete a package.
*
@@ -306,7 +309,9 @@
* MountService uses this to call into the package manager to update
* status of sdcard.
*/
- void updateExternalMediaStatus(boolean mounted);
+ boolean updateExternalMediaStatus(boolean mounted);
String nextPackageToClean(String lastPackage);
+
+ void movePackage(String packageName, IPackageMoveObserver observer, int flags);
}
diff --git a/core/java/android/content/pm/IPackageMoveObserver.aidl b/core/java/android/content/pm/IPackageMoveObserver.aidl
new file mode 100644
index 0000000..baa1595
--- /dev/null
+++ b/core/java/android/content/pm/IPackageMoveObserver.aidl
@@ -0,0 +1,27 @@
+/*
+**
+** Copyright 2007, 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;
+
+/**
+ * Callback for moving package resources from the Package Manager.
+ * @hide
+ */
+oneway interface IPackageMoveObserver {
+ void packageMoved(in String packageName, int returnCode);
+}
+
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ff2ed3d..2edb430 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -551,6 +551,69 @@
public static final int DONT_DELETE_DATA = 0x00000001;
/**
+ * Return code that is passed to the {@link IPackageMoveObserver} by
+ * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
+ * when the package has been successfully moved by the system.
+ * @hide
+ */
+ public static final int MOVE_SUCCEEDED = 1;
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} by
+ * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
+ * when the package hasn't been successfully moved by the system
+ * because of insufficient memory on specified media.
+ * @hide
+ */
+ public static final int MOVE_FAILED_INSUFFICIENT_STORAGE = -1;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} by
+ * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
+ * if the specified package doesn't exist.
+ * @hide
+ */
+ public static final int MOVE_FAILED_DOESNT_EXIST = -2;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} by
+ * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
+ * if the specified package cannot be moved since its a system package.
+ * @hide
+ */
+ public static final int MOVE_FAILED_SYSTEM_PACKAGE = -3;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} by
+ * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
+ * if the specified package cannot be moved since its forward locked.
+ * @hide
+ */
+ public static final int MOVE_FAILED_FORWARD_LOCKED = -4;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} by
+ * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
+ * if the specified package cannot be moved to the specified location.
+ * @hide
+ */
+ public static final int MOVE_FAILED_INVALID_LOCATION = -5;
+
+ /**
+ * Flag parameter for {@link #movePackage} to indicate that
+ * the package should be moved to internal storage if its
+ * been installed on external media.
+ * @hide
+ */
+ public static final int MOVE_INTERNAL = 0x00000001;
+
+ /**
+ * Flag parameter for {@link #movePackage} to indicate that
+ * the package should be moved to external media.
+ * @hide
+ */
+ public static final int MOVE_EXTERNAL_MEDIA = 0x00000002;
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has a camera facing away
* from the screen.
@@ -1153,7 +1216,9 @@
*
* @return Returns a ResolveInfo containing the final activity intent that
* was determined to be the best action. Returns null if no
- * matching activity was found.
+ * matching activity was found. If multiple matching activities are
+ * found and there is no default set, returns a ResolveInfo
+ * containing something else, such as the activity resolver.
*
* @see #MATCH_DEFAULT_ONLY
* @see #GET_INTENT_FILTERS
@@ -1920,4 +1985,23 @@
* Return whether the device has been booted into safe mode.
*/
public abstract boolean isSafeMode();
+
+ /**
+ * Attempts to move package resources from internal to external media or vice versa.
+ * Since this may take a little while, the result will
+ * be posted back to the given observer. This call may fail if the calling context
+ * lacks the {@link android.Manifest.permission#MOVE_PACKAGE} permission, if the
+ * named package cannot be found, or if the named package is a "system package".
+ *
+ * @param packageName The name of the package to delete
+ * @param observer An observer callback to get notified when the package move is
+ * complete. {@link android.content.pm.IPackageMoveObserver#packageMoved(boolean)} will be
+ * called when that happens. observer may be null to indicate that no callback is desired.
+ * @param flags To indicate install location {@link #MOVE_INTERNAL} or
+ * {@link #MOVE_EXTERNAL_MEDIA}
+ *
+ * @hide
+ */
+ public abstract void movePackage(
+ String packageName, IPackageMoveObserver observer, int flags);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5823560..98aacaa 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1369,8 +1369,8 @@
if (allowBackup) {
ai.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
- // backupAgent, killAfterRestore, and restoreNeedsApplication are only relevant
- // if backup is possible for the given application.
+ // backupAgent, killAfterRestore, restoreNeedsApplication, and restoreAnyVersion
+ // are only relevant if backup is possible for the given application.
String backupAgent = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestApplication_backupAgent);
if (backupAgent != null) {
@@ -1390,6 +1390,11 @@
false)) {
ai.flags |= ApplicationInfo.FLAG_RESTORE_NEEDS_APPLICATION;
}
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_restoreAnyVersion,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+ }
}
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 7f9a5c6..1070f08 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -24,6 +24,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.util.HashMap;
/**
* Provides access to an application's raw asset files; see {@link Resources}
@@ -59,6 +60,8 @@
private static final String TAG = "AssetManager";
private static final boolean localLOGV = Config.LOGV || false;
+ private static final boolean DEBUG_REFS = false;
+
private static final Object sSync = new Object();
private static AssetManager sSystem = null;
@@ -72,6 +75,7 @@
private int mNumRefs = 1;
private boolean mOpen = true;
+ private HashMap<Integer, RuntimeException> mRefStacks;
/**
* Create a new AssetManager containing only the basic system assets.
@@ -82,6 +86,10 @@
*/
public AssetManager() {
synchronized (this) {
+ if (DEBUG_REFS) {
+ mNumRefs = 0;
+ incRefsLocked(this.hashCode());
+ }
init();
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
ensureSystemAssets();
@@ -99,6 +107,12 @@
}
private AssetManager(boolean isSystem) {
+ if (DEBUG_REFS) {
+ synchronized (this) {
+ mNumRefs = 0;
+ incRefsLocked(this.hashCode());
+ }
+ }
init();
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
}
@@ -122,7 +136,7 @@
// + ", released=" + mReleased);
if (mOpen) {
mOpen = false;
- decRefsLocked();
+ decRefsLocked(this.hashCode());
}
}
}
@@ -298,8 +312,9 @@
}
int asset = openAsset(fileName, accessMode);
if (asset != 0) {
- mNumRefs++;
- return new AssetInputStream(asset);
+ AssetInputStream res = new AssetInputStream(asset);
+ incRefsLocked(res.hashCode());
+ return res;
}
}
throw new FileNotFoundException("Asset file: " + fileName);
@@ -389,8 +404,9 @@
}
int asset = openNonAssetNative(cookie, fileName, accessMode);
if (asset != 0) {
- mNumRefs++;
- return new AssetInputStream(asset);
+ AssetInputStream res = new AssetInputStream(asset);
+ incRefsLocked(res.hashCode());
+ return res;
}
}
throw new FileNotFoundException("Asset absolute file: " + fileName);
@@ -468,16 +484,17 @@
}
int xmlBlock = openXmlAssetNative(cookie, fileName);
if (xmlBlock != 0) {
- mNumRefs++;
- return new XmlBlock(this, xmlBlock);
+ XmlBlock res = new XmlBlock(this, xmlBlock);
+ incRefsLocked(res.hashCode());
+ return res;
}
}
throw new FileNotFoundException("Asset XML file: " + fileName);
}
- /*package*/ void xmlBlockGone() {
+ /*package*/ void xmlBlockGone(int id) {
synchronized (this) {
- decRefsLocked();
+ decRefsLocked(id);
}
}
@@ -486,20 +503,34 @@
if (!mOpen) {
throw new RuntimeException("Assetmanager has been closed");
}
- mNumRefs++;
- return newTheme();
+ int res = newTheme();
+ incRefsLocked(res);
+ return res;
}
}
/*package*/ final void releaseTheme(int theme) {
synchronized (this) {
deleteTheme(theme);
- decRefsLocked();
+ decRefsLocked(theme);
}
}
protected void finalize() throws Throwable {
- destroy();
+ try {
+ if (DEBUG_REFS && mNumRefs != 0) {
+ Log.w(TAG, "AssetManager " + this
+ + " finalized with non-zero refs: " + mNumRefs);
+ if (mRefStacks != null) {
+ for (RuntimeException e : mRefStacks.values()) {
+ Log.w(TAG, "Reference from here", e);
+ }
+ }
+ }
+ destroy();
+ } finally {
+ super.finalize();
+ }
}
public final class AssetInputStream extends InputStream {
@@ -526,7 +557,7 @@
if (mAsset != 0) {
destroyAsset(mAsset);
mAsset = 0;
- decRefsLocked();
+ decRefsLocked(hashCode());
}
}
}
@@ -710,7 +741,22 @@
private native final void init();
private native final void destroy();
- private final void decRefsLocked() {
+ private final void incRefsLocked(int id) {
+ if (DEBUG_REFS) {
+ if (mRefStacks == null) {
+ mRefStacks = new HashMap<Integer, RuntimeException>();
+ RuntimeException ex = new RuntimeException();
+ ex.fillInStackTrace();
+ mRefStacks.put(this.hashCode(), ex);
+ }
+ }
+ mNumRefs++;
+ }
+
+ private final void decRefsLocked(int id) {
+ if (DEBUG_REFS && mRefStacks != null) {
+ mRefStacks.remove(id);
+ }
mNumRefs--;
//System.out.println("Dec streams: mNumRefs=" + mNumRefs
// + " mReleased=" + mReleased);
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index aa5f128..6490b65 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -193,6 +193,11 @@
public int uiMode;
/**
+ * @hide Internal book-keeping.
+ */
+ public int seq;
+
+ /**
* Construct an invalid Configuration. You must call {@link #setToDefaults}
* for this object to be valid. {@more}
*/
@@ -220,6 +225,7 @@
orientation = o.orientation;
screenLayout = o.screenLayout;
uiMode = o.uiMode;
+ seq = o.seq;
}
public String toString() {
@@ -250,6 +256,10 @@
sb.append(screenLayout);
sb.append(" uiMode=");
sb.append(uiMode);
+ if (seq != 0) {
+ sb.append(" seq=");
+ sb.append(seq);
+ }
sb.append('}');
return sb.toString();
}
@@ -260,7 +270,7 @@
public void setToDefaults() {
fontScale = 1;
mcc = mnc = 0;
- locale = Locale.getDefault();
+ locale = null;
userSetLocale = false;
touchscreen = TOUCHSCREEN_UNDEFINED;
keyboard = KEYBOARD_UNDEFINED;
@@ -271,6 +281,7 @@
orientation = ORIENTATION_UNDEFINED;
screenLayout = SCREENLAYOUT_SIZE_UNDEFINED;
uiMode = UI_MODE_TYPE_NORMAL;
+ seq = 0;
}
/** {@hide} */
@@ -357,6 +368,10 @@
uiMode = delta.uiMode;
}
+ if (delta.seq != 0) {
+ seq = delta.seq;
+ }
+
return changed;
}
@@ -456,6 +471,35 @@
}
/**
+ * @hide Return true if the sequence of 'other' is better than this. Assumes
+ * that 'this' is your current sequence and 'other' is a new one you have
+ * received some how and want to compare with what you have.
+ */
+ public boolean isOtherSeqNewer(Configuration other) {
+ if (other == null) {
+ // Sanity check.
+ return false;
+ }
+ if (other.seq == 0) {
+ // If the other sequence is not specified, then we must assume
+ // it is newer since we don't know any better.
+ return true;
+ }
+ if (seq == 0) {
+ // If this sequence is not specified, then we also consider the
+ // other is better. Yes we have a preference for other. Sue us.
+ return true;
+ }
+ int diff = other.seq - seq;
+ if (diff > 0x10000) {
+ // If there has been a sufficiently large jump, assume the
+ // sequence has wrapped around.
+ return false;
+ }
+ return diff > 0;
+ }
+
+ /**
* Parcelable methods
*/
public int describeContents() {
@@ -488,6 +532,7 @@
dest.writeInt(orientation);
dest.writeInt(screenLayout);
dest.writeInt(uiMode);
+ dest.writeInt(seq);
}
public static final Parcelable.Creator<Configuration> CREATOR
@@ -522,6 +567,7 @@
orientation = source.readInt();
screenLayout = source.readInt();
uiMode = source.readInt();
+ seq = source.readInt();
}
public int compareTo(Configuration that) {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index ae8e297..a5e39d4 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -39,6 +39,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
+import java.util.Locale;
/**
* Class for accessing an application's resources. This sits on top of the
@@ -1259,6 +1260,9 @@
if (config != null) {
configChanges = mConfiguration.updateFrom(config);
}
+ if (mConfiguration.locale == null) {
+ mConfiguration.locale = Locale.getDefault();
+ }
if (metrics != null) {
mMetrics.setTo(metrics);
mMetrics.updateMetrics(mCompatibilityInfo,
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index f800232..3c2c30a 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -59,7 +59,7 @@
if (mOpenCount == 0) {
nativeDestroy(mNative);
if (mAssets != null) {
- mAssets.xmlBlockGone();
+ mAssets.xmlBlockGone(hashCode());
}
}
}
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index 7776520..e589f34 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -16,6 +16,8 @@
package android.database.sqlite;
+import android.database.CursorWindow;
+
/**
* An object create from a SQLiteDatabase that can be closed.
*/
@@ -29,9 +31,9 @@
synchronized(mLock) {
if (mReferenceCount <= 0) {
throw new IllegalStateException(
- "attempt to acquire a reference on an already-closed SQLiteClosable obj.");
+ "attempt to acquire a reference on an already-closed " + getObjInfo());
}
- mReferenceCount++;
+ mReferenceCount++;
}
}
@@ -52,4 +54,24 @@
}
}
}
+
+ private String getObjInfo() {
+ StringBuilder buff = new StringBuilder();
+ buff.append(this.getClass().getName());
+ buff.append(" Obj");
+ buff.append(" (");
+ if (this instanceof SQLiteDatabase) {
+ buff.append("database = ");
+ buff.append(((SQLiteDatabase)this).getPath());
+ } else if (this instanceof SQLiteProgram || this instanceof SQLiteStatement ||
+ this instanceof SQLiteQuery) {
+ buff.append("mSql = ");
+ buff.append(((SQLiteProgram)this).mSql);
+ } else if (this instanceof CursorWindow) {
+ buff.append("mStartPos = ");
+ buff.append(((CursorWindow)this).getStartPosition());
+ }
+ buff.append(") ");
+ return buff.toString();
+ }
}
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
index 79527b4..a7a1d9a 100644
--- a/core/java/android/database/sqlite/SQLiteCompiledSql.java
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -44,6 +44,9 @@
*/
/* package */ int nStatement = 0;
+ /** when in cache and is in use, this member is set */
+ private boolean mInUse = false;
+
/* package */ SQLiteCompiledSql(SQLiteDatabase db, String sql) {
mDatabase = db;
this.nHandle = db.mNativeHandle;
@@ -93,6 +96,22 @@
}
/**
+ * returns true if acquire() succeeds. false otherwise.
+ */
+ /* package */ synchronized boolean acquire() {
+ if (mInUse) {
+ // someone already has acquired it.
+ return false;
+ }
+ mInUse = true;
+ return true;
+ }
+
+ /* package */ synchronized void release() {
+ mInUse = false;
+ }
+
+ /**
* Make sure that the native resource is cleaned up.
*/
@Override
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 9ac8a4d..c13dd23 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -41,6 +41,7 @@
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.regex.Pattern;
/**
* Exposes methods to manage a SQLite database.
@@ -199,15 +200,19 @@
private static final int SLEEP_AFTER_YIELD_QUANTUM = 1000;
+ // The pattern we remove from database filenames before
+ // potentially logging them.
+ private static final Pattern EMAIL_IN_DB_PATTERN = Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+");
+
private long mLastLockMessageTime = 0L;
// Things related to query logging/sampling for debugging
// slow/frequent queries during development. Always log queries
- // which take 100ms+; shorter queries are sampled accordingly.
+ // which take 500ms+; shorter queries are sampled accordingly.
// Commit statements, which are typically slow, are logged
// together with the most recently executed SQL statement, for
// disambiguation.
- private static final int QUERY_LOG_TIME_IN_MILLIS = 100;
+ private static final int QUERY_LOG_TIME_IN_MILLIS = 500;
private static final int QUERY_LOG_SQL_LENGTH = 64;
private static final String COMMIT_SQL = "COMMIT;";
private final Random mRandom = new Random();
@@ -222,6 +227,9 @@
/** The path for the database file */
private String mPath;
+ /** The anonymized path for the database file for logging purposes */
+ private String mPathForLogs = null; // lazily populated
+
/** The flags passed to open/create */
private int mFlags;
@@ -1833,7 +1841,32 @@
if (blockingPackage == null) blockingPackage = "";
EventLog.writeEvent(
- EVENT_DB_OPERATION, mPath, sql, durationMillis, blockingPackage, samplePercent);
+ EVENT_DB_OPERATION,
+ getPathForLogs(),
+ sql,
+ durationMillis,
+ blockingPackage,
+ samplePercent);
+ }
+
+ /**
+ * Removes email addresses from database filenames before they're
+ * logged to the EventLog where otherwise apps could potentially
+ * read them.
+ */
+ private String getPathForLogs() {
+ if (mPathForLogs != null) {
+ return mPathForLogs;
+ }
+ if (mPath == null) {
+ return null;
+ }
+ if (mPath.indexOf('@') == -1) {
+ mPathForLogs = mPath;
+ } else {
+ mPathForLogs = EMAIL_IN_DB_PATTERN.matcher(mPath).replaceAll("XX@YY");
+ }
+ return mPathForLogs;
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 2d0aa39..7a29cb4 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -57,33 +57,51 @@
mCompiledSql = new SQLiteCompiledSql(db, sql);
// add it to the cache of compiled-sqls
+ // but before adding it and thus making it available for anyone else to use it,
+ // make sure it is acquired by me.
+ mCompiledSql.acquire();
db.addToCompiledQueries(sql, mCompiledSql);
+ } else {
+ // it is already in compiled-sql cache.
+ // try to acquire the object.
+ if (!mCompiledSql.acquire()) {
+ // the SQLiteCompiledSql in cache is in use by some other SQLiteProgram object.
+ // we can't have two different SQLiteProgam objects can't share the same
+ // CompiledSql object. create a new one.
+ // finalize it when I am done with it in "this" object.
+ mCompiledSql = new SQLiteCompiledSql(db, sql);
+ }
}
nStatement = mCompiledSql.nStatement;
}
@Override
protected void onAllReferencesReleased() {
- releaseCompiledSqlIfInCache();
+ releaseCompiledSqlIfNotInCache();
mDatabase.releaseReference();
mDatabase.removeSQLiteClosable(this);
}
@Override
protected void onAllReferencesReleasedFromContainer() {
- releaseCompiledSqlIfInCache();
+ releaseCompiledSqlIfNotInCache();
mDatabase.releaseReference();
}
- private void releaseCompiledSqlIfInCache() {
+ private void releaseCompiledSqlIfNotInCache() {
if (mCompiledSql == null) {
return;
}
synchronized(mDatabase.mCompiledQueries) {
if (!mDatabase.mCompiledQueries.containsValue(mCompiledSql)) {
+ // it is NOT in compiled-sql cache. i.e., responsibility of
+ // release this statement is on me.
mCompiledSql.releaseSqlStatement();
mCompiledSql = null; // so that GC doesn't call finalize() on it
nStatement = 0;
+ } else {
+ // it is in compiled-sql cache. reset its CompiledSql#mInUse flag
+ mCompiledSql.release();
}
}
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index c0bff66..6dba94d 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -26,7 +26,7 @@
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
-import android.graphics.PixelFormat;
+import android.graphics.ImageFormat;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -192,7 +192,7 @@
* The callback that delivers the preview frames.
*
* @param data The contents of the preview frame in the format defined
- * by {@link android.graphics.PixelFormat}, which can be queried
+ * by {@link android.graphics.ImageFormat}, which can be queried
* with {@link android.hardware.Camera.Parameters#getPreviewFormat()}.
* If {@link android.hardware.Camera.Parameters#setPreviewFormat(int)}
* is never called, the default will be the YCbCr_420_SP
@@ -276,7 +276,7 @@
* Adds a pre-allocated buffer to the callback buffer queue.
* Preview width and height can be determined from getPreviewSize, and bitsPerPixel can be
* found from from {@link android.hardware.Camera.Parameters#getPreviewFormat()} and
- * {@link android.graphics.PixelFormat#getPixelFormatInfo(int, PixelFormat)}
+ * {@link android.graphics.ImageFormat#getBitsPerPixel(int)}
*
* Alternatively, a buffer from a previous callback may be passed in or used
* to determine the size of new preview frame buffers.
@@ -1086,15 +1086,15 @@
/**
* Sets the image format for preview pictures.
* <p>If this is never called, the default format will be
- * {@link android.graphics.PixelFormat#YCbCr_420_SP}, which
+ * {@link android.graphics.ImageFormat#NV21}, which
* uses the NV21 encoding format.</p>
*
* @param pixel_format the desired preview picture format, defined
- * by one of the {@link android.graphics.PixelFormat} constants.
- * (E.g., <var>PixelFormat.YCbCr_420_SP</var> (default),
- * <var>PixelFormat.RGB_565</var>, or
- * <var>PixelFormat.JPEG</var>)
- * @see android.graphics.PixelFormat
+ * by one of the {@link android.graphics.ImageFormat} constants.
+ * (E.g., <var>ImageFormat.NV21</var> (default),
+ * <var>ImageFormat.RGB_565</var>, or
+ * <var>ImageFormat.JPEG</var>)
+ * @see android.graphics.ImageFormat
*/
public void setPreviewFormat(int pixel_format) {
String s = cameraFormatForPixelFormat(pixel_format);
@@ -1110,7 +1110,7 @@
* Returns the image format for preview pictures got from
* {@link PreviewCallback}.
*
- * @return the {@link android.graphics.PixelFormat} int representing
+ * @return the {@link android.graphics.ImageFormat} int representing
* the preview picture format.
*/
public int getPreviewFormat() {
@@ -1128,7 +1128,7 @@
ArrayList<Integer> formats = new ArrayList<Integer>();
for (String s : split(str)) {
int f = pixelFormatForCameraFormat(s);
- if (f == PixelFormat.UNKNOWN) continue;
+ if (f == ImageFormat.UNKNOWN) continue;
formats.add(f);
}
return formats;
@@ -1171,10 +1171,10 @@
* Sets the image format for pictures.
*
* @param pixel_format the desired picture format
- * (<var>PixelFormat.YCbCr_420_SP (NV21)</var>,
- * <var>PixelFormat.RGB_565</var>, or
- * <var>PixelFormat.JPEG</var>)
- * @see android.graphics.PixelFormat
+ * (<var>ImageFormat.NV21</var>,
+ * <var>ImageFormat.RGB_565</var>, or
+ * <var>ImageFormat.JPEG</var>)
+ * @see android.graphics.ImageFormat
*/
public void setPictureFormat(int pixel_format) {
String s = cameraFormatForPixelFormat(pixel_format);
@@ -1189,7 +1189,7 @@
/**
* Returns the image format for pictures.
*
- * @return the PixelFormat int representing the picture format
+ * @return the ImageFormat int representing the picture format
*/
public int getPictureFormat() {
return pixelFormatForCameraFormat(get(KEY_PICTURE_FORMAT));
@@ -1198,7 +1198,7 @@
/**
* Gets the supported picture formats.
*
- * @return a List of Integer objects (values are PixelFormat.XXX). This
+ * @return a List of Integer objects (values are ImageFormat.XXX). This
* method will always return a list with at least one element.
*/
public List<Integer> getSupportedPictureFormats() {
@@ -1206,7 +1206,7 @@
ArrayList<Integer> formats = new ArrayList<Integer>();
for (String s : split(str)) {
int f = pixelFormatForCameraFormat(s);
- if (f == PixelFormat.UNKNOWN) continue;
+ if (f == ImageFormat.UNKNOWN) continue;
formats.add(f);
}
return formats;
@@ -1214,35 +1214,35 @@
private String cameraFormatForPixelFormat(int pixel_format) {
switch(pixel_format) {
- case PixelFormat.YCbCr_422_SP: return PIXEL_FORMAT_YUV422SP;
- case PixelFormat.YCbCr_420_SP: return PIXEL_FORMAT_YUV420SP;
- case PixelFormat.YCbCr_422_I: return PIXEL_FORMAT_YUV422I;
- case PixelFormat.RGB_565: return PIXEL_FORMAT_RGB565;
- case PixelFormat.JPEG: return PIXEL_FORMAT_JPEG;
- default: return null;
+ case ImageFormat.NV16: return PIXEL_FORMAT_YUV422SP;
+ case ImageFormat.NV21: return PIXEL_FORMAT_YUV420SP;
+ case ImageFormat.YUY2: return PIXEL_FORMAT_YUV422I;
+ case ImageFormat.RGB_565: return PIXEL_FORMAT_RGB565;
+ case ImageFormat.JPEG: return PIXEL_FORMAT_JPEG;
+ default: return null;
}
}
private int pixelFormatForCameraFormat(String format) {
if (format == null)
- return PixelFormat.UNKNOWN;
+ return ImageFormat.UNKNOWN;
if (format.equals(PIXEL_FORMAT_YUV422SP))
- return PixelFormat.YCbCr_422_SP;
+ return ImageFormat.NV16;
if (format.equals(PIXEL_FORMAT_YUV420SP))
- return PixelFormat.YCbCr_420_SP;
+ return ImageFormat.NV21;
if (format.equals(PIXEL_FORMAT_YUV422I))
- return PixelFormat.YCbCr_422_I;
+ return ImageFormat.YUY2;
if (format.equals(PIXEL_FORMAT_RGB565))
- return PixelFormat.RGB_565;
+ return ImageFormat.RGB_565;
if (format.equals(PIXEL_FORMAT_JPEG))
- return PixelFormat.JPEG;
+ return ImageFormat.JPEG;
- return PixelFormat.UNKNOWN;
+ return ImageFormat.UNKNOWN;
}
/**
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index badb767..c76aca1 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -332,10 +332,10 @@
/**
* Sets the value of the setting for background data usage.
- *
+ *
* @param allowBackgroundData Whether an application should use data while
* it is in the background.
- *
+ *
* @attr ref android.Manifest.permission#CHANGE_BACKGROUND_DATA_SETTING
* @see #getBackgroundDataSetting()
* @hide
@@ -346,7 +346,35 @@
} catch (RemoteException e) {
}
}
-
+
+ /**
+ * Gets the value of the setting for enabling Mobile data.
+ *
+ * @return Whether mobile data is enabled.
+ * @hide
+ */
+ public boolean getMobileDataEnabled() {
+ try {
+ return mService.getMobileDataEnabled();
+ } catch (RemoteException e) {
+ return true;
+ }
+ }
+
+ /**
+ * Sets the persisted value for enabling/disabling Mobile data.
+ *
+ * @param allowMobileData Whether the mobile data connection should be
+ * used or not.
+ * @hide
+ */
+ public void setMobileDataEnabled(boolean enabled) {
+ try {
+ mService.setMobileDataEnabled(enabled);
+ } catch (RemoteException e) {
+ }
+ }
+
/**
* Don't allow use of default constructor.
*/
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 508e9c3a..2514693 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -51,6 +51,10 @@
void setBackgroundDataSetting(boolean allowBackgroundData);
+ boolean getMobileDataEnabled();
+
+ void setMobileDataEnabled(boolean enabled);
+
boolean tether(String iface);
boolean untether(String iface);
diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java
index 9aa23fe..915c5d7 100644
--- a/core/java/android/net/InterfaceConfiguration.java
+++ b/core/java/android/net/InterfaceConfiguration.java
@@ -45,10 +45,10 @@
}
private static void putAddress(StringBuffer buf, int addr) {
- buf.append(addr & 0xff).append('.').
- append((addr >>>= 8) & 0xff).append('.').
- append((addr >>>= 8) & 0xff).append('.').
- append((addr >>>= 8) & 0xff);
+ buf.append((addr >> 24) & 0xff).append('.').
+ append((addr >> 16) & 0xff).append('.').
+ append((addr >> 8) & 0xff).append('.').
+ append(addr & 0xff);
}
/** Implement the Parcelable interface {@hide} */
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b706c5c..56a05ee 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -750,11 +750,8 @@
* Checkin server version of dump to produce more compact, computer-readable log.
*
* NOTE: all times are expressed in 'ms'.
- * @param fd
- * @param pw
- * @param which
*/
- private final void dumpCheckinLocked(PrintWriter pw, int which) {
+ public final void dumpCheckinLocked(PrintWriter pw, int which, int reqUid) {
final long rawUptime = SystemClock.uptimeMillis() * 1000;
final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
final long batteryUptime = getBatteryUptime(rawUptime);
@@ -856,19 +853,24 @@
getDischargeCurrentLevel());
}
- Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats();
- if (kernelWakelocks.size() > 0) {
- for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) {
- sb.setLength(0);
- printWakeLockCheckin(sb, ent.getValue(), batteryRealtime, null, which, "");
-
- dumpLine(pw, 0 /* uid */, category, KERNEL_WAKELOCK_DATA, ent.getKey(),
- sb.toString());
+ if (reqUid < 0) {
+ Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats();
+ if (kernelWakelocks.size() > 0) {
+ for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) {
+ sb.setLength(0);
+ printWakeLockCheckin(sb, ent.getValue(), batteryRealtime, null, which, "");
+
+ dumpLine(pw, 0 /* uid */, category, KERNEL_WAKELOCK_DATA, ent.getKey(),
+ sb.toString());
+ }
}
}
for (int iu = 0; iu < NU; iu++) {
final int uid = uidStats.keyAt(iu);
+ if (reqUid >= 0 && uid != reqUid) {
+ continue;
+ }
Uid u = uidStats.valueAt(iu);
// Dump Network stats per uid, if any
long rx = u.getTcpBytesReceived(which);
@@ -987,7 +989,7 @@
}
@SuppressWarnings("unused")
- private final void dumpLocked(PrintWriter pw, String prefix, int which) {
+ public final void dumpLocked(PrintWriter pw, String prefix, int which, int reqUid) {
final long rawUptime = SystemClock.uptimeMillis() * 1000;
final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
final long batteryUptime = getBatteryUptime(rawUptime);
@@ -1063,23 +1065,25 @@
long fullWakeLockTimeTotalMicros = 0;
long partialWakeLockTimeTotalMicros = 0;
- Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats();
- if (kernelWakelocks.size() > 0) {
- for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) {
-
- String linePrefix = ": ";
- sb.setLength(0);
- sb.append(prefix);
- sb.append(" Kernel Wake lock ");
- sb.append(ent.getKey());
- linePrefix = printWakeLock(sb, ent.getValue(), batteryRealtime, null, which,
- linePrefix);
- if (!linePrefix.equals(": ")) {
- sb.append(" realtime");
- } else {
- sb.append(": (nothing executed)");
+ if (reqUid < 0) {
+ Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats();
+ if (kernelWakelocks.size() > 0) {
+ for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) {
+
+ String linePrefix = ": ";
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Kernel Wake lock ");
+ sb.append(ent.getKey());
+ linePrefix = printWakeLock(sb, ent.getValue(), batteryRealtime, null, which,
+ linePrefix);
+ if (!linePrefix.equals(": ")) {
+ sb.append(" realtime");
+ } else {
+ sb.append(": (nothing executed)");
+ }
+ pw.println(sb.toString());
}
- pw.println(sb.toString());
}
}
@@ -1212,7 +1216,12 @@
for (int iu=0; iu<NU; iu++) {
final int uid = uidStats.keyAt(iu);
+ if (reqUid >= 0 && uid != reqUid) {
+ continue;
+ }
+
Uid u = uidStats.valueAt(iu);
+
pw.println(prefix + " #" + uid + ":");
boolean uidActivity = false;
@@ -1421,16 +1430,16 @@
pw.println("Total Statistics (Current and Historic):");
pw.println(" System starts: " + getStartCount()
+ ", currently on battery: " + getIsOnBattery());
- dumpLocked(pw, "", STATS_TOTAL);
+ dumpLocked(pw, "", STATS_TOTAL, -1);
pw.println("");
pw.println("Last Run Statistics (Previous run of system):");
- dumpLocked(pw, "", STATS_LAST);
+ dumpLocked(pw, "", STATS_LAST, -1);
pw.println("");
pw.println("Current Battery Statistics (Currently running system):");
- dumpLocked(pw, "", STATS_CURRENT);
+ dumpLocked(pw, "", STATS_CURRENT, -1);
pw.println("");
pw.println("Unplugged Statistics (Since last unplugged from power):");
- dumpLocked(pw, "", STATS_UNPLUGGED);
+ dumpLocked(pw, "", STATS_UNPLUGGED, -1);
}
@SuppressWarnings("unused")
@@ -1445,13 +1454,13 @@
}
if (isUnpluggedOnly) {
- dumpCheckinLocked(pw, STATS_UNPLUGGED);
+ dumpCheckinLocked(pw, STATS_UNPLUGGED, -1);
}
else {
- dumpCheckinLocked(pw, STATS_TOTAL);
- dumpCheckinLocked(pw, STATS_LAST);
- dumpCheckinLocked(pw, STATS_UNPLUGGED);
- dumpCheckinLocked(pw, STATS_CURRENT);
+ dumpCheckinLocked(pw, STATS_TOTAL, -1);
+ dumpCheckinLocked(pw, STATS_LAST, -1);
+ dumpCheckinLocked(pw, STATS_UNPLUGGED, -1);
+ dumpCheckinLocked(pw, STATS_CURRENT, -1);
}
}
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 9ee251e..a4c595d 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -541,6 +541,14 @@
public static int getGlobalFreedSize() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES);
}
+ public static int getGlobalClassInitCount() {
+ /* number of classes that have been successfully initialized */
+ return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_COUNT);
+ }
+ public static int getGlobalClassInitTime() {
+ /* cumulative elapsed time for class initialization, in usec */
+ return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
+ }
public static int getGlobalExternalAllocCount() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_OBJECTS);
}
@@ -584,6 +592,12 @@
public static void resetGlobalFreedSize() {
VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES);
}
+ public static void resetGlobalClassInitCount() {
+ VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_COUNT);
+ }
+ public static void resetGlobalClassInitTime() {
+ VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
+ }
public static void resetGlobalExternalAllocCount() {
VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_OBJECTS);
}
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index 65301e4..911439a 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -53,9 +53,9 @@
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
- Process.setThreadPriority(mPriority);
notifyAll();
}
+ Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index f48f45f..92041d8 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -148,4 +148,18 @@
*/
void detachPppd(String tty);
+ /**
+ * Turn on USB RNDIS support - this will turn off thinks like adb/mass-storage
+ */
+ void startUsbRNDIS();
+
+ /**
+ * Turn off USB RNDIS support
+ */
+ void stopUsbRNDIS();
+
+ /**
+ * Check the status of USB RNDIS support
+ */
+ boolean isUsbRNDISStarted();
}
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 4130109..476da1d 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -40,20 +40,36 @@
*/
public int what;
- // Use these fields instead of using the class's Bundle if you can.
- /** arg1 and arg2 are lower-cost alternatives to using {@link #setData(Bundle) setData()}
- if you only need to store a few integer values. */
+ /**
+ * arg1 and arg2 are lower-cost alternatives to using
+ * {@link #setData(Bundle) setData()} if you only need to store a
+ * few integer values.
+ */
public int arg1;
- /** arg1 and arg2 are lower-cost alternatives to using {@link #setData(Bundle) setData()}
- if you only need to store a few integer values.*/
+ /**
+ * arg1 and arg2 are lower-cost alternatives to using
+ * {@link #setData(Bundle) setData()} if you only need to store a
+ * few integer values.
+ */
public int arg2;
- /** An arbitrary object to send to the recipient. This must be null when
- * sending messages across processes. */
+ /**
+ * An arbitrary object to send to the recipient. When using
+ * {@link Messenger} to send the message across processes this can only
+ * be non-null if it contains a Parcelable of a framework class (not one
+ * implemented by the application). For other data transfer use
+ * {@link #setData}.
+ *
+ * <p>Note that Parcelable objects here are not supported prior to
+ * the {@link android.os.Build.VERSION_CODES#FROYO} release.
+ */
public Object obj;
- /** Optional Messenger where replies to this message can be sent.
+ /**
+ * Optional Messenger where replies to this message can be sent. The
+ * semantics of exactly how this is used are up to the sender and
+ * receiver.
*/
public Messenger replyTo;
@@ -278,14 +294,22 @@
* the <em>target</em> {@link Handler} that is receiving this Message to
* dispatch it. If
* not set, the message will be dispatched to the receiving Handler's
- * {@link Handler#handleMessage(Message Handler.handleMessage())}. */
+ * {@link Handler#handleMessage(Message Handler.handleMessage())}.
+ */
public Runnable getCallback() {
return callback;
}
/**
* Obtains a Bundle of arbitrary data associated with this
- * event, lazily creating it if necessary. Set this value by calling {@link #setData(Bundle)}.
+ * event, lazily creating it if necessary. Set this value by calling
+ * {@link #setData(Bundle)}. Note that when transferring data across
+ * processes via {@link Messenger}, you will need to set your ClassLoader
+ * on the Bundle via {@link Bundle#setClassLoader(ClassLoader)
+ * Bundle.setClassLoader()} so that it can instantiate your objects when
+ * you retrieve them.
+ * @see #peekData()
+ * @see #setData(Bundle)
*/
public Bundle getData() {
if (data == null) {
@@ -297,14 +321,21 @@
/**
* Like getData(), but does not lazily create the Bundle. A null
- * is returned if the Bundle does not already exist.
+ * is returned if the Bundle does not already exist. See
+ * {@link #getData} for further information on this.
+ * @see #getData()
+ * @see #setData(Bundle)
*/
public Bundle peekData() {
return data;
}
- /** Sets a Bundle of arbitrary data values. Use arg1 and arg1 members
- * as a lower cost way to send a few simple integer values, if you can. */
+ /**
+ * Sets a Bundle of arbitrary data values. Use arg1 and arg1 members
+ * as a lower cost way to send a few simple integer values, if you can.
+ * @see #getData()
+ * @see #peekData()
+ */
public void setData(Bundle data) {
this.data = data;
}
@@ -381,13 +412,25 @@
}
public void writeToParcel(Parcel dest, int flags) {
- if (obj != null || callback != null) {
+ if (callback != null) {
throw new RuntimeException(
- "Can't marshal objects across processes.");
+ "Can't marshal callbacks across processes.");
}
dest.writeInt(what);
dest.writeInt(arg1);
dest.writeInt(arg2);
+ if (obj != null) {
+ try {
+ Parcelable p = (Parcelable)obj;
+ dest.writeInt(1);
+ dest.writeParcelable(p, flags);
+ } catch (ClassCastException e) {
+ throw new RuntimeException(
+ "Can't marshal non-Parcelable objects across processes.");
+ }
+ } else {
+ dest.writeInt(0);
+ }
dest.writeLong(when);
dest.writeBundle(data);
Messenger.writeMessengerOrNullToParcel(replyTo, dest);
@@ -397,6 +440,9 @@
what = source.readInt();
arg1 = source.readInt();
arg2 = source.readInt();
+ if (source.readInt() != 0) {
+ obj = source.readParcelable(getClass().getClassLoader());
+ }
when = source.readLong();
data = source.readBundle();
replyTo = Messenger.readMessengerOrNullFromParcel(source);
diff --git a/core/java/android/os/storage/IMountService.aidl b/core/java/android/os/storage/IMountService.aidl
index 816baf3..2b2dcf4 100644
--- a/core/java/android/os/storage/IMountService.aidl
+++ b/core/java/android/os/storage/IMountService.aidl
@@ -61,9 +61,12 @@
/**
* Safely unmount external storage at given mount point.
- * Returns an int consistent with MountServiceResultCode
+ * The unmount is an asynchronous operation. Applications
+ * should register StorageEventListener for storage related
+ * status changes.
+ *
*/
- int unmountVolume(String mountPoint);
+ void unmountVolume(String mountPoint, boolean force);
/**
* Format external storage given a mount point.
@@ -100,7 +103,7 @@
* NOTE: Ensure all references are released prior to deleting.
* Returns an int consistent with MountServiceResultCode
*/
- int destroySecureContainer(String id);
+ int destroySecureContainer(String id, boolean force);
/*
* Mount a secure container with the specified key and owner UID.
@@ -112,7 +115,7 @@
* Unount a secure container.
* Returns an int consistent with MountServiceResultCode
*/
- int unmountSecureContainer(String id);
+ int unmountSecureContainer(String id, boolean force);
/*
* Returns true if the specified container is mounted
diff --git a/core/java/android/os/storage/StorageResultCode.java b/core/java/android/os/storage/StorageResultCode.java
index 249bacf..07d95df 100644
--- a/core/java/android/os/storage/StorageResultCode.java
+++ b/core/java/android/os/storage/StorageResultCode.java
@@ -64,4 +64,10 @@
*/
public static final int OperationFailedStorageMounted = -6;
+ /**
+ * Operation failed: Storage is busy.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationFailedStorageBusy = -7;
+
}
diff --git a/core/java/android/pim/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java
index bd7924a..635323e 100644
--- a/core/java/android/pim/RecurrenceSet.java
+++ b/core/java/android/pim/RecurrenceSet.java
@@ -18,7 +18,6 @@
import android.content.ContentValues;
import android.database.Cursor;
-import android.os.Bundle;
import android.provider.Calendar;
import android.text.TextUtils;
import android.text.format.Time;
@@ -26,6 +25,7 @@
import android.util.Log;
import java.util.List;
+import java.util.regex.Pattern;
/**
* Basic information about a recurrence, following RFC 2445 Section 4.8.5.
@@ -36,6 +36,7 @@
private final static String TAG = "CalendarProvider";
private final static String RULE_SEPARATOR = "\n";
+ private final static String FOLDING_SEPARATOR = "\n ";
// TODO: make these final?
public EventRecurrence[] rrules = null;
@@ -48,7 +49,8 @@
* events table in the CalendarProvider.
* @param values The values retrieved from the Events table.
*/
- public RecurrenceSet(ContentValues values) {
+ public RecurrenceSet(ContentValues values)
+ throws EventRecurrence.InvalidFormatException {
String rruleStr = values.getAsString(Calendar.Events.RRULE);
String rdateStr = values.getAsString(Calendar.Events.RDATE);
String exruleStr = values.getAsString(Calendar.Events.EXRULE);
@@ -65,7 +67,8 @@
* @param cursor The cursor containing the RRULE, RDATE, EXRULE, and EXDATE
* columns.
*/
- public RecurrenceSet(Cursor cursor) {
+ public RecurrenceSet(Cursor cursor)
+ throws EventRecurrence.InvalidFormatException {
int rruleColumn = cursor.getColumnIndex(Calendar.Events.RRULE);
int rdateColumn = cursor.getColumnIndex(Calendar.Events.RDATE);
int exruleColumn = cursor.getColumnIndex(Calendar.Events.EXRULE);
@@ -78,12 +81,14 @@
}
public RecurrenceSet(String rruleStr, String rdateStr,
- String exruleStr, String exdateStr) {
+ String exruleStr, String exdateStr)
+ throws EventRecurrence.InvalidFormatException {
init(rruleStr, rdateStr, exruleStr, exdateStr);
}
private void init(String rruleStr, String rdateStr,
- String exruleStr, String exdateStr) {
+ String exruleStr, String exdateStr)
+ throws EventRecurrence.InvalidFormatException {
if (!TextUtils.isEmpty(rruleStr) || !TextUtils.isEmpty(rdateStr)) {
if (!TextUtils.isEmpty(rruleStr)) {
@@ -305,7 +310,8 @@
String rdateStr = values.getAsString(Calendar.Events.RDATE);
String exruleStr = values.getAsString(Calendar.Events.EXRULE);
String exdateStr = values.getAsString(Calendar.Events.EXDATE);
- boolean allDay = values.getAsInteger(Calendar.Events.ALL_DAY) == 1;
+ Integer allDayInteger = values.getAsInteger(Calendar.Events.ALL_DAY);
+ boolean allDay = (null != allDayInteger) ? (allDayInteger == 1) : false;
if ((dtstart == -1) ||
(TextUtils.isEmpty(duration))||
@@ -357,7 +363,7 @@
if (TextUtils.isEmpty(ruleStr)) {
return;
}
- String[] rrules = ruleStr.split(RULE_SEPARATOR);
+ String[] rrules = getRuleStrings(ruleStr);
for (String rrule : rrules) {
ICalendar.Property prop = new ICalendar.Property(propertyName);
prop.setValue(rrule);
@@ -365,6 +371,52 @@
}
}
+ private static String[] getRuleStrings(String ruleStr) {
+ if (null == ruleStr) {
+ return new String[0];
+ }
+ String unfoldedRuleStr = unfold(ruleStr);
+ String[] split = unfoldedRuleStr.split(RULE_SEPARATOR);
+ int count = split.length;
+ for (int n = 0; n < count; n++) {
+ split[n] = fold(split[n]);
+ }
+ return split;
+ }
+
+
+ private static final Pattern IGNORABLE_ICAL_WHITESPACE_RE =
+ Pattern.compile("(?:\\r\\n?|\\n)[ \t]");
+
+ private static final Pattern FOLD_RE = Pattern.compile(".{75}");
+
+ /**
+ * fold and unfolds ical content lines as per RFC 2445 section 4.1.
+ *
+ * <h3>4.1 Content Lines</h3>
+ *
+ * <p>The iCalendar object is organized into individual lines of text, called
+ * content lines. Content lines are delimited by a line break, which is a CRLF
+ * sequence (US-ASCII decimal 13, followed by US-ASCII decimal 10).
+ *
+ * <p>Lines of text SHOULD NOT be longer than 75 octets, excluding the line
+ * break. Long content lines SHOULD be split into a multiple line
+ * representations using a line "folding" technique. That is, a long line can
+ * be split between any two characters by inserting a CRLF immediately
+ * followed by a single linear white space character (i.e., SPACE, US-ASCII
+ * decimal 32 or HTAB, US-ASCII decimal 9). Any sequence of CRLF followed
+ * immediately by a single linear white space character is ignored (i.e.,
+ * removed) when processing the content type.
+ */
+ public static String fold(String unfoldedIcalContent) {
+ return FOLD_RE.matcher(unfoldedIcalContent).replaceAll("$0\r\n ");
+ }
+
+ public static String unfold(String foldedIcalContent) {
+ return IGNORABLE_ICAL_WHITESPACE_RE.matcher(
+ foldedIcalContent).replaceAll("");
+ }
+
private static void addPropertyForDateStr(ICalendar.Component component,
String propertyName,
String dateStr) {
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 2eb25954..194fe33 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -26,8 +26,6 @@
import android.net.Uri;
import android.os.RemoteException;
import android.pim.vcard.exception.VCardException;
-import android.provider.CallLog;
-import android.provider.CallLog.Calls;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
@@ -44,8 +42,6 @@
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.text.TextUtils;
-import android.text.format.Time;
import android.util.CharsetUtils;
import android.util.Log;
@@ -60,7 +56,6 @@
import java.lang.reflect.Method;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -125,12 +120,6 @@
public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
- // Property for call log entry
- private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
- private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "INCOMING";
- private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING";
- private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
-
private static final String SHIFT_JIS = "SHIFT_JIS";
private static final String UTF_8 = "UTF-8";
@@ -275,30 +264,14 @@
private final String mCharsetString;
private boolean mTerminateIsCalled;
- final private List<OneEntryHandler> mHandlerList;
+ private final List<OneEntryHandler> mHandlerList;
private String mErrorReason = NO_ERROR;
- private boolean mIsCallLogComposer;
-
private static final String[] sContactsProjection = new String[] {
Contacts._ID,
};
- /** The projection to use when querying the call log table */
- private static final String[] sCallLogProjection = new String[] {
- Calls.NUMBER, Calls.DATE, Calls.TYPE, Calls.CACHED_NAME, Calls.CACHED_NUMBER_TYPE,
- Calls.CACHED_NUMBER_LABEL
- };
- private static final int NUMBER_COLUMN_INDEX = 0;
- private static final int DATE_COLUMN_INDEX = 1;
- private static final int CALL_TYPE_COLUMN_INDEX = 2;
- private static final int CALLER_NAME_COLUMN_INDEX = 3;
- private static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 4;
- private static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 5;
-
- private static final String FLAG_TIMEZONE_UTC = "Z";
-
public VCardComposer(Context context) {
this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
}
@@ -377,6 +350,7 @@
if (contentUri == null) {
return false;
}
+
if (mCareHandlerErrors) {
List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
@@ -396,10 +370,7 @@
}
final String[] projection;
- if (CallLog.Calls.CONTENT_URI.equals(contentUri)) {
- projection = sCallLogProjection;
- mIsCallLogComposer = true;
- } else if (Contacts.CONTENT_URI.equals(contentUri) ||
+ if (Contacts.CONTENT_URI.equals(contentUri) ||
CONTACTS_TEST_CONTENT_URI.equals(contentUri)) {
projection = sContactsProjection;
} else {
@@ -426,11 +397,7 @@
return false;
}
- if (mIsCallLogComposer) {
- mIdColumn = -1;
- } else {
- mIdColumn = mCursor.getColumnIndex(Contacts._ID);
- }
+ mIdColumn = mCursor.getColumnIndex(Contacts._ID);
return true;
}
@@ -448,19 +415,14 @@
mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
return false;
}
- String name = null;
String vcard;
try {
- if (mIsCallLogComposer) {
- vcard = createOneCallLogEntryInternal();
+ if (mIdColumn >= 0) {
+ vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
+ getEntityIteratorMethod);
} else {
- if (mIdColumn >= 0) {
- vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
- getEntityIteratorMethod);
- } else {
- Log.e(LOG_TAG, "Incorrect mIdColumn: " + mIdColumn);
- return true;
- }
+ Log.e(LOG_TAG, "Incorrect mIdColumn: " + mIdColumn);
+ return true;
}
} catch (VCardException e) {
Log.e(LOG_TAG, "VCardException has been thrown: " + e.getMessage());
@@ -468,7 +430,7 @@
} catch (OutOfMemoryError error) {
// Maybe some data (e.g. photo) is too big to have in memory. But it
// should be rare.
- Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry: " + name);
+ Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry.");
System.gc();
// TODO: should tell users what happened?
return true;
@@ -630,99 +592,4 @@
public String getErrorReason() {
return mErrorReason;
}
-
- /**
- * This static function is to compose vCard for phone own number
- */
- public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
- String phoneNumber, boolean vcardVer21) {
- final int vcardType = (vcardVer21 ?
- VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8 :
- VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8);
- final VCardBuilder builder = new VCardBuilder(vcardType);
- boolean needCharset = false;
- if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
- needCharset = true;
- }
- builder.appendLine(VCardConstants.PROPERTY_FN, phoneName, needCharset, false);
- builder.appendLine(VCardConstants.PROPERTY_N, phoneName, needCharset, false);
-
- if (!TextUtils.isEmpty(phoneNumber)) {
- String label = Integer.toString(phonetype);
- builder.appendTelLine(phonetype, label, phoneNumber, false);
- }
-
- return builder.toString();
- }
-
- /**
- * Format according to RFC 2445 DATETIME type.
- * The format is: ("%Y%m%dT%H%M%SZ").
- */
- private final String toRfc2455Format(final long millSecs) {
- Time startDate = new Time();
- startDate.set(millSecs);
- String date = startDate.format2445();
- return date + FLAG_TIMEZONE_UTC;
- }
-
- /**
- * Try to append the property line for a call history time stamp field if possible.
- * Do nothing if the call log type gotton from the database is invalid.
- */
- private void tryAppendCallHistoryTimeStampField(final VCardBuilder builder) {
- // Extension for call history as defined in
- // in the Specification for Ic Mobile Communcation - ver 1.1,
- // Oct 2000. This is used to send the details of the call
- // history - missed, incoming, outgoing along with date and time
- // to the requesting device (For example, transferring phone book
- // when connected over bluetooth)
- //
- // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000Z"
- final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX);
- final String callLogTypeStr;
- switch (callLogType) {
- case Calls.INCOMING_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
- break;
- }
- case Calls.OUTGOING_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
- break;
- }
- case Calls.MISSED_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
- break;
- }
- default: {
- Log.w(LOG_TAG, "Call log type not correct.");
- return;
- }
- }
-
- final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
- builder.appendLine(VCARD_PROPERTY_X_TIMESTAMP,
- Arrays.asList(callLogTypeStr), toRfc2455Format(dateAsLong));
- }
-
- private String createOneCallLogEntryInternal() {
- final VCardBuilder builder = new VCardBuilder(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
- String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
- if (TextUtils.isEmpty(name)) {
- name = mCursor.getString(NUMBER_COLUMN_INDEX);
- }
- final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
- builder.appendLine(VCardConstants.PROPERTY_FN, name, needCharset, false);
- builder.appendLine(VCardConstants.PROPERTY_N, name, needCharset, false);
-
- final String number = mCursor.getString(NUMBER_COLUMN_INDEX);
- final int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
- String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
- if (TextUtils.isEmpty(label)) {
- label = Integer.toString(type);
- }
- builder.appendTelLine(type, label, number, false);
- tryAppendCallHistoryTimeStampField(builder);
- return builder.toString();
- }
}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index acb8473..1163106 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -3176,6 +3176,56 @@
}
/**
+ * Additional columns returned by the {@link Contacts#CONTENT_FILTER_URI} providing the
+ * explanation of why the filter matched the contact. Specifically, they contain the
+ * data type and element that was used for matching.
+ * <p>
+ * This is temporary API, it will need to change when we move to FTS.
+ *
+ * @hide
+ */
+ public static class SearchSnippetColumns {
+
+ /**
+ * The ID of the data row that was matched by the filter.
+ *
+ * @hide
+ */
+ public static final String SNIPPET_DATA_ID = "snippet_data_id";
+
+ /**
+ * The type of data that was matched by the filter.
+ *
+ * @hide
+ */
+ public static final String SNIPPET_MIMETYPE = "snippet_mimetype";
+
+ /**
+ * The {@link CommonDataKinds.CommonColumns#DATA} field of the data row
+ * that was matched by the filter.
+ *
+ * @hide
+ */
+ public static final String SNIPPET_DATA = "snippet_data";
+
+ /**
+ * The {@link CommonDataKinds.CommonColumns#TYPE} field of the data row
+ * that was matched by the filter.
+ *
+ * @hide
+ */
+ public static final String SNIPPET_TYPE = "snippet_type";
+
+ /**
+ * The {@link CommonDataKinds.CommonColumns#LABEL} field of the data row
+ * that was matched by the filter.
+ *
+ * @hide
+ */
+ public static final String SNIPPET_LABEL = "snippet_label";
+ }
+
+ /**
* Container for definitions of common data types stored in the {@link ContactsContract.Data}
* table.
*/
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 74a03da..dda9018 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -54,6 +54,13 @@
private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/";
/**
+ * Activity Action: Launch a music player.
+ * The activity should be able to play, browse, or manipulate music files stored on the device.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String INTENT_ACTION_MUSIC_PLAYER = "android.intent.action.MUSIC_PLAYER";
+
+ /**
* Activity Action: Perform a search for media.
* Contains at least the {@link android.app.SearchManager#QUERY} extra.
* May also contain any combination of the following extras:
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 14e27eb..b75a8cc 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -374,6 +374,11 @@
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
* <p>
+ * The account types available to add via the add account button may be restricted by adding an
+ * {@link #EXTRA_AUTHORITIES} extra to this Intent with one or more syncable content provider's
+ * authorities. Only account types which can sync with that content provider will be offered to
+ * the user.
+ * <p>
* Input: Nothing.
* <p>
* Output: Nothing.
@@ -383,6 +388,24 @@
"android.settings.SYNC_SETTINGS";
/**
+ * Activity Action: Show add account screen for creating a new account.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * The account types available to add may be restricted by adding an {@link #EXTRA_AUTHORITIES}
+ * extra to the Intent with one or more syncable content provider's authorities. Only account
+ * types which can sync with that content provider will be offered to the user.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_ADD_ACCOUNT =
+ "android.settings.ADD_ACCOUNT_SETTINGS";
+
+ /**
* Activity Action: Show settings for selecting the network operator.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -468,6 +491,19 @@
// End of Intent actions for Settings
+ /**
+ * Activity Extra: Limit available options in launched activity based on the given authority.
+ * <p>
+ * This can be passed as an extra field in an Activity Intent with one or more syncable content
+ * provider's authorities as a String[]. This field is used by some intents to alter the
+ * behavior of the called activity.
+ * <p>
+ * Example: The {@link #ACTION_ADD_ACCOUNT} intent restricts the account types available based
+ * on the authority given.
+ */
+ public static final String EXTRA_AUTHORITIES =
+ "authorities";
+
private static final String JID_RESOURCE_PREFIX = "android";
public static final String AUTHORITY = "settings";
@@ -2430,6 +2466,13 @@
public static final String BACKGROUND_DATA = "background_data";
/**
+ * Whether mobile data connections are allowed by the user. See
+ * ConnectivityManager for more info.
+ * @hide
+ */
+ public static final String MOBILE_DATA = "mobile_data";
+
+ /**
* The CDMA roaming mode 0 = Home Networks, CDMA default
* 1 = Roaming on Affiliated networks
* 2 = Roaming on any networks
@@ -3033,11 +3076,11 @@
* @hide
*/
public static final String ANR_SHOW_BACKGROUND = "anr_show_background";
-
+
/**
* The {@link ComponentName} string of the service to be used as the voice recognition
* service.
- *
+ *
* @hide
*/
public static final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service";
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index fbc4a81..caa3144 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -16,20 +16,14 @@
package android.server.search;
-import android.app.ActivityManagerNative;
-import android.app.IActivityWatcher;
+import com.android.internal.content.PackageMonitor;
+
import android.app.ISearchManager;
-import android.app.ISearchManagerCallback;
import android.app.SearchManager;
import android.app.SearchableInfo;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
import android.util.Log;
import java.util.List;
@@ -42,13 +36,11 @@
// general debugging support
private static final String TAG = "SearchManagerService";
- private static final boolean DBG = false;
// Context that the service is running in.
private final Context mContext;
- // This field is initialized in ensureSearchablesCreated(), and then never modified.
- // Only accessed by ensureSearchablesCreated() and getSearchables()
+ // This field is initialized lazily in getSearchables(), and then never modified.
private Searchables mSearchables;
/**
@@ -61,58 +53,28 @@
mContext = context;
}
- private synchronized void ensureSearchablesCreated() {
- if (mSearchables != null) return; // already created
-
- mSearchables = new Searchables(mContext);
- mSearchables.buildSearchableList();
-
- IntentFilter packageFilter = new IntentFilter();
- packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- packageFilter.addDataScheme("package");
- mContext.registerReceiver(mPackageChangedReceiver, packageFilter);
- // Register for events related to sdcard installation.
- IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- mContext.registerReceiver(mPackageChangedReceiver, sdFilter);
- }
-
private synchronized Searchables getSearchables() {
- ensureSearchablesCreated();
+ if (mSearchables == null) {
+ mSearchables = new Searchables(mContext);
+ mSearchables.buildSearchableList();
+ new MyPackageMonitor().register(mContext, true);
+ }
return mSearchables;
}
/**
* Refreshes the "searchables" list when packages are added/removed.
*/
- private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() {
+ class MyPackageMonitor extends PackageMonitor {
@Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
-
- if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
- Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
- Intent.ACTION_PACKAGE_CHANGED.equals(action) ||
- Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action) ||
- Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
- if (DBG) Log.d(TAG, "Got " + action);
- // Update list of searchable activities
- getSearchables().buildSearchableList();
- broadcastSearchablesChanged();
- }
+ public void onSomePackagesChanged() {
+ // Update list of searchable activities
+ getSearchables().buildSearchableList();
+ // Inform all listeners that the list of searchables has been updated.
+ Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ mContext.sendBroadcast(intent);
}
- };
-
- /**
- * Informs all listeners that the list of searchables has been updated.
- */
- void broadcastSearchablesChanged() {
- Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- mContext.sendBroadcast(intent);
}
//
@@ -123,24 +85,15 @@
* Returns the SearchableInfo for a given activity.
*
* @param launchActivity The activity from which we're launching this search.
- * @param globalSearch If false, this will only launch the search that has been specifically
- * defined by the application (which is usually defined as a local search). If no default
- * search is defined in the current application or activity, no search will be launched.
- * If true, this will always launch a platform-global (e.g. web-based) search instead.
* @return Returns a SearchableInfo record describing the parameters of the search,
* or null if no searchable metadata was available.
*/
- public SearchableInfo getSearchableInfo(final ComponentName launchActivity,
- final boolean globalSearch) {
- if (globalSearch) {
- return getSearchables().getDefaultSearchable();
- } else {
- if (launchActivity == null) {
- Log.e(TAG, "getSearchableInfo(), activity == null");
- return null;
- }
- return getSearchables().getSearchableInfo(launchActivity);
+ public SearchableInfo getSearchableInfo(final ComponentName launchActivity) {
+ if (launchActivity == null) {
+ Log.e(TAG, "getSearchableInfo(), activity == null");
+ return null;
}
+ return getSearchables().getSearchableInfo(launchActivity);
}
/**
@@ -151,27 +104,17 @@
}
/**
- * Returns a list of the searchable activities that handle web searches.
- * Can be called from any thread.
+ * Gets the name of the global search activity.
*/
- public List<SearchableInfo> getSearchablesForWebSearch() {
- return getSearchables().getSearchablesForWebSearchList();
+ public ComponentName getGlobalSearchActivity() {
+ return getSearchables().getGlobalSearchActivity();
}
/**
- * Returns the default searchable activity for web searches.
- * Can be called from any thread.
+ * Gets the name of the web search activity.
*/
- public SearchableInfo getDefaultSearchableForWebSearch() {
- return getSearchables().getDefaultSearchableForWebSearch();
+ public ComponentName getWebSearchActivity() {
+ return getSearchables().getWebSearchActivity();
}
- /**
- * Sets the default searchable activity for web searches.
- * Can be called from any thread.
- */
- public void setDefaultWebSearch(final ComponentName component) {
- getSearchables().setDefaultWebSearch(component);
- broadcastSearchablesChanged();
- }
}
diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java
index cbb63a5..279c17d 100644
--- a/core/java/android/server/search/Searchables.java
+++ b/core/java/android/server/search/Searchables.java
@@ -16,14 +16,12 @@
package android.server.search;
-import com.android.internal.app.ResolverActivity;
-
+import android.Manifest;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -52,9 +50,8 @@
private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
private ArrayList<SearchableInfo> mSearchablesList = null;
private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
- private ArrayList<SearchableInfo> mSearchablesForWebSearchList = null;
- private SearchableInfo mDefaultSearchable = null;
- private SearchableInfo mDefaultSearchableForWebSearch = null;
+ private ComponentName mGlobalSearchActivity = null;
+ private ComponentName mWebSearchActivity = null;
public static String GOOGLE_SEARCH_COMPONENT_NAME =
"com.android.googlesearch/.GoogleSearch";
@@ -131,10 +128,9 @@
// Irrespective of source, if a reference was found, follow it.
if (refActivityName != null)
{
- // An app or activity can declare that we should simply launch
- // "system default search" if search is invoked.
+ // This value is deprecated, return null
if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
- return getDefaultSearchable();
+ return null;
}
String pkg = activity.getPackageName();
ComponentName referredActivity;
@@ -164,20 +160,6 @@
}
/**
- * Provides the system-default search activity, which you can use
- * whenever getSearchableInfo() returns null;
- *
- * @return Returns the system-default search activity, null if never defined
- */
- public synchronized SearchableInfo getDefaultSearchable() {
- return mDefaultSearchable;
- }
-
- public synchronized boolean isDefaultSearchable(SearchableInfo searchable) {
- return searchable == mDefaultSearchable;
- }
-
- /**
* Builds an entire list (suitable for display) of
* activities that are searchable, by iterating the entire set of
* ACTION_SEARCH & ACTION_WEB_SEARCH intents.
@@ -205,8 +187,6 @@
= new ArrayList<SearchableInfo>();
ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
= new ArrayList<SearchableInfo>();
- ArrayList<SearchableInfo> newSearchablesForWebSearchList
- = new ArrayList<SearchableInfo>();
final PackageManager pm = mContext.getPackageManager();
@@ -244,127 +224,71 @@
}
}
- if (webSearchInfoList != null) {
- for (int i = 0; i < webSearchInfoList.size(); ++i) {
- ActivityInfo ai = webSearchInfoList.get(i).activityInfo;
- ComponentName component = new ComponentName(ai.packageName, ai.name);
- SearchableInfo searchable = newSearchablesMap.get(component);
- if (searchable == null) {
- Log.w(LOG_TAG, "did not find component in searchables: " + component);
- } else {
- newSearchablesForWebSearchList.add(searchable);
- }
- }
- }
+ // Find the global search activity
+ ComponentName newGlobalSearchActivity = findGlobalSearchActivity();
- // Find the global search provider
- Intent globalSearchIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
- ComponentName globalSearchActivity = globalSearchIntent.resolveActivity(pm);
- SearchableInfo newDefaultSearchable = newSearchablesMap.get(globalSearchActivity);
-
- if (newDefaultSearchable == null) {
- Log.w(LOG_TAG, "No searchable info found for new default searchable activity "
- + globalSearchActivity);
- }
-
- // Find the default web search provider.
- ComponentName webSearchActivity = getPreferredWebSearchActivity(mContext);
- SearchableInfo newDefaultSearchableForWebSearch = null;
- if (webSearchActivity != null) {
- newDefaultSearchableForWebSearch = newSearchablesMap.get(webSearchActivity);
- }
- if (newDefaultSearchableForWebSearch == null) {
- Log.w(LOG_TAG, "No searchable info found for new default web search activity "
- + webSearchActivity);
- }
+ // Find the web search activity
+ ComponentName newWebSearchActivity = findWebSearchActivity(newGlobalSearchActivity);
// Store a consistent set of new values
synchronized (this) {
mSearchablesMap = newSearchablesMap;
mSearchablesList = newSearchablesList;
mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
- mSearchablesForWebSearchList = newSearchablesForWebSearchList;
- mDefaultSearchable = newDefaultSearchable;
- mDefaultSearchableForWebSearch = newDefaultSearchableForWebSearch;
+ mGlobalSearchActivity = newGlobalSearchActivity;
+ mWebSearchActivity = newWebSearchActivity;
}
}
/**
- * Checks if the given activity component is present in the system and if so makes it the
- * preferred activity for handling ACTION_WEB_SEARCH.
- * @param component Name of the component to check and set as preferred.
- * @param action Intent action for which this activity is to be set as preferred.
- * @return true if component was detected and set as preferred activity, false if not.
+ * Finds the global search activity.
+ *
+ * This is currently implemented by returning the first activity that handles
+ * the GLOBAL_SEARCH intent and has the GLOBAL_SEARCH permission. If we allow
+ * more than one global search activity to be installed, this code must be changed.
*/
- private static boolean setPreferredActivity(Context context,
- ComponentName component, String action) {
- Log.d(LOG_TAG, "Checking component " + component);
- PackageManager pm = context.getPackageManager();
- ActivityInfo ai;
- try {
- ai = pm.getActivityInfo(component, 0);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
+ private ComponentName findGlobalSearchActivity() {
+ Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> activities =
+ pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ int count = activities == null ? 0 : activities.size();
+ for (int i = 0; i < count; i++) {
+ ActivityInfo ai = activities.get(i).activityInfo;
+ if (pm.checkPermission(Manifest.permission.GLOBAL_SEARCH,
+ ai.packageName) == PackageManager.PERMISSION_GRANTED) {
+ return new ComponentName(ai.packageName, ai.name);
+ } else {
+ Log.w(LOG_TAG, "Package " + ai.packageName + " wants to handle GLOBAL_SEARCH, "
+ + "but does not have the GLOBAL_SEARCH permission.");
+ }
}
-
- // The code here to find the value for bestMatch is heavily inspired by the code
- // in ResolverActivity where the preferred activity is set.
- Intent intent = new Intent(action);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- List<ResolveInfo> webSearchActivities = pm.queryIntentActivities(intent, 0);
- ComponentName set[] = new ComponentName[webSearchActivities.size()];
- int bestMatch = 0;
- for (int i = 0; i < webSearchActivities.size(); ++i) {
- ResolveInfo ri = webSearchActivities.get(i);
- set[i] = new ComponentName(ri.activityInfo.packageName,
- ri.activityInfo.name);
- if (ri.match > bestMatch) bestMatch = ri.match;
- }
-
- Log.d(LOG_TAG, "Setting preferred web search activity to " + component);
- IntentFilter filter = new IntentFilter(action);
- filter.addCategory(Intent.CATEGORY_DEFAULT);
- pm.replacePreferredActivity(filter, bestMatch, set, component);
- return true;
+ Log.w(LOG_TAG, "No global search activity found");
+ return null;
}
- private static ComponentName getPreferredWebSearchActivity(Context context) {
- // Check if we have a preferred web search activity.
- Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
- PackageManager pm = context.getPackageManager();
- ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
-
- if (ri == null || ri.activityInfo.name.equals(ResolverActivity.class.getName())) {
- Log.d(LOG_TAG, "No preferred activity set for action web search.");
-
- // The components in the providers array are checked in the order of declaration so the
- // first one has the highest priority. If the component exists in the system it is set
- // as the preferred activity to handle intent action web search.
- String[] preferredActivities = context.getResources().getStringArray(
- com.android.internal.R.array.default_web_search_providers);
- for (String componentName : preferredActivities) {
- ComponentName component = ComponentName.unflattenFromString(componentName);
- if (setPreferredActivity(context, component, Intent.ACTION_WEB_SEARCH)) {
- return component;
- }
- }
- } else {
- // If the current preferred activity is GoogleSearch, and we detect
- // EnhancedGoogleSearch installed as well, set the latter as preferred since that
- // is a superset and provides more functionality.
- ComponentName cn = new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
- if (cn.flattenToShortString().equals(GOOGLE_SEARCH_COMPONENT_NAME)) {
- ComponentName enhancedGoogleSearch = ComponentName.unflattenFromString(
- ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME);
- if (setPreferredActivity(context, enhancedGoogleSearch,
- Intent.ACTION_WEB_SEARCH)) {
- return enhancedGoogleSearch;
- }
- }
+ /**
+ * Finds the web search activity.
+ *
+ * Only looks in the package of the global search activity.
+ */
+ private ComponentName findWebSearchActivity(ComponentName globalSearchActivity) {
+ if (globalSearchActivity == null) {
+ return null;
}
-
- if (ri == null) return null;
- return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
+ Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
+ intent.setPackage(globalSearchActivity.getPackageName());
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> activities =
+ pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ int count = activities == null ? 0 : activities.size();
+ for (int i = 0; i < count; i++) {
+ ActivityInfo ai = activities.get(i).activityInfo;
+ // TODO: do some sanity checks here?
+ return new ComponentName(ai.packageName, ai.name);
+ }
+ Log.w(LOG_TAG, "No web search activity found");
+ return null;
}
/**
@@ -383,24 +307,16 @@
}
/**
- * Returns a list of the searchable activities that handle web searches.
+ * Gets the name of the global search activity.
*/
- public synchronized ArrayList<SearchableInfo> getSearchablesForWebSearchList() {
- return new ArrayList<SearchableInfo>(mSearchablesForWebSearchList);
+ public synchronized ComponentName getGlobalSearchActivity() {
+ return mGlobalSearchActivity;
}
/**
- * Returns the default searchable activity for web searches.
+ * Gets the name of the web search activity.
*/
- public synchronized SearchableInfo getDefaultSearchableForWebSearch() {
- return mDefaultSearchableForWebSearch;
- }
-
- /**
- * Sets the default searchable activity for web searches.
- */
- public synchronized void setDefaultWebSearch(ComponentName component) {
- setPreferredActivity(mContext, component, Intent.ACTION_WEB_SEARCH);
- buildSearchableList();
+ public synchronized ComponentName getWebSearchActivity() {
+ return mWebSearchActivity;
}
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index eb48a0c..52de64c 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -226,7 +227,7 @@
@Override
public void resized(int w, int h, Rect coveredInsets,
- Rect visibleInsets, boolean reportDraw) {
+ Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED,
reportDraw ? 1 : 0);
mCaller.sendMessage(msg);
diff --git a/core/java/android/speech/RecognitionManager.java b/core/java/android/speech/RecognitionManager.java
index 7f55ad6..16b1f89 100644
--- a/core/java/android/speech/RecognitionManager.java
+++ b/core/java/android/speech/RecognitionManager.java
@@ -98,6 +98,9 @@
/** Context with which the manager was created */
private final Context mContext;
+
+ /** Component to direct service intent to */
+ private final ComponentName mServiceComponent;
/** Handler that will execute the main tasks */
private Handler mHandler = new Handler() {
@@ -133,8 +136,9 @@
* The right way to create a {@code RecognitionManager} is by using
* {@link #createRecognitionManager} static factory method
*/
- private RecognitionManager(final Context context) {
+ private RecognitionManager(final Context context, final ComponentName serviceComponent) {
mContext = context;
+ mServiceComponent = serviceComponent;
}
/**
@@ -184,11 +188,31 @@
* @return a new {@code RecognitionManager}
*/
public static RecognitionManager createRecognitionManager(final Context context) {
+ return createRecognitionManager(context, null);
+ }
+
+ /**
+ * Factory method to create a new {@code RecognitionManager}, please note that
+ * {@link #setRecognitionListener(RecognitionListener)} must be called before dispatching any
+ * command to the created {@code RecognitionManager}.
+ *
+ * Use this version of the method to specify a specific service to direct this
+ * {@link RecognitionManager} to. Normally you would not use this; use
+ * {@link #createRecognitionManager(Context)} instead to use the system default
+ * recognition service.
+ *
+ * @param context in which to create {@code RecognitionManager}
+ * @param serviceComponent the {@link ComponentName} of a specific service to direct this
+ * {@code RecognitionManager} to
+ * @return a new {@code RecognitionManager}
+ */
+ public static RecognitionManager createRecognitionManager(final Context context,
+ final ComponentName serviceComponent) {
if (context == null) {
throw new IllegalArgumentException("Context cannot be null)");
}
checkIsCalledFromMainThread();
- return new RecognitionManager(context);
+ return new RecognitionManager(context, serviceComponent);
}
/**
@@ -222,17 +246,22 @@
mConnection = new Connection();
Intent serviceIntent = new Intent(RecognitionService.SERVICE_INTERFACE);
- String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.VOICE_RECOGNITION_SERVICE);
- if (TextUtils.isEmpty(serviceComponent)) {
- Log.e(TAG, "no selected voice recognition service");
- mListener.onError(ERROR_CLIENT);
- return;
+ if (mServiceComponent == null) {
+ String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE);
+
+ if (TextUtils.isEmpty(serviceComponent)) {
+ Log.e(TAG, "no selected voice recognition service");
+ mListener.onError(ERROR_CLIENT);
+ return;
+ }
+
+ serviceIntent.setComponent(ComponentName.unflattenFromString(serviceComponent));
+ } else {
+ serviceIntent.setComponent(mServiceComponent);
}
- serviceIntent.setComponent(ComponentName.unflattenFromString(serviceComponent));
-
if (!mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
Log.e(TAG, "bind to recognition service failed");
mConnection = null;
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index 5f651e1..7c15cec 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -16,9 +16,17 @@
package android.speech;
+import java.util.ArrayList;
+
import android.app.Activity;
import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
/**
* Constants for supporting speech recognition through starting an {@link Intent}
@@ -208,4 +216,92 @@
* an activity result. In a PendingIntent, the lack of this extra indicates failure.
*/
public static final String EXTRA_RESULTS = "android.speech.extra.RESULTS";
+
+ /**
+ * Returns the broadcast intent to fire with
+ * {@link Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler, int, String, Bundle)}
+ * to receive details from the package that implements voice search.
+ * <p>
+ * This is based on the value specified by the voice search {@link Activity} in
+ * {@link #DETAILS_META_DATA}, and if this is not specified, will return null. Also if there
+ * is no chosen default to resolve for {@link #ACTION_WEB_SEARCH}, this will return null.
+ * <p>
+ * If an intent is returned and is fired, a {@link Bundle} of extras will be returned to the
+ * provided result receiver, and should ideally contain values for
+ * {@link #EXTRA_LANGUAGE_PREFERENCE} and {@link #EXTRA_SUPPORTED_LANGUAGES}.
+ * <p>
+ * (Whether these are actually provided is up to the particular implementation. It is
+ * recommended that {@link Activity}s implementing {@link #ACTION_WEB_SEARCH} provide this
+ * information, but it is not required.)
+ *
+ * @param context a context object
+ * @return the broadcast intent to fire or null if not available
+ */
+ public static final Intent getVoiceDetailsIntent(Context context) {
+ Intent voiceSearchIntent = new Intent(ACTION_WEB_SEARCH);
+ ResolveInfo ri = context.getPackageManager().resolveActivity(
+ voiceSearchIntent, PackageManager.GET_META_DATA);
+ if (ri == null || ri.activityInfo == null || ri.activityInfo.metaData == null) return null;
+
+ String className = ri.activityInfo.metaData.getString(DETAILS_META_DATA);
+ if (className == null) return null;
+
+ Intent detailsIntent = new Intent(ACTION_GET_LANGUAGE_DETAILS);
+ detailsIntent.setComponent(new ComponentName(ri.activityInfo.packageName, className));
+ return detailsIntent;
+ }
+
+ /**
+ * Meta-data name under which an {@link Activity} implementing {@link #ACTION_WEB_SEARCH} can
+ * use to expose the class name of a {@link BroadcastReceiver} which can respond to request for
+ * more information, from any of the broadcast intents specified in this class.
+ * <p>
+ * Broadcast intents can be directed to the class name specified in the meta-data by creating
+ * an {@link Intent}, setting the component with
+ * {@link Intent#setComponent(android.content.ComponentName)}, and using
+ * {@link Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle)}
+ * with another {@link BroadcastReceiver} which can receive the results.
+ * <p>
+ * The {@link #getVoiceDetailsIntent(Context)} method is provided as a convenience to create
+ * a broadcast intent based on the value of this meta-data, if available.
+ * <p>
+ * This is optional and not all {@link Activity}s which implement {@link #ACTION_WEB_SEARCH}
+ * are required to implement this. Thus retrieving this meta-data may be null.
+ */
+ public static final String DETAILS_META_DATA = "android.speech.DETAILS";
+
+ /**
+ * A broadcast intent which can be fired to the {@link BroadcastReceiver} component specified
+ * in the meta-data defined in the {@link #DETAILS_META_DATA} meta-data of an
+ * {@link Activity} satisfying {@link #ACTION_WEB_SEARCH}.
+ * <p>
+ * When fired with
+ * {@link Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle)},
+ * a {@link Bundle} of extras will be returned to the provided result receiver, and should
+ * ideally contain values for {@link #EXTRA_LANGUAGE_PREFERENCE} and
+ * {@link #EXTRA_SUPPORTED_LANGUAGES}.
+ * <p>
+ * (Whether these are actually provided is up to the particular implementation. It is
+ * recommended that {@link Activity}s implementing {@link #ACTION_WEB_SEARCH} provide this
+ * information, but it is not required.)
+ */
+ public static final String ACTION_GET_LANGUAGE_DETAILS =
+ "android.speech.action.GET_LANGUAGE_DETAILS";
+
+ /**
+ * The key to the extra in the {@link Bundle} returned by {@link #ACTION_GET_LANGUAGE_DETAILS}
+ * which is a {@link String} that represents the current language preference this user has
+ * specified - a locale string like "en-US".
+ */
+ public static final String EXTRA_LANGUAGE_PREFERENCE =
+ "android.speech.extra.LANGUAGE_PREFERENCE";
+
+ /**
+ * The key to the extra in the {@link Bundle} returned by {@link #ACTION_GET_LANGUAGE_DETAILS}
+ * which is an {@link ArrayList} of {@link String}s that represents the languages supported by
+ * this implementation of voice recognition - a list of strings like "en-US", "cmn-Hans-CN",
+ * etc.
+ */
+ public static final String EXTRA_SUPPORTED_LANGUAGES =
+ "android.speech.extra.SUPPORTED_LANGUAGES";
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 1023036..38ac9b7 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1936,6 +1936,11 @@
public static final int DIR_LEFT_TO_RIGHT = 1;
public static final int DIR_RIGHT_TO_LEFT = -1;
+
+ /* package */ static final int DIR_REQUEST_LTR = 1;
+ /* package */ static final int DIR_REQUEST_RTL = -1;
+ /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
+ /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
public enum Alignment {
ALIGN_NORMAL,
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 6c89f92..600ec7e 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -234,215 +234,9 @@
}
if (!easy) {
- AndroidCharacter.getDirectionalities(chs, chdirs, end - start);
-
- /*
- * Determine primary paragraph direction
- */
-
- for (int j = start; j < end; j++) {
- int d = chdirs[j - start];
-
- if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
- dir = DIR_LEFT_TO_RIGHT;
- break;
- }
- if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- dir = DIR_RIGHT_TO_LEFT;
- break;
- }
- }
-
- /*
- * XXX Explicit overrides should go here
- */
-
- /*
- * Weak type resolution
- */
-
- final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
- Character.DIRECTIONALITY_LEFT_TO_RIGHT :
- Character.DIRECTIONALITY_RIGHT_TO_LEFT;
-
- // dump(chdirs, n, "initial");
-
- // W1 non spacing marks
- for (int j = 0; j < n; j++) {
- if (chdirs[j] == Character.NON_SPACING_MARK) {
- if (j == 0)
- chdirs[j] = SOR;
- else
- chdirs[j] = chdirs[j - 1];
- }
- }
-
- // dump(chdirs, n, "W1");
-
- // W2 european numbers
- byte cur = SOR;
- for (int j = 0; j < n; j++) {
- byte d = chdirs[j];
-
- if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
- cur = d;
- else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
- if (cur ==
- Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
- chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
- }
- }
-
- // dump(chdirs, n, "W2");
-
- // W3 arabic letters
- for (int j = 0; j < n; j++) {
- if (chdirs[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
- chdirs[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
- }
-
- // dump(chdirs, n, "W3");
-
- // W4 single separator between numbers
- for (int j = 1; j < n - 1; j++) {
- byte d = chdirs[j];
- byte prev = chdirs[j - 1];
- byte next = chdirs[j + 1];
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
- if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
- next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
- if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
- next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
- next == Character.DIRECTIONALITY_ARABIC_NUMBER)
- chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
- }
- }
-
- // dump(chdirs, n, "W4");
-
- // W5 european number terminators
- boolean adjacent = false;
- for (int j = 0; j < n; j++) {
- byte d = chdirs[j];
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- adjacent = true;
- else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
- chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- else
- adjacent = false;
- }
-
- //dump(chdirs, n, "W5");
-
- // W5 european number terminators part 2,
- // W6 separators and terminators
- adjacent = false;
- for (int j = n - 1; j >= 0; j--) {
- byte d = chdirs[j];
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- adjacent = true;
- else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
- if (adjacent)
- chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- else
- chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
- }
- else {
- adjacent = false;
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
- d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
- d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
- d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
- chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
- }
- }
-
- // dump(chdirs, n, "W6");
-
- // W7 strong direction of european numbers
- cur = SOR;
- for (int j = 0; j < n; j++) {
- byte d = chdirs[j];
-
- if (d == SOR ||
- d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
- cur = d;
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- chdirs[j] = cur;
- }
-
- // dump(chdirs, n, "W7");
-
- // N1, N2 neutrals
- cur = SOR;
- for (int j = 0; j < n; j++) {
- byte d = chdirs[j];
-
- if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- cur = d;
- } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
- d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
- cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
- } else {
- byte dd = SOR;
- int k;
-
- for (k = j + 1; k < n; k++) {
- dd = chdirs[k];
-
- if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- break;
- }
- if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
- dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
- dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
- break;
- }
- }
-
- for (int y = j; y < k; y++) {
- if (dd == cur)
- chdirs[y] = cur;
- else
- chdirs[y] = SOR;
- }
-
- j = k - 1;
- }
- }
-
- // dump(chdirs, n, "final");
-
- // extra: enforce that all tabs and surrogate characters go the
- // primary direction
- // TODO: actually do directions right for surrogates
-
- for (int j = 0; j < n; j++) {
- char c = chs[j];
-
- if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) {
- chdirs[j] = SOR;
- }
- }
-
- // extra: enforce that object replacements go to the
- // primary direction
- // and that none of the underlying characters are treated
- // as viable breakpoints
+ // Ensure that none of the underlying characters are treated
+ // as viable breakpoints, and that the entire run gets the
+ // same bidi direction.
if (source instanceof Spanned) {
Spanned sp = (Spanned) source;
@@ -453,12 +247,14 @@
int b = sp.getSpanEnd(spans[y]);
for (int x = a; x < b; x++) {
- chdirs[x - start] = SOR;
chs[x - start] = '\uFFFC';
}
}
}
+ // XXX put override flags, etc. into chdirs
+ dir = bidi(dir, chs, chdirs, n, false);
+
// Do mirroring for right-to-left segments
for (int i = 0; i < n; i++) {
@@ -810,6 +606,239 @@
}
}
+ /**
+ * Runs the unicode bidi algorithm on the first n chars in chs, returning
+ * the char dirs in chInfo and the base line direction of the first
+ * paragraph.
+ *
+ * XXX change result from dirs to levels
+ *
+ * @param dir the direction flag, either DIR_REQUEST_LTR,
+ * DIR_REQUEST_RTL, DIR_REQUEST_DEFAULT_LTR, or DIR_REQUEST_DEFAULT_RTL.
+ * @param chs the text to examine
+ * @param chInfo on input, if hasInfo is true, override and other flags
+ * representing out-of-band embedding information. On output, the generated
+ * dirs of the text.
+ * @param n the length of the text/information in chs and chInfo
+ * @param hasInfo true if chInfo has input information, otherwise the
+ * input data in chInfo is ignored.
+ * @return the resolved direction level of the first paragraph, either
+ * DIR_LEFT_TO_RIGHT or DIR_RIGHT_TO_LEFT.
+ */
+ /* package */ static int bidi(int dir, char[] chs, byte[] chInfo, int n,
+ boolean hasInfo) {
+
+ AndroidCharacter.getDirectionalities(chs, chInfo, n);
+
+ /*
+ * Determine primary paragraph direction if not specified
+ */
+ if (dir != DIR_REQUEST_LTR && dir != DIR_REQUEST_RTL) {
+ // set up default
+ dir = dir >= 0 ? DIR_LEFT_TO_RIGHT : DIR_RIGHT_TO_LEFT;
+ for (int j = 0; j < n; j++) {
+ int d = chInfo[j];
+
+ if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
+ dir = DIR_LEFT_TO_RIGHT;
+ break;
+ }
+ if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
+ dir = DIR_RIGHT_TO_LEFT;
+ break;
+ }
+ }
+ }
+
+ final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
+ Character.DIRECTIONALITY_LEFT_TO_RIGHT :
+ Character.DIRECTIONALITY_RIGHT_TO_LEFT;
+
+ /*
+ * XXX Explicit overrides should go here
+ */
+
+ /*
+ * Weak type resolution
+ */
+
+ // dump(chdirs, n, "initial");
+
+ // W1 non spacing marks
+ for (int j = 0; j < n; j++) {
+ if (chInfo[j] == Character.NON_SPACING_MARK) {
+ if (j == 0)
+ chInfo[j] = SOR;
+ else
+ chInfo[j] = chInfo[j - 1];
+ }
+ }
+
+ // dump(chdirs, n, "W1");
+
+ // W2 european numbers
+ byte cur = SOR;
+ for (int j = 0; j < n; j++) {
+ byte d = chInfo[j];
+
+ if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
+ d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
+ d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
+ cur = d;
+ else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
+ if (cur ==
+ Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
+ chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
+ }
+ }
+
+ // dump(chdirs, n, "W2");
+
+ // W3 arabic letters
+ for (int j = 0; j < n; j++) {
+ if (chInfo[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
+ chInfo[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
+ }
+
+ // dump(chdirs, n, "W3");
+
+ // W4 single separator between numbers
+ for (int j = 1; j < n - 1; j++) {
+ byte d = chInfo[j];
+ byte prev = chInfo[j - 1];
+ byte next = chInfo[j + 1];
+
+ if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
+ if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
+ next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+ chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
+ } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
+ if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
+ next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+ chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
+ if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
+ next == Character.DIRECTIONALITY_ARABIC_NUMBER)
+ chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
+ }
+ }
+
+ // dump(chdirs, n, "W4");
+
+ // W5 european number terminators
+ boolean adjacent = false;
+ for (int j = 0; j < n; j++) {
+ byte d = chInfo[j];
+
+ if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+ adjacent = true;
+ else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
+ chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
+ else
+ adjacent = false;
+ }
+
+ //dump(chdirs, n, "W5");
+
+ // W5 european number terminators part 2,
+ // W6 separators and terminators
+ adjacent = false;
+ for (int j = n - 1; j >= 0; j--) {
+ byte d = chInfo[j];
+
+ if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+ adjacent = true;
+ else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
+ if (adjacent)
+ chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
+ else
+ chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
+ }
+ else {
+ adjacent = false;
+
+ if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
+ d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
+ d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
+ d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
+ chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
+ }
+ }
+
+ // dump(chdirs, n, "W6");
+
+ // W7 strong direction of european numbers
+ cur = SOR;
+ for (int j = 0; j < n; j++) {
+ byte d = chInfo[j];
+
+ if (d == SOR ||
+ d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
+ d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
+ cur = d;
+
+ if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+ chInfo[j] = cur;
+ }
+
+ // dump(chdirs, n, "W7");
+
+ // N1, N2 neutrals
+ cur = SOR;
+ for (int j = 0; j < n; j++) {
+ byte d = chInfo[j];
+
+ if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
+ d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
+ cur = d;
+ } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
+ d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
+ cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
+ } else {
+ byte dd = SOR;
+ int k;
+
+ for (k = j + 1; k < n; k++) {
+ dd = chInfo[k];
+
+ if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
+ dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
+ break;
+ }
+ if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
+ dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
+ dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
+ break;
+ }
+ }
+
+ for (int y = j; y < k; y++) {
+ if (dd == cur)
+ chInfo[y] = cur;
+ else
+ chInfo[y] = SOR;
+ }
+
+ j = k - 1;
+ }
+ }
+
+ // dump(chdirs, n, "final");
+
+ // extra: enforce that all tabs and surrogate characters go the
+ // primary direction
+ // TODO: actually do directions right for surrogates
+
+ for (int j = 0; j < n; j++) {
+ char c = chs[j];
+
+ if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) {
+ chInfo[j] = SOR;
+ }
+ }
+
+ return dir;
+ }
+
private static final char FIRST_CJK = '\u2E80';
/**
* Returns true if the specified character is one of those specified
@@ -1012,12 +1041,12 @@
int extra;
if (needMultiply) {
- // XXX: this looks like it is using the +0.5 and the cast to int
- // to do rounding, but this I expect this isn't doing the intended
- // thing when spacingmult < 1. An intended extra of, say, -1.2
- // will get 'rounded' to -.7 and then truncated to 0.
- extra = (int) ((below - above) * (spacingmult - 1)
- + spacingadd + 0.5);
+ double ex = (below - above) * (spacingmult - 1) + spacingadd;
+ if (ex >= 0) {
+ extra = (int)(ex + 0.5);
+ } else {
+ extra = -(int)(-ex + 0.5);
+ }
} else {
extra = 0;
}
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 58f998e..c1e1049 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -494,6 +494,9 @@
mLastMotionX = x;
mLastMotionY = y;
+ if (mCurrentDownEvent != null) {
+ mCurrentDownEvent.recycle();
+ }
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
mAlwaysInBiggerTapRegion = true;
@@ -562,10 +565,14 @@
if ((Math.abs(velocityY) > mMinimumFlingVelocity)
|| (Math.abs(velocityX) > mMinimumFlingVelocity)){
- handled = mListener.onFling(mCurrentDownEvent, currentUpEvent, velocityX, velocityY);
+ handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
}
}
- mPreviousUpEvent = MotionEvent.obtain(ev);
+ if (mPreviousUpEvent != null) {
+ mPreviousUpEvent.recycle();
+ }
+ // Hold the event we obtained above - listeners may have changed the original.
+ mPreviousUpEvent = currentUpEvent;
mVelocityTracker.recycle();
mVelocityTracker = null;
mIsDoubleTapping = false;
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 71302cb..3b09808 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -17,6 +17,7 @@
package android.view;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -44,7 +45,7 @@
void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor);
void resized(int w, int h, in Rect coveredInsets, in Rect visibleInsets,
- boolean reportDraw);
+ boolean reportDraw, in Configuration newConfig);
void dispatchKey(in KeyEvent event);
void dispatchPointer(in MotionEvent event, long eventTime, boolean callWhenDone);
void dispatchTrackball(in MotionEvent event, long eventTime, boolean callWhenDone);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0ebe360..9b7b2f40 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -64,8 +64,6 @@
void addAppToken(int addPos, IApplicationToken token,
int groupId, int requestedOrientation, boolean fullscreen);
void setAppGroupId(IBinder token, int groupId);
- Configuration updateOrientationFromAppTokens(in Configuration currentConfig,
- IBinder freezeThisOneIfNeeded);
void setAppOrientation(IApplicationToken token, int requestedOrientation);
int getAppOrientation(IApplicationToken token);
void setFocusedApp(IBinder token, boolean moveFocusNow);
@@ -85,6 +83,13 @@
void moveAppTokensToTop(in List<IBinder> tokens);
void moveAppTokensToBottom(in List<IBinder> tokens);
+ // Re-evaluate the current orientation from the caller's state.
+ // If there is a change, the new Configuration is returned and the
+ // caller must call setNewConfiguration() sometime later.
+ Configuration updateOrientationFromAppTokens(in Configuration currentConfig,
+ IBinder freezeThisOneIfNeeded);
+ void setNewConfiguration(in Configuration config);
+
// these require DISABLE_KEYGUARD permission
void disableKeyguard(IBinder token, String tag);
void reenableKeyguard(IBinder token);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index ca5e1de..d7f2539 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -19,6 +19,7 @@
import com.android.internal.view.BaseIWindow;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.CompatibilityInfo.Translator;
import android.graphics.Canvas;
@@ -504,7 +505,7 @@
}
public void resized(int w, int h, Rect coveredInsets,
- Rect visibleInsets, boolean reportDraw) {
+ Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
SurfaceView surfaceView = mSurfaceView.get();
if (surfaceView != null) {
if (localLOGV) Log.v(
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index bc49439..679206d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -20,6 +20,7 @@
import com.android.internal.view.menu.MenuBuilder;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -3649,14 +3650,27 @@
}
/**
- * This is called when a container is going to temporarily detach a child
- * that currently has focus, with
+ * @hide
+ */
+ public void dispatchStartTemporaryDetach() {
+ onStartTemporaryDetach();
+ }
+
+ /**
+ * This is called when a container is going to temporarily detach a child, with
* {@link ViewGroup#detachViewFromParent(View) ViewGroup.detachViewFromParent}.
* It will either be followed by {@link #onFinishTemporaryDetach()} or
- * {@link #onDetachedFromWindow()} when the container is done. Generally
- * this is currently only done ListView for a view with focus.
+ * {@link #onDetachedFromWindow()} when the container is done.
*/
public void onStartTemporaryDetach() {
+ removeUnsetPressCallback();
+ }
+
+ /**
+ * @hide
+ */
+ public void dispatchFinishTemporaryDetach() {
+ onFinishTemporaryDetach();
}
/**
@@ -3933,6 +3947,32 @@
}
/**
+ * Dispatch a notification about a resource configuration change down
+ * the view hierarchy.
+ * ViewGroups should override to route to their children.
+ *
+ * @param newConfig The new resource configuration.
+ *
+ * @see #onConfigurationChanged
+ */
+ public void dispatchConfigurationChanged(Configuration newConfig) {
+ onConfigurationChanged(newConfig);
+ }
+
+ /**
+ * Called when the current configuration of the resources being used
+ * by the application have changed. You can use this to decide when
+ * to reload resources that can changed based on orientation and other
+ * configuration characterstics. You only need to use this if you are
+ * not relying on the normal {@link android.app.Activity} mechanism of
+ * recreating the activity instance upon a configuration change.
+ *
+ * @param newConfig The new resource configuration.
+ */
+ protected void onConfigurationChanged(Configuration newConfig) {
+ }
+
+ /**
* Private function to aggregate all per-view attributes in to the view
* root.
*/
@@ -4335,6 +4375,16 @@
}
/**
+ * Remove the prepress detection timer.
+ */
+ private void removeUnsetPressCallback() {
+ if ((mPrivateFlags & PRESSED) != 0 && mUnsetPressedState != null) {
+ setPressed(false);
+ removeCallbacks(mUnsetPressedState);
+ }
+ }
+
+ /**
* Remove the tap detection timer.
*/
private void removeTapCallback() {
@@ -5859,6 +5909,7 @@
* @see #onAttachedToWindow()
*/
protected void onDetachedFromWindow() {
+ removeUnsetPressCallback();
removeLongPressCallback();
destroyDrawingCache();
}
@@ -7285,6 +7336,7 @@
* Sets the background color for this view.
* @param color the color of the background
*/
+ @RemotableViewMethod
public void setBackgroundColor(int color) {
setBackgroundDrawable(new ColorDrawable(color));
}
@@ -7295,6 +7347,7 @@
* @param resid The identifier of the resource.
* @attr ref android.R.styleable#View_background
*/
+ @RemotableViewMethod
public void setBackgroundResource(int resid) {
if (resid != 0 && resid == mBackgroundResource) {
return;
@@ -8704,7 +8757,7 @@
boolean clampedX, boolean clampedY) {
// Intentionally empty.
}
-
+
/**
* A MeasureSpec encapsulates the layout requirements passed from parent to child.
* Each MeasureSpec represents a requirement for either the width or the height.
@@ -9155,12 +9208,6 @@
boolean mRecomputeGlobalAttributes;
/**
- * Set to true when attributes (like mKeepScreenOn) need to be
- * recomputed.
- */
- boolean mAttributesChanged;
-
- /**
* Set during a traveral if any views want to keep the screen on.
*/
boolean mKeepScreenOn;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 2ed623d..d05416d 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -19,6 +19,7 @@
import com.android.internal.R;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -722,6 +723,19 @@
/**
* {@inheritDoc}
*/
+ @Override
+ public void dispatchConfigurationChanged(Configuration newConfig) {
+ super.dispatchConfigurationChanged(newConfig);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchConfigurationChanged(newConfig);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public void recomputeViewAttributes(View child) {
ViewParent parent = mParent;
if (parent != null) parent.recomputeViewAttributes(this);
@@ -1051,6 +1065,36 @@
}
return false;
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @hide
+ */
+ @Override
+ public void dispatchStartTemporaryDetach() {
+ super.dispatchStartTemporaryDetach();
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchStartTemporaryDetach();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @hide
+ */
+ @Override
+ public void dispatchFinishTemporaryDetach() {
+ super.dispatchFinishTemporaryDetach();
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchFinishTemporaryDetach();
+ }
+ }
/**
* {@inheritDoc}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 07b2d1c..264b8c9 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -41,7 +41,9 @@
import android.widget.Scroller;
import android.content.pm.PackageManager;
import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.ComponentCallbacks;
import android.content.Context;
import android.app.ActivityManagerNative;
import android.Manifest;
@@ -101,6 +103,9 @@
static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<Runnable>();
static boolean sFirstDrawComplete = false;
+ static final ArrayList<ComponentCallbacks> sConfigCallbacks
+ = new ArrayList<ComponentCallbacks>();
+
private static int sDrawTime;
long mLastTrackballTime = 0;
@@ -171,6 +176,12 @@
final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
= new ViewTreeObserver.InternalInsetsInfo();
+ class ResizedInfo {
+ Rect coveredInsets;
+ Rect visibleInsets;
+ Configuration newConfig;
+ }
+
boolean mScrollMayChange;
int mSoftInputMode;
View mLastScrolledFocus;
@@ -265,6 +276,12 @@
}
}
+ public static void addConfigCallback(ComponentCallbacks callback) {
+ synchronized (sConfigCallbacks) {
+ sConfigCallbacks.add(callback);
+ }
+ }
+
// FIXME for perf testing only
private boolean mProfile = false;
@@ -1782,23 +1799,33 @@
handleGetNewSurface();
break;
case RESIZED:
- Rect coveredInsets = ((Rect[])msg.obj)[0];
- Rect visibleInsets = ((Rect[])msg.obj)[1];
+ ResizedInfo ri = (ResizedInfo)msg.obj;
if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2
- && mPendingContentInsets.equals(coveredInsets)
- && mPendingVisibleInsets.equals(visibleInsets)) {
+ && mPendingContentInsets.equals(ri.coveredInsets)
+ && mPendingVisibleInsets.equals(ri.visibleInsets)) {
break;
}
// fall through...
case RESIZED_REPORT:
if (mAdded) {
+ Configuration config = ((ResizedInfo)msg.obj).newConfig;
+ if (config != null) {
+ synchronized (sConfigCallbacks) {
+ for (int i=sConfigCallbacks.size()-1; i>=0; i--) {
+ sConfigCallbacks.get(i).onConfigurationChanged(config);
+ }
+ }
+ if (mView != null) {
+ mView.dispatchConfigurationChanged(config);
+ }
+ }
mWinFrame.left = 0;
mWinFrame.right = msg.arg1;
mWinFrame.top = 0;
mWinFrame.bottom = msg.arg2;
- mPendingContentInsets.set(((Rect[])msg.obj)[0]);
- mPendingVisibleInsets.set(((Rect[])msg.obj)[1]);
+ mPendingContentInsets.set(((ResizedInfo)msg.obj).coveredInsets);
+ mPendingVisibleInsets.set(((ResizedInfo)msg.obj).visibleInsets);
if (msg.what == RESIZED_REPORT) {
mReportNextDraw = true;
}
@@ -2587,7 +2614,7 @@
}
public void dispatchResized(int w, int h, Rect coveredInsets,
- Rect visibleInsets, boolean reportDraw) {
+ Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": w=" + w
+ " h=" + h + " coveredInsets=" + coveredInsets.toShortString()
+ " visibleInsets=" + visibleInsets.toShortString()
@@ -2601,7 +2628,11 @@
}
msg.arg1 = w;
msg.arg2 = h;
- msg.obj = new Rect[] { new Rect(coveredInsets), new Rect(visibleInsets) };
+ ResizedInfo ri = new ResizedInfo();
+ ri.coveredInsets = new Rect(coveredInsets);
+ ri.visibleInsets = new Rect(visibleInsets);
+ ri.newConfig = newConfig;
+ msg.obj = ri;
sendMessage(msg);
}
@@ -2802,11 +2833,11 @@
}
public void resized(int w, int h, Rect coveredInsets,
- Rect visibleInsets, boolean reportDraw) {
+ Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
final ViewRoot viewRoot = mViewRoot.get();
if (viewRoot != null) {
viewRoot.dispatchResized(w, h, coveredInsets,
- visibleInsets, reportDraw);
+ visibleInsets, reportDraw, newConfig);
}
}
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 94bedde..84e34bc 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -150,8 +150,13 @@
}
boolean exactMatch(Cookie in) {
+ // An exact match means that domain, path, and name are equal. If
+ // both values are null, the cookies match. If both values are
+ // non-null, the cookies match. If one value is null and the other
+ // is non-null, the cookies do not match (i.e. "foo=;" and "foo;")
+ boolean valuesMatch = !((value == null) ^ (in.value == null));
return domain.equals(in.domain) && path.equals(in.path) &&
- name.equals(in.name);
+ name.equals(in.name) && valuesMatch;
}
boolean domainMatch(String urlHost) {
@@ -206,17 +211,29 @@
// As Set is not modified if the two objects are same, we do want to
// assign different value for each cookie.
int diff = cookie2.path.length() - cookie1.path.length();
- if (diff == 0) {
- diff = cookie2.domain.length() - cookie1.domain.length();
- if (diff == 0) {
- diff = cookie2.name.hashCode() - cookie1.name.hashCode();
- if (diff == 0) {
- Log.w(LOGTAG, "Found two cookies with the same value." +
- "cookie1=" + cookie1 + " , cookie2=" + cookie2);
- }
- }
+ if (diff != 0) return diff;
+
+ diff = cookie2.domain.length() - cookie1.domain.length();
+ if (diff != 0) return diff;
+
+ diff = cookie2.name.hashCode() - cookie1.name.hashCode();
+ if (diff != 0) return diff;
+
+ // If cookie2 has a null value, it should come later in
+ // the list.
+ if (cookie2.value == null) {
+ return -1;
+ } else if (cookie1.value == null) {
+ // Now we know that cookie2 does not have a null value, if
+ // cookie1 has a null value, place it later in the list.
+ return 1;
}
- return diff;
+
+ // cookie1 and cookie2 both have non-null values so we emit a
+ // warning and treat them as the same.
+ Log.w(LOGTAG, "Found two cookies with the same value."
+ + "cookie1=" + cookie1 + " , cookie2=" + cookie2);
+ return 0;
}
}
@@ -459,8 +476,10 @@
}
ret.append(cookie.name);
- ret.append(EQUAL);
- ret.append(cookie.value);
+ if (cookie.value != null) {
+ ret.append(EQUAL);
+ ret.append(cookie.value);
+ }
}
if (ret.length() > 0) {
@@ -634,7 +653,10 @@
byteCount += cookie.domain.length()
+ cookie.path.length()
+ cookie.name.length()
- + cookie.value.length() + 14;
+ + (cookie.value != null
+ ? cookie.value.length()
+ : 0)
+ + 14;
count++;
}
} else {
@@ -779,38 +801,45 @@
*/
int semicolonIndex = cookieString.indexOf(SEMICOLON, index);
int equalIndex = cookieString.indexOf(EQUAL, index);
- if (equalIndex == -1) {
- // bad format, force return
- break;
- }
- if (semicolonIndex > -1 && semicolonIndex < equalIndex) {
- // empty cookie, like "; path=/", return
- break;
- }
cookie = new Cookie(host, path);
- cookie.name = cookieString.substring(index, equalIndex);
- if (cookieString.charAt(equalIndex + 1) == QUOTATION) {
- index = cookieString.indexOf(QUOTATION, equalIndex + 2);
- if (index == -1) {
- // bad format, force return
- break;
+
+ // Cookies like "testcookie; path=/;" are valid and used
+ // (lovefilm.se). Check for equal as in the string "testcookie"
+ // Check for equalIndex == -1 as in the string "testcookie;"
+ if (semicolonIndex <= equalIndex || equalIndex == -1) {
+ // Fix up the index in case we have a string like "testcookie"
+ if (semicolonIndex == -1) {
+ semicolonIndex = length;
}
- }
- semicolonIndex = cookieString.indexOf(SEMICOLON, index);
- if (semicolonIndex == -1) {
- semicolonIndex = length;
- }
- if (semicolonIndex - equalIndex > MAX_COOKIE_LENGTH) {
- // cookie is too big, trim it
- cookie.value = cookieString.substring(equalIndex + 1,
- equalIndex + MAX_COOKIE_LENGTH);
- } else if (equalIndex + 1 == semicolonIndex
- || semicolonIndex < equalIndex) {
- // these are unusual case like foo=; and foo; path=/
- cookie.value = "";
+ cookie.name = cookieString.substring(index, semicolonIndex);
+ cookie.value = null;
} else {
- cookie.value = cookieString.substring(equalIndex + 1,
- semicolonIndex);
+ cookie.name = cookieString.substring(index, equalIndex);
+ if (cookieString.charAt(equalIndex + 1) == QUOTATION) {
+ index = cookieString.indexOf(QUOTATION, equalIndex + 2);
+ if (index == -1) {
+ // bad format, force return
+ break;
+ }
+ }
+ // Get the semicolon index again in case it was contained within
+ // the quotations.
+ semicolonIndex = cookieString.indexOf(SEMICOLON, index);
+ if (semicolonIndex == -1) {
+ semicolonIndex = length;
+ }
+ if (semicolonIndex - equalIndex > MAX_COOKIE_LENGTH) {
+ // cookie is too big, trim it
+ cookie.value = cookieString.substring(equalIndex + 1,
+ equalIndex + 1 + MAX_COOKIE_LENGTH);
+ } else if (equalIndex + 1 == semicolonIndex
+ || semicolonIndex < equalIndex) {
+ // this is an unusual case like foo=;
+ cookie.value = "";
+ } else {
+ cookie.value = cookieString.substring(equalIndex + 1,
+ semicolonIndex);
+ }
}
// get attributes
index = semicolonIndex;
diff --git a/core/java/android/webkit/DateSorter.java b/core/java/android/webkit/DateSorter.java
index 16feaa9..0e8ad7e 100644
--- a/core/java/android/webkit/DateSorter.java
+++ b/core/java/android/webkit/DateSorter.java
@@ -26,7 +26,7 @@
* Sorts dates into the following groups:
* Today
* Yesterday
- * five days ago
+ * seven days ago
* one month ago
* older than a month ago
*/
@@ -41,7 +41,7 @@
private long [] mBins = new long[DAY_COUNT-1];
private String [] mLabels = new String[DAY_COUNT];
- private static final int NUM_DAYS_AGO = 5;
+ private static final int NUM_DAYS_AGO = 7;
/**
* @param context Application context
@@ -66,13 +66,12 @@
mLabels[0] = context.getText(com.android.internal.R.string.today).toString();
mLabels[1] = context.getText(com.android.internal.R.string.yesterday).toString();
- int resId = com.android.internal.R.plurals.num_days_ago;
+ int resId = com.android.internal.R.plurals.last_num_days;
String format = resources.getQuantityString(resId, NUM_DAYS_AGO);
mLabels[2] = String.format(format, NUM_DAYS_AGO);
- mLabels[3] = context.getText(com.android.internal.R.string.oneMonthDurationPast).toString();
- mLabels[4] = context.getText(com.android.internal.R.string.beforeOneMonthDurationPast)
- .toString();
+ mLabels[3] = context.getString(com.android.internal.R.string.last_month);
+ mLabels[4] = context.getString(com.android.internal.R.string.older);
}
/**
diff --git a/core/java/android/webkit/GeolocationPermissions.java b/core/java/android/webkit/GeolocationPermissions.java
index d12d828..4565b756 100755
--- a/core/java/android/webkit/GeolocationPermissions.java
+++ b/core/java/android/webkit/GeolocationPermissions.java
@@ -23,6 +23,7 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.Vector;
/**
@@ -61,11 +62,8 @@
private Handler mHandler;
private Handler mUIHandler;
- // Members used to transfer the origins and permissions between threads.
- private Set<String> mOrigins;
- private boolean mAllowed;
- private Set<String> mOriginsToClear;
- private Set<String> mOriginsToAllow;
+ // A queue to store messages until the handler is ready.
+ private Vector<Message> mQueuedMessages;
// Message ids
static final int GET_ORIGINS = 0;
@@ -126,7 +124,7 @@
* Creates the message handler. Must be called on the WebKit thread.
* @hide
*/
- public void createHandler() {
+ public synchronized void createHandler() {
if (mHandler == null) {
mHandler = new Handler() {
@Override
@@ -134,21 +132,21 @@
// Runs on the WebKit thread.
switch (msg.what) {
case GET_ORIGINS: {
- getOriginsImpl();
+ Set origins = nativeGetOrigins();
ValueCallback callback = (ValueCallback) msg.obj;
Map values = new HashMap<String, Object>();
values.put(CALLBACK, callback);
- values.put(ORIGINS, mOrigins);
+ values.put(ORIGINS, origins);
postUIMessage(Message.obtain(null, RETURN_ORIGINS, values));
} break;
case GET_ALLOWED: {
Map values = (Map) msg.obj;
String origin = (String) values.get(ORIGIN);
ValueCallback callback = (ValueCallback) values.get(CALLBACK);
- getAllowedImpl(origin);
+ boolean allowed = nativeGetAllowed(origin);
Map retValues = new HashMap<String, Object>();
retValues.put(CALLBACK, callback);
- retValues.put(ALLOWED, new Boolean(mAllowed));
+ retValues.put(ALLOWED, new Boolean(allowed));
postUIMessage(Message.obtain(null, RETURN_ALLOWED, retValues));
} break;
case CLEAR:
@@ -164,15 +162,12 @@
}
};
- if (mOriginsToClear != null) {
- for (String origin : mOriginsToClear) {
- nativeClear(origin);
+ // Handle the queued messages
+ if (mQueuedMessages != null) {
+ while (!mQueuedMessages.isEmpty()) {
+ mHandler.sendMessage(mQueuedMessages.remove(0));
}
- }
- if (mOriginsToAllow != null) {
- for (String origin : mOriginsToAllow) {
- nativeAllow(origin);
- }
+ mQueuedMessages = null;
}
}
}
@@ -180,9 +175,15 @@
/**
* Utility function to send a message to our handler.
*/
- private void postMessage(Message msg) {
- assert(mHandler != null);
- mHandler.sendMessage(msg);
+ private synchronized void postMessage(Message msg) {
+ if (mHandler == null) {
+ if (mQueuedMessages == null) {
+ mQueuedMessages = new Vector<Message>();
+ }
+ mQueuedMessages.add(msg);
+ } else {
+ mHandler.sendMessage(msg);
+ }
}
/**
@@ -207,8 +208,8 @@
public void getOrigins(ValueCallback<Set<String> > callback) {
if (callback != null) {
if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- getOriginsImpl();
- callback.onReceiveValue(mOrigins);
+ Set origins = nativeGetOrigins();
+ callback.onReceiveValue(origins);
} else {
postMessage(Message.obtain(null, GET_ORIGINS, callback));
}
@@ -216,14 +217,6 @@
}
/**
- * Helper method to get the set of origins.
- */
- private void getOriginsImpl() {
- // Called on the WebKit thread.
- mOrigins = nativeGetOrigins();
- }
-
- /**
* Gets the permission state for the specified origin.
*
* Callback is a ValueCallback object whose onReceiveValue method will be
@@ -238,8 +231,8 @@
return;
}
if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- getAllowedImpl(origin);
- callback.onReceiveValue(new Boolean(mAllowed));
+ boolean allowed = nativeGetAllowed(origin);
+ callback.onReceiveValue(new Boolean(allowed));
} else {
Map values = new HashMap<String, Object>();
values.put(ORIGIN, origin);
@@ -249,31 +242,13 @@
}
/**
- * Helper method to get the permission state for the specified origin.
- */
- private void getAllowedImpl(String origin) {
- // Called on the WebKit thread.
- mAllowed = nativeGetAllowed(origin);
- }
-
- /**
* Clears the permission state for the specified origin. This method may be
* called before the WebKit thread has intialized the message handler.
* Messages will be queued until this time.
*/
public void clear(String origin) {
// Called on the UI thread.
- if (mHandler == null) {
- if (mOriginsToClear == null) {
- mOriginsToClear = new HashSet<String>();
- }
- mOriginsToClear.add(origin);
- if (mOriginsToAllow != null) {
- mOriginsToAllow.remove(origin);
- }
- } else {
- postMessage(Message.obtain(null, CLEAR, origin));
- }
+ postMessage(Message.obtain(null, CLEAR, origin));
}
/**
@@ -283,17 +258,7 @@
*/
public void allow(String origin) {
// Called on the UI thread.
- if (mHandler == null) {
- if (mOriginsToAllow == null) {
- mOriginsToAllow = new HashSet<String>();
- }
- mOriginsToAllow.add(origin);
- if (mOriginsToClear != null) {
- mOriginsToClear.remove(origin);
- }
- } else {
- postMessage(Message.obtain(null, ALLOW, origin));
- }
+ postMessage(Message.obtain(null, ALLOW, origin));
}
/**
diff --git a/core/java/android/webkit/PluginManager.java b/core/java/android/webkit/PluginManager.java
index cdcb662..df7d0c4 100644
--- a/core/java/android/webkit/PluginManager.java
+++ b/core/java/android/webkit/PluginManager.java
@@ -165,6 +165,7 @@
continue;
}
+/* temporarily disable signatures checking
// check to ensure the plugin is properly signed
Signature signatures[] = pkgInfo.signatures;
if (signatures == null) {
@@ -184,7 +185,7 @@
continue;
}
}
-
+*/
// determine the type of plugin from the manifest
if (serviceInfo.metaData == null) {
Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no type defined");
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 39e5275..662be95 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -916,9 +916,12 @@
}
/**
- * Tell the WebView to block network image. This is only checked when
- * getLoadsImagesAutomatically() is true.
- * @param flag True if the WebView should block network image
+ * Tell the WebView to block network images. This is only checked when
+ * {@link #getLoadsImagesAutomatically} is true. If you set the value to
+ * false, images will automatically be loaded. Use this api to reduce
+ * bandwidth only. Use {@link #setBlockNetworkLoads} if possible.
+ * @param flag True if the WebView should block network images.
+ * @see #setBlockNetworkLoads
*/
public synchronized void setBlockNetworkImage(boolean flag) {
if (mBlockNetworkImage != flag) {
@@ -928,17 +931,21 @@
}
/**
- * Return true if the WebView will block network image. The default is false.
- * @return True if the WebView blocks network image.
+ * Return true if the WebView will block network images. The default is
+ * false.
+ * @return True if the WebView blocks network images.
*/
public synchronized boolean getBlockNetworkImage() {
return mBlockNetworkImage;
}
/**
- * @hide
- * Tell the WebView to block all network load requests.
- * @param flag True if the WebView should block all network loads
+ * Tell the WebView to block all network load requests. If you set the
+ * value to false, you must call {@link android.webkit.WebView#reload} to
+ * fetch remote resources. This flag supercedes the value passed to
+ * {@link #setBlockNetworkImage}.
+ * @param flag True if the WebView should block all network loads.
+ * @see android.webkit.WebView#reload
*/
public synchronized void setBlockNetworkLoads(boolean flag) {
if (mBlockNetworkLoads != flag) {
@@ -948,9 +955,8 @@
}
/**
- * @hide
- * Return true if the WebView will block all network loads.
- * The default is false.
+ * Return true if the WebView will block all network loads. The default is
+ * false.
* @return True if the WebView blocks all network loads.
*/
public synchronized boolean getBlockNetworkLoads() {
diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java
index cf71a84..9314d7b 100644
--- a/core/java/android/webkit/WebStorage.java
+++ b/core/java/android/webkit/WebStorage.java
@@ -146,7 +146,7 @@
* @hide
* Message handler, webcore side
*/
- public void createHandler() {
+ public synchronized void createHandler() {
if (mHandler == null) {
mHandler = new Handler() {
@Override
@@ -342,7 +342,7 @@
/**
* Utility function to send a message to our handler
*/
- private void postMessage(Message msg) {
+ private synchronized void postMessage(Message msg) {
if (mHandler != null) {
mHandler.sendMessage(msg);
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 9672892..c1a4a49 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -213,7 +213,6 @@
static private final boolean AUTO_REDRAW_HACK = false;
// true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
private boolean mAutoRedraw;
- private int mRootLayer; // C++ pointer to the root layer
static final String LOGTAG = "webview";
@@ -375,6 +374,7 @@
private static final int PREVENT_DRAG_NO = 0;
private static final int PREVENT_DRAG_MAYBE_YES = 1;
private static final int PREVENT_DRAG_YES = 2;
+ private static final int PREVENT_DRAG_CANCEL = 3;
private int mPreventDrag = PREVENT_DRAG_NO;
// by default mPreventLongPress is false. If it is true, long press event
@@ -617,6 +617,12 @@
private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK
private boolean mSnapPositive;
+ // keep these in sync with their counterparts in WebView.cpp
+ private static final int DRAW_EXTRAS_NONE = 0;
+ private static final int DRAW_EXTRAS_FIND = 1;
+ private static final int DRAW_EXTRAS_SELECTION = 2;
+ private static final int DRAW_EXTRAS_CURSOR_RING = 3;
+
// Used to match key downs and key ups
private boolean mGotKeyDown;
@@ -1305,6 +1311,7 @@
// onSizeChanged() is called, the rest will be set
// correctly
mActualScale = scale;
+ mInvActualScale = 1 / scale;
mTextWrapScale = b.getFloat("textwrapScale", scale);
mInZoomOverview = b.getBoolean("overview");
invalidate();
@@ -3088,14 +3095,11 @@
Rect vBox = contentToViewRect(contentBounds);
Rect visibleRect = new Rect();
calcOurVisibleRect(visibleRect);
- // The IME may have shown, resulting in the textfield being offscreen.
- // If so, the textfield will be scrolled on screen, so treat it as
- // though it is on screen. If it is on screen, place the WebTextView in
- // its new place, accounting for our new scroll/zoom values.
- InputMethodManager imm = InputMethodManager.peekInstance();
- if ((imm != null && imm.isActive(mWebTextView))
- || (allowIntersect ? Rect.intersects(visibleRect, vBox)
- : visibleRect.contains(vBox))) {
+ // If the textfield is on screen, place the WebTextView in
+ // its new place, accounting for our new scroll/zoom values,
+ // and adjust its textsize.
+ if (allowIntersect ? Rect.intersects(visibleRect, vBox)
+ : visibleRect.contains(vBox)) {
mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
vBox.height());
mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
@@ -3118,7 +3122,7 @@
int mScrollY;
int mWidth;
int mHeight;
- float mScale;
+ float mInvScale;
}
private Metrics getViewMetrics() {
@@ -3127,23 +3131,21 @@
metrics.mScrollY = computeVerticalScrollOffset();
metrics.mWidth = getWidth();
metrics.mHeight = getHeight() - getVisibleTitleHeight();
- metrics.mScale = mActualScale;
+ metrics.mInvScale = mInvActualScale;
return metrics;
}
- private void drawLayers(Canvas canvas) {
- if (mRootLayer != 0) {
- // Currently for each draw we compute the animation values;
- // We may in the future decide to do that independently.
- if (nativeEvaluateLayersAnimations(mRootLayer)) {
- // If we have unfinished (or unstarted) animations,
- // we ask for a repaint.
- invalidate();
- }
-
- // We can now draw the layers.
- nativeDrawLayers(mRootLayer, canvas);
+ private void drawExtras(Canvas canvas, int extras) {
+ if (mNativeClass == 0) return;
+ // Currently for each draw we compute the animation values;
+ // We may in the future decide to do that independently.
+ if (nativeEvaluateLayersAnimations()) {
+ // If we have unfinished (or unstarted) animations,
+ // we ask for a repaint.
+ invalidate();
}
+
+ nativeDrawExtras(canvas, extras);
}
private void drawCoreAndCursorRing(Canvas canvas, int color,
@@ -3151,7 +3153,7 @@
if (mDrawHistory) {
canvas.scale(mActualScale, mActualScale);
canvas.drawPicture(mHistoryPicture);
- drawLayers(canvas);
+ drawExtras(canvas, DRAW_EXTRAS_NONE);
return;
}
@@ -3230,27 +3232,29 @@
mWebViewCore.drawContentPicture(canvas, color,
(animateZoom || mPreviewZoomOnly), animateScroll);
- boolean cursorIsInLayer = nativeCursorIsInLayer();
- if (drawCursorRing && !cursorIsInLayer) {
- nativeDrawCursorRing(canvas);
- }
- // When the FindDialog is up, only draw the matches if we are not in
- // the process of scrolling them into view.
- if (mFindIsUp && !animateScroll) {
- nativeDrawMatches(canvas);
- }
- drawLayers(canvas);
-
if (mNativeClass == 0) return;
- if (mShiftIsPressed && !(animateZoom || mPreviewZoomOnly)) {
- if (mTouchSelection || mExtendSelection) {
- nativeDrawSelectionRegion(canvas);
+ // decide which adornments to draw
+ int extras = DRAW_EXTRAS_NONE;
+ if (mFindIsUp) {
+ // When the FindDialog is up, only draw the matches if we are not in
+ // the process of scrolling them into view.
+ if (!animateScroll) {
+ extras = DRAW_EXTRAS_FIND;
}
- if (!mTouchSelection) {
- nativeDrawSelectionPointer(canvas, mInvActualScale, mSelectX,
- mSelectY - getTitleHeight(), mExtendSelection);
+ } else if (mShiftIsPressed) {
+ if (!animateZoom && !mPreviewZoomOnly) {
+ extras = DRAW_EXTRAS_SELECTION;
+ nativeSetSelectionRegion(mTouchSelection || mExtendSelection);
+ nativeSetSelectionPointer(!mTouchSelection, mInvActualScale,
+ mSelectX, mSelectY - getTitleHeight(),
+ mExtendSelection);
}
} else if (drawCursorRing) {
+ extras = DRAW_EXTRAS_CURSOR_RING;
+ }
+ drawExtras(canvas, extras);
+
+ if (extras == DRAW_EXTRAS_CURSOR_RING) {
if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
mTouchMode = TOUCH_SHORTPRESS_MODE;
HitTestResult hitTest = getHitTestResult();
@@ -3261,7 +3265,6 @@
LONG_PRESS_TIMEOUT);
}
}
- if (cursorIsInLayer) nativeDrawCursorRing(canvas);
}
if (mFocusSizeChanged) {
mFocusSizeChanged = false;
@@ -3354,23 +3357,31 @@
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ // bring it back to the default scale so that user can enter text
+ boolean zoom = mActualScale < mDefaultScale;
+ if (zoom) {
+ mInZoomOverview = false;
+ mZoomCenterX = mLastTouchX;
+ mZoomCenterY = mLastTouchY;
+ // do not change text wrap scale so that there is no reflow
+ setNewZoomScale(mDefaultScale, false, false);
+ }
if (isTextView) {
rebuildWebTextView();
- if (!inEditingMode()) return;
- imm.showSoftInput(mWebTextView, 0);
- // bring it back to the default scale so that user can enter text
- if (mActualScale < mDefaultScale) {
- mInZoomOverview = false;
- mZoomCenterX = mLastTouchX;
- mZoomCenterY = mLastTouchY;
- // do not change text wrap scale so that there is no reflow
- setNewZoomScale(mDefaultScale, false, false);
- didUpdateTextViewBounds(true);
+ if (inEditingMode()) {
+ imm.showSoftInput(mWebTextView, 0);
+ if (zoom) {
+ didUpdateTextViewBounds(true);
+ }
+ return;
}
}
- else { // used by plugins
- imm.showSoftInput(this, 0);
- }
+ // Used by plugins.
+ // Also used if the navigation cache is out of date, and
+ // does not recognize that a textfield is in focus. In that
+ // case, use WebView as the targeted view.
+ // see http://b/issue?id=2457459
+ imm.showSoftInput(this, 0);
}
// Called by WebKit to instruct the UI to hide the keyboard
@@ -4432,13 +4443,17 @@
}
// pass the touch events from UI thread to WebCore thread
- if (mForwardTouchEvents && (action != MotionEvent.ACTION_MOVE
- || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) {
+ if (mForwardTouchEvents
+ && (action != MotionEvent.ACTION_MOVE || eventTime
+ - mLastSentTouchTime > mCurrentTouchInterval)
+ && (action == MotionEvent.ACTION_DOWN
+ || mPreventDrag != PREVENT_DRAG_CANCEL)) {
WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData();
ted.mAction = action;
ted.mX = viewToContentX((int) x + mScrollX);
ted.mY = viewToContentY((int) y + mScrollY);
ted.mEventTime = eventTime;
+ ted.mMetaState = ev.getMetaState();
mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
mLastSentTouchTime = eventTime;
}
@@ -4702,6 +4717,7 @@
ted.mX = viewToContentX((int) x + mScrollX);
ted.mY = viewToContentY((int) y + mScrollY);
ted.mEventTime = eventTime;
+ ted.mMetaState = ev.getMetaState();
mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
} else if (mFullScreenHolder == null) {
doDoubleTap();
@@ -5712,12 +5728,17 @@
break;
}
case SWITCH_TO_SHORTPRESS: {
- // if mPreventDrag is not confirmed, treat it as no so that
- // it won't block panning the page.
+ // if mPreventDrag is not confirmed, cancel it so that it
+ // won't block panning the page.
if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
- mPreventDrag = PREVENT_DRAG_NO;
+ mPreventDrag = PREVENT_DRAG_CANCEL;
mPreventLongPress = false;
mPreventDoubleTap = false;
+ // remove the pending TOUCH_EVENT and send a cancel
+ mWebViewCore.removeMessages(EventHub.TOUCH_EVENT);
+ WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData();
+ ted.mAction = MotionEvent.ACTION_CANCEL;
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
}
if (mTouchMode == TOUCH_INIT_MODE) {
mTouchMode = mFullScreenHolder == null
@@ -5738,8 +5759,13 @@
ted.mX = viewToContentX((int) mLastTouchX + mScrollX);
ted.mY = viewToContentY((int) mLastTouchY + mScrollY);
ted.mEventTime = SystemClock.uptimeMillis();
+ // metaState for long press is tricky. Should it be the state
+ // when the press started or when the press was released? Or
+ // some intermediary key state? For simplicity for now, we
+ // don't set it.
+ ted.mMetaState = 0;
mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- } else if (mPreventDrag == PREVENT_DRAG_NO) {
+ } else if (mPreventDrag != PREVENT_DRAG_YES) {
mTouchMode = TOUCH_DONE_MODE;
if (mFullScreenHolder == null) {
performLongClick();
@@ -5750,13 +5776,18 @@
}
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;
+ // if mPreventDrag is not confirmed, cancel it so that
+ // it won't block panning the page.
+ mPreventDrag = PREVENT_DRAG_CANCEL;
mPreventLongPress = false;
mPreventDoubleTap = false;
+ // remove the pending TOUCH_EVENT and send a cancel
+ mWebViewCore.removeMessages(EventHub.TOUCH_EVENT);
+ WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData();
+ ted.mAction = MotionEvent.ACTION_CANCEL;
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
}
- if (mPreventDrag == PREVENT_DRAG_NO) {
+ if (mPreventDrag != PREVENT_DRAG_YES) {
mTouchMode = TOUCH_DONE_MODE;
doShortPress();
}
@@ -5975,12 +6006,7 @@
break;
}
case SET_ROOT_LAYER_MSG_ID: {
- int oldLayer = mRootLayer;
- mRootLayer = msg.arg1;
- nativeSetRootLayer(mRootLayer);
- if (oldLayer > 0) {
- nativeDestroyLayer(oldLayer);
- }
+ nativeSetRootLayer(msg.arg1);
invalidate();
break;
}
@@ -6740,7 +6766,6 @@
/* package */ native boolean nativeCursorMatchesFocus();
private native boolean nativeCursorIntersects(Rect visibleRect);
private native boolean nativeCursorIsAnchor();
- private native boolean nativeCursorIsInLayer();
private native boolean nativeCursorIsTextInput();
private native Point nativeCursorPosition();
private native String nativeCursorText();
@@ -6751,14 +6776,8 @@
private native boolean nativeCursorWantsKeyEvents();
private native void nativeDebugDump();
private native void nativeDestroy();
- private native void nativeDrawCursorRing(Canvas content);
- private native void nativeDestroyLayer(int layer);
- private native boolean nativeEvaluateLayersAnimations(int layer);
- private native void nativeDrawLayers(int layer, Canvas canvas);
- private native void nativeDrawMatches(Canvas canvas);
- private native void nativeDrawSelectionPointer(Canvas content,
- float scale, int x, int y, boolean extendSelection);
- private native void nativeDrawSelectionRegion(Canvas content);
+ private native boolean nativeEvaluateLayersAnimations();
+ private native void nativeDrawExtras(Canvas canvas, int extra);
private native void nativeDumpDisplayTree(String urlOrNull);
private native int nativeFindAll(String findLower, String findUpper);
private native void nativeFindNext(boolean forward);
@@ -6805,6 +6824,9 @@
private native void nativeSetFollowedLink(boolean followed);
private native void nativeSetHeightCanMeasure(boolean measure);
private native void nativeSetRootLayer(int layer);
+ private native void nativeSetSelectionPointer(boolean set,
+ float scale, int x, int y, boolean extendSelection);
+ private native void nativeSetSelectionRegion(boolean set);
private native int nativeTextGeneration();
// Never call this version except by updateCachedTextfield(String) -
// we always want to pass in our generation number.
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 3a3e445..361ec56 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -515,7 +515,7 @@
private native void nativeTouchUp(int touchGeneration,
int framePtr, int nodePtr, int x, int y);
- private native int nativeHandleTouchEvent(int action, int x, int y, long time);
+ private native int nativeHandleTouchEvent(int action, int x, int y, long time, int metaState);
private native void nativeUpdateFrameCache();
@@ -735,6 +735,7 @@
int mX;
int mY;
long mEventTime;
+ int mMetaState;
}
static class GeolocationPermissionsData {
@@ -1198,7 +1199,7 @@
mWebView.mPrivateHandler,
WebView.PREVENT_TOUCH_ID, ted.mAction,
nativeHandleTouchEvent(ted.mAction, ted.mX,
- ted.mY, ted.mEventTime)).sendToTarget();
+ ted.mY, ted.mEventTime, ted.mMetaState)).sendToTarget();
break;
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index a79bbee..9ddfeff 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1314,7 +1314,8 @@
position, -1);
}
} else {
- isScrap[0] = true;
+ isScrap[0] = true;
+ child.dispatchFinishTemporaryDetach();
}
} else {
child = mAdapter.getView(position, null, this);
@@ -1960,7 +1961,6 @@
if (getHeight() > 0 && getChildCount() > 0) {
// We do not lose focus initiating a touch (since AbsListView is focusable in
// touch mode). Force an initial layout to get rid of the selection.
- mLayoutMode = LAYOUT_NORMAL;
layoutChildren();
}
} else {
@@ -3118,7 +3118,9 @@
void hideSelector() {
if (mSelectedPosition != INVALID_POSITION) {
- mResurrectToPosition = mSelectedPosition;
+ if (mLayoutMode != LAYOUT_SPECIFIC) {
+ mResurrectToPosition = mSelectedPosition;
+ }
if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
mResurrectToPosition = mNextSelectedPosition;
}
@@ -4144,8 +4146,10 @@
}
if (mViewTypeCount == 1) {
+ scrap.dispatchStartTemporaryDetach();
mCurrentScrap.add(scrap);
} else {
+ scrap.dispatchStartTemporaryDetach();
mScrapViews[viewType].add(scrap);
}
@@ -4164,7 +4168,7 @@
ArrayList<View> scrapViews = mCurrentScrap;
final int count = activeViews.length;
- for (int i = 0; i < count; ++i) {
+ for (int i = count - 1; i >= 0; i--) {
final View victim = activeViews[i];
if (victim != null) {
int whichScrap = ((AbsListView.LayoutParams) victim.getLayoutParams()).viewType;
@@ -4180,6 +4184,7 @@
if (multipleScraps) {
scrapViews = mScrapViews[whichScrap];
}
+ victim.dispatchStartTemporaryDetach();
scrapViews.add(victim);
if (hasListener) {
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index c939e3f..2b3b98d 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -28,8 +28,6 @@
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.Interpolator;
-
/**
* An abstract base class for spinner widgets. SDK users will probably not
@@ -38,24 +36,21 @@
* @attr ref android.R.styleable#AbsSpinner_entries
*/
public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
-
SpinnerAdapter mAdapter;
int mHeightMeasureSpec;
int mWidthMeasureSpec;
boolean mBlockLayoutRequests;
+
int mSelectionLeftPadding = 0;
int mSelectionTopPadding = 0;
int mSelectionRightPadding = 0;
int mSelectionBottomPadding = 0;
- Rect mSpinnerPadding = new Rect();
- View mSelectedView = null;
- Interpolator mInterpolator;
+ final Rect mSpinnerPadding = new Rect();
- RecycleBin mRecycler = new RecycleBin();
+ final RecycleBin mRecycler = new RecycleBin();
private DataSetObserver mDataSetObserver;
-
/** Temporary frame to hold a child View's frame rectangle */
private Rect mTouchFrame;
@@ -95,7 +90,6 @@
setWillNotDraw(false);
}
-
/**
* The Adapter is used to provide the data which backs this Spinner.
* It also provides methods to transform spinner items based on their position
@@ -190,7 +184,7 @@
boolean needsMeasuring = true;
int selectedPosition = getSelectedItemPosition();
- if (selectedPosition >= 0 && mAdapter != null) {
+ if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) {
// Try looking in the recycler. (Maybe we were measured once already)
View view = mRecycler.get(selectedPosition);
if (view == null) {
@@ -237,7 +231,6 @@
mWidthMeasureSpec = widthMeasureSpec;
}
-
int getChildHeight(View child) {
return child.getMeasuredHeight();
}
@@ -254,26 +247,17 @@
}
void recycleAllViews() {
- int childCount = getChildCount();
+ final int childCount = getChildCount();
final AbsSpinner.RecycleBin recycleBin = mRecycler;
+ final int position = mFirstPosition;
// All views go in recycler
- for (int i=0; i<childCount; i++) {
+ for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
- int index = mFirstPosition + i;
+ int index = position + i;
recycleBin.put(index, v);
}
}
-
- @Override
- void handleDataChanged() {
- // FIXME -- this is called from both measure and layout.
- // This is harmless right now, but we don't want to do redundant work if
- // this gets more complicated
- super.handleDataChanged();
- }
-
-
/**
* Jump directly to a specific item in the adapter data.
@@ -284,7 +268,6 @@
position <= mFirstPosition + getChildCount() - 1;
setSelectionInt(position, shouldAnimate);
}
-
@Override
public void setSelection(int position) {
@@ -335,8 +318,6 @@
}
}
-
-
@Override
public SpinnerAdapter getAdapter() {
return mAdapter;
@@ -452,7 +433,7 @@
}
class RecycleBin {
- private SparseArray<View> mScrapHeap = new SparseArray<View>();
+ private final SparseArray<View> mScrapHeap = new SparseArray<View>();
public void put(int position, View v) {
mScrapHeap.put(position, v);
@@ -469,12 +450,7 @@
}
return result;
}
-
- View peek(int position) {
- // System.out.print("Looking for " + position);
- return mScrapHeap.get(position);
- }
-
+
void clear() {
final SparseArray<View> scrapHeap = mScrapHeap;
final int count = scrapHeap.size();
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index aa9062b..bf63607 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -24,6 +24,7 @@
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
+import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
@@ -73,7 +74,8 @@
public void toggle() {
setChecked(!mChecked);
}
-
+
+ @ViewDebug.ExportedProperty
public boolean isChecked() {
return mChecked;
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 98b0976..bf02ad3 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -26,6 +26,7 @@
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.Gravity;
+import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
/**
@@ -98,6 +99,7 @@
return super.performClick();
}
+ @ViewDebug.ExportedProperty
public boolean isChecked() {
return mChecked;
}
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 9fcb829..4bd3a82 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -658,14 +658,13 @@
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
- final int totalLength = mTotalLength;
- mTotalLength = Math.max(totalLength, totalLength + lp.leftMargin + lp.rightMargin);
+ mTotalLength += lp.leftMargin + lp.rightMargin;
// Baseline alignment requires to measure widgets to obtain the
- // baseline offset (in particular for TextViews).
- // The following defeats the optimization mentioned above.
- // Allow the child to use as much space as it wants because we
- // can shrink things later (and re-measure).
+ // baseline offset (in particular for TextViews). The following
+ // defeats the optimization mentioned above. Allow the child to
+ // use as much space as it wants because we can shrink things
+ // later (and re-measure).
if (baselineAligned) {
final int freeSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
child.measure(freeSpec, freeSpec);
@@ -695,9 +694,8 @@
}
final int childWidth = child.getMeasuredWidth();
- final int totalLength = mTotalLength;
- mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin +
- lp.rightMargin + getNextLocationOffset(child));
+ mTotalLength += childWidth + lp.leftMargin + lp.rightMargin +
+ getNextLocationOffset(child);
if (useLargestChild) {
largestChildWidth = Math.max(childWidth, largestChildWidth);
@@ -782,9 +780,8 @@
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
- final int totalLength = mTotalLength;
- mTotalLength = Math.max(totalLength, totalLength + largestChildWidth +
- lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
+ mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin +
+ getNextLocationOffset(child);
}
}
@@ -854,9 +851,8 @@
}
}
- final int totalLength = mTotalLength;
- mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() +
- lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
+ mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin +
+ getNextLocationOffset(child);
boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY &&
lp.height == LayoutParams.MATCH_PARENT;
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 5308725c..8d688a5 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1485,7 +1485,6 @@
}
// Clear out old views
- //removeAllViewsInLayout();
detachAllViewsFromParent();
switch (mLayoutMode) {
@@ -3092,9 +3091,9 @@
previouslyFocusedRect.offset(mScrollX, mScrollY);
final ListAdapter adapter = mAdapter;
- final int firstPosition = mFirstPosition;
- // Don't cache the result of getChildCount here, it could change in layoutChildren.
- if (adapter.getCount() < getChildCount() + firstPosition) {
+ // Don't cache the result of getChildCount or mFirstPosition here,
+ // it could change in layoutChildren.
+ if (adapter.getCount() < getChildCount() + mFirstPosition) {
mLayoutMode = LAYOUT_NORMAL;
layoutChildren();
}
@@ -3104,6 +3103,7 @@
Rect otherRect = mTempRect;
int minDistance = Integer.MAX_VALUE;
final int childCount = getChildCount();
+ final int firstPosition = mFirstPosition;
for (int i = 0; i < childCount; i++) {
// only consider selectable views
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 1dcb203..6dc9f78 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -36,6 +36,7 @@
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
+import android.view.ViewDebug;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -335,6 +336,7 @@
*
* @return true if the progress bar is in indeterminate mode
*/
+ @ViewDebug.ExportedProperty
public synchronized boolean isIndeterminate() {
return mIndeterminate;
}
@@ -607,6 +609,7 @@
* @see #setMax(int)
* @see #getMax()
*/
+ @ViewDebug.ExportedProperty
public synchronized int getProgress() {
return mIndeterminate ? 0 : mProgress;
}
@@ -623,6 +626,7 @@
* @see #setMax(int)
* @see #getMax()
*/
+ @ViewDebug.ExportedProperty
public synchronized int getSecondaryProgress() {
return mIndeterminate ? 0 : mSecondaryProgress;
}
@@ -636,6 +640,7 @@
* @see #getProgress()
* @see #getSecondaryProgress()
*/
+ @ViewDebug.ExportedProperty
public synchronized int getMax() {
return mMax;
}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 52ed11d..fd24058 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -22,6 +22,7 @@
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -51,6 +52,8 @@
* <p>ScrollView only supports vertical scrolling.
*/
public class ScrollView extends FrameLayout {
+ private static final String TAG = "ScrollView";
+
static final int ANIMATED_SCROLL_GAP = 250;
static final float MAX_SCROLL_FACTOR = 0.5f;
@@ -112,6 +115,18 @@
private int mTouchSlop;
private int mMinimumVelocity;
private int mMaximumVelocity;
+
+ /**
+ * ID of the active pointer. This is used to retain consistency during
+ * drags/flings if multiple pointers are used.
+ */
+ private int mActivePointerId = INVALID_POINTER;
+
+ /**
+ * Sentinel value for no current active pointer.
+ * Used by {@link #mActivePointerId}.
+ */
+ private static final int INVALID_POINTER = -1;
public ScrollView(Context context) {
this(context, null);
@@ -360,6 +375,17 @@
return handled;
}
+ private boolean inChild(int x, int y) {
+ if (getChildCount() > 0) {
+ final View child = getChildAt(0);
+ return !(y < child.getTop()
+ || y >= child.getBottom()
+ || x < child.getLeft()
+ || x >= child.getRight());
+ }
+ return false;
+ }
+
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
@@ -378,10 +404,8 @@
return true;
}
- final float y = ev.getY();
-
- switch (action) {
- case MotionEvent.ACTION_MOVE:
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
@@ -391,16 +415,29 @@
* Locally do absolute value. mLastMotionY is set to the y value
* of the down event.
*/
+ final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ final float y = ev.getY(pointerIndex);
final int yDiff = (int) Math.abs(y - mLastMotionY);
if (yDiff > mTouchSlop) {
mIsBeingDragged = true;
mLastMotionY = y;
}
break;
+ }
- case MotionEvent.ACTION_DOWN:
- /* Remember location of down touch */
+ case MotionEvent.ACTION_DOWN: {
+ final float y = ev.getY();
+ if (!inChild((int)ev.getX(), (int)y)) {
+ mIsBeingDragged = false;
+ break;
+ }
+
+ /*
+ * Remember location of down touch.
+ * ACTION_DOWN always refers to pointer index 0.
+ */
mLastMotionY = y;
+ mActivePointerId = ev.getPointerId(0);
/*
* If being flinged and user touches the screen, initiate drag;
@@ -409,11 +446,16 @@
*/
mIsBeingDragged = !mScroller.isFinished();
break;
+ }
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
/* Release the drag */
mIsBeingDragged = false;
+ mActivePointerId = INVALID_POINTER;
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
break;
}
@@ -439,10 +481,9 @@
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
- final float y = ev.getY();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
@@ -451,41 +492,78 @@
mScroller.abortAnimation();
}
+ final float y = ev.getY();
+ if (!(mIsBeingDragged = inChild((int)ev.getX(), (int)y))) {
+ return false;
+ }
+
// Remember where the motion event started
mLastMotionY = y;
+ mActivePointerId = ev.getPointerId(0);
break;
+ }
case MotionEvent.ACTION_MOVE:
- // Scroll to follow the motion event
- final int deltaY = (int) (mLastMotionY - y);
- mLastMotionY = y;
+ if (mIsBeingDragged) {
+ // Scroll to follow the motion event
+ final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ final float y = ev.getY(activePointerIndex);
+ final int deltaY = (int) (mLastMotionY - y);
+ mLastMotionY = y;
- overscrollBy(0, deltaY, 0, mScrollY, 0, getScrollRange(),
- 0, getOverscrollMax());
+ overscrollBy(0, deltaY, 0, mScrollY, 0, getScrollRange(),
+ 0, getOverscrollMax());
+ }
break;
- case MotionEvent.ACTION_UP:
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int initialVelocity = (int) velocityTracker.getYVelocity();
+ case MotionEvent.ACTION_UP:
+ if (mIsBeingDragged) {
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
- if (getChildCount() > 0) {
- if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
- fling(-initialVelocity);
- } else {
- final int bottom = getScrollRange();
- if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, bottom)) {
- invalidate();
+ if (getChildCount() > 0) {
+ if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
+ fling(-initialVelocity);
+ } else {
+ final int bottom = getScrollRange();
+ if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, bottom)) {
+ invalidate();
+ }
}
}
- }
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
+ mActivePointerId = INVALID_POINTER;
+ mIsBeingDragged = false;
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
}
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ break;
}
return true;
}
+ private void onSecondaryPointerUp(MotionEvent ev) {
+ final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
+ MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ // TODO: Make this decision more intelligent.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mLastMotionY = ev.getY(newPointerIndex);
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
+
@Override
protected void onOverscrolled(int scrollX, int scrollY,
boolean clampedX, boolean clampedY) {
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index bcddca1..2f6dd1e 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -295,14 +295,18 @@
*/
private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
private SpinnerAdapter mAdapter;
+ private ListAdapter mListAdapter;
/**
- * <p>Creates a new ListAddapter wrapper for the specified adapter.</p>
+ * <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
*
* @param adapter the Adapter to transform into a ListAdapter
*/
public DropDownAdapter(SpinnerAdapter adapter) {
this.mAdapter = adapter;
+ if (adapter instanceof ListAdapter) {
+ this.mListAdapter = (ListAdapter) adapter;
+ }
}
public int getCount() {
@@ -343,21 +347,29 @@
}
/**
- * <p>Always returns false.</p>
- *
- * @return false
+ * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
+ * Otherwise, return true.
*/
public boolean areAllItemsEnabled() {
- return true;
+ final ListAdapter adapter = mListAdapter;
+ if (adapter != null) {
+ return adapter.areAllItemsEnabled();
+ } else {
+ return true;
+ }
}
/**
- * <p>Always returns false.</p>
- *
- * @return false
+ * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
+ * Otherwise, return true.
*/
public boolean isEnabled(int position) {
- return true;
+ final ListAdapter adapter = mListAdapter;
+ if (adapter != null) {
+ return adapter.isEnabled(position);
+ } else {
+ return true;
+ }
}
public int getItemViewType(int position) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7ba0fa1..951563a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -198,6 +198,7 @@
private boolean mFreezesText;
private boolean mFrozenWithFocus;
private boolean mTemporaryDetach;
+ private boolean mDispatchTemporaryDetach;
private boolean mEatTouchRelease = false;
private boolean mScrolled = false;
@@ -5731,6 +5732,7 @@
/**
* Convenience for {@link Selection#getSelectionStart}.
*/
+ @ViewDebug.ExportedProperty
public int getSelectionStart() {
return Selection.getSelectionStart(getText());
}
@@ -5738,6 +5740,7 @@
/**
* Convenience for {@link Selection#getSelectionEnd}.
*/
+ @ViewDebug.ExportedProperty
public int getSelectionEnd() {
return Selection.getSelectionEnd(getText());
}
@@ -6369,14 +6372,30 @@
mBlink.postAtTime(mBlink, mShowCursor + BLINK);
}
+ /**
+ * @hide
+ */
+ @Override
+ public void dispatchFinishTemporaryDetach() {
+ mDispatchTemporaryDetach = true;
+ super.dispatchFinishTemporaryDetach();
+ mDispatchTemporaryDetach = false;
+ }
+
@Override
public void onStartTemporaryDetach() {
- mTemporaryDetach = true;
+ super.onStartTemporaryDetach();
+ // Only track when onStartTemporaryDetach() is called directly,
+ // usually because this instance is an editable field in a list
+ if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
}
@Override
public void onFinishTemporaryDetach() {
- mTemporaryDetach = false;
+ super.onFinishTemporaryDetach();
+ // Only track when onStartTemporaryDetach() is called directly,
+ // usually because this instance is an editable field in a list
+ if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
}
@Override
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index 906bca1c..c2517a8 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -62,6 +62,8 @@
private static final int STATE_PLAYING = 3;
private static final int STATE_PAUSED = 4;
private static final int STATE_PLAYBACK_COMPLETED = 5;
+ private static final int STATE_SUSPEND = 6;
+ private static final int STATE_RESUME = 7;
// mCurrentState is a VideoView object's current state.
// mTargetState is the state that a method caller intends to reach.
@@ -87,6 +89,7 @@
private boolean mCanPause;
private boolean mCanSeekBack;
private boolean mCanSeekForward;
+ private int mStateWhenSuspended; //state before calling suspend()
public VideoView(Context context) {
super(context);
@@ -466,7 +469,14 @@
public void surfaceCreated(SurfaceHolder holder)
{
mSurfaceHolder = holder;
- openVideo();
+ //resume() was called before surfaceCreated()
+ if (mMediaPlayer != null && mCurrentState == STATE_SUSPEND
+ && mTargetState == STATE_RESUME) {
+ mMediaPlayer.setDisplay(mSurfaceHolder);
+ resume();
+ } else {
+ openVideo();
+ }
}
public void surfaceDestroyed(SurfaceHolder holder)
@@ -474,7 +484,6 @@
// after we return from this we can't use the surface any more
mSurfaceHolder = null;
if (mMediaController != null) mMediaController.hide();
- release(true);
}
};
@@ -567,7 +576,36 @@
mTargetState = STATE_PAUSED;
}
- // cache duration as mDuration for faster access
+ public void suspend() {
+ if (isInPlaybackState()) {
+ if (mMediaPlayer.suspend()) {
+ mStateWhenSuspended = mCurrentState;
+ mCurrentState = STATE_SUSPEND;
+ mTargetState = STATE_SUSPEND;
+ } else {
+ Log.w(TAG, "Unable to suspend video");
+ mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ }
+
+ public void resume() {
+ if (mSurfaceHolder == null && mCurrentState == STATE_SUSPEND){
+ mTargetState = STATE_RESUME;
+ return;
+ }
+ if (mMediaPlayer != null && mCurrentState == STATE_SUSPEND) {
+ if (mMediaPlayer.resume()) {
+ mCurrentState = mStateWhenSuspended;
+ mTargetState = mStateWhenSuspended;
+ } else {
+ Log.w(TAG, "Unable to resume video");
+ mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ }
+
+ // cache duration as mDuration for faster access
public int getDuration() {
if (isInPlaybackState()) {
if (mDuration > 0) {
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index f56b15c..107b145 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -26,6 +26,7 @@
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
+import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -48,7 +49,6 @@
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
-import android.util.AttributeSet;
import com.android.internal.R;
@@ -322,24 +322,24 @@
public Button getButton(int whichButton) {
switch (whichButton) {
case DialogInterface.BUTTON_POSITIVE:
- return mButtonPositiveMessage != null ? mButtonPositive : null;
+ return mButtonPositive;
case DialogInterface.BUTTON_NEGATIVE:
- return mButtonNegativeMessage != null ? mButtonNegative : null;
+ return mButtonNegative;
case DialogInterface.BUTTON_NEUTRAL:
- return mButtonNeutralMessage != null ? mButtonNeutral : null;
+ return mButtonNeutral;
default:
return null;
}
}
+ @SuppressWarnings({"UnusedDeclaration"})
public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (mScrollView != null && mScrollView.executeKeyEvent(event)) return true;
- return false;
+ return mScrollView != null && mScrollView.executeKeyEvent(event);
}
+ @SuppressWarnings({"UnusedDeclaration"})
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (mScrollView != null && mScrollView.executeKeyEvent(event)) return true;
- return false;
+ return mScrollView != null && mScrollView.executeKeyEvent(event);
}
private void setupView() {
@@ -469,7 +469,6 @@
}
private boolean setupButtons() {
- View defaultButton = null;
int BIT_BUTTON_POSITIVE = 1;
int BIT_BUTTON_NEGATIVE = 2;
int BIT_BUTTON_NEUTRAL = 4;
@@ -482,7 +481,6 @@
} else {
mButtonPositive.setText(mButtonPositiveText);
mButtonPositive.setVisibility(View.VISIBLE);
- defaultButton = mButtonPositive;
whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
}
@@ -495,9 +493,6 @@
mButtonNegative.setText(mButtonNegativeText);
mButtonNegative.setVisibility(View.VISIBLE);
- if (defaultButton == null) {
- defaultButton = mButtonNegative;
- }
whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
}
@@ -510,9 +505,6 @@
mButtonNeutral.setText(mButtonNeutralText);
mButtonNeutral.setVisibility(View.VISIBLE);
- if (defaultButton == null) {
- defaultButton = mButtonNeutral;
- }
whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
}
@@ -565,8 +557,6 @@
R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
int bottomMedium = a.getResourceId(
R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);
- int centerMedium = a.getResourceId(
- R.styleable.AlertDialog_centerMedium, R.drawable.popup_center_medium);
/*
* We now set the background of all of the sections of the alert.
@@ -596,7 +586,7 @@
*/
views[pos] = (contentPanel.getVisibility() == View.GONE)
? null : contentPanel;
- light[pos] = mListView == null ? false : true;
+ light[pos] = mListView != null;
pos++;
if (customPanel != null) {
views[pos] = customPanel;
diff --git a/core/java/com/android/internal/app/DisableCarModeActivity.java b/core/java/com/android/internal/app/DisableCarModeActivity.java
new file mode 100644
index 0000000..95dc1f9
--- /dev/null
+++ b/core/java/com/android/internal/app/DisableCarModeActivity.java
@@ -0,0 +1,42 @@
+/*
+ * 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.internal.app;
+
+import android.app.Activity;
+import android.app.IUiModeManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+public class DisableCarModeActivity extends Activity {
+ private static final String TAG = "DisableCarModeActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ try {
+ IUiModeManager uiModeManager = IUiModeManager.Stub.asInterface(
+ ServiceManager.getService("uimode"));
+ uiModeManager.disableCarMode();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to disable car mode", e);
+ }
+ finish();
+ }
+
+}
diff --git a/core/java/com/android/internal/app/TetherActivity.java b/core/java/com/android/internal/app/TetherActivity.java
index a48ccf9..5d71231 100644
--- a/core/java/com/android/internal/app/TetherActivity.java
+++ b/core/java/com/android/internal/app/TetherActivity.java
@@ -141,7 +141,7 @@
}
} else {
for (String t : tethered) {
- if (!cm.untether("ppp0")) {
+ if (!cm.untether(t)) {
error = true;
}
}
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index c5b869b..c5db83f 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -36,7 +36,7 @@
public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1;
public static final int RECOMMEND_FAILED_INVALID_APK = -2;
- private static final boolean DEBUG_SD_INSTALL = true;
+ private static final boolean localLOGV = true;
private static final String TAG = "PackageHelper";
public static IMountService getMountService() {
@@ -58,7 +58,7 @@
if ((len - (mbLen * 1024 * 1024)) > 0) {
mbLen++;
}
- if (DEBUG_SD_INSTALL) Log.i(TAG, "Size of resource " + mbLen);
+ if (localLOGV) Log.i(TAG, "Size of resource " + mbLen);
try {
int rc = mountService.createSecureContainer(
@@ -68,7 +68,7 @@
return null;
}
String cachePath = mountService.getSecureContainerPath(cid);
- if (DEBUG_SD_INSTALL) Log.i(TAG, "Created secure container " + cid +
+ if (localLOGV) Log.i(TAG, "Created secure container " + cid +
" at " + cachePath);
return cachePath;
} catch (RemoteException e) {
@@ -93,7 +93,7 @@
public static boolean unMountSdDir(String cid) {
try {
- int rc = getMountService().unmountSecureContainer(cid);
+ int rc = getMountService().unmountSecureContainer(cid, true);
if (rc != StorageResultCode.OperationSucceeded) {
Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc);
return false;
@@ -148,7 +148,8 @@
public static boolean destroySdDir(String cid) {
try {
- int rc = getMountService().destroySecureContainer(cid);
+ if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid);
+ int rc = getMountService().destroySecureContainer(cid, true);
if (rc != StorageResultCode.OperationSucceeded) {
Log.i(TAG, "Failed to destroy container " + cid);
return false;
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
new file mode 100644
index 0000000..343041f
--- /dev/null
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -0,0 +1,287 @@
+package com.android.internal.content;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+
+import java.util.HashSet;
+
+/**
+ * Helper class for monitoring the state of packages: adding, removing,
+ * updating, and disappearing and reappearing on the SD card.
+ */
+public abstract class PackageMonitor extends android.content.BroadcastReceiver {
+ static final IntentFilter sPackageFilt = new IntentFilter();
+ static final IntentFilter sNonDataFilt = new IntentFilter();
+ static final IntentFilter sExternalFilt = new IntentFilter();
+
+ static {
+ sPackageFilt.addAction(Intent.ACTION_PACKAGE_ADDED);
+ sPackageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ sPackageFilt.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ sPackageFilt.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
+ sPackageFilt.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ sPackageFilt.addAction(Intent.ACTION_UID_REMOVED);
+ sPackageFilt.addDataScheme("package");
+ sNonDataFilt.addAction(Intent.ACTION_UID_REMOVED);
+ sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ }
+
+ final HashSet<String> mUpdatingPackages = new HashSet<String>();
+
+ Context mRegisteredContext;
+ String[] mDisappearingPackages;
+ String[] mAppearingPackages;
+ String[] mModifiedPackages;
+ int mChangeType;
+ boolean mSomePackagesChanged;
+
+ String[] mTempArray = new String[1];
+
+ public void register(Context context, boolean externalStorage) {
+ if (mRegisteredContext != null) {
+ throw new IllegalStateException("Already registered");
+ }
+ mRegisteredContext = context;
+ context.registerReceiver(this, sPackageFilt);
+ context.registerReceiver(this, sNonDataFilt);
+ if (externalStorage) {
+ context.registerReceiver(this, sExternalFilt);
+ }
+ }
+
+ public void unregister() {
+ if (mRegisteredContext == null) {
+ throw new IllegalStateException("Not registered");
+ }
+ mRegisteredContext.unregisterReceiver(this);
+ mRegisteredContext = null;
+ }
+
+ //not yet implemented
+ boolean isPackageUpdating(String packageName) {
+ synchronized (mUpdatingPackages) {
+ return mUpdatingPackages.contains(packageName);
+ }
+ }
+
+ public void onBeginPackageChanges() {
+ }
+
+ public void onPackageAdded(String packageName, int uid) {
+ }
+
+ public void onPackageRemoved(String packageName, int uid) {
+ }
+
+ public void onPackageUpdateStarted(String packageName, int uid) {
+ }
+
+ public void onPackageUpdateFinished(String packageName, int uid) {
+ }
+
+ public void onPackageChanged(String packageName, int uid, String[] components) {
+ }
+
+ public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+ return false;
+ }
+
+ public void onUidRemoved(int uid) {
+ }
+
+ public void onPackagesAvailable(String[] packages) {
+ }
+
+ public void onPackagesUnavailable(String[] packages) {
+ }
+
+ public static final int PACKAGE_UNCHANGED = 0;
+ public static final int PACKAGE_UPDATING = 1;
+ public static final int PACKAGE_TEMPORARY_CHANGE = 2;
+ public static final int PACKAGE_PERMANENT_CHANGE = 3;
+
+ public void onPackageDisappeared(String packageName, int reason) {
+ }
+
+ public void onPackageAppeared(String packageName, int reason) {
+ }
+
+ public void onPackageModified(String packageName) {
+ }
+
+ public boolean didSomePackagesChange() {
+ return mSomePackagesChanged;
+ }
+
+ public int isPackageAppearing(String packageName) {
+ if (mAppearingPackages != null) {
+ for (int i=mAppearingPackages.length-1; i>=0; i--) {
+ if (packageName.equals(mAppearingPackages[i])) {
+ return mChangeType;
+ }
+ }
+ }
+ return PACKAGE_UNCHANGED;
+ }
+
+ public boolean anyPackagesAppearing() {
+ return mAppearingPackages != null;
+ }
+
+ public int isPackageDisappearing(String packageName) {
+ if (mDisappearingPackages != null) {
+ for (int i=mDisappearingPackages.length-1; i>=0; i--) {
+ if (packageName.equals(mDisappearingPackages[i])) {
+ return mChangeType;
+ }
+ }
+ }
+ return PACKAGE_UNCHANGED;
+ }
+
+ public boolean anyPackagesDisappearing() {
+ return mDisappearingPackages != null;
+ }
+
+ public boolean isPackageModified(String packageName) {
+ if (mModifiedPackages != null) {
+ for (int i=mModifiedPackages.length-1; i>=0; i--) {
+ if (packageName.equals(mModifiedPackages[i])) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public void onSomePackagesChanged() {
+ }
+
+ public void onFinishPackageChanges() {
+ }
+
+ String getPackageName(Intent intent) {
+ Uri uri = intent.getData();
+ String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
+ return pkg;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onBeginPackageChanges();
+
+ mDisappearingPackages = mAppearingPackages = null;
+ mSomePackagesChanged = false;
+
+ String action = intent.getAction();
+ if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ String pkg = getPackageName(intent);
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+ // We consider something to have changed regardless of whether
+ // this is just an update, because the update is now finished
+ // and the contents of the package may have changed.
+ mSomePackagesChanged = true;
+ if (pkg != null) {
+ mAppearingPackages = mTempArray;
+ mTempArray[0] = pkg;
+ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ mModifiedPackages = mTempArray;
+ mChangeType = PACKAGE_UPDATING;
+ onPackageUpdateFinished(pkg, uid);
+ onPackageModified(pkg);
+ } else {
+ mChangeType = PACKAGE_PERMANENT_CHANGE;
+ onPackageAdded(pkg, uid);
+ }
+ onPackageAppeared(pkg, mChangeType);
+ if (mChangeType == PACKAGE_UPDATING) {
+ synchronized (mUpdatingPackages) {
+ mUpdatingPackages.remove(pkg);
+ }
+ }
+ }
+ } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ String pkg = getPackageName(intent);
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+ if (pkg != null) {
+ mDisappearingPackages = mTempArray;
+ mTempArray[0] = pkg;
+ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ mChangeType = PACKAGE_UPDATING;
+ synchronized (mUpdatingPackages) {
+ //not used for now
+ //mUpdatingPackages.add(pkg);
+ }
+ onPackageUpdateStarted(pkg, uid);
+ } else {
+ mChangeType = PACKAGE_PERMANENT_CHANGE;
+ // We only consider something to have changed if this is
+ // not a replace; for a replace, we just need to consider
+ // it when it is re-added.
+ mSomePackagesChanged = true;
+ onPackageRemoved(pkg, uid);
+ }
+ onPackageDisappeared(pkg, mChangeType);
+ }
+ } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+ String pkg = getPackageName(intent);
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+ String[] components = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+ if (pkg != null) {
+ mModifiedPackages = mTempArray;
+ mTempArray[0] = pkg;
+ onPackageChanged(pkg, uid, components);
+ // XXX Don't want this to always cause mSomePackagesChanged,
+ // since it can happen a fair amount.
+ onPackageModified(pkg);
+ }
+ } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
+ mDisappearingPackages = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+ mChangeType = PACKAGE_TEMPORARY_CHANGE;
+ boolean canRestart = onHandleForceStop(intent,
+ mDisappearingPackages,
+ intent.getIntExtra(Intent.EXTRA_UID, 0), false);
+ if (canRestart) setResultCode(Activity.RESULT_OK);
+ } else if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
+ mDisappearingPackages = new String[] {getPackageName(intent)};
+ mChangeType = PACKAGE_TEMPORARY_CHANGE;
+ onHandleForceStop(intent, mDisappearingPackages,
+ intent.getIntExtra(Intent.EXTRA_UID, 0), true);
+ } else if (Intent.ACTION_UID_REMOVED.equals(action)) {
+ onUidRemoved(intent.getIntExtra(Intent.EXTRA_UID, 0));
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+ String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ mAppearingPackages = pkgList;
+ mChangeType = PACKAGE_TEMPORARY_CHANGE;
+ mSomePackagesChanged = true;
+ if (pkgList != null) {
+ onPackagesAvailable(pkgList);
+ for (int i=0; i<pkgList.length; i++) {
+ onPackageAppeared(pkgList[i], PACKAGE_TEMPORARY_CHANGE);
+ }
+ }
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ mDisappearingPackages = pkgList;
+ mChangeType = PACKAGE_TEMPORARY_CHANGE;
+ mSomePackagesChanged = true;
+ if (pkgList != null) {
+ onPackagesUnavailable(pkgList);
+ for (int i=0; i<pkgList.length; i++) {
+ onPackageDisappeared(pkgList[i], PACKAGE_TEMPORARY_CHANGE);
+ }
+ }
+ }
+
+ if (mSomePackagesChanged) {
+ onSomePackagesChanged();
+ }
+
+ onFinishPackageChanges();
+ }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index dfcc8f7..71ccb3b 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -59,6 +59,13 @@
// Current on-disk Parcel version
private static final int VERSION = 42;
+ // The maximum number of names wakelocks we will keep track of
+ // per uid; once the limit is reached, we batch the remaining wakelocks
+ // in to one common name.
+ private static final int MAX_WAKELOCKS_PER_UID = 20;
+
+ private static final String BATCHED_WAKELOCK_NAME = "*overflow*";
+
private static int sNumSpeedSteps;
private final File mFile;
@@ -1757,7 +1764,12 @@
String wakelockName = in.readString();
Uid.Wakelock wakelock = new Wakelock();
wakelock.readFromParcelLocked(unpluggables, in);
- mWakelockStats.put(wakelockName, wakelock);
+ if (mWakelockStats.size() < MAX_WAKELOCKS_PER_UID) {
+ // We will just drop some random set of wakelocks if
+ // the previous run of the system was an older version
+ // that didn't impose a limit.
+ mWakelockStats.put(wakelockName, wakelock);
+ }
}
int numSensors = in.readInt();
@@ -2583,8 +2595,14 @@
public StopwatchTimer getWakeTimerLocked(String name, int type) {
Wakelock wl = mWakelockStats.get(name);
if (wl == null) {
- wl = new Wakelock();
- mWakelockStats.put(name, wl);
+ if (mWakelockStats.size() > MAX_WAKELOCKS_PER_UID) {
+ name = BATCHED_WAKELOCK_NAME;
+ wl = mWakelockStats.get(name);
+ }
+ if (wl == null) {
+ wl = new Wakelock();
+ mWakelockStats.put(name, wl);
+ }
}
StopwatchTimer t = null;
switch (type) {
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 15dcbd6..22c6e79 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -1,5 +1,6 @@
package com.android.internal.view;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -17,7 +18,7 @@
}
public void resized(int w, int h, Rect coveredInsets,
- Rect visibleInsets, boolean reportDraw) {
+ Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
if (reportDraw) {
try {
mSession.finishDrawing(this);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index f074b80..9713c27f 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -104,14 +104,24 @@
private static String sLockPatternFilename;
private static String sLockPasswordFilename;
+ DevicePolicyManager getDevicePolicyManager() {
+ if (mDevicePolicyManager == null) {
+ mDevicePolicyManager =
+ (DevicePolicyManager)mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ if (mDevicePolicyManager == null) {
+ Log.e(TAG, "Can't get DevicePolicyManagerService: is it running?",
+ new IllegalStateException("Stack trace:"));
+ }
+ }
+ return mDevicePolicyManager;
+ }
/**
* @param contentResolver Used to look up and save settings.
*/
public LockPatternUtils(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
- mDevicePolicyManager =
- (DevicePolicyManager)context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mDevicePolicyManager = getDevicePolicyManager();
// Initialize the location of gesture lock file
if (sLockPatternFilename == null) {
sLockPatternFilename = android.os.Environment.getDataDirectory()
@@ -123,7 +133,7 @@
}
public int getRequestedMinimumPasswordLength() {
- return mDevicePolicyManager.getPasswordMinimumLength(null);
+ return getDevicePolicyManager().getPasswordMinimumLength(null);
}
/**
@@ -133,7 +143,7 @@
* @return
*/
public int getRequestedPasswordMode() {
- int policyMode = mDevicePolicyManager.getPasswordQuality(null);
+ int policyMode = getDevicePolicyManager().getPasswordQuality(null);
switch (policyMode) {
case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
return MODE_PASSWORD;
@@ -151,11 +161,11 @@
* @return
*/
public void reportFailedPasswordAttempt() {
- mDevicePolicyManager.reportFailedPasswordAttempt();
+ getDevicePolicyManager().reportFailedPasswordAttempt();
}
public void reportSuccessfulPasswordAttempt() {
- mDevicePolicyManager.reportSuccessfulPasswordAttempt();
+ getDevicePolicyManager().reportSuccessfulPasswordAttempt();
}
public void setActivePasswordState(int mode, int length) {
@@ -171,7 +181,7 @@
policyMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
break;
}
- mDevicePolicyManager.setActivePasswordState(policyMode, length);
+ getDevicePolicyManager().setActivePasswordState(policyMode, length);
}
/**
@@ -279,7 +289,7 @@
saveLockPattern(null);
setLong(PASSWORD_TYPE_KEY, MODE_PATTERN);
}
-
+
/**
* Save a lock pattern.
* @param pattern The new pattern to save.
@@ -334,7 +344,7 @@
hasNonDigit = true;
}
}
-
+
// First check if it is sufficient.
switch (reqMode) {
case MODE_PASSWORD: {
@@ -342,19 +352,19 @@
return MODE_UNSPECIFIED;
}
} break;
-
+
case MODE_PIN:
case MODE_PATTERN: {
// Whatever we have is acceptable; we may need to promote the
// mode later.
} break;
-
+
default:
// If it isn't a mode we specifically know, then fail fast.
Log.w(TAG, "adjustPasswordMode: unknown mode " + reqMode);
return MODE_UNSPECIFIED;
}
-
+
// Do we need to promote?
if (hasNonDigit) {
if (reqMode < MODE_PASSWORD) {
@@ -366,10 +376,10 @@
reqMode = MODE_PIN;
}
}
-
+
return reqMode;
}
-
+
/**
* Save a lock password. Does not ensure that the pattern is as good
* as the requested mode, but will adjust the mode to be as good as the
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
index c2862b0..28bf6bb 100644
--- a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
@@ -17,12 +17,15 @@
package com.android.internal.widget;
import android.content.Context;
+import android.content.res.Resources;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.inputmethodservice.KeyboardView.OnKeyboardActionListener;
import android.os.Handler;
import android.os.SystemClock;
+import android.os.Vibrator;
import android.provider.Settings;
+import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
@@ -36,6 +39,7 @@
private static final int KEYBOARD_STATE_NORMAL = 0;
private static final int KEYBOARD_STATE_SHIFTED = 1;
private static final int KEYBOARD_STATE_CAPSLOCK = 2;
+ private static final String TAG = "PasswordEntryKeyboardHelper";
private int mKeyboardMode = KEYBOARD_MODE_ALPHA;
private int mKeyboardState = KEYBOARD_STATE_NORMAL;
private PasswordEntryKeyboard mQwertyKeyboard;
@@ -46,6 +50,8 @@
private Context mContext;
private View mTargetView;
private KeyboardView mKeyboardView;
+ private long[] mVibratePattern;
+ private Vibrator mVibrator;
public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView) {
mContext = context;
@@ -53,6 +59,7 @@
mKeyboardView = keyboardView;
createKeyboards();
mKeyboardView.setOnKeyboardActionListener(this);
+ mVibrator = new Vibrator();
}
public boolean isAlpha() {
@@ -142,6 +149,29 @@
}
}
+ /**
+ * Sets and enables vibrate pattern. If id is 0 (or can't be loaded), vibrate is disabled.
+ * @param id resource id for array containing vibrate pattern.
+ */
+ public void setVibratePattern(int id) {
+ int[] tmpArray = null;
+ try {
+ tmpArray = mContext.getResources().getIntArray(id);
+ } catch (Resources.NotFoundException e) {
+ if (id != 0) {
+ Log.e(TAG, "Vibrate pattern missing", e);
+ }
+ }
+ if (tmpArray == null) {
+ mVibratePattern = null;
+ return;
+ }
+ mVibratePattern = new long[tmpArray.length];
+ for (int i = 0; i < tmpArray.length; i++) {
+ mVibratePattern[i] = tmpArray[i];
+ }
+ }
+
private void handleModeChange() {
final Keyboard current = mKeyboardView.getKeyboard();
Keyboard next = null;
@@ -200,7 +230,9 @@
}
public void onPress(int primaryCode) {
- // TODO: vibration support.
+ if (mVibratePattern != null) {
+ mVibrator.vibrate(mVibratePattern, -1);
+ }
}
public void onRelease(int primaryCode) {
diff --git a/core/jni/android/graphics/YuvToJpegEncoder.cpp b/core/jni/android/graphics/YuvToJpegEncoder.cpp
index e6a1872..819cce8 100644
--- a/core/jni/android/graphics/YuvToJpegEncoder.cpp
+++ b/core/jni/android/graphics/YuvToJpegEncoder.cpp
@@ -7,9 +7,9 @@
#include <jni.h>
YuvToJpegEncoder* YuvToJpegEncoder::create(int format, int* strides) {
- // Only PIXEL_FORMAT_YCbCr_420_SP and PIXEl_FOMAT_YCbCr_422_I are supported
+ // Only ImageFormat.NV21 and ImageFormat.YUY2 are supported
// for now.
- if (format == HAL_PIXEL_FORMAT_YCbCr_420_SP) {
+ if (format == HAL_PIXEL_FORMAT_YCrCb_420_SP) {
return new Yuv420SpToJpegEncoder(strides);
} else if (format == HAL_PIXEL_FORMAT_YCbCr_422_I) {
return new Yuv422IToJpegEncoder(strides);
diff --git a/core/jni/android_graphics_PixelFormat.cpp b/core/jni/android_graphics_PixelFormat.cpp
index 0643622..5b8363c 100644
--- a/core/jni/android_graphics_PixelFormat.cpp
+++ b/core/jni/android_graphics_PixelFormat.cpp
@@ -48,11 +48,35 @@
JNIEnv* env, jobject clazz, jint format, jobject pixelFormatObject)
{
PixelFormatInfo info;
- status_t err = getPixelFormatInfo(format, &info);
+ status_t err;
+
+ // we need this for backward compatibility with PixelFormat's
+ // deprecated constants
+ switch (format) {
+ case HAL_PIXEL_FORMAT_YCbCr_422_SP:
+ // defined as the bytes per pixel of the Y plane
+ info.bytesPerPixel = 1;
+ info.bitsPerPixel = 16;
+ goto done;
+ case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ // defined as the bytes per pixel of the Y plane
+ info.bytesPerPixel = 1;
+ info.bitsPerPixel = 12;
+ goto done;
+ case HAL_PIXEL_FORMAT_YCbCr_422_I:
+ // defined as the bytes per pixel of the Y plane
+ info.bytesPerPixel = 1;
+ info.bitsPerPixel = 16;
+ goto done;
+ }
+
+ err = getPixelFormatInfo(format, &info);
if (err < 0) {
doThrow(env, "java/lang/IllegalArgumentException");
return;
}
+
+done:
env->SetIntField(pixelFormatObject, offsets.bytesPerPixel, info.bytesPerPixel);
env->SetIntField(pixelFormatObject, offsets.bitsPerPixel, info.bitsPerPixel);
}
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index 4e1ae62..5432efb 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -1122,9 +1122,10 @@
if (dbus_set_error_from_message(&err, msg)) {
if (dbus_error_has_name(&err, "org.bluez.Error.AlreadyExists")) {
result = CREATE_DEVICE_ALREADY_EXISTS;
+ } else {
+ result = CREATE_DEVICE_FAILED;
}
LOG_AND_FREE_DBUS_ERROR(&err);
- result = CREATE_DEVICE_FAILED;
}
env->CallVoidMethod(nat->me,
method_onCreateDeviceResult,
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index a82a21e..5afa0342 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -1466,19 +1466,25 @@
for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
value = bag->map.value;
jstring str = NULL;
-
+
// Take care of resolving the found resource to its final value.
ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
if (value.dataType == Res_value::TYPE_STRING) {
- const char16_t* str16 = res.getTableStringBlock(block)->stringAt(value.data, &strLen);
- str = env->NewString(str16, strLen);
- if (str == NULL) {
- doThrow(env, "java/lang/OutOfMemoryError");
- res.unlockBag(startOfBag);
- return NULL;
+ const ResStringPool* pool = res.getTableStringBlock(block);
+ const char* str8 = pool->string8At(value.data, &strLen);
+ if (str8 != NULL) {
+ str = env->NewStringUTF(str8);
+ } else {
+ const char16_t* str16 = pool->stringAt(value.data, &strLen);
+ str = env->NewString(str16, strLen);
+ if (str == NULL) {
+ doThrow(env, "java/lang/OutOfMemoryError");
+ res.unlockBag(startOfBag);
+ return NULL;
+ }
}
}
-
+
env->SetObjectArrayElement(array, i, str);
}
res.unlockBag(startOfBag);
diff --git a/core/jni/android_util_StringBlock.cpp b/core/jni/android_util_StringBlock.cpp
index ffb271c..641fbce 100644
--- a/core/jni/android_util_StringBlock.cpp
+++ b/core/jni/android_util_StringBlock.cpp
@@ -89,6 +89,11 @@
}
size_t len;
+ const char* str8 = osb->string8At(idx, &len);
+ if (str8 != NULL) {
+ return env->NewStringUTF(str8);
+ }
+
const char16_t* str = osb->stringAt(idx, &len);
if (str == NULL) {
doThrow(env, "java/lang/IndexOutOfBoundsException");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a27d28f..63584ed 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1041,6 +1041,12 @@
android:description="@string/permdesc_deletePackages"
android:protectionLevel="signatureOrSystem" />
+ <!-- Allows an application to move location of installed package. -->
+ <permission android:name="android.permission.MOVE_PACKAGE"
+ android:label="@string/permlab_movePackage"
+ android:description="@string/permdesc_movePackage"
+ android:protectionLevel="signatureOrSystem" />
+
<!-- Allows an application to change whether an application component (other than its own) is
enabled or not. -->
<permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"
@@ -1252,6 +1258,10 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+ <activity android:name="com.android.internal.app.DisableCarModeActivity"
+ android:theme="@style/Theme.NoDisplay"
+ android:excludeFromRecents="true">
+ </activity>
<activity android:name="com.android.internal.app.RingtonePickerActivity"
android:theme="@style/Theme.Dialog.Alert"
android:excludeFromRecents="true"
diff --git a/core/res/res/drawable-hdpi/btn_dropdown_disabled.9.png b/core/res/res/drawable-hdpi/btn_dropdown_disabled.9.png
new file mode 100644
index 0000000..c6503c7
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_dropdown_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.png b/core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.png
new file mode 100644
index 0000000..152de8b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_default.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_default.9.png
index 7acd0df..72faccf 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_default.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_pressed.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_pressed.9.png
index a8b843a..369be10 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_pressed.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_selected.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_selected.9.png
index cfbc092..7e996ec 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_selected.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_voice_default.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_voice_default.9.png
index 3d0d16e..44668b3 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_voice_default.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_voice_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_voice_pressed.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_voice_pressed.9.png
index 2ccd3da..3a4571e 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_voice_pressed.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_voice_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_voice_selected.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_voice_selected.9.png
index 966ea44..60dc632 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_voice_selected.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_voice_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinnerbox_arrow_first.9.png b/core/res/res/drawable-hdpi/spinnerbox_arrow_first.9.png
deleted file mode 100644
index af80855..0000000
--- a/core/res/res/drawable-hdpi/spinnerbox_arrow_first.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinnerbox_arrow_last.9.png b/core/res/res/drawable-hdpi/spinnerbox_arrow_last.9.png
deleted file mode 100644
index dc47275..0000000
--- a/core/res/res/drawable-hdpi/spinnerbox_arrow_last.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinnerbox_arrow_middle.9.png b/core/res/res/drawable-hdpi/spinnerbox_arrow_middle.9.png
deleted file mode 100644
index 007f279..0000000
--- a/core/res/res/drawable-hdpi/spinnerbox_arrow_middle.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinnerbox_arrow_single.9.png b/core/res/res/drawable-hdpi/spinnerbox_arrow_single.9.png
deleted file mode 100644
index 24592a3..0000000
--- a/core/res/res/drawable-hdpi/spinnerbox_arrow_single.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_car_mode.png b/core/res/res/drawable-hdpi/stat_notify_car_mode.png
new file mode 100644
index 0000000..6c51b32
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_notify_car_mode.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_dropdown_disabled.9.png b/core/res/res/drawable-mdpi/btn_dropdown_disabled.9.png
new file mode 100644
index 0000000..f7464c7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_dropdown_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_dropdown_disabled_focused.9.png b/core/res/res/drawable-mdpi/btn_dropdown_disabled_focused.9.png
new file mode 100644
index 0000000..ffe219f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_dropdown_disabled_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_search_dialog_voice_default.9.png b/core/res/res/drawable-mdpi/btn_search_dialog_voice_default.9.png
index febf222..437fbc7 100644
--- a/core/res/res/drawable-mdpi/btn_search_dialog_voice_default.9.png
+++ b/core/res/res/drawable-mdpi/btn_search_dialog_voice_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_search_dialog_voice_pressed.9.png b/core/res/res/drawable-mdpi/btn_search_dialog_voice_pressed.9.png
index 70a200b..a679426 100644
--- a/core/res/res/drawable-mdpi/btn_search_dialog_voice_pressed.9.png
+++ b/core/res/res/drawable-mdpi/btn_search_dialog_voice_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_search_dialog_voice_selected.9.png b/core/res/res/drawable-mdpi/btn_search_dialog_voice_selected.9.png
index 6f2989f..eb95f22 100644
--- a/core/res/res/drawable-mdpi/btn_search_dialog_voice_selected.9.png
+++ b/core/res/res/drawable-mdpi/btn_search_dialog_voice_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/spinnerbox_arrow_first.9.png b/core/res/res/drawable-mdpi/spinnerbox_arrow_first.9.png
deleted file mode 100644
index d8e268d..0000000
--- a/core/res/res/drawable-mdpi/spinnerbox_arrow_first.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/spinnerbox_arrow_last.9.png b/core/res/res/drawable-mdpi/spinnerbox_arrow_last.9.png
deleted file mode 100644
index 087e650..0000000
--- a/core/res/res/drawable-mdpi/spinnerbox_arrow_last.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/spinnerbox_arrow_middle.9.png b/core/res/res/drawable-mdpi/spinnerbox_arrow_middle.9.png
deleted file mode 100644
index f1f2ff5..0000000
--- a/core/res/res/drawable-mdpi/spinnerbox_arrow_middle.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/spinnerbox_arrow_single.9.png b/core/res/res/drawable-mdpi/spinnerbox_arrow_single.9.png
deleted file mode 100644
index f537b3b..0000000
--- a/core/res/res/drawable-mdpi/spinnerbox_arrow_single.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_notify_car_mode.png b/core/res/res/drawable-mdpi/stat_notify_car_mode.png
new file mode 100644
index 0000000..c664244
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_notify_car_mode.png
Binary files differ
diff --git a/core/res/res/drawable/btn_circle.xml b/core/res/res/drawable/btn_circle.xml
index 9208010..243f506 100644
--- a/core/res/res/drawable/btn_circle.xml
+++ b/core/res/res/drawable/btn_circle.xml
@@ -19,6 +19,8 @@
android:drawable="@drawable/btn_circle_normal" />
<item android:state_window_focused="false" android:state_enabled="false"
android:drawable="@drawable/btn_circle_disable" />
+ <item android:state_pressed="true" android:state_enabled="false"
+ android:drawable="@drawable/btn_circle_disable" />
<item android:state_pressed="true"
android:drawable="@drawable/btn_circle_pressed" />
<item android:state_focused="true" android:state_enabled="true"
diff --git a/core/res/res/drawable/btn_dropdown.xml b/core/res/res/drawable/btn_dropdown.xml
index 8ec8ece..34a0504 100644
--- a/core/res/res/drawable/btn_dropdown.xml
+++ b/core/res/res/drawable/btn_dropdown.xml
@@ -15,10 +15,24 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_window_focused="false" android:drawable="@drawable/btn_dropdown_normal" />
- <item android:state_pressed="true" android:drawable="@drawable/btn_dropdown_pressed" />
- <item android:state_focused="true" android:state_pressed="false"
+ <item
+ android:state_window_focused="false" android:state_enabled="true"
+ android:drawable="@drawable/btn_dropdown_normal" />
+ <item
+ android:state_window_focused="false" android:state_enabled="false"
+ android:drawable="@drawable/btn_dropdown_disabled" />
+ <item
+ android:state_pressed="true"
+ android:drawable="@drawable/btn_dropdown_pressed" />
+ <item
+ android:state_focused="true" android:state_enabled="true"
android:drawable="@drawable/btn_dropdown_selected" />
- <item android:drawable="@drawable/btn_dropdown_normal" />
+ <item
+ android:state_enabled="true"
+ android:drawable="@drawable/btn_dropdown_normal" />
+ <item
+ android:state_focused="true"
+ android:drawable="@drawable/btn_dropdown_disabled_focused" />
+ <item
+ android:drawable="@drawable/btn_dropdown_disabled" />
</selector>
-
diff --git a/core/res/res/drawable/spinnerbox_arrows.xml b/core/res/res/drawable/spinnerbox_arrows.xml
deleted file mode 100644
index 276a0f0..0000000
--- a/core/res/res/drawable/spinnerbox_arrows.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/res/drawable/spinnerbox_arrows.xml
-**
-** Copyright 2007, 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.
-*/
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_single="true" android:drawable="@drawable/spinnerbox_arrow_single" />
- <item android:state_first="true" android:drawable="@drawable/spinnerbox_arrow_first" />
- <item android:state_last="true" android:drawable="@drawable/spinnerbox_arrow_last" />
- <item android:state_middle="true" android:drawable="@drawable/spinnerbox_arrow_middle" />
- <item android:state_pressed="true" android:drawable="@drawable/spinnerbox_arrow_single" />
-</selector>
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
index 7ae68f9..25a41f8 100644
--- a/core/res/res/layout/alert_dialog.xml
+++ b/core/res/res/layout/alert_dialog.xml
@@ -80,7 +80,8 @@
android:paddingTop="2dip"
android:paddingBottom="12dip"
android:paddingLeft="14dip"
- android:paddingRight="10dip">
+ android:paddingRight="10dip"
+ android:overscrollMode="ifContentScrolls">
<TextView android:id="@+id/message"
style="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/search_bar.xml b/core/res/res/layout/search_bar.xml
index cf246ba..7935e2a 100644
--- a/core/res/res/layout/search_bar.xml
+++ b/core/res/res/layout/search_bar.xml
@@ -34,7 +34,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="12dip"
- android:paddingRight="12dip"
+ android:paddingRight="6dip"
android:paddingTop="7dip"
android:paddingBottom="16dip"
android:background="@drawable/search_plate_global" >
@@ -95,7 +95,10 @@
android:id="@+id/search_voice_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_marginLeft="8dip"
+ android:layout_marginLeft="0dip"
+ android:layout_marginTop="-6.5dip"
+ android:layout_marginBottom="-7dip"
+ android:layout_marginRight="-5dip"
android:background="@drawable/btn_search_dialog_voice"
android:src="@android:drawable/ic_btn_speak_now"
/>
diff --git a/core/res/res/layout/status_bar_expanded.xml b/core/res/res/layout/status_bar_expanded.xml
index a0cd11d..30138a7 100644
--- a/core/res/res/layout/status_bar_expanded.xml
+++ b/core/res/res/layout/status_bar_expanded.xml
@@ -20,9 +20,9 @@
<com.android.server.status.ExpandedView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
- android:background="@drawable/status_bar_background"
android:focusable="true"
- android:descendantFocusability="afterDescendants">
+ android:descendantFocusability="afterDescendants"
+ >
<LinearLayout
android:layout_width="match_parent"
@@ -79,19 +79,18 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_weight="1"
>
<ScrollView
android:id="@+id/scroll"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
+ android:layout_height="match_parent"
android:fadingEdge="none"
>
<com.android.server.status.NotificationLinearLayout
android:id="@+id/notificationLinearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
android:orientation="vertical"
>
@@ -136,7 +135,7 @@
<ImageView
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:src="@drawable/title_bar_shadow"
android:scaleType="fitXY"
/>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index cdc15c2..0c065ef 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -127,15 +127,4 @@
<item><xliff:g id="id">ime</xliff:g></item>
</string-array>
- <!-- Do not translate. Each string points to the component name of an ACTION_WEB_SEARCH
- handling activity. On startup if there were no preferred ACTION_WEB_SEARCH handlers,
- the first component from this list which is found to be installed is set as the
- preferred activity. -->
- <string-array name="default_web_search_providers">
- <item>com.google.android.googlequicksearchbox/.google.GoogleSearch</item>
- <item>com.android.quicksearchbox/.google.GoogleSearch</item>
- <item>com.google.android.providers.enhancedgooglesearch/.Launcher</item>
- <item>com.android.googlesearch/.GoogleSearch</item>
- <item>com.android.websearch/.Search.1</item>
- </string-array>
</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 33b509b..d66f513 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -302,7 +302,7 @@
<!-- Specify whether an activity should be finished when a "close system
windows" request has been made. This happens, for example, when
the home key is pressed, when the device is locked, when a system
- dialog like recent apps is displayed, etc. -->
+ dialog showing recent applications is displayed, etc. -->
<attr name="finishOnCloseSystemDialogs" format="boolean" />
<!-- Specify whether an activity's task should be cleared when it
@@ -487,7 +487,7 @@
the display will rotate based on how the user moves the device. -->
<enum name="sensor" value="4" />
<!-- Always ignore orientation determined by orientation sensor:
- tthe display will not rotate when the user moves the device. -->
+ the display will not rotate when the user moves the device. -->
<enum name="nosensor" value="5" />
</attr>
@@ -523,7 +523,7 @@
<flag name="keyboard" value="0x0010" />
<!-- The keyboard or navigation accessibility has changed, for example
the user has slid the keyboard out to expose it. Note that
- inspite of its name, this applied to any accessibility: keyboard
+ despite its name, this applied to any accessibility: keyboard
or navigation. -->
<flag name="keyboardHidden" value="0x0020" />
<!-- The navigation type has changed. Should never normally happen. -->
@@ -592,11 +592,16 @@
backup and restore of the application's settings to external storage. -->
<attr name="backupAgent" format="string" />
- <!-- This is not the attribute you are looking for. -->
+ <!-- Whether to allow the application to participate in backup
+ infrastructure. If this attribute is set to <code>false</code>, no backup
+ of the application will ever be performed, even by a full-system backup that
+ would otherwise cause all application data to be saved via adb. The
+ default value of this attribute is <code>true</code>. -->
<attr name="allowBackup" format="boolean" />
<!-- Whether the application in question should be terminated after its
- settings have been restored. The default is to do so. -->
+ settings have been restored. The default is <code>true</code>,
+ which means to do so. -->
<attr name="killAfterRestore" format="boolean" />
<!-- Whether the application needs to have its own Application subclass
@@ -604,15 +609,25 @@
Application class to avoid interference with application logic. -->
<attr name="restoreNeedsApplication" format="boolean" />
+ <!-- Indicate that the application is prepared to attempt a restore of any
+ backed-up dataset, even if the backup is apparently from a newer version
+ of the application than is currently installed on the device. Setting
+ this attribute to <code>true</code> will permit the Backup Manager to
+ attempt restore even when a version mismatch suggests that the data are
+ incompatible. <em>Use with caution!</em>
+
+ <p>The default value of this attribute is <code>false</code>. -->
+ <attr name="restoreAnyVersion" format="boolean" />
+
<!-- The default install location defined by an application. -->
<attr name="installLocation">
<!-- Let the system decide ideal install location -->
<enum name="auto" value="0" />
- <!-- Explicitly request to be installed on internal phone storate
+ <!-- Explicitly request to be installed on internal phone storage
only. -->
<enum name="internalOnly" value="1" />
- <!-- Prefer to be installed on sdcard. There is no guarantee that
- the system will honour this request. The application might end
+ <!-- Prefer to be installed on SD card. There is no guarantee that
+ the system will honor this request. The application might end
up being installed on internal storage if external media
is unavailable or too full. -->
<enum name="preferExternal" value="2" />
@@ -698,6 +713,7 @@
<attr name="allowBackup" />
<attr name="killAfterRestore" />
<attr name="restoreNeedsApplication" />
+ <attr name="restoreAnyVersion" />
<attr name="neverEncrypt" />
</declare-styleable>
@@ -813,7 +829,7 @@
<!-- The <code>uses-feature</code> tag specifies
a specific feature used by the application.
For example an application might specify that it requires
- specific version of open gl. Multiple such attribute
+ specific version of OpenGL. Multiple such attribute
values can be specified by the application.
<p>This appears as a child tag of the root
@@ -1359,7 +1375,7 @@
{@link android.content.Intent#setData Intent.setData()}.
<p><em>Note: scheme and host name matching in the Android framework is
case-sensitive, unlike the formal RFC. As a result,
- Uris here should always be normalized to use lower case letters
+ URIs here should always be normalized to use lower case letters
for these elements (as well as other proper Uri normalization).</em></p> -->
<attr name="data" format="string" />
<!-- The MIME type name to assign to the Intent, as per
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 5cea28db..8a92757 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -38,4 +38,6 @@
<dimen name="password_keyboard_key_height">56dip</dimen>
<!-- Default correction for the space key in the password keyboard -->
<dimen name="password_keyboard_spacebar_vertical_correction">4dip</dimen>
+ <!-- Margin at the edge of the screen to ignore touch events for in the windowshade. -->
+ <dimen name="status_bar_edge_ignore">5dp</dimen>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index b334337..5da8e85 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1231,6 +1231,7 @@
<public type="attr" name="safeMode" id="0x010102b8" />
<public type="attr" name="webTextViewStyle" id="0x010102b9" />
<public type="attr" name="overscrollMode" id="0x010102ba" />
+ <public type="attr" name="restoreAnyVersion" id="0x010102bb" />
<public type="anim" name="cycle_interpolator" id="0x010a000c" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6f34b4f..5d2d272 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -680,6 +680,11 @@
restricted usually to system process.</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_movePackage">Move application resources</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_movePackage">Allows an application to move application resources from internal to external media and vice versa.</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_readLogs">read system log files</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_readLogs">Allows an application to read from the
@@ -774,18 +779,18 @@
applications can use this to read phone owner data.</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_readCalendar">read calendar data</string>
+ <string name="permlab_readCalendar">read calendar events</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_readCalendar">Allows an application to read all
of the calendar events stored on your phone. Malicious applications
can use this to send your calendar events to other people.</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_writeCalendar">write calendar data</string>
+ <string name="permlab_writeCalendar">add or modify calendar events and send email to guests</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_writeCalendar">Allows an application to modify the
- calendar events stored on your phone. Malicious
- applications can use this to erase or modify your calendar data.</string>
+ <string name="permdesc_writeCalendar">Allows an application to add or change the
+ events on your calendar, which may send email to guests. Malicious applications can use this
+ to erase or modify your calendar events or to send email to guests.</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_accessMockLocation">mock location sources for testing</string>
@@ -1694,6 +1699,17 @@
<item quantity="other"><xliff:g id="count">%d</xliff:g> hours ago</item>
</plurals>
+ <!-- This is used to express that something occurred within the last X days (e.g., Last 7 days). -->
+ <plurals name="last_num_days">
+ <item quantity="other">Last <xliff:g id="count">%d</xliff:g> days</item>
+ </plurals>
+
+ <!-- This is used to express that something has occurred within the last month -->
+ <string name="last_month">Last month</string>
+
+ <!-- This is used to express that something happened longer ago than the previous options -->
+ <string name="older">Older</string>
+
<!-- This is used to express that something occurred some number of days in the past (e.g., 5 days ago). -->
<plurals name="num_days_ago">
<item quantity="one">yesterday</item>
@@ -2261,4 +2277,9 @@
<!-- See TETHER_STOP_DIALOG. If there was an error disconnect, this is the text. -->
<string name="tether_stop_error_message">We\'ve encountered a problem turning off Tethering. Please try again.</string>
+ <!-- Strings for car mode notification -->
+ <!-- Shown when car mode is enabled -->
+ <string name="car_mode_disable_notification_title">Car mode enabled</string>
+ <string name="car_mode_disable_notification_message">Select to disable car mode.</string>
+
</resources>
diff --git a/core/res/res/xml-mdpi/password_kbd_qwerty.xml b/core/res/res/xml-mdpi/password_kbd_qwerty.xml
index a3d4e88..bae1b42 100755
--- a/core/res/res/xml-mdpi/password_kbd_qwerty.xml
+++ b/core/res/res/xml-mdpi/password_kbd_qwerty.xml
@@ -80,8 +80,8 @@
<Key android:keyLabel="=" />
<Key android:codes="46" android:keyLabel="."
android:keyWidth="10%p"/>
- <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
- android:iconPreview="@drawable/sym_keyboard_feedback_return"
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
android:keyWidth="20%p" android:keyEdgeFlags="right"/>
</Row>
diff --git a/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml b/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml
index 2285d91..612df9c 100755
--- a/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml
+++ b/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml
@@ -79,8 +79,8 @@
android:keyWidth="20%p" android:isRepeatable="true"/>
<Key android:keyLabel="+" />
<Key android:codes="46" android:keyLabel="."/>
- <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
- android:iconPreview="@drawable/sym_keyboard_feedback_return"
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
android:keyWidth="20%p" android:keyEdgeFlags="right"/>
</Row>
diff --git a/core/tests/coretests/src/android/app/SearchablesTest.java b/core/tests/coretests/src/android/app/SearchablesTest.java
index 9b4520e..6cb31d4 100644
--- a/core/tests/coretests/src/android/app/SearchablesTest.java
+++ b/core/tests/coretests/src/android/app/SearchablesTest.java
@@ -64,39 +64,7 @@
* findActionKey works
* getIcon works
*/
-
- /**
- * The goal of this test is to confirm proper operation of the
- * SearchableInfo helper class.
- *
- * TODO: The metadata source needs to be mocked out because adding
- * searchability metadata via this test is causing it to leak into the
- * real system. So for now I'm just going to test for existence of the
- * GlobalSearch app (which is searchable).
- */
- public void testSearchableGlobalSearch() {
- // test basic array & hashmap
- Searchables searchables = new Searchables(mContext);
- searchables.buildSearchableList();
- // test linkage from another activity
- // TODO inject this via mocking into the package manager.
- // TODO for now, just check for searchable GlobalSearch app (this isn't really a unit test)
- ComponentName thisActivity = new ComponentName(
- "com.android.globalsearch",
- "com.android.globalsearch.GlobalSearch");
-
- SearchableInfo si = searchables.getSearchableInfo(thisActivity);
- assertNotNull(si);
- assertEquals(thisActivity, si.getSearchActivity());
-
- Context appContext = si.getActivityContext(mContext);
- assertNotNull(appContext);
- MoreAsserts.assertNotEqual(appContext, mContext);
- assertEquals("Quick Search Box", appContext.getString(si.getHintId()));
- assertEquals("Quick Search Box", appContext.getString(si.getLabelId()));
- }
-
/**
* Test that non-searchable activities return no searchable info (this would typically
* trigger the use of the default searchable e.g. contacts)
@@ -113,18 +81,7 @@
SearchableInfo si = searchables.getSearchableInfo(nonActivity);
assertNull(si);
}
-
- /**
- * Test that there is a default searchable (aka global search provider).
- */
- public void testDefaultSearchable() {
- Searchables searchables = new Searchables(mContext);
- searchables.buildSearchableList();
- SearchableInfo si = searchables.getDefaultSearchable();
- checkSearchable(si);
- assertTrue(searchables.isDefaultSearchable(si));
- }
-
+
/**
* This is an attempt to run the searchable info list with a mocked context. Here are some
* things I'd like to test.
@@ -371,7 +328,8 @@
public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
assertNotNull(intent);
assertTrue(intent.getAction().equals(Intent.ACTION_SEARCH)
- || intent.getAction().equals(Intent.ACTION_WEB_SEARCH));
+ || intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
+ || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH));
switch (mSearchablesMode) {
case SEARCHABLES_PASSTHROUGH:
return mRealPackageManager.queryIntentActivities(intent, flags);
@@ -472,6 +430,20 @@
throw new UnsupportedOperationException();
}
}
+
+ @Override
+ public int checkPermission(String permName, String pkgName) {
+ assertNotNull(permName);
+ assertNotNull(pkgName);
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.checkPermission(permName, pkgName);
+ case SEARCHABLES_MOCK_ZERO:
+ return PackageManager.PERMISSION_DENIED;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
}
}
diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
index ca650e0..e43031c 100644
--- a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
@@ -944,6 +944,7 @@
Assert.assertNull(cur.getString(3));
Assert.assertEquals(1234, cur.getLong(4));
Assert.assertNull(cur.getString(5));
+ cur.close();
cv = new ContentValues();
cv.put("s", "two");
@@ -956,6 +957,7 @@
Assert.assertNull(cur.getString(3));
Assert.assertEquals(1234, cur.getLong(4));
Assert.assertNull(cur.getString(5));
+ cur.close();
cv = new ContentValues();
cv.put("t", "goodbye world");
@@ -975,6 +977,7 @@
Assert.assertEquals(2345, cur.getLong(3));
Assert.assertEquals(3456, cur.getLong(4));
Assert.assertEquals("tricky", cur.getString(5));
+ cur.close();
cv = new ContentValues();
cv.put("s", "three");
@@ -987,6 +990,7 @@
Assert.assertEquals("three", cur.getString(1));
Assert.assertEquals("hello world", cur.getString(2));
Assert.assertEquals(6789, cur.getLong(3));
+ cur.close();
ih.close();
}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteGeneralTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteGeneralTest.java
new file mode 100644
index 0000000..af7ccceb
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteGeneralTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2006 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.database.sqlite;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.test.FlakyTest;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.io.File;
+
+public class SQLiteGeneralTest extends AndroidTestCase {
+
+ private SQLiteDatabase mDatabase;
+ private File mDatabaseFile;
+ Boolean exceptionRecvd = false;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ exceptionRecvd = false;
+ File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
+ mDatabaseFile = new File(dbDir, "database_test.db");
+ if (mDatabaseFile.exists()) {
+ mDatabaseFile.delete();
+ }
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+ assertNotNull(mDatabase);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDatabase.close();
+ mDatabaseFile.delete();
+ super.tearDown();
+ }
+
+ @LargeTest
+ public void testUseOfSameSqlStatementBy2Threads() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
+
+ // thread 1 creates a prepared statement
+ final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
+
+ // start 2 threads to do repeatedly execute "stmt"
+ // since these 2 threads are executing the same sql, they each should get
+ // their own copy and
+ // there SHOULD NOT be an error from sqlite: "prepared statement is busy"
+ class RunStmtThread extends Thread {
+ private static final int N = 1000;
+ @Override public void run() {
+ int i = 0;
+ try {
+ // execute many times
+ for (i = 0; i < N; i++) {
+ SQLiteStatement s1 = mDatabase.compileStatement(stmt);
+ s1.bindLong(1, i);
+ s1.execute();
+ s1.close();
+ }
+ } catch (SQLiteException e) {
+ fail("SQLiteException: " + e.getMessage());
+ return;
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("random unexpected exception: " + e.getMessage());
+ return;
+ }
+ }
+ }
+ RunStmtThread t1 = new RunStmtThread();
+ t1.start();
+ RunStmtThread t2 = new RunStmtThread();
+ t2.start();
+ while (t1.isAlive() || t2.isAlive()) {
+ Thread.sleep(1000);
+ }
+ }
+
+ @FlakyTest
+ public void testUseOfSamePreparedStatementBy2Threads() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
+
+ // thread 1 creates a prepared statement
+ final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
+ final SQLiteStatement s1 = mDatabase.compileStatement(stmt);
+
+ // start 2 threads to do repeatedly execute "stmt"
+ // since these 2 threads are executing the same prepared statement,
+ // should see an error from sqlite: "prepared statement is busy"
+ class RunStmtThread extends Thread {
+ private static final int N = 1000;
+ @Override public void run() {
+ int i = 0;
+ try {
+ // execute many times
+ for (i = 0; i < N; i++) {
+ s1.bindLong(1, i);
+ s1.execute();
+ }
+ } catch (SQLiteException e) {
+ // expect it
+ assertTrue(e.getMessage().contains("library routine called out of sequence:"));
+ exceptionRecvd = true;
+ return;
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("random unexpected exception: " + e.getMessage());
+ return;
+ }
+ }
+ }
+ RunStmtThread t1 = new RunStmtThread();
+ t1.start();
+ RunStmtThread t2 = new RunStmtThread();
+ t2.start();
+ while (t1.isAlive() || t2.isAlive()) {
+ Thread.sleep(1000);
+ }
+ assertTrue(exceptionRecvd);
+ }
+}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
new file mode 100644
index 0000000..ccd0dae
--- /dev/null
+++ b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.text;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests StaticLayout bidi implementation.
+ */
+public class StaticLayoutBidiTest extends TestCase {
+
+ public static final int REQ_DL = 2; // Layout.DIR_REQUEST_DEFAULT_LTR;
+ public static final int REQ_DR = -2; // Layout.DIR_REQUEST_DEFAULT_RTL;
+ public static final int REQ_L = 1; // Layout.DIR_REQUEST_LTR;
+ public static final int REQ_R = -1; // Layout.DIR_REQUEST_RTL;
+ public static final int L = Layout.DIR_LEFT_TO_RIGHT;
+ public static final int R = Layout.DIR_RIGHT_TO_LEFT;
+
+ public static final String SP = " ";
+ public static final String ALEF = "\u05d0";
+ public static final String BET = "\u05d1";
+ public static final String GIMEL = "\u05d2";
+ public static final String DALET = "\u05d3";
+
+ @SmallTest
+ public void testAllLtr() {
+ expectBidi(REQ_DL, "a test", "000000", L);
+ }
+
+ @SmallTest
+ public void testLtrRtl() {
+ expectBidi(REQ_DL, "abc " + ALEF + BET + GIMEL, "0000111", L);
+ }
+
+ @SmallTest
+ public void testAllRtl() {
+ expectBidi(REQ_DL, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", R);
+ }
+
+ @SmallTest
+ public void testRtlLtr() {
+ expectBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111000", R);
+ }
+
+ @SmallTest
+ public void testRAllLtr() {
+ expectBidi(REQ_R, "a test", "000000", R);
+ }
+
+ @SmallTest
+ public void testRLtrRtl() {
+ expectBidi(REQ_R, "abc " + ALEF + BET + GIMEL, "0001111", R);
+ }
+
+ @SmallTest
+ public void testLAllRtl() {
+ expectBidi(REQ_L, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", L);
+ }
+
+ @SmallTest
+ public void testLRtlLtr() {
+ expectBidi(REQ_L, ALEF + BET + GIMEL + " abc", "1110000", L);
+ }
+
+ private void expectBidi(int dir, String text,
+ String expectedLevels, int expectedDir) {
+ char[] chs = text.toCharArray();
+ int n = chs.length;
+ byte[] chInfo = new byte[n];
+
+ int resultDir = StaticLayout.bidi(dir, chs, chInfo, n, false);
+
+ {
+ StringBuilder sb = new StringBuilder("xdirs:");
+ for (int i = 0; i < n; ++i) {
+ sb.append(" ").append(String.valueOf(chInfo[i]));
+ }
+ Log.i("BIDI", sb.toString());
+ }
+
+ char[] resultLevelChars = new char[n];
+ for (int i = 0; i < n; ++i) {
+ resultLevelChars[i] = (char)('0' + chInfo[i]);
+ }
+ String resultLevels = new String(resultLevelChars);
+ assertEquals("direction", expectedDir, resultDir);
+ assertEquals("levels", expectedLevels, resultLevels);
+ }
+}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
new file mode 100644
index 0000000..7511ec1
--- /dev/null
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -0,0 +1,319 @@
+/*
+ * 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.text;
+
+import android.graphics.Paint.FontMetricsInt;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.Layout.Alignment;
+import static android.text.Layout.Alignment.*;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests StaticLayout vertical metrics behavior.
+ */
+public class StaticLayoutTest extends TestCase {
+
+ /**
+ * Basic test showing expected behavior and relationship between font
+ * metrics and line metrics.
+ */
+ @SmallTest
+ public void testGetters1() {
+ LayoutBuilder b = builder();
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+
+ // check default paint
+ Log.i("TG1:paint", fmi.toString());
+
+ Layout l = b.build();
+ assertVertMetrics(l, 0, 0,
+ fmi.ascent, fmi.descent);
+
+ // other quick metrics
+ assertEquals(0, l.getLineStart(0));
+ assertEquals(Layout.DIR_LEFT_TO_RIGHT, l.getParagraphDirection(0));
+ assertEquals(false, l.getLineContainsTab(0));
+ assertEquals(Layout.DIRS_ALL_LEFT_TO_RIGHT, l.getLineDirections(0));
+ assertEquals(0, l.getEllipsisCount(0));
+ assertEquals(0, l.getEllipsisStart(0));
+ assertEquals(b.width, l.getEllipsizedWidth());
+ }
+
+ /**
+ * Basic test showing effect of includePad = true with 1 line.
+ * Top and bottom padding are affected, as is the line descent and height.
+ */
+ @SmallTest
+ public void testGetters2() {
+ LayoutBuilder b = builder()
+ .setIncludePad(true);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.bottom);
+ }
+
+ /**
+ * Basic test showing effect of includePad = true wrapping to 2 lines.
+ * Ascent of top line and descent of bottom line are affected.
+ */
+ @SmallTest
+ public void testGetters3() {
+ LayoutBuilder b = builder()
+ .setIncludePad(true)
+ .setWidth(50);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.descent,
+ fmi.ascent, fmi.bottom);
+ }
+
+ /**
+ * Basic test showing effect of includePad = true wrapping to 3 lines.
+ * First line ascent is top, bottom line descent is bottom.
+ */
+ @SmallTest
+ public void testGetters4() {
+ LayoutBuilder b = builder()
+ .setText("This is a longer test")
+ .setIncludePad(true)
+ .setWidth(50);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.descent,
+ fmi.ascent, fmi.descent,
+ fmi.ascent, fmi.bottom);
+ }
+
+ /**
+ * Basic test showing effect of includePad = true wrapping to 3 lines and
+ * large text. See effect of leading. Currently, we don't expect there to
+ * even be non-zero leading.
+ */
+ @SmallTest
+ public void testGetters5() {
+ LayoutBuilder b = builder()
+ .setText("This is a longer test")
+ .setIncludePad(true)
+ .setWidth(150);
+ b.paint.setTextSize(36);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+
+ if (fmi.leading == 0) { // nothing to test
+ Log.i("TG5", "leading is 0, skipping test");
+ return;
+ }
+
+ // So far, leading is not used, so this is the same as TG4. If we start
+ // using leading, this will fail.
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.descent,
+ fmi.ascent, fmi.descent,
+ fmi.ascent, fmi.bottom);
+ }
+
+ /**
+ * Basic test showing effect of includePad = true, spacingAdd = 2, wrapping
+ * to 3 lines.
+ */
+ @SmallTest
+ public void testGetters6() {
+ int spacingAdd = 2; // int so expressions return int
+ LayoutBuilder b = builder()
+ .setText("This is a longer test")
+ .setIncludePad(true)
+ .setWidth(50)
+ .setSpacingAdd(spacingAdd);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.descent + spacingAdd,
+ fmi.ascent, fmi.descent + spacingAdd,
+ fmi.ascent, fmi.bottom + spacingAdd);
+ }
+
+ /**
+ * Basic test showing effect of includePad = true, spacingAdd = 2,
+ * spacingMult = 1.5, wrapping to 3 lines.
+ */
+ @SmallTest
+ public void testGetters7() {
+ LayoutBuilder b = builder()
+ .setText("This is a longer test")
+ .setIncludePad(true)
+ .setWidth(50)
+ .setSpacingAdd(2)
+ .setSpacingMult(1.5f);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+ Scaler s = new Scaler(b.spacingMult, b.spacingAdd);
+
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.descent + s.scale(fmi.descent - fmi.top),
+ fmi.ascent, fmi.descent + s.scale(fmi.descent - fmi.ascent),
+ fmi.ascent, fmi.bottom + s.scale(fmi.bottom - fmi.ascent));
+ }
+
+ /**
+ * Basic test showing effect of includePad = true, spacingAdd = 0,
+ * spacingMult = 0.8 when wrapping to 3 lines.
+ */
+ @SmallTest
+ public void testGetters8() {
+ LayoutBuilder b = builder()
+ .setText("This is a longer test")
+ .setIncludePad(true)
+ .setWidth(50)
+ .setSpacingAdd(2)
+ .setSpacingMult(.8f);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+ Scaler s = new Scaler(b.spacingMult, b.spacingAdd);
+
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.descent + s.scale(fmi.descent - fmi.top),
+ fmi.ascent, fmi.descent + s.scale(fmi.descent - fmi.ascent),
+ fmi.ascent, fmi.bottom + s.scale(fmi.bottom - fmi.ascent));
+ }
+
+ // ----- test utility classes and methods -----
+
+ // Models the effect of the scale and add parameters. I think the current
+ // implementation misbehaves.
+ private static class Scaler {
+ private final float sMult;
+ private final float sAdd;
+
+ Scaler(float sMult, float sAdd) {
+ this.sMult = sMult - 1;
+ this.sAdd = sAdd;
+ }
+
+ public int scale(float height) {
+ int altVal = (int)(height * sMult + sAdd + 0.5);
+ int rndVal = Math.round(height * sMult + sAdd);
+ if (altVal != rndVal) {
+ Log.i("Scale", "expected scale: " + rndVal +
+ " != returned scale: " + altVal);
+ }
+ return rndVal;
+ }
+ }
+
+ private static LayoutBuilder builder() {
+ return new LayoutBuilder();
+ }
+
+ private static class LayoutBuilder {
+ String text = "This is a test";
+ TextPaint paint = new TextPaint(); // default
+ int width = 100;
+ Alignment align = ALIGN_NORMAL;
+ float spacingMult = 1;
+ float spacingAdd = 0;
+ boolean includePad = false;
+
+ LayoutBuilder setText(String text) {
+ this.text = text;
+ return this;
+ }
+
+ LayoutBuilder setPaint(TextPaint paint) {
+ this.paint = paint;
+ return this;
+ }
+
+ LayoutBuilder setWidth(int width) {
+ this.width = width;
+ return this;
+ }
+
+ LayoutBuilder setAlignment(Alignment align) {
+ this.align = align;
+ return this;
+ }
+
+ LayoutBuilder setSpacingMult(float spacingMult) {
+ this.spacingMult = spacingMult;
+ return this;
+ }
+
+ LayoutBuilder setSpacingAdd(float spacingAdd) {
+ this.spacingAdd = spacingAdd;
+ return this;
+ }
+
+ LayoutBuilder setIncludePad(boolean includePad) {
+ this.includePad = includePad;
+ return this;
+ }
+
+ Layout build() {
+ return new StaticLayout(text, paint, width, align, spacingMult,
+ spacingAdd, includePad);
+ }
+ }
+
+ private void assertVertMetrics(Layout l, int topPad, int botPad, int... values) {
+ assertTopBotPadding(l, topPad, botPad);
+ assertLinesMetrics(l, values);
+ }
+
+ private void assertLinesMetrics(Layout l, int... values) {
+ // sanity check
+ if ((values.length & 0x1) != 0) {
+ throw new IllegalArgumentException(String.valueOf(values.length));
+ }
+
+ int lines = values.length >> 1;
+ assertEquals(lines, l.getLineCount());
+
+ int t = 0;
+ for (int i = 0, n = 0; i < lines; ++i, n += 2) {
+ int a = values[n];
+ int d = values[n+1];
+ int h = -a + d;
+ assertLineMetrics(l, i, t, a, d, h);
+ t += h;
+ }
+
+ assertEquals(t, l.getHeight());
+ }
+
+ private void assertLineMetrics(Layout l, int line,
+ int top, int ascent, int descent, int height) {
+ String info = "line " + line;
+ assertEquals(info, top, l.getLineTop(line));
+ assertEquals(info, ascent, l.getLineAscent(line));
+ assertEquals(info, descent, l.getLineDescent(line));
+ assertEquals(info, height, l.getLineBottom(line) - top);
+ }
+
+ private void assertTopBotPadding(Layout l, int topPad, int botPad) {
+ assertEquals(topPad, l.getTopPadding());
+ assertEquals(botPad, l.getBottomPadding());
+ }
+}
diff --git a/docs/html/guide/practices/design/performance.jd b/docs/html/guide/practices/design/performance.jd
index ec34ac9..ab3b3d3 100644
--- a/docs/html/guide/practices/design/performance.jd
+++ b/docs/html/guide/practices/design/performance.jd
@@ -1,39 +1,37 @@
page.title=Designing for Performance
@jd:body
-<p>An Android application should be fast. Well, it's probably more accurate to
-say that it should be <em>efficient</em>. That is, it should execute as
-efficiently as possible in the mobile device environment, with its limited
-computing power and data storage, smaller screen, and constrained battery life.
+<p>An Android application should be <em>efficient</em>. It will run on a mobile
+device with limited computing power and storage, a smaller screen, and
+constrained battery life. Battery life is one reason you might want to
+optimize your app even if it already seems to run "fast enough". Battery life
+is important to users, and Android's battery usage breakdown means users will
+know if your app is responsible draining their battery.</p>
-<p>As you develop your application, keep in mind that, while the application may
-perform well enough in your emulator, running on your dual-core development
-computer, it will not perform that well when run a mobile device — even
-the most powerful mobile device can't match the capabilities of a typical
-desktop system. For that reason, you should strive to write efficient code, to
-ensure the best possible performance on a variety of mobile devices.</p>
-
-<p>Generally speaking, writing fast or efficient code means keeping memory
-allocations to a minimum, writing tight code, and avoiding certain language and
-programming idioms that can subtly cripple performance. In object-oriented
-terms, most of this work takes place at the <em>method</em> level, on the order of
-actual lines of code, loops, and so on.</p>
+<p>One of the trickiest problems you'll face when optimizing Android apps is
+that it's not generally the case that you can say "device X is a factor F
+faster/slower than device Y".
+This is especially true if one of the devices is the emulator, or one of the
+devices has a JIT. If you want to know how your app performs on a given device,
+you need to test it on that device. Drawing conclusions from the emulator is
+particularly dangerous, as is attempting to compare JIT versus non-JIT
+performance: the performance <em>profiles</em> can differ wildly.</p>
<p>This document covers these topics: </p>
<ul>
<li><a href="#intro">Introduction</a></li>
<li><a href="#optimize_judiciously">Optimize Judiciously</a></li>
<li><a href="#object_creation">Avoid Creating Objects</a></li>
- <li><a href="#native_methods">Use Native Methods</a></li>
- <li><a href="#prefer_virtual">Prefer Virtual Over Interface</a></li>
+ <li><a href="#myths">Performance Myths</a></li>
<li><a href="#prefer_static">Prefer Static Over Virtual</a></li>
<li><a href="#internal_get_set">Avoid Internal Getters/Setters</a></li>
- <li><a href="#cache_fields">Cache Field Lookups</a></li>
- <li><a href="#use_final">Declare Constants Final</a></li>
- <li><a href="#foreach">Use Enhanced For Loop Syntax With Caution</a></li>
- <li><a href="#avoid_enums">Avoid Enums</a></li>
+ <li><a href="#use_final">Use Static Final For Constants</a></li>
+ <li><a href="#foreach">Use Enhanced For Loop Syntax</a></li>
+ <li><a href="#avoid_enums">Avoid Enums Where You Only Need Ints</a></li>
<li><a href="#package_inner">Use Package Scope with Inner Classes</a></li>
- <li><a href="#avoidfloat">Avoid Float</a> </li>
+ <li><a href="#avoidfloat">Use Floating-Point Judiciously</a> </li>
+ <li><a href="#library">Know And Use The Libraries</a></li>
+ <li><a href="#native_methods">Use Native Methods Judiciously</a></li>
<li><a href="#samples">Some Sample Performance Numbers</a> </li>
<li><a href="#closing_notes">Closing Notes</a></li>
</ul>
@@ -41,43 +39,17 @@
<a name="intro" id="intro"></a>
<h2>Introduction</h2>
-<p>There are two basic rules for resource-constrained systems:</p>
+<p>There are two basic rules for writing efficient code:</p>
<ul>
<li>Don't do work that you don't need to do.</li>
<li>Don't allocate memory if you can avoid it.</li>
</ul>
-<p>All the tips below follow from these two basic tenets.</p>
-
-<p>Some would argue that much of the advice on this page amounts to "premature
-optimization." While it's true that micro-optimizations sometimes make it
-harder to develop efficient data structures and algorithms, on embedded
-devices like handsets you often simply have no choice. For instance, if you
-bring your assumptions about VM performance on desktop machines to Android,
-you're quite likely to write code that exhausts system memory. This will bring
-your application to a crawl — let alone what it will do to other programs
-running on the system!</p>
-
-<p>That's why these guidelines are important. Android's success depends on
-the user experience that your applications provide, and that user experience
-depends in part on whether your code is responsive and snappy, or slow and
-aggravating. Since all our applications will run on the same devices, we're
-all in this together, in a way. Think of this document as like the rules of
-the road you had to learn when you got your driver's license: things run
-smoothly when everybody follows them, but when you don't, you get your car
-smashed up.</p>
-
-<p>Before we get down to brass tacks, a brief observation: nearly all issues
-described below are valid whether or not the VM features a JIT compiler. If I
-have two methods that accomplish the same thing, and the interpreted execution
-of foo() is faster than bar(), then the compiled version of foo() will
-probably be as fast or faster than compiled bar(). It is unwise to rely on a
-compiler to "save" you and make your code fast enough.</p>
-
<h2 id="optimize_judiciously">Optimize Judiciously</h2>
-<p>As you get started thinking about how to design your application, consider
+<p>As you get started thinking about how to design your application, and as
+you write it, consider
the cautionary points about optimization that Josh Bloch makes in his book
<em>Effective Java</em>. Here's "Item 47: Optimize Judiciously", excerpted from
the latest edition of the book with permission. Although Josh didn't have
@@ -273,8 +245,8 @@
instead of creating a short-lived temporary object.</li>
</ul>
-<p>A somewhat more radical idea is to slice up multidimensional arrays into parallel
-single one-dimension arrays:</p>
+<p>A somewhat more radical idea is to slice up multidimensional arrays into
+parallel single one-dimension arrays:</p>
<ul>
<li>An array of ints is a much better than an array of Integers,
@@ -294,49 +266,32 @@
can. Fewer objects created mean less-frequent garbage collection, which has
a direct impact on user experience.</p>
-<a name="native_methods" id="native_methods"></a>
-<h2>Use Native Methods</h2>
+<a name="myths" id="myths"></a>
+<h2>Performance Myths</h2>
-<p>When processing strings, don't hesitate to use specialty methods like
-String.indexOf(), String.lastIndexOf(), and their cousins. These are typically
-implemented in C/C++ code that easily runs 10-100x faster than doing the same
-thing in a Java loop.</p>
+<p>Previous versions of this document made various misleading claims. We
+address some of them here.</p>
-<p>The flip side of that advice is that punching through to a native
-method is more expensive than calling an interpreted method. Don't use native
-methods for trivial computation, if you can avoid it.</p>
+<p>On devices without a JIT, it is true that invoking methods via a
+variable with an exact type rather than an interface is slightly more
+efficient. (So, for example, it was cheaper to invoke methods on a
+<code>Map map</code> than a <code>HashMap map</code>, even though in both
+cases the map was a <code>HashMap</code>.) It was not the case that this
+was 2x slower; the actual difference was more like 6% slower. Furthermore,
+the JIT makes the two effectively indistinguishable.</p>
-<a name="prefer_virtual" id="prefer_virtual"></a>
-<h2>Prefer Virtual Over Interface</h2>
-
-<p>Suppose you have a HashMap object. You can declare it as a HashMap or as
-a generic Map:</p>
-
-<pre>Map myMap1 = new HashMap();
-HashMap myMap2 = new HashMap();</pre>
-
-<p>Which is better?</p>
-
-<p>Conventional wisdom says that you should prefer Map, because it
-allows you to change the underlying implementation to anything that
-implements the Map interface. Conventional wisdom is correct for
-conventional programming, but isn't so great for embedded systems. Calling
-through an interface reference can take 2x longer than a virtual
-method call through a concrete reference.</p>
-
-<p>If you have chosen a HashMap because it fits what you're doing, there
-is little value in calling it a Map. Given the availability of
-IDEs that refactor your code for you, there's not much value in calling
-it a Map even if you're not sure where the code is headed. (Again, though,
-public APIs are an exception: a good API usually trumps small performance
-concerns.)</p>
+<p>On devices without a JIT, caching field accesses is about 20% faster than
+repeatedly accesssing the field. With a JIT, field access costs about the same
+as local access, so this isn't a worthwhile optimization unless you feel it
+makes your code easier to read. (This is true of final, static, and static
+final fields too.)
<a name="prefer_static" id="prefer_static"></a>
<h2>Prefer Static Over Virtual</h2>
-<p>If you don't need to access an object's fields, make your method static. It can
-be called faster, because it doesn't require a virtual method table
-indirection. It's also good practice, because you can tell from the method
+<p>If you don't need to access an object's fields, make your method static.
+Invocations will be about 15%-20% faster.
+It's also good practice, because you can tell from the method
signature that calling the method can't alter the object's state.</p>
<a name="internal_get_set" id="internal_get_set"></a>
@@ -354,62 +309,14 @@
in the public interface, but within a class you should always access
fields directly.</p>
-<a name="cache_fields" id="cache_fields"></a>
-<h2>Cache Field Lookups</h2>
-
-<p>Accessing object fields is much slower than accessing local variables.
-Instead of writing:</p>
-<pre>for (int i = 0; i < this.mCount; i++)
- dumpItem(this.mItems[i]);</pre>
-
-<p>You should write:</p>
-<pre> int count = this.mCount;
- Item[] items = this.mItems;
-
- for (int i = 0; i < count; i++)
- dumpItems(items[i]);
-</pre>
-
-<p>(We're using an explicit "this" to make it clear that these are
-member variables.)</p>
-
-<p>A similar guideline is never call a method in the second clause of a "for"
-statement. For example, the following code will execute the getCount() method
-once per iteration, which is a huge waste when you could have simply cached
-the value as an int:</p>
-
-<pre>for (int i = 0; i < this.getCount(); i++)
- dumpItems(this.getItem(i));
-</pre>
-
-<p>It's also usually a good idea to create a local variable if you're going to be
-accessing an instance field more than once. For example:</p>
-
-<pre>
- protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) {
- if (isHorizontalScrollBarEnabled()) {
- int size = <strong>mScrollBar</strong>.getSize(<em>false</em>);
- if (size <= 0) {
- size = mScrollBarSize;
- }
- <strong>mScrollBar</strong>.setBounds(0, <em>height</em> - size, width, height);
- <strong>mScrollBar</strong>.setParams(
- computeHorizontalScrollRange(),
- computeHorizontalScrollOffset(),
- computeHorizontalScrollExtent(), <em>false</em>);
- <strong>mScrollBar</strong>.draw(canvas);
- }
- }</pre>
-
-<p>That's four separate lookups of the member field <code>mScrollBar</code>.
-By caching mScrollBar in a local stack variable, the four member field lookups
-become four stack variable references, which are much more efficient.</p>
-
-<p>Incidentally, method arguments have the same performance characteristics
-as local variables.</p>
+<p>Without a JIT, direct field access is about 3x faster than invoking a
+trivial getter. With the JIT (where direct field access is as cheap as
+accessing a local), direct field access is about 7x faster than invoking a
+trivial getter. This is true in Froyo, but will improve in the future when
+the JIT inlines getter methods.</p>
<a name="use_final" id="use_final"></a>
-<h2>Declare Constants Final</h2>
+<h2>Use Static Final For Constants</h2>
<p>Consider the following declaration at the top of a class:</p>
@@ -429,39 +336,40 @@
static final String strVal = "Hello, world!";</pre>
<p>The class no longer requires a <code><clinit></code> method,
-because the constants go into classfile static field initializers, which are
-handled directly by the VM. Code accessing <code>intVal</code> will use
+because the constants go into static field initializers in the dex file.
+Code that refers to <code>intVal</code> will use
the integer value 42 directly, and accesses to <code>strVal</code> will
use a relatively inexpensive "string constant" instruction instead of a
-field lookup.</p>
-
-<p>Declaring a method or class "final" does not confer any immediate
-performance benefits, but it does allow certain optimizations. For example, if
-the compiler knows that a "getter" method can't be overridden by a sub-class,
-it can inline the method call.</p>
-
-<p>You can also declare local variables final. However, this has no definitive
-performance benefits. For local variables, only use "final" if it makes the
-code clearer (or you have to, e.g. for use in an anonymous inner class).</p>
+field lookup. (Note that this optimization only applies to primitive types and
+<code>String</code> constants, not arbitrary reference types. Still, it's good
+practice to declare constants <code>static final</code> whenever possible.)</p>
<a name="foreach" id="foreach"></a>
-<h2>Use Enhanced For Loop Syntax With Caution</h2>
+<h2>Use Enhanced For Loop Syntax</h2>
-<p>The enhanced for loop (also sometimes known as "for-each" loop) can be used for collections that implement the Iterable interface.
-With these objects, an iterator is allocated to make interface calls
-to hasNext() and next(). With an ArrayList, you're better off walking through
-it directly, but for other collections the enhanced for loop syntax will be equivalent
-to explicit iterator usage.</p>
+<p>The enhanced for loop (also sometimes known as "for-each" loop) can be used
+for collections that implement the Iterable interface and for arrays.
+With collections, an iterator is allocated to make interface calls
+to hasNext() and next(). With an ArrayList, a hand-written counted loop is
+about 3x faster (with or without JIT), but for other collections the enhanced
+for loop syntax will be exactly equivalent to explicit iterator usage.</p>
-<p>Nevertheless, the following code shows an acceptable use of the enhanced for loop:</p>
+<p>There are several alternatives for iterating through an array:</p>
<pre>public class Foo {
int mSplat;
- static Foo mArray[] = new Foo[27];
+}
+public class ArrayBenchmark {
+ Foo[] mArray = new Foo[27];
+ {
+ for (int i = 0; i < mArray.length; ++i) {
+ mArray[i] = new Foo();
+ }
+ }
public static void zero() {
int sum = 0;
- for (int i = 0; i < mArray.length; i++) {
+ for (int i = 0; i < mArray.length; ++i) {
sum += mArray[i].mSplat;
}
}
@@ -471,53 +379,51 @@
Foo[] localArray = mArray;
int len = localArray.length;
- for (int i = 0; i < len; i++) {
+ for (int i = 0; i < len; ++i) {
sum += localArray[i].mSplat;
}
}
public static void two() {
int sum = 0;
- for (Foo a: mArray) {
+ for (Foo a : mArray) {
sum += a.mSplat;
}
}
}</pre>
-<p><strong>zero()</strong> retrieves the static field twice and gets the array
-length once for every iteration through the loop.</p>
+<p><strong>zero()</strong> is slowest, because the JIT can't yet optimize away
+the cost of getting the array length once for every iteration through the
+loop.</p>
-<p><strong>one()</strong> pulls everything out into local variables, avoiding
-the lookups.</p>
+<p><strong>one()</strong> is faster. It pulls everything out into local
+variables, avoiding the lookups. Only the array length offers a performance
+benefit.</p>
-<p><strong>two()</strong> uses the enhanced for loop syntax introduced in version 1.5 of
-the Java programming language. The code generated by the compiler takes care
-of copying the array reference and the array length to local variables, making
-it a good choice for walking through all elements of an array. It does
-generate an extra local load/store in the main loop (apparently preserving
-"a"), making it a teensy bit slower and 4 bytes longer than one().</p>
+<p><strong>two()</strong> is fastest for devices without a JIT, and
+indistinguishable from <strong>one()</strong> for devices with a JIT.
+It uses the enhanced for loop syntax introduced in version 1.5 of the Java
+programming language.</p>
-<p>To summarize all that a bit more clearly: enhanced for loop syntax performs well
-with arrays, but be cautious when using it with Iterable objects since there is
-additional object creation.</p>
+<p>To summarize: use the enhanced for loop by default, but consider a
+hand-written counted loop for performance-critical ArrayList iteration.</p>
+
+<p>(See also <em>Effective Java</em> item 46.)</p>
<a name="avoid_enums" id="avoid_enums"></a>
-<h2>Avoid Enums</h2>
+<h2>Avoid Enums Where You Only Need Ints</h2>
<p>Enums are very convenient, but unfortunately can be painful when size
and speed matter. For example, this:</p>
-<pre>public class Foo {
- public enum Shrubbery { GROUND, CRAWLING, HANGING }
-}</pre>
+<pre>public enum Shrubbery { GROUND, CRAWLING, HANGING }</pre>
-<p>turns into a 900 byte .class file (Foo$Shrubbery.class). On first use, the
+<p>adds 740 bytes to your .dex file compared to the equivalent class
+with three public static final ints. On first use, the
class initializer invokes the <init> method on objects representing each
of the enumerated values. Each object gets its own static field, and the full
set is stored in an array (a static field called "$VALUES"). That's a lot of
-code and data, just for three integers.</p>
-
-<p>This:</p>
+code and data, just for three integers. Additionally, this:</p>
<pre>Shrubbery shrub = Shrubbery.GROUND;</pre>
@@ -529,34 +435,11 @@
by all means use enums for public APIs, but try to avoid them when performance
matters.</p>
-<p>In some circumstances it can be helpful to get enum integer values
-through the <code>ordinal()</code> method. For example, replace:</p>
-
-<pre>for (int n = 0; n < list.size(); n++) {
- if (list.items[n].e == MyEnum.VAL_X)
- // do stuff 1
- else if (list.items[n].e == MyEnum.VAL_Y)
- // do stuff 2
-}</pre>
-
-<p>with:</p>
-
-<pre> int valX = MyEnum.VAL_X.ordinal();
- int valY = MyEnum.VAL_Y.ordinal();
- int count = list.size();
- MyItem items = list.items();
-
- for (int n = 0; n < count; n++)
- {
- int valItem = items[n].e.ordinal();
-
- if (valItem == valX)
- // do stuff 1
- else if (valItem == valY)
- // do stuff 2
- }</pre>
-
-<p>In some cases, this will be faster, though this is not guaranteed.</p>
+<p>If you're using <code>Enum.ordinal</code>, that's usually a sign that you
+should be using ints instead. As a rule of thumb, if an enum doesn't have a
+constructor and doesn't define its own methods, and it's used in
+performance-critical code, you should consider <code>static final int</code>
+constants instead.</p>
<a name="package_inner" id="package_inner"></a>
<h2>Use Package Scope with Inner Classes</h2>
@@ -588,10 +471,11 @@
in the outer class. This is legal, and the code prints "Value is 27" as
expected.</p>
-<p>The problem is that Foo$Inner is technically (behind the scenes) a totally
-separate class, which makes direct access to Foo's private
-members illegal. To bridge that gap, the compiler generates a
-couple of synthetic methods:</p>
+<p>The problem is that the VM considers direct access to Foo's private members
+from Foo$Inner to be illegal because Foo and Foo$Inner are different classes,
+even though the Java language allows an inner class to access an outer class'
+private members. To bridge the gap, the compiler generates a couple of
+synthetic methods:</p>
<pre>/*package*/ static int Foo.access$100(Foo foo) {
return foo.mValue;
@@ -612,31 +496,53 @@
by inner classes to have package scope, rather than private scope.
This runs faster and removes the overhead of the generated methods.
(Unfortunately it also means the fields could be accessed directly by other
-classes in the same package, which runs counter to the standard OO
+classes in the same package, which runs counter to the standard
practice of making all fields private. Once again, if you're
designing a public API you might want to carefully consider using this
optimization.)</p>
<a name="avoidfloat" id="avoidfloat"></a>
-<h2>Avoid Float</h2>
+<h2>Use Floating-Point Judiciously</h2>
-<p>Before the release of the Pentium CPU, it was common for game authors to do
-as much as possible with integer math. With the Pentium, the floating point
-math co-processor became a built-in feature, and by interleaving integer and
-floating-point operations your game would actually go faster than it would
-with purely integer math. The common practice on desktop systems is to use
-floating point freely.</p>
+<p>As a rule of thumb, floating-point is about 2x slower than integer on
+Android devices. This is true on a FPU-less, JIT-less G1 and a Nexus One with
+an FPU and the JIT. (Of course, absolute speed difference between those two
+devices is about 10x for arithmetic operations.)</p>
-<p>Unfortunately, embedded processors frequently do not have hardware floating
-point support, so all operations on "float" and "double" are performed in
-software. Some basic floating point operations can take on the order of a
-millisecond to complete.</p>
+<p>In speed terms, there's no difference between <code>float</code> and
+<code>double</code> on the more modern hardware. Space-wise, <code>double</code>
+is 2x larger. As with desktop machines, assuming space isn't an issue, you
+should prefer <code>double</code> to <code>float</code>.</p>
<p>Also, even for integers, some chips have hardware multiply but lack
hardware divide. In such cases, integer division and modulus operations are
performed in software — something to think about if you're designing a
hash table or doing lots of math.</p>
+<a name="library" id="library"></a>
+<h2>Know And Use The Libraries</h2>
+
+<p>In addition to all the usual reasons to prefer library code over rolling
+your own, bear in mind that the system is at liberty to replace calls
+to library methods with hand-coded assembler, which may be better than the
+best code the JIT can produce for the equivalent Java. The typical example
+here is <code>String.indexOf</code> and friends, which Dalvik replaces with
+an inlined intrinsic. Similarly, the <code>System.arraycopy</code> method
+is about 9x faster than a hand-coded loop on a Nexus One with the JIT.</p>
+
+<p>(See also <em>Effective Java</em> item 47.)</p>
+
+<a name="native_methods" id="native_methods"></a>
+<h2>Use Native Methods Judiciously</h2>
+
+<p>Native code isn't necessarily more efficient than Java. There's a cost
+associated with the Java-native transition, it can be significantly more
+difficult to arrange timely collection of your native resources, and you
+need to compile your code for each architecture you wish to run on (rather
+than rely on it having a JIT).</p>
+
+<p>(See also <em>Effective Java</em> item 54.)</p>
+
<a name="samples" id="samples"></a>
<h2>Some Sample Performance Numbers</h2>
@@ -714,11 +620,6 @@
<a name="closing_notes" id="closing_notes"></a>
<h2>Closing Notes</h2>
-<p>The best way to write good, efficient code for embedded systems is to
-understand what the code you write really does. If you really want to allocate
-an iterator, by all means use enhanced for loop syntax on a List; just make it a
-deliberate choice, not an inadvertent side effect.</p>
-
-<p>Forewarned is forearmed! Know what you're getting into! Insert your
-favorite maxim here, but always think carefully about what your code is doing,
-and be on the lookout for ways to speed it up.</p>
+<p>One last thing: always measure. Before you start optimizing, make sure you
+have a problem. Make sure you can accurately measure your existing performance,
+or you won't be able to measure the benefit of the alternatives you try.</p>
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
new file mode 100644
index 0000000..f126374
--- /dev/null
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+public class ImageFormat
+{
+ /* these constants are chosen to be binary compatible with
+ * their previous location in PixelFormat.java */
+
+ public static final int UNKNOWN = 0;
+
+ /** RGB format used for pictures encoded as RGB_565
+ * see {@link android.hardware.Camera.Parameters#setPictureFormat(int)}.
+ */
+ public static final int RGB_565 = 4;
+
+ /**
+ * YCbCr formats, used for video. These are not necessarily supported
+ * by the hardware.
+ */
+ public static final int NV16 = 0x10;
+
+
+ /** YCrCb format used for images, which uses the NV21 encoding format.
+ * This is the default format for camera preview images, when not
+ * otherwise set with
+ * {@link android.hardware.Camera.Parameters#setPreviewFormat(int)}.
+ */
+ public static final int NV21 = 0x11;
+
+
+ /** YCbCr format used for images, which uses YUYV (YUY2) encoding format.
+ * This is an alternative format for camera preview images. Whether this
+ * format is supported by the camera hardware can be determined by
+ * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+ */
+ public static final int YUY2 = 0x14;
+
+
+ /**
+ * Encoded formats. These are not necessarily supported by the hardware.
+ */
+ public static final int JPEG = 0x100;
+
+
+ /**
+ * Use this function to retrieve the number of bits per pixel of
+ * an ImageFormat.
+ * @param format
+ * @return the number of bits per pixel of the given format or -1 if the
+ * format doesn't exist or is not supported.
+ */
+ public static int getBitsPerPixel(int format) {
+ switch (format) {
+ case RGB_565: return 16;
+ case NV16: return 16;
+ case NV21: return 12;
+ case YUY2: return 16;
+ }
+ return -1;
+ }
+}
diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java
index c76cee7..182f14d 100644
--- a/graphics/java/android/graphics/PixelFormat.java
+++ b/graphics/java/android/graphics/PixelFormat.java
@@ -18,8 +18,7 @@
public class PixelFormat
{
- /* these constants need to match those
- in ui/PixelFormat.h & pixelflinger/format.h */
+ /* these constants need to match those in hardware/hardware.h */
public static final int UNKNOWN = 0;
@@ -46,30 +45,34 @@
public static final int L_8 = 9;
public static final int LA_88 = 0xA;
public static final int RGB_332 = 0xB;
-
+
+
/**
- * YCbCr formats, used for video. These are not necessarily supported
- * by the hardware.
+ * @deprecated use {@link android.graphics.ImageFormat#NV16
+ * ImageFormat.NV16} instead.
*/
+ @Deprecated
public static final int YCbCr_422_SP= 0x10;
- /** YCbCr format used for images, which uses the NV21 encoding format.
- * This is the default format for camera preview images, when not
- * otherwise set with
- * {@link android.hardware.Camera.Parameters#setPreviewFormat(int)}.
+ /**
+ * @deprecated use {@link android.graphics.ImageFormat#NV21
+ * ImageFormat.NV21} instead.
*/
+ @Deprecated
public static final int YCbCr_420_SP= 0x11;
- /** YCbCr format used for images, which uses YUYV (YUY2) encoding format.
- * This is an alternative format for camera preview images. Whether this
- * format is supported by the camera hardware can be determined by
- * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+ /**
+ * @deprecated use {@link android.graphics.ImageFormat#YUY2
+ * ImageFormat.YUY2} instead.
*/
+ @Deprecated
public static final int YCbCr_422_I = 0x14;
/**
- * Encoded formats. These are not necessarily supported by the hardware.
+ * @deprecated use {@link android.graphics.ImageFormat#JPEG
+ * ImageFormat.JPEG} instead.
*/
+ @Deprecated
public static final int JPEG = 0x100;
/*
diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java
index 5a4531b..9368da6 100644
--- a/graphics/java/android/graphics/YuvImage.java
+++ b/graphics/java/android/graphics/YuvImage.java
@@ -22,7 +22,7 @@
* 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.
- * Currently only PixelFormat.YCbCr_420_SP and PixelFormat.YCbCr_422_I are supported.
+ * Currently only ImageFormat.NV21 and ImageFormat.YUY2 are supported.
*
* To compress a rectangle region in the YUV data, users have to specify the
* region by left, top, width and height.
@@ -77,11 +77,11 @@
* null.
*/
public YuvImage(byte[] yuv, int format, int width, int height, int[] strides) {
- if (format != PixelFormat.YCbCr_420_SP &&
- format != PixelFormat.YCbCr_422_I) {
+ if (format != ImageFormat.NV21 &&
+ format != ImageFormat.YUY2) {
throw new IllegalArgumentException(
- "only support PixelFormat.YCbCr_420_SP " +
- "and PixelFormat.YCbCr_422_I for now");
+ "only support ImageFormat.NV21 " +
+ "and ImageFormat.YUY2 for now");
}
if (width <= 0 || height <= 0) {
@@ -107,7 +107,7 @@
/**
* Compress a rectangle region in the YuvImage to a jpeg.
- * Only PixelFormat.YCbCr_420_SP and PixelFormat.YCbCr_422_I
+ * Only ImageFormat.NV21 and ImageFormat.YUY2
* are supported for now.
*
* @param rectangle The rectangle region to be compressed. The medthod checks if rectangle is
@@ -181,14 +181,14 @@
int[] calculateOffsets(int left, int top) {
int[] offsets = null;
- if (mFormat == PixelFormat.YCbCr_420_SP) {
+ if (mFormat == ImageFormat.NV21) {
offsets = new int[] {top * mStrides[0] + left,
mHeight * mStrides[0] + top / 2 * mStrides[1]
+ left / 2 * 2 };
return offsets;
}
- if (mFormat == PixelFormat.YCbCr_422_I) {
+ if (mFormat == ImageFormat.YUY2) {
offsets = new int[] {top * mStrides[0] + left / 2 * 4};
return offsets;
}
@@ -198,12 +198,12 @@
private int[] calculateStrides(int width, int format) {
int[] strides = null;
- if (format == PixelFormat.YCbCr_420_SP) {
+ if (format == ImageFormat.NV21) {
strides = new int[] {width, width};
return strides;
}
- if (format == PixelFormat.YCbCr_422_I) {
+ if (format == ImageFormat.YUY2) {
strides = new int[] {width * 2};
return strides;
}
@@ -214,7 +214,7 @@
private void adjustRectangle(Rect rect) {
int width = rect.width();
int height = rect.height();
- if (mFormat == PixelFormat.YCbCr_420_SP) {
+ if (mFormat == ImageFormat.NV21) {
// Make sure left, top, width and height are all even.
width &= ~1;
height &= ~1;
@@ -224,7 +224,7 @@
rect.bottom = rect.top + height;
}
- if (mFormat == PixelFormat.YCbCr_422_I) {
+ if (mFormat == ImageFormat.YUY2) {
// Make sure left and width are both even.
width &= ~1;
rect.left &= ~1;
diff --git a/include/binder/Parcel.h b/include/binder/Parcel.h
index ba6c711..66c34b2 100644
--- a/include/binder/Parcel.h
+++ b/include/binder/Parcel.h
@@ -30,6 +30,7 @@
class ProcessState;
class String8;
class TextOutput;
+class Flattenable;
struct flat_binder_object; // defined in support_p/binder_module.h
@@ -81,6 +82,7 @@
status_t writeString16(const char16_t* str, size_t len);
status_t writeStrongBinder(const sp<IBinder>& val);
status_t writeWeakBinder(const wp<IBinder>& val);
+ status_t write(const Flattenable& val);
// Place a native_handle into the parcel (the native_handle's file-
// descriptors are dup'ed, so it is safe to delete the native_handle
@@ -119,7 +121,7 @@
const char16_t* readString16Inplace(size_t* outLen) const;
sp<IBinder> readStrongBinder() const;
wp<IBinder> readWeakBinder() const;
-
+ status_t read(Flattenable& val) const;
// Retrieve native_handle from the parcel. This returns a copy of the
// parcel's native_handle (the caller takes ownership). The caller
diff --git a/include/media/stagefright/AudioPlayer.h b/include/media/stagefright/AudioPlayer.h
index 8e5f05f..ea15a5c 100644
--- a/include/media/stagefright/AudioPlayer.h
+++ b/include/media/stagefright/AudioPlayer.h
@@ -61,7 +61,7 @@
status_t seekTo(int64_t time_us);
bool isSeeking();
- bool reachedEOS();
+ bool reachedEOS(status_t *finalStatus);
private:
sp<MediaSource> mSource;
@@ -81,6 +81,7 @@
bool mSeeking;
bool mReachedEOS;
+ status_t mFinalStatus;
int64_t mSeekTimeUs;
bool mStarted;
diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h
index f8bc7ab..24c2f46 100644
--- a/include/media/stagefright/OMXCodec.h
+++ b/include/media/stagefright/OMXCodec.h
@@ -96,6 +96,7 @@
kRequiresFlushBeforeShutdown = 64,
kDefersOutputBufferAllocation = 128,
kDecoderLiesAboutNumberOfChannels = 256,
+ kInputBufferSizesAreBogus = 512,
};
struct BufferInfo {
@@ -131,6 +132,7 @@
PortStatus mPortStatus[2];
bool mInitialBufferSubmit;
bool mSignalledEOS;
+ status_t mFinalStatus;
bool mNoMoreOutputData;
bool mOutputPortSettingsHaveChanged;
int64_t mSeekTimeUs;
diff --git a/include/ui/GraphicBuffer.h b/include/ui/GraphicBuffer.h
index b9c491be..e72b6b3 100644
--- a/include/ui/GraphicBuffer.h
+++ b/include/ui/GraphicBuffer.h
@@ -23,6 +23,7 @@
#include <ui/android_native_buffer.h>
#include <ui/PixelFormat.h>
#include <ui/Rect.h>
+#include <utils/Flattenable.h>
#include <pixelflinger/pixelflinger.h>
struct android_native_buffer_t;
@@ -30,7 +31,6 @@
namespace android {
class GraphicBufferMapper;
-class Parcel;
// ===========================================================================
// GraphicBuffer
@@ -40,7 +40,7 @@
: public EGLNativeBase<
android_native_buffer_t,
GraphicBuffer,
- LightRefBase<GraphicBuffer> >
+ LightRefBase<GraphicBuffer> >, public Flattenable
{
public:
@@ -97,7 +97,6 @@
uint32_t getVerticalStride() const;
protected:
- GraphicBuffer(const Parcel& reply);
virtual ~GraphicBuffer();
enum {
@@ -122,8 +121,16 @@
status_t initSize(uint32_t w, uint32_t h, PixelFormat format,
uint32_t usage);
- static status_t writeToParcel(Parcel* reply,
- android_native_buffer_t const* buffer);
+ void free_handle();
+
+ // Flattenable interface
+ size_t getFlattenedSize() const;
+ size_t getFdCount() const;
+ status_t flatten(void* buffer, size_t size,
+ int fds[], size_t count) const;
+ status_t unflatten(void const* buffer, size_t size,
+ int fds[], size_t count);
+
GraphicBufferMapper& mBufferMapper;
ssize_t mInitCheck;
diff --git a/include/utils/Flattenable.h b/include/utils/Flattenable.h
new file mode 100644
index 0000000..852be3b
--- /dev/null
+++ b/include/utils/Flattenable.h
@@ -0,0 +1,62 @@
+/*
+ * 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 ANDROID_UTILS_FLATTENABLE_H
+#define ANDROID_UTILS_FLATTENABLE_H
+
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <utils/Errors.h>
+
+namespace android {
+
+class Flattenable
+{
+public:
+ // size in bytes of the flattened object
+ virtual size_t getFlattenedSize() const = 0;
+
+ // number of file descriptors to flatten
+ virtual size_t getFdCount() const = 0;
+
+ // flattens the object into buffer.
+ // size should be at least of getFlattenedSize()
+ // file descriptors are written in the fds[] array but ownership is
+ // not transfered (ie: they must be dupped by the caller of
+ // flatten() if needed).
+ virtual status_t flatten(void* buffer, size_t size,
+ int fds[], size_t count) const = 0;
+
+ // unflattens the object from buffer.
+ // size should be equal to the value of getFlattenedSize() when the
+ // object was flattened.
+ // unflattened file descriptors are found in the fds[] array and
+ // don't need to be dupped(). ie: the caller of unflatten doesn't
+ // keep ownership. If a fd is not retained by unflatten() it must be
+ // explicitly closed.
+ virtual status_t unflatten(void const* buffer, size_t size,
+ int fds[], size_t count) = 0;
+
+protected:
+ virtual ~Flattenable() = 0;
+
+};
+
+}; // namespace android
+
+
+#endif /* ANDROID_UTILS_FLATTENABLE_H */
diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h
index 13ea27e..cd657e8 100644
--- a/include/utils/ResourceTypes.h
+++ b/include/utils/ResourceTypes.h
@@ -447,6 +447,8 @@
}
const char16_t* stringAt(size_t idx, size_t* outLen) const;
+ const char* string8At(size_t idx, size_t* outLen) const;
+
const ResStringPool_span* styleAt(const ResStringPool_ref& ref) const;
const ResStringPool_span* styleAt(size_t idx) const;
diff --git a/keystore/java/android/security/SystemKeyStore.java b/keystore/java/android/security/SystemKeyStore.java
index 61a4293..abdb0ae 100644
--- a/keystore/java/android/security/SystemKeyStore.java
+++ b/keystore/java/android/security/SystemKeyStore.java
@@ -17,6 +17,7 @@
package android.security;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.Process;
import java.io.File;
@@ -92,6 +93,8 @@
fos.write(retKey);
fos.flush();
fos.close();
+ FileUtils.setPermissions(keyFile.getName(), (FileUtils.S_IRUSR | FileUtils.S_IWUSR),
+ -1, -1);
} catch (IOException ioe) {
return null;
}
diff --git a/libs/audioflinger/AudioPolicyManagerBase.cpp b/libs/audioflinger/AudioPolicyManagerBase.cpp
index 096aa73..7b866c7 100644
--- a/libs/audioflinger/AudioPolicyManagerBase.cpp
+++ b/libs/audioflinger/AudioPolicyManagerBase.cpp
@@ -82,8 +82,8 @@
// keep track of SCO device address
mScoDeviceAddress = String8(device_address, MAX_DEVICE_ADDRESS_LEN);
#ifdef WITH_A2DP
- if ((mA2dpDeviceAddress == mScoDeviceAddress) &&
- (mPhoneState != AudioSystem::MODE_NORMAL)) {
+ if (mA2dpOutput != 0 &&
+ mPhoneState != AudioSystem::MODE_NORMAL) {
mpClientInterface->suspendOutput(mA2dpOutput);
}
#endif
@@ -116,8 +116,8 @@
if (AudioSystem::isBluetoothScoDevice(device)) {
mScoDeviceAddress = "";
#ifdef WITH_A2DP
- if ((mA2dpDeviceAddress == mScoDeviceAddress) &&
- (mPhoneState != AudioSystem::MODE_NORMAL)) {
+ if (mA2dpOutput != 0 &&
+ mPhoneState != AudioSystem::MODE_NORMAL) {
mpClientInterface->restoreOutput(mA2dpOutput);
}
#endif
@@ -275,10 +275,8 @@
newDevice = getNewDevice(mHardwareOutput, false);
#ifdef WITH_A2DP
checkOutputForAllStrategies(newDevice);
- // suspend A2DP output if SCO device address is the same as A2DP device address.
- // no need to check that a SCO device is actually connected as mScoDeviceAddress == ""
- // if none is connected and the test below will fail.
- if (mA2dpDeviceAddress == mScoDeviceAddress) {
+ // suspend A2DP output if a SCO device is present.
+ if (mA2dpOutput != 0 && mScoDeviceAddress != "") {
if (oldState == AudioSystem::MODE_NORMAL) {
mpClientInterface->suspendOutput(mA2dpOutput);
} else if (state == AudioSystem::MODE_NORMAL) {
@@ -295,13 +293,31 @@
if (oldState == AudioSystem::MODE_IN_CALL && newDevice == 0) {
newDevice = hwOutputDesc->device();
}
+
+ // when changing from ring tone to in call mode, mute the ringing tone
+ // immediately and delay the route change to avoid sending the ring tone
+ // tail into the earpiece or headset.
+ int delayMs = 0;
+ if (state == AudioSystem::MODE_IN_CALL && oldState == AudioSystem::MODE_RINGTONE) {
+ // delay the device change command by twice the output latency to have some margin
+ // and be sure that audio buffers not yet affected by the mute are out when
+ // we actually apply the route change
+ delayMs = hwOutputDesc->mLatency*2;
+ setStreamMute(AudioSystem::RING, true, mHardwareOutput);
+ }
+
// change routing is necessary
- setOutputDevice(mHardwareOutput, newDevice, force);
+ setOutputDevice(mHardwareOutput, newDevice, force, delayMs);
// if entering in call state, handle special case of active streams
// pertaining to sonification strategy see handleIncallSonification()
if (state == AudioSystem::MODE_IN_CALL) {
LOGV("setPhoneState() in call state management: new state is %d", state);
+ // unmute the ringing tone after a sufficient delay if it was muted before
+ // setting output device above
+ if (oldState == AudioSystem::MODE_RINGTONE) {
+ setStreamMute(AudioSystem::RING, false, mHardwareOutput, MUTE_TIME_MS);
+ }
for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
handleIncallSonification(stream, true, true);
}
@@ -1173,7 +1189,7 @@
}
AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput);
- if (mA2dpDeviceAddress == mScoDeviceAddress) {
+ if (mScoDeviceAddress != "") {
// It is normal to suspend twice if we are both in call,
// and have the hardware audio output routed to BT SCO
if (mPhoneState != AudioSystem::MODE_NORMAL) {
@@ -1207,10 +1223,10 @@
return INVALID_OPERATION;
}
- // mute media during 2 seconds to avoid outputing sound on hardware output while music stream
+ // mute media strategy to avoid outputting sound on hardware output while music stream
// is switched from A2DP output and before music is paused by music application
setStrategyMute(STRATEGY_MEDIA, true, mHardwareOutput);
- setStrategyMute(STRATEGY_MEDIA, false, mHardwareOutput, 2000);
+ setStrategyMute(STRATEGY_MEDIA, false, mHardwareOutput, MUTE_TIME_MS);
if (!a2dpUsedForSonification()) {
// unmute music on A2DP output if a notification or ringtone is playing
@@ -1538,9 +1554,9 @@
usleep(outputDesc->mLatency*2*1000);
}
#ifdef WITH_A2DP
- // suspend A2D output if SCO device is selected
+ // suspend A2DP output if SCO device is selected
if (AudioSystem::isBluetoothScoDevice((AudioSystem::audio_devices)device)) {
- if (mA2dpOutput && mScoDeviceAddress == mA2dpDeviceAddress) {
+ if (mA2dpOutput != 0) {
mpClientInterface->suspendOutput(mA2dpOutput);
}
}
@@ -1555,7 +1571,7 @@
#ifdef WITH_A2DP
// if disconnecting SCO device, restore A2DP output
if (AudioSystem::isBluetoothScoDevice((AudioSystem::audio_devices)prevDevice)) {
- if (mA2dpOutput && mScoDeviceAddress == mA2dpDeviceAddress) {
+ if (mA2dpOutput != 0) {
LOGV("restore A2DP output");
mpClientInterface->restoreOutput(mA2dpOutput);
}
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index e397bce..00d2210 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -28,6 +28,7 @@
#include <utils/String16.h>
#include <utils/TextOutput.h>
#include <utils/misc.h>
+#include <utils/Flattenable.h>
#include <private/binder/binder_module.h>
@@ -675,6 +676,42 @@
return writeObject(obj, true);
}
+status_t Parcel::write(const Flattenable& val)
+{
+ status_t err;
+
+ // size if needed
+ size_t len = val.getFlattenedSize();
+ size_t fd_count = val.getFdCount();
+
+ err = this->writeInt32(len);
+ if (err) return err;
+
+ err = this->writeInt32(fd_count);
+ if (err) return err;
+
+ // payload
+ void* buf = this->writeInplace(PAD_SIZE(len));
+ if (buf == NULL)
+ return BAD_VALUE;
+
+ int* fds = NULL;
+ if (fd_count) {
+ fds = new int[fd_count];
+ }
+
+ err = val.flatten(buf, len, fds, fd_count);
+ for (size_t i=0 ; i<fd_count && err==NO_ERROR ; i++) {
+ err = this->writeDupFileDescriptor( fds[i] );
+ }
+
+ if (fd_count) {
+ delete [] fds;
+ }
+
+ return err;
+}
+
status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData)
{
const bool enoughData = (mDataPos+sizeof(val)) <= mDataCapacity;
@@ -713,7 +750,6 @@
goto restart_write;
}
-
void Parcel::remove(size_t start, size_t amt)
{
LOG_ALWAYS_FATAL("Parcel::remove() not yet implemented!");
@@ -940,6 +976,38 @@
return BAD_TYPE;
}
+status_t Parcel::read(Flattenable& val) const
+{
+ // size
+ const size_t len = this->readInt32();
+ const size_t fd_count = this->readInt32();
+
+ // payload
+ void const* buf = this->readInplace(PAD_SIZE(len));
+ if (buf == NULL)
+ return BAD_VALUE;
+
+ int* fds = NULL;
+ if (fd_count) {
+ fds = new int[fd_count];
+ }
+
+ status_t err = NO_ERROR;
+ for (size_t i=0 ; i<fd_count && err==NO_ERROR ; i++) {
+ fds[i] = dup(this->readFileDescriptor());
+ if (fds[i] < 0) err = BAD_VALUE;
+ }
+
+ if (err == NO_ERROR) {
+ err = val.unflatten(buf, len, fds, fd_count);
+ }
+
+ if (fd_count) {
+ delete [] fds;
+ }
+
+ return err;
+}
const flat_binder_object* Parcel::readObject(bool nullMetaData) const
{
const size_t DPOS = mDataPos;
diff --git a/libs/rs/java/Fountain/AndroidManifest.xml b/libs/rs/java/Fountain/AndroidManifest.xml
index 1185930..951c451 100644
--- a/libs/rs/java/Fountain/AndroidManifest.xml
+++ b/libs/rs/java/Fountain/AndroidManifest.xml
@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.fountain">
- <application android:label="Fountain">
+ <application
+ android:label="Fountain"
+ android:icon="@drawable/test_pattern">
<activity android:name="Fountain">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/libs/rs/java/Fountain/res/drawable/test_pattern.png b/libs/rs/java/Fountain/res/drawable/test_pattern.png
new file mode 100644
index 0000000..e7d1455
--- /dev/null
+++ b/libs/rs/java/Fountain/res/drawable/test_pattern.png
Binary files differ
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index dec993a..cc3a74fb 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -546,6 +546,8 @@
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &mGL.mMaxFragmentTextureImageUnits);
glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &mGL.mMaxFragmentUniformVectors);
+
+ mGL.OES_texture_npot = NULL != strstr((const char *)mGL.mExtensions, "GL_OES_texture_npot");
}
}
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index 03e65f1..04bd748 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -163,6 +163,8 @@
mutable const ObjectBase * mObjHead;
+ bool ext_OES_texture_npot() const {return mGL.OES_texture_npot;}
+
protected:
Device *mDev;
@@ -196,6 +198,8 @@
int32_t mMaxVertexAttribs;
int32_t mMaxVertexUniformVectors;
int32_t mMaxVertexTextureUnits;
+
+ bool OES_texture_npot;
} mGL;
uint32_t mWidth;
diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index 15f3269..c17b94c 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -109,7 +109,7 @@
}
if (mSamplers[ct].get()) {
- mSamplers[ct]->setupGL(rsc);
+ mSamplers[ct]->setupGL(rsc, mTextures[ct]->getType()->getIsNp2());
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -159,7 +159,7 @@
glBindTexture(GL_TEXTURE_2D, mTextures[ct]->getTextureID());
rsc->checkError("ProgramFragment::setupGL2 tex bind");
if (mSamplers[ct].get()) {
- mSamplers[ct]->setupGL(rsc);
+ mSamplers[ct]->setupGL(rsc, mTextures[ct]->getType()->getIsNp2());
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
diff --git a/libs/rs/rsSampler.cpp b/libs/rs/rsSampler.cpp
index 7552d54..71f508f 100644
--- a/libs/rs/rsSampler.cpp
+++ b/libs/rs/rsSampler.cpp
@@ -53,7 +53,7 @@
{
}
-void Sampler::setupGL(const Context *rsc)
+void Sampler::setupGL(const Context *rsc, bool npot)
{
GLenum trans[] = {
GL_NEAREST, //RS_SAMPLER_NEAREST,
@@ -64,11 +64,21 @@
};
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, trans[mMinFilter]);
+ bool forceNonMip = false;
+ if (!rsc->ext_OES_texture_npot() && npot) {
+ forceNonMip = true;
+ }
+
+ if ((mMinFilter == RS_SAMPLER_LINEAR_MIP_LINEAR) && forceNonMip) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ } else {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, trans[mMinFilter]);
+ }
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, trans[mMagFilter]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, trans[mWrapS]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, trans[mWrapT]);
+
rsc->checkError("ProgramFragment::setupGL2 tex env");
}
diff --git a/libs/rs/rsSampler.h b/libs/rs/rsSampler.h
index 9e20a2f..0506081 100644
--- a/libs/rs/rsSampler.h
+++ b/libs/rs/rsSampler.h
@@ -41,7 +41,7 @@
virtual ~Sampler();
void bind(Allocation *);
- void setupGL(const Context *);
+ void setupGL(const Context *, bool npot);
void bindToContext(SamplerState *, uint32_t slot);
void unbindFromContext(SamplerState *);
diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp
index 9d24c6c..c09e979 100644
--- a/libs/rs/rsType.cpp
+++ b/libs/rs/rsType.cpp
@@ -283,6 +283,24 @@
mElement->dumpLOGV(buf);
}
+bool Type::getIsNp2() const
+{
+ uint32_t x = getDimX();
+ uint32_t y = getDimY();
+ uint32_t z = getDimZ();
+
+ if (x && (x & (x-1))) {
+ return true;
+ }
+ if (y && (y & (y-1))) {
+ return true;
+ }
+ if (z && (z & (z-1))) {
+ return true;
+ }
+ return false;
+}
+
//////////////////////////////////////////////////
//
diff --git a/libs/rs/rsType.h b/libs/rs/rsType.h
index 28e6274..c25577c 100644
--- a/libs/rs/rsType.h
+++ b/libs/rs/rsType.h
@@ -56,6 +56,7 @@
uint32_t getLODOffset(uint32_t lod, uint32_t x, uint32_t y, uint32_t z) const;
uint32_t getLODCount() const {return mLODCount;}
+ bool getIsNp2() const;
void setElement(const Element *e) {mElement.set(e);}
@@ -65,6 +66,7 @@
void setDimFaces(bool v) {mFaces = v;}
void setDimLOD(bool v) {mDimLOD = v;}
+
void clear();
void compute();
diff --git a/libs/surfaceflinger/Android.mk b/libs/surfaceflinger/Android.mk
index 395a937..86eb78d 100644
--- a/libs/surfaceflinger/Android.mk
+++ b/libs/surfaceflinger/Android.mk
@@ -35,7 +35,6 @@
libpixelflinger \
libhardware \
libutils \
- libskia \
libEGL \
libGLESv1_CM \
libbinder \
diff --git a/libs/surfaceflinger/LayerBase.cpp b/libs/surfaceflinger/LayerBase.cpp
index 5b96e9d..a3d293f 100644
--- a/libs/surfaceflinger/LayerBase.cpp
+++ b/libs/surfaceflinger/LayerBase.cpp
@@ -52,6 +52,7 @@
mTransformed(false),
mUseLinearFiltering(false),
mOrientation(0),
+ mLeft(0), mTop(0),
mTransactionFlags(0),
mPremultipliedAlpha(true),
mInvalidate(0)
diff --git a/libs/surfaceflinger/LayerBase.h b/libs/surfaceflinger/LayerBase.h
index d1bbd04..6aacd82 100644
--- a/libs/surfaceflinger/LayerBase.h
+++ b/libs/surfaceflinger/LayerBase.h
@@ -22,6 +22,7 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
+#include <GLES/gl.h>
#include <utils/RefBase.h>
diff --git a/libs/surfaceflinger/SurfaceFlinger.cpp b/libs/surfaceflinger/SurfaceFlinger.cpp
index 2d6152e..b408779 100644
--- a/libs/surfaceflinger/SurfaceFlinger.cpp
+++ b/libs/surfaceflinger/SurfaceFlinger.cpp
@@ -1496,8 +1496,8 @@
layer->needsBlending(), layer->needsDithering(),
layer->contentDirty,
s.alpha, s.flags,
- s.transform[0], s.transform[1],
- s.transform[2], s.transform[3]);
+ s.transform[0][0], s.transform[0][1],
+ s.transform[1][0], s.transform[1][1]);
result.append(buffer);
buffer[0] = 0;
/*** LayerBaseClient ***/
@@ -1833,27 +1833,25 @@
status_t GraphicPlane::orientationToTransfrom(
int orientation, int w, int h, Transform* tr)
-{
- float a, b, c, d, x, y;
+{
+ uint32_t flags = 0;
switch (orientation) {
case ISurfaceComposer::eOrientationDefault:
- // make sure the default orientation is optimal
- tr->reset();
- return NO_ERROR;
+ flags = Transform::ROT_0;
+ break;
case ISurfaceComposer::eOrientation90:
- a=0; b=-1; c=1; d=0; x=w; y=0;
+ flags = Transform::ROT_90;
break;
case ISurfaceComposer::eOrientation180:
- a=-1; b=0; c=0; d=-1; x=w; y=h;
+ flags = Transform::ROT_180;
break;
case ISurfaceComposer::eOrientation270:
- a=0; b=1; c=-1; d=0; x=0; y=h;
+ flags = Transform::ROT_270;
break;
default:
return BAD_VALUE;
}
- tr->set(a, b, c, d);
- tr->set(x, y);
+ tr->set(flags, w, h);
return NO_ERROR;
}
@@ -1869,24 +1867,13 @@
mHeight = int(h);
Transform orientationTransform;
- if (UNLIKELY(orientation == 42)) {
- float a, b, c, d, x, y;
- const float r = (3.14159265f / 180.0f) * 42.0f;
- const float si = sinf(r);
- const float co = cosf(r);
- a=co; b=-si; c=si; d=co;
- x = si*(h*0.5f) + (1-co)*(w*0.5f);
- y =-si*(w*0.5f) + (1-co)*(h*0.5f);
- orientationTransform.set(a, b, c, d);
- orientationTransform.set(x, y);
- } else {
- GraphicPlane::orientationToTransfrom(orientation, w, h,
- &orientationTransform);
- if (orientation & ISurfaceComposer::eOrientationSwapMask) {
- mWidth = int(h);
- mHeight = int(w);
- }
+ GraphicPlane::orientationToTransfrom(orientation, w, h,
+ &orientationTransform);
+ if (orientation & ISurfaceComposer::eOrientationSwapMask) {
+ mWidth = int(h);
+ mHeight = int(w);
}
+
mOrientation = orientation;
mGlobalTransform = mDisplayTransform * orientationTransform;
return NO_ERROR;
diff --git a/libs/surfaceflinger/Transform.cpp b/libs/surfaceflinger/Transform.cpp
index ab6f7ba..b2d5856 100644
--- a/libs/surfaceflinger/Transform.cpp
+++ b/libs/surfaceflinger/Transform.cpp
@@ -14,180 +14,257 @@
* limitations under the License.
*/
+#include <math.h>
+
+#include <cutils/compiler.h>
+#include <utils/String8.h>
#include <ui/Region.h>
-#include <private/pixelflinger/ggl_fixed.h>
-
#include "Transform.h"
// ---------------------------------------------------------------------------
-#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true ))
-#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false ))
-
-// ---------------------------------------------------------------------------
-
namespace android {
// ---------------------------------------------------------------------------
-Transform::Transform()
- : mType(0)
-{
- mTransform.reset();
+template <typename T> inline T min(T a, T b) {
+ return a<b ? a : b;
+}
+template <typename T> inline T min(T a, T b, T c) {
+ return min(a, min(b, c));
+}
+template <typename T> inline T min(T a, T b, T c, T d) {
+ return min(a, b, min(c, d));
+}
+
+template <typename T> inline T max(T a, T b) {
+ return a>b ? a : b;
+}
+template <typename T> inline T max(T a, T b, T c) {
+ return max(a, max(b, c));
+}
+template <typename T> inline T max(T a, T b, T c, T d) {
+ return max(a, b, max(c, d));
+}
+
+// ---------------------------------------------------------------------------
+
+Transform::Transform() {
+ reset();
}
Transform::Transform(const Transform& other)
- : mTransform(other.mTransform), mType(other.mType)
-{
+ : mMatrix(other.mMatrix), mType(other.mType) {
}
-Transform::Transform(int32_t flags) {
- mTransform.reset();
- int sx = (flags & FLIP_H) ? -1 : 1;
- int sy = (flags & FLIP_V) ? -1 : 1;
- if (flags & ROT_90) {
- this->set(0, -sy, sx, 0);
- } else {
- this->set(sx, 0, 0, sy);
- }
+Transform::Transform(uint32_t orientation) {
+ set(orientation, 0, 0);
}
Transform::~Transform() {
}
+
+bool Transform::absIsOne(float f) {
+ return fabs(f) == 1.0f;
+}
+
+bool Transform::isZero(float f) {
+ return fabs(f) == 0.0f;
+}
+
+bool Transform::absEqual(float a, float b) {
+ return fabs(a) == fabs(b);
+}
+
Transform Transform::operator * (const Transform& rhs) const
{
- if (LIKELY(mType == 0))
+ if (CC_LIKELY(mType == IDENTITY))
return rhs;
Transform r(*this);
- r.mTransform.preConcat(rhs.mTransform);
+ if (rhs.mType == IDENTITY)
+ return r;
+
+ // TODO: we could use mType to optimize the matrix multiply
+ const mat33& A(mMatrix);
+ const mat33& B(rhs.mMatrix);
+ mat33& D(r.mMatrix);
+ for (int i=0 ; i<3 ; i++) {
+ const float v0 = A[0][i];
+ const float v1 = A[1][i];
+ const float v2 = A[2][i];
+ D[0][i] = v0*B[0][0] + v1*B[0][1] + v2*B[0][2];
+ D[1][i] = v0*B[1][0] + v1*B[1][1] + v2*B[1][2];
+ D[2][i] = v0*B[2][0] + v1*B[2][1] + v2*B[2][2];
+ }
r.mType |= rhs.mType;
+
+ // TODO: we could recompute this value from r and rhs
+ r.mType &= 0xFF;
+ r.mType |= UNKNOWN_TYPE;
return r;
}
-float Transform::operator [] (int i) const
-{
- float r = 0;
- switch(i) {
- case 0: r = SkScalarToFloat( mTransform[SkMatrix::kMScaleX] ); break;
- case 1: r = SkScalarToFloat( mTransform[SkMatrix::kMSkewX] ); break;
- case 2: r = SkScalarToFloat( mTransform[SkMatrix::kMSkewY] ); break;
- case 3: r = SkScalarToFloat( mTransform[SkMatrix::kMScaleY] ); break;
- }
- return r;
-}
-
-uint8_t Transform::type() const
-{
- if (UNLIKELY(mType & 0x80000000)) {
- mType = mTransform.getType();
- }
- return uint8_t(mType & 0xFF);
+float const* Transform::operator [] (int i) const {
+ return mMatrix[i].v;
}
bool Transform::transformed() const {
- return type() > SkMatrix::kTranslate_Mask;
+ return type() > TRANSLATE;
}
int Transform::tx() const {
- return SkScalarRound( mTransform[SkMatrix::kMTransX] );
+ return floorf(mMatrix[2][0] + 0.5f);
}
int Transform::ty() const {
- return SkScalarRound( mTransform[SkMatrix::kMTransY] );
+ return floorf(mMatrix[2][1] + 0.5f);
}
void Transform::reset() {
- mTransform.reset();
- mType = 0;
-}
-
-void Transform::set( float xx, float xy,
- float yx, float yy)
-{
- mTransform.set(SkMatrix::kMScaleX, SkFloatToScalar(xx));
- mTransform.set(SkMatrix::kMSkewX, SkFloatToScalar(xy));
- mTransform.set(SkMatrix::kMSkewY, SkFloatToScalar(yx));
- mTransform.set(SkMatrix::kMScaleY, SkFloatToScalar(yy));
- mType |= 0x80000000;
-}
-
-void Transform::set(float radian, float x, float y)
-{
- float r00 = cosf(radian); float r01 = -sinf(radian);
- float r10 = sinf(radian); float r11 = cosf(radian);
- mTransform.set(SkMatrix::kMScaleX, SkFloatToScalar(r00));
- mTransform.set(SkMatrix::kMSkewX, SkFloatToScalar(r01));
- mTransform.set(SkMatrix::kMSkewY, SkFloatToScalar(r10));
- mTransform.set(SkMatrix::kMScaleY, SkFloatToScalar(r11));
- mTransform.set(SkMatrix::kMTransX, SkIntToScalar(x - r00*x - r01*y));
- mTransform.set(SkMatrix::kMTransY, SkIntToScalar(y - r10*x - r11*y));
- mType |= 0x80000000 | SkMatrix::kTranslate_Mask;
-}
-
-void Transform::scale(float s, float x, float y)
-{
- mTransform.postScale(s, s, x, y);
- mType |= 0x80000000;
-}
-
-void Transform::set(int tx, int ty)
-{
- if (tx | ty) {
- mTransform.set(SkMatrix::kMTransX, SkIntToScalar(tx));
- mTransform.set(SkMatrix::kMTransY, SkIntToScalar(ty));
- mType |= SkMatrix::kTranslate_Mask;
- } else {
- mTransform.set(SkMatrix::kMTransX, 0);
- mTransform.set(SkMatrix::kMTransY, 0);
- mType &= ~SkMatrix::kTranslate_Mask;
+ mType = IDENTITY;
+ for(int i=0 ; i<3 ; i++) {
+ vec3& v(mMatrix[i]);
+ for (int j=0 ; j<3 ; j++)
+ v[j] = ((i==j) ? 1.0f : 0.0f);
}
}
-void Transform::transform(GLfixed* point, int x, int y) const
+void Transform::set(float tx, float ty)
{
- SkPoint s;
- mTransform.mapXY(SkIntToScalar(x), SkIntToScalar(y), &s);
- point[0] = SkScalarToFixed(s.fX);
- point[1] = SkScalarToFixed(s.fY);
+ mMatrix[2][0] = tx;
+ mMatrix[2][1] = ty;
+ mMatrix[2][2] = 1.0f;
+
+ if (isZero(tx) && isZero(ty)) {
+ mType &= ~TRANSLATE;
+ } else {
+ mType |= TRANSLATE;
+ }
+}
+
+void Transform::set(float a, float b, float c, float d)
+{
+ mat33& M(mMatrix);
+ M[0][0] = a; M[1][0] = b;
+ M[0][1] = c; M[1][1] = d;
+ M[0][2] = 0; M[1][2] = 0;
+ mType = UNKNOWN_TYPE;
+}
+
+void Transform::set(uint32_t flags, float w, float h)
+{
+ mType = flags << 8;
+ float sx = (flags & FLIP_H) ? -1 : 1;
+ float sy = (flags & FLIP_V) ? -1 : 1;
+ float a=0, b=0, c=0, d=0, x=0, y=0;
+ int xmask = 0;
+
+ // computation of x,y
+ // x y
+ // 0 0 0
+ // w 0 ROT90
+ // w h FLIPH|FLIPV
+ // 0 h FLIPH|FLIPV|ROT90
+
+ if (flags & ROT_90) {
+ mType |= ROTATE;
+ b = -sy;
+ c = sx;
+ xmask = 1;
+ } else {
+ a = sx;
+ d = sy;
+ }
+
+ if (flags & FLIP_H) {
+ mType ^= SCALE;
+ xmask ^= 1;
+ }
+
+ if (flags & FLIP_V) {
+ mType ^= SCALE;
+ y = h;
+ }
+
+ if ((flags & ROT_180) == ROT_180) {
+ mType |= ROTATE;
+ }
+
+ if (xmask) {
+ x = w;
+ }
+
+ if (!isZero(x) || !isZero(y)) {
+ mType |= TRANSLATE;
+ }
+
+ mat33& M(mMatrix);
+ M[0][0] = a; M[1][0] = b; M[2][0] = x;
+ M[0][1] = c; M[1][1] = d; M[2][1] = y;
+ M[0][2] = 0; M[1][2] = 0; M[2][2] = 1;
+}
+
+Transform::vec2 Transform::transform(const vec2& v) const {
+ vec2 r;
+ const mat33& M(mMatrix);
+ r[0] = M[0][0]*v[0] + M[1][0]*v[1] + M[2][0];
+ r[1] = M[0][1]*v[0] + M[1][1]*v[1] + M[2][1];
+ return r;
+}
+
+Transform::vec3 Transform::transform(const vec3& v) const {
+ vec3 r;
+ const mat33& M(mMatrix);
+ r[0] = M[0][0]*v[0] + M[1][0]*v[1] + M[2][0]*v[2];
+ r[1] = M[0][1]*v[0] + M[1][1]*v[1] + M[2][1]*v[2];
+ r[2] = M[0][2]*v[0] + M[1][2]*v[1] + M[2][2]*v[2];
+ return r;
+}
+
+void Transform::transform(fixed1616* point, int x, int y) const
+{
+ const float toFixed = 65536.0f;
+ const mat33& M(mMatrix);
+ vec2 v(x, y);
+ v = transform(v);
+ point[0] = v[0] * toFixed;
+ point[1] = v[1] * toFixed;
}
Rect Transform::makeBounds(int w, int h) const
{
- Rect r;
- SkRect d, s;
- s.set(0, 0, SkIntToScalar(w), SkIntToScalar(h));
- mTransform.mapRect(&d, s);
- r.left = SkScalarRound( d.fLeft );
- r.top = SkScalarRound( d.fTop );
- r.right = SkScalarRound( d.fRight );
- r.bottom = SkScalarRound( d.fBottom );
- return r;
+ return transform( Rect(w, h) );
}
Rect Transform::transform(const Rect& bounds) const
{
Rect r;
- SkRect d, s;
- s.set( SkIntToScalar( bounds.left ),
- SkIntToScalar( bounds.top ),
- SkIntToScalar( bounds.right ),
- SkIntToScalar( bounds.bottom ));
- mTransform.mapRect(&d, s);
- r.left = SkScalarRound( d.fLeft );
- r.top = SkScalarRound( d.fTop );
- r.right = SkScalarRound( d.fRight );
- r.bottom = SkScalarRound( d.fBottom );
+ vec2 lt( bounds.left, bounds.top );
+ vec2 rt( bounds.right, bounds.top );
+ vec2 lb( bounds.left, bounds.bottom );
+ vec2 rb( bounds.right, bounds.bottom );
+
+ lt = transform(lt);
+ rt = transform(rt);
+ lb = transform(lb);
+ rb = transform(rb);
+
+ r.left = floorf(min(lt[0], rt[0], lb[0], rb[0]) + 0.5f);
+ r.top = floorf(min(lt[1], rt[1], lb[1], rb[1]) + 0.5f);
+ r.right = floorf(max(lt[0], rt[0], lb[0], rb[0]) + 0.5f);
+ r.bottom = floorf(max(lt[1], rt[1], lb[1], rb[1]) + 0.5f);
+
return r;
}
Region Transform::transform(const Region& reg) const
{
Region out;
- if (UNLIKELY(transformed())) {
- if (LIKELY(preserveRects())) {
+ if (CC_UNLIKELY(transformed())) {
+ if (CC_LIKELY(preserveRects())) {
Region::const_iterator it = reg.begin();
Region::const_iterator const end = reg.end();
while (it != end) {
@@ -202,31 +279,108 @@
return out;
}
-int32_t Transform::getOrientation() const
+uint32_t Transform::type() const
{
- uint32_t flags = 0;
- if (UNLIKELY(transformed())) {
- SkScalar a = mTransform[SkMatrix::kMScaleX];
- SkScalar b = mTransform[SkMatrix::kMSkewX];
- SkScalar c = mTransform[SkMatrix::kMSkewY];
- SkScalar d = mTransform[SkMatrix::kMScaleY];
- if (b==0 && c==0 && a && d) {
- if (a<0) flags |= FLIP_H;
- if (d<0) flags |= FLIP_V;
- } else if (b && c && a==0 && d==0) {
- flags |= ROT_90;
- if (b>0) flags |= FLIP_H;
- if (c<0) flags |= FLIP_V;
+ if (mType & UNKNOWN_TYPE) {
+ // recompute what this transform is
+
+ const mat33& M(mMatrix);
+ const float a = M[0][0];
+ const float b = M[1][0];
+ const float c = M[0][1];
+ const float d = M[1][1];
+ const float x = M[2][0];
+ const float y = M[2][1];
+
+ bool scale = false;
+ uint32_t flags = ROT_0;
+ if (isZero(b) && isZero(c)) {
+ if (absEqual(a, d)) {
+ if (a<0) flags |= FLIP_H;
+ if (d<0) flags |= FLIP_V;
+ if (!absIsOne(a) || !absIsOne(d)) {
+ scale = true;
+ }
+ } else {
+ flags = ROT_INVALID;
+ }
+ } else if (isZero(a) && isZero(d)) {
+ if (absEqual(b, c)) {
+ flags |= ROT_90;
+ if (b>0) flags |= FLIP_H;
+ if (c<0) flags |= FLIP_V;
+ if (!absIsOne(b) || !absIsOne(c)) {
+ scale = true;
+ }
+ } else {
+ flags = ROT_INVALID;
+ }
} else {
- flags = 0x80000000;
+ flags = ROT_INVALID;
}
+
+ mType = flags << 8;
+ if (flags & ROT_INVALID) {
+ mType |= UNKNOWN;
+ } else {
+ if ((flags & ROT_90) || ((flags & ROT_180) == ROT_180))
+ mType |= ROTATE;
+ if (flags & FLIP_H)
+ mType ^= SCALE;
+ if (flags & FLIP_V)
+ mType ^= SCALE;
+ if (scale)
+ mType |= SCALE;
+ }
+
+ if (!isZero(x) || !isZero(y))
+ mType |= TRANSLATE;
}
- return flags;
+ return mType;
+}
+
+uint32_t Transform::getType() const {
+ return type() & 0xFF;
+}
+
+uint32_t Transform::getOrientation() const
+{
+ return (type() >> 8) & 0xFF;
}
bool Transform::preserveRects() const
{
- return mTransform.rectStaysRect();
+ return (type() & ROT_INVALID) ? false : true;
+}
+
+void Transform::dump(const char* name) const
+{
+ type(); // updates the type
+
+ String8 flags, type;
+ const mat33& m(mMatrix);
+ uint32_t orient = mType >> 8;
+
+ if (orient&ROT_INVALID)
+ flags.append("ROT_INVALID ");
+ if (orient&ROT_90)
+ flags.append("ROT_90 ");
+ if (orient&FLIP_V)
+ flags.append("FLIP_V ");
+ if (orient&FLIP_H)
+ flags.append("FLIP_H ");
+
+ if (mType&SCALE)
+ type.append("SCALE ");
+ if (mType&ROTATE)
+ type.append("ROTATE ");
+ if (mType&TRANSLATE)
+ type.append("TRANSLATE ");
+
+ LOGD("%s (%s, %s)", name, flags.string(), type.string());
+ LOGD("%.2f %.2f %.2f", m[0][0], m[1][0], m[2][0]);
+ LOGD("%.2f %.2f %.2f", m[0][1], m[1][1], m[2][1]);
+ LOGD("%.2f %.2f %.2f", m[0][2], m[1][2], m[2][2]);
}
// ---------------------------------------------------------------------------
diff --git a/libs/surfaceflinger/Transform.h b/libs/surfaceflinger/Transform.h
index ddab404..51d3e3f 100644
--- a/libs/surfaceflinger/Transform.h
+++ b/libs/surfaceflinger/Transform.h
@@ -23,10 +23,6 @@
#include <ui/Point.h>
#include <ui/Rect.h>
-#include <GLES/gl.h>
-
-#include <core/SkMatrix.h>
-
namespace android {
class Region;
@@ -38,9 +34,12 @@
public:
Transform();
Transform(const Transform& other);
- Transform(int32_t flags);
+ explicit Transform(uint32_t orientation);
~Transform();
+ typedef int32_t fixed1616;
+
+ // FIXME: must match OVERLAY_TRANSFORM_*, pull from hardware.h
enum orientation_flags {
ROT_0 = 0x00000000,
FLIP_H = 0x00000001,
@@ -48,48 +47,79 @@
ROT_90 = 0x00000004,
ROT_180 = FLIP_H|FLIP_V,
ROT_270 = ROT_180|ROT_90,
- ROT_INVALID = 0x80000000
+ ROT_INVALID = 0x80
};
enum type_mask {
IDENTITY = 0,
TRANSLATE = 0x1,
- SCALE = 0x2,
- AFFINE = 0x4,
- PERSPECTIVE = 0x8
+ ROTATE = 0x2,
+ SCALE = 0x4,
+ UNKNOWN = 0x8
};
- bool transformed() const;
- int32_t getOrientation() const;
- bool preserveRects() const;
-
+ // query the transform
+ bool transformed() const;
+ bool preserveRects() const;
+ uint32_t getType() const;
+ uint32_t getOrientation() const;
+
+ float const* operator [] (int i) const; // returns column i
int tx() const;
int ty() const;
-
+
+ // modify the transform
void reset();
- void set(float xx, float xy, float yx, float yy);
- void set(int tx, int ty);
- void set(float radian, float x, float y);
- void scale(float s, float x, float y);
-
+ void set(float tx, float ty);
+ void set(float a, float b, float c, float d);
+ void set(uint32_t flags, float w, float h);
+
+ // transform data
Rect makeBounds(int w, int h) const;
- void transform(GLfixed* point, int x, int y) const;
+ void transform(fixed1616* point, int x, int y) const;
Region transform(const Region& reg) const;
- Rect transform(const Rect& bounds) const;
-
Transform operator * (const Transform& rhs) const;
- float operator [] (int i) const;
-
- inline uint32_t getType() const { return type(); }
-
- inline Transform(bool) : mType(0xFF) { };
private:
- uint8_t type() const;
+ struct vec3 {
+ float v[3];
+ inline vec3() { }
+ inline vec3(float a, float b, float c) {
+ v[0] = a; v[1] = b; v[2] = c;
+ }
+ inline float operator [] (int i) const { return v[i]; }
+ inline float& operator [] (int i) { return v[i]; }
+ };
+ struct vec2 {
+ float v[2];
+ inline vec2() { }
+ inline vec2(float a, float b) {
+ v[0] = a; v[1] = b;
+ }
+ inline float operator [] (int i) const { return v[i]; }
+ inline float& operator [] (int i) { return v[i]; }
+ };
+ struct mat33 {
+ vec3 v[3];
+ inline const vec3& operator [] (int i) const { return v[i]; }
+ inline vec3& operator [] (int i) { return v[i]; }
+ };
-private:
- SkMatrix mTransform;
- mutable uint32_t mType;
+ enum { UNKNOWN_TYPE = 0x80000000 };
+
+ // assumes the last row is < 0 , 0 , 1 >
+ vec2 transform(const vec2& v) const;
+ vec3 transform(const vec3& v) const;
+ Rect transform(const Rect& bounds) const;
+ uint32_t type() const;
+ static bool absIsOne(float f);
+ static bool absEqual(float a, float b);
+ static bool isZero(float f);
+
+ void dump(const char* name) const;
+
+ mat33 mMatrix;
+ mutable uint32_t mType;
};
// ---------------------------------------------------------------------------
diff --git a/libs/surfaceflinger_client/ISurface.cpp b/libs/surfaceflinger_client/ISurface.cpp
index 9125146..bb86199 100644
--- a/libs/surfaceflinger_client/ISurface.cpp
+++ b/libs/surfaceflinger_client/ISurface.cpp
@@ -78,7 +78,8 @@
data.writeInt32(bufferIdx);
data.writeInt32(usage);
remote()->transact(REQUEST_BUFFER, data, &reply);
- sp<GraphicBuffer> buffer = new GraphicBuffer(reply);
+ sp<GraphicBuffer> buffer = new GraphicBuffer();
+ reply.read(*buffer);
return buffer;
}
@@ -141,7 +142,9 @@
int bufferIdx = data.readInt32();
int usage = data.readInt32();
sp<GraphicBuffer> buffer(requestBuffer(bufferIdx, usage));
- return GraphicBuffer::writeToParcel(reply, buffer.get());
+ if (buffer == NULL)
+ return BAD_VALUE;
+ return reply->write(*buffer);
}
case REGISTER_BUFFERS: {
CHECK_INTERFACE(ISurface, data, reply);
diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp
index 6a5c8a9..ba1fd9c 100644
--- a/libs/ui/GraphicBuffer.cpp
+++ b/libs/ui/GraphicBuffer.cpp
@@ -14,12 +14,12 @@
* limitations under the License.
*/
+#define LOG_TAG "GraphicBuffer"
+
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
-#include <binder/Parcel.h>
-
#include <utils/Errors.h>
#include <utils/Log.h>
@@ -77,34 +77,21 @@
handle = inHandle;
}
-GraphicBuffer::GraphicBuffer(const Parcel& data)
- : BASE(), mOwner(ownHandle), mBufferMapper(GraphicBufferMapper::get()),
- mInitCheck(NO_ERROR), mVStride(0), mIndex(-1)
-{
- // we own the handle in this case
- width = data.readInt32();
- if (width < 0) {
- width = height = stride = format = usage = 0;
- handle = 0;
- } else {
- height = data.readInt32();
- stride = data.readInt32();
- format = data.readInt32();
- usage = data.readInt32();
- handle = data.readNativeHandle();
- }
-}
-
GraphicBuffer::~GraphicBuffer()
{
if (handle) {
- if (mOwner == ownHandle) {
- native_handle_close(handle);
- native_handle_delete(const_cast<native_handle*>(handle));
- } else if (mOwner == ownData) {
- GraphicBufferAllocator& allocator(GraphicBufferAllocator::get());
- allocator.free(handle);
- }
+ free_handle();
+ }
+}
+
+void GraphicBuffer::free_handle()
+{
+ if (mOwner == ownHandle) {
+ native_handle_close(handle);
+ native_handle_delete(const_cast<native_handle*>(handle));
+ } else if (mOwner == ownData) {
+ GraphicBufferAllocator& allocator(GraphicBufferAllocator::get());
+ allocator.free(handle);
}
}
@@ -192,29 +179,83 @@
return res;
}
+size_t GraphicBuffer::getFlattenedSize() const {
+ return (8 + (handle ? handle->numInts : 0))*sizeof(int);
+}
-status_t GraphicBuffer::writeToParcel(Parcel* reply,
- android_native_buffer_t const* buffer)
+size_t GraphicBuffer::getFdCount() const {
+ return handle ? handle->numFds : 0;
+}
+
+status_t GraphicBuffer::flatten(void* buffer, size_t size,
+ int fds[], size_t count) const
{
- if (buffer == NULL)
- return BAD_VALUE;
+ size_t sizeNeeded = GraphicBuffer::getFlattenedSize();
+ if (size < sizeNeeded) return NO_MEMORY;
- if (buffer->width < 0 || buffer->height < 0)
- return BAD_VALUE;
+ size_t fdCountNeeded = GraphicBuffer::getFdCount();
+ if (count < fdCountNeeded) return NO_MEMORY;
- status_t err = NO_ERROR;
- if (buffer->handle == NULL) {
- // this buffer doesn't have a handle
- reply->writeInt32(NO_MEMORY);
- } else {
- reply->writeInt32(buffer->width);
- reply->writeInt32(buffer->height);
- reply->writeInt32(buffer->stride);
- reply->writeInt32(buffer->format);
- reply->writeInt32(buffer->usage);
- err = reply->writeNativeHandle(buffer->handle);
+ int* buf = static_cast<int*>(buffer);
+ buf[0] = 'GBFR';
+ buf[1] = width;
+ buf[2] = height;
+ buf[3] = stride;
+ buf[4] = format;
+ buf[5] = usage;
+ buf[6] = 0;
+ buf[7] = 0;
+
+ if (handle) {
+ buf[6] = handle->numFds;
+ buf[7] = handle->numInts;
+ native_handle_t const* const h = handle;
+ memcpy(fds, h->data, h->numFds*sizeof(int));
+ memcpy(&buf[8], h->data + h->numFds, h->numInts*sizeof(int));
}
- return err;
+
+ return NO_ERROR;
+}
+
+status_t GraphicBuffer::unflatten(void const* buffer, size_t size,
+ int fds[], size_t count)
+{
+ if (size < 8*sizeof(int)) return NO_MEMORY;
+
+ int const* buf = static_cast<int const*>(buffer);
+ if (buf[0] != 'GBFR') return BAD_TYPE;
+
+ const size_t numFds = buf[6];
+ const size_t numInts = buf[7];
+
+ const size_t sizeNeeded = (8 + numInts) * sizeof(int);
+ if (size < sizeNeeded) return NO_MEMORY;
+
+ size_t fdCountNeeded = 0;
+ if (count < fdCountNeeded) return NO_MEMORY;
+
+ if (handle) {
+ // free previous handle if any
+ free_handle();
+ }
+
+ if (numFds || numInts) {
+ width = buf[1];
+ height = buf[2];
+ stride = buf[3];
+ format = buf[4];
+ usage = buf[5];
+ native_handle* h = native_handle_create(numFds, numInts);
+ memcpy(h->data, fds, numFds*sizeof(int));
+ memcpy(h->data + numFds, &buf[8], numInts*sizeof(int));
+ handle = h;
+ } else {
+ width = height = stride = format = usage = 0;
+ handle = NULL;
+ }
+
+ mOwner = ownHandle;
+ return NO_ERROR;
}
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk
index d2cfd3b..d0eedb4 100644
--- a/libs/utils/Android.mk
+++ b/libs/utils/Android.mk
@@ -25,6 +25,7 @@
CallStack.cpp \
Debug.cpp \
FileMap.cpp \
+ Flattenable.cpp \
RefBase.cpp \
ResourceTypes.cpp \
SharedBuffer.cpp \
diff --git a/libs/utils/Flattenable.cpp b/libs/utils/Flattenable.cpp
new file mode 100644
index 0000000..1f2ffaa
--- /dev/null
+++ b/libs/utils/Flattenable.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+#include <utils/Flattenable.h>
+
+namespace android {
+
+Flattenable::~Flattenable() {
+}
+
+}; // namespace android
diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp
index e8bd5cf..38600b9 100644
--- a/libs/utils/ResourceTypes.cpp
+++ b/libs/utils/ResourceTypes.cpp
@@ -497,6 +497,34 @@
return NULL;
}
+const char* ResStringPool::string8At(size_t idx, size_t* outLen) const
+{
+ if (mError == NO_ERROR && idx < mHeader->stringCount) {
+ const bool isUTF8 = (mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0;
+ const uint32_t off = mEntries[idx]/(isUTF8?sizeof(char):sizeof(char16_t));
+ if (off < (mStringPoolSize-1)) {
+ if (isUTF8) {
+ const uint8_t* strings = (uint8_t*)mStrings;
+ const uint8_t* str = strings+off;
+ DECODE_LENGTH(str, sizeof(uint8_t), *outLen)
+ size_t encLen;
+ DECODE_LENGTH(str, sizeof(uint8_t), encLen)
+ if ((uint32_t)(str+encLen-strings) < mStringPoolSize) {
+ return (const char*)str;
+ } else {
+ LOGW("Bad string block: string #%d extends to %d, past end at %d\n",
+ (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize);
+ }
+ }
+ } else {
+ LOGW("Bad string block: string #%d entry is at %d, past end at %d\n",
+ (int)idx, (int)(off*sizeof(uint16_t)),
+ (int)(mStringPoolSize*sizeof(uint16_t)));
+ }
+ }
+ return NULL;
+}
+
const ResStringPool_span* ResStringPool::styleAt(const ResStringPool_ref& ref) const
{
return styleAt(ref.index);
@@ -4018,14 +4046,19 @@
printf("(attribute) 0x%08x\n", value.data);
} else if (value.dataType == Res_value::TYPE_STRING) {
size_t len;
- const char16_t* str = pkg->header->values.stringAt(
+ const char* str8 = pkg->header->values.string8At(
value.data, &len);
- if (str == NULL) {
- printf("(string) null\n");
+ if (str8 != NULL) {
+ printf("(string8) \"%s\"\n", str8);
} else {
- printf("(string%d) \"%s\"\n",
- pkg->header->values.isUTF8()?8:16,
- String8(str, len).string());
+ const char16_t* str16 = pkg->header->values.stringAt(
+ value.data, &len);
+ if (str16 != NULL) {
+ printf("(string16) \"%s\"\n",
+ String8(str16, len).string());
+ } else {
+ printf("(string) null\n");
+ }
}
} else if (value.dataType == Res_value::TYPE_FLOAT) {
printf("(float) %g\n", *(const float*)&value.data);
diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java
index 8e84106..90b50cc 100755
--- a/location/java/com/android/internal/location/GpsLocationProvider.java
+++ b/location/java/com/android/internal/location/GpsLocationProvider.java
@@ -321,7 +321,9 @@
public GpsLocationProvider(Context context, ILocationManager locationManager) {
mContext = context;
mLocationManager = locationManager;
- mNIHandler= new GpsNetInitiatedHandler(context, this);
+ mNIHandler = new GpsNetInitiatedHandler(context, this);
+
+ mLocation.setExtras(mLocationExtras);
// Create a wake lock
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -759,7 +761,7 @@
positionMode = GPS_POSITION_MODE_STANDALONE;
}
- if (!native_start(positionMode, false, mFixInterval)) {
+ if (!native_start(positionMode, false, 1)) {
mStarted = false;
Log.e(TAG, "native_start failed in startNavigating()");
return;
@@ -870,7 +872,12 @@
}
if (mStarted && mStatus != LocationProvider.AVAILABLE) {
- mAlarmManager.cancel(mTimeoutIntent);
+ // we still want to time out if we do not receive MIN_FIX_COUNT
+ // within the time out and we are requesting infrequent fixes
+ if (mFixInterval < NO_FIX_TIMEOUT) {
+ mAlarmManager.cancel(mTimeoutIntent);
+ }
+
// send an intent to notify that the GPS is receiving fixes.
Intent intent = new Intent(GPS_FIX_CHANGE_ACTION);
intent.putExtra(EXTRA_ENABLED, true);
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
index 2294069..9d1d420 100644
--- a/media/java/android/media/MediaFile.java
+++ b/media/java/android/media/MediaFile.java
@@ -17,13 +17,16 @@
package android.media;
import android.content.ContentValues;
-import android.os.SystemProperties;
import android.provider.MediaStore.Audio;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Video;
+import android.media.DecoderCapabilities;
+import android.media.DecoderCapabilities.VideoDecoder;
+import android.media.DecoderCapabilities.AudioDecoder;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
/**
* MediaScanner helper class.
@@ -98,13 +101,34 @@
sFileTypeMap.put(extension, new MediaFileType(fileType, mimeType));
sMimeTypeMap.put(mimeType, Integer.valueOf(fileType));
}
+
+ private static boolean isWMAEnabled() {
+ List<AudioDecoder> decoders = DecoderCapabilities.getAudioDecoders();
+ for (AudioDecoder decoder: decoders) {
+ if (decoder == AudioDecoder.AUDIO_DECODER_WMA) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isWMVEnabled() {
+ List<VideoDecoder> decoders = DecoderCapabilities.getVideoDecoders();
+ for (VideoDecoder decoder: decoders) {
+ if (decoder == VideoDecoder.VIDEO_DECODER_WMV) {
+ return true;
+ }
+ }
+ return false;
+ }
+
static {
addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg");
addFileType("M4A", FILE_TYPE_M4A, "audio/mp4");
addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav");
addFileType("AMR", FILE_TYPE_AMR, "audio/amr");
addFileType("AWB", FILE_TYPE_AWB, "audio/amr-wb");
- if (SystemProperties.getInt("ro.media.dec.aud.wma.enabled", 0) != 0) {
+ if (isWMAEnabled()) {
addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma");
}
addFileType("OGG", FILE_TYPE_OGG, "application/ogg");
@@ -127,7 +151,7 @@
addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp");
addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2");
addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2");
- if (SystemProperties.getInt("ro.media.dec.vid.wmv.enabled", 0) != 0) {
+ if (isWMVEnabled()) {
addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv");
addFileType("ASF", FILE_TYPE_ASF, "video/x-ms-asf");
}
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 34252ab..1f02608 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -500,22 +500,6 @@
doScanFile(path, mimeType, lastModified, fileSize, false);
}
- private boolean isMetadataSupported(int fileType) {
- if (mFileType == MediaFile.FILE_TYPE_MP3 ||
- mFileType == MediaFile.FILE_TYPE_MP4 ||
- mFileType == MediaFile.FILE_TYPE_M4A ||
- mFileType == MediaFile.FILE_TYPE_3GPP ||
- mFileType == MediaFile.FILE_TYPE_3GPP2 ||
- mFileType == MediaFile.FILE_TYPE_OGG ||
- mFileType == MediaFile.FILE_TYPE_AAC ||
- mFileType == MediaFile.FILE_TYPE_MID ||
- mFileType == MediaFile.FILE_TYPE_WMA) {
- // we only extract metadata from MP3, M4A, OGG, MID, AAC and WMA files.
- // check MP4 files, to determine if they contain only audio.
- return true;
- }
- return false;
- }
public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) {
Uri result = null;
// long t1 = System.currentTimeMillis();
@@ -531,10 +515,8 @@
boolean music = (lowpath.indexOf(MUSIC_DIR) > 0) ||
(!ringtones && !notifications && !alarms && !podcasts);
- if( isMetadataSupported(mFileType) ) {
+ if (!MediaFile.isImageFileType(mFileType)) {
processFile(path, mimeType, this);
- } else if (MediaFile.isImageFileType(mFileType)) {
- // we used to compute the width and height but it's not worth it
}
result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
diff --git a/media/jni/android_media_ResampleInputStream.cpp b/media/jni/android_media_ResampleInputStream.cpp
index f248557..d965d9a 100644
--- a/media/jni/android_media_ResampleInputStream.cpp
+++ b/media/jni/android_media_ResampleInputStream.cpp
@@ -92,7 +92,7 @@
jint jNpoints) {
// safety first!
- if (nFir21 + jNpoints > BUF_SIZE) {
+ if (nFir21 + jNpoints * 2 > BUF_SIZE) {
throwException(env, "java/lang/IllegalArgumentException",
"FIR+data too long %d", nFir21 + jNpoints);
return;
diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp
index c5dfbb5..3f9c6d6 100644
--- a/media/libmedia/AudioSystem.cpp
+++ b/media/libmedia/AudioSystem.cpp
@@ -709,7 +709,9 @@
bool AudioSystem::isLowVisibility(stream_type stream)
{
- if (stream == AudioSystem::SYSTEM || stream == AudioSystem::NOTIFICATION) {
+ if (stream == AudioSystem::SYSTEM ||
+ stream == AudioSystem::NOTIFICATION ||
+ stream == AudioSystem::RING) {
return true;
} else {
return false;
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index b4fc035..50dad33 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -1614,6 +1614,8 @@
size_t actualSize = (*me->mCallback)(
me, buffer->raw, buffer->size, me->mCallbackCookie);
+ buffer->size = actualSize;
+
if (actualSize > 0) {
me->snoopWrite(buffer->raw, actualSize);
}
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 071bb9e..2a65766 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -85,6 +85,8 @@
ifeq ($(TARGET_OS)-$(TARGET_SIMULATOR),linux-true)
LOCAL_LDLIBS += -lpthread -ldl
+ LOCAL_SHARED_LIBRARIES += libdvm
+ LOCAL_CPPFLAGS += -DANDROID_SIMULATOR
endif
ifneq ($(TARGET_SIMULATOR),true)
diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp
index 7997cd6..57f58be 100644
--- a/media/libstagefright/AudioPlayer.cpp
+++ b/media/libstagefright/AudioPlayer.cpp
@@ -38,6 +38,7 @@
mPositionTimeRealUs(-1),
mSeeking(false),
mReachedEOS(false),
+ mFinalStatus(OK),
mStarted(false),
mAudioSink(audioSink) {
}
@@ -168,6 +169,7 @@
mPositionTimeRealUs = -1;
mSeeking = false;
mReachedEOS = false;
+ mFinalStatus = OK;
mStarted = false;
}
@@ -181,8 +183,11 @@
return mSeeking;
}
-bool AudioPlayer::reachedEOS() {
+bool AudioPlayer::reachedEOS(status_t *finalStatus) {
+ *finalStatus = OK;
+
Mutex::Autolock autoLock(mLock);
+ *finalStatus = mFinalStatus;
return mReachedEOS;
}
@@ -245,6 +250,7 @@
if (err != OK) {
mReachedEOS = true;
+ mFinalStatus = err;
break;
}
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 59a5f9d..ab65b44 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -178,7 +178,8 @@
}
AwesomePlayer::AwesomePlayer()
- : mTimeSource(NULL),
+ : mQueueStarted(false),
+ mTimeSource(NULL),
mVideoRendererIsPreview(false),
mAudioPlayer(NULL),
mFlags(0),
@@ -201,13 +202,13 @@
mAudioStatusEventPending = false;
- mQueue.start();
-
reset();
}
AwesomePlayer::~AwesomePlayer() {
- mQueue.stop();
+ if (mQueueStarted) {
+ mQueue.stop();
+ }
reset();
@@ -433,16 +434,26 @@
}
mStreamDoneEventPending = false;
- if (mFlags & LOOPING) {
+ if (mStreamDoneStatus == ERROR_END_OF_STREAM && (mFlags & LOOPING)) {
seekTo_l(0);
if (mVideoSource != NULL) {
postVideoEvent_l();
}
} else {
- notifyListener_l(MEDIA_PLAYBACK_COMPLETE);
+ if (mStreamDoneStatus == ERROR_END_OF_STREAM) {
+ LOGV("MEDIA_PLAYBACK_COMPLETE");
+ notifyListener_l(MEDIA_PLAYBACK_COMPLETE);
+ } else {
+ LOGV("MEDIA_ERROR %d", mStreamDoneStatus);
+
+ notifyListener_l(
+ MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, mStreamDoneStatus);
+ }
pause_l();
+
+ mFlags |= AT_EOS;
}
}
@@ -517,6 +528,12 @@
postBufferingEvent_l();
+ if (mFlags & AT_EOS) {
+ // Legacy behaviour, if a stream finishes playing and then
+ // is started again, we play from the start...
+ seekTo_l(0);
+ }
+
return OK;
}
@@ -651,6 +668,7 @@
status_t AwesomePlayer::seekTo_l(int64_t timeUs) {
mSeeking = true;
mSeekTimeUs = timeUs;
+ mFlags &= ~AT_EOS;
seekAudioIfNecessary_l();
@@ -792,7 +810,7 @@
continue;
}
- postStreamDoneEvent_l();
+ postStreamDoneEvent_l(err);
return;
}
@@ -894,11 +912,13 @@
mQueue.postEventWithDelay(mVideoEvent, delayUs < 0 ? 10000 : delayUs);
}
-void AwesomePlayer::postStreamDoneEvent_l() {
+void AwesomePlayer::postStreamDoneEvent_l(status_t status) {
if (mStreamDoneEventPending) {
return;
}
mStreamDoneEventPending = true;
+
+ mStreamDoneStatus = status;
mQueue.postEvent(mStreamDoneEvent);
}
@@ -937,9 +957,10 @@
notifyListener_l(MEDIA_SEEK_COMPLETE);
}
- if (mWatchForAudioEOS && mAudioPlayer->reachedEOS()) {
+ status_t finalStatus;
+ if (mWatchForAudioEOS && mAudioPlayer->reachedEOS(&finalStatus)) {
mWatchForAudioEOS = false;
- postStreamDoneEvent_l();
+ postStreamDoneEvent_l(finalStatus);
}
postCheckAudioStatusEvent_l();
@@ -989,6 +1010,11 @@
return UNKNOWN_ERROR; // async prepare already pending
}
+ if (!mQueueStarted) {
+ mQueue.start();
+ mQueueStarted = true;
+ }
+
mFlags |= PREPARING;
mAsyncPrepareEvent = new AwesomeEvent(
this, &AwesomePlayer::onPrepareAsyncEvent);
@@ -1089,7 +1115,7 @@
state->mUriHeaders = mUriHeaders;
state->mFileSource = mFileSource;
- state->mFlags = mFlags & (PLAYING | LOOPING);
+ state->mFlags = mFlags & (PLAYING | LOOPING | AT_EOS);
getPosition_l(&state->mPositionUs);
if (mLastVideoBuffer) {
@@ -1150,7 +1176,7 @@
seekTo_l(state->mPositionUs);
- mFlags = state->mFlags & LOOPING;
+ mFlags = state->mFlags & (LOOPING | AT_EOS);
if (state->mLastVideoFrame && mISurface != NULL) {
mVideoRenderer =
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 16635d3..165ac09 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -463,7 +463,10 @@
return err;
}
}
- CHECK_EQ(*offset, stop_offset);
+
+ if (*offset != stop_offset) {
+ return ERROR_MALFORMED;
+ }
return OK;
}
@@ -496,6 +499,23 @@
}
}
+ if (chunk_type == FOURCC('t', 'r', 'a', 'k')) {
+ Track *track = new Track;
+ track->next = NULL;
+ if (mLastTrack) {
+ mLastTrack->next = track;
+ } else {
+ mFirstTrack = track;
+ }
+ mLastTrack = track;
+
+ track->meta = new MetaData;
+ track->includes_expensive_metadata = false;
+ track->timescale = 0;
+ track->sampleTable = new SampleTable(mDataSource);
+ track->meta->setCString(kKeyMIMEType, "application/octet-stream");
+ }
+
off_t stop_offset = *offset + chunk_size;
*offset = data_offset;
while (*offset < stop_offset) {
@@ -504,9 +524,18 @@
return err;
}
}
- CHECK_EQ(*offset, stop_offset);
- if (chunk_type == FOURCC('m', 'o', 'o', 'v')) {
+ if (*offset != stop_offset) {
+ return ERROR_MALFORMED;
+ }
+
+ if (chunk_type == FOURCC('t', 'r', 'a', 'k')) {
+ status_t err = verifyTrack(mLastTrack);
+
+ if (err != OK) {
+ return err;
+ }
+ } else if (chunk_type == FOURCC('m', 'o', 'o', 'v')) {
mHaveMetadata = true;
return UNKNOWN_ERROR; // Return a dummy error.
@@ -516,7 +545,9 @@
case FOURCC('t', 'k', 'h', 'd'):
{
- CHECK(chunk_data_size >= 4);
+ if (chunk_data_size < 4) {
+ return ERROR_MALFORMED;
+ }
uint8_t version;
if (mDataSource->readAt(data_offset, &version, 1) < 1) {
@@ -562,21 +593,6 @@
height = U32_AT(&buffer[80]);
}
- Track *track = new Track;
- track->next = NULL;
- if (mLastTrack) {
- mLastTrack->next = track;
- } else {
- mFirstTrack = track;
- }
- mLastTrack = track;
-
- track->meta = new MetaData;
- track->includes_expensive_metadata = false;
- track->timescale = 0;
- track->sampleTable = new SampleTable(mDataSource);
- track->meta->setCString(kKeyMIMEType, "application/octet-stream");
-
*offset += chunk_size;
break;
}
@@ -670,7 +686,10 @@
}
uint8_t buffer[8];
- CHECK(chunk_data_size >= (off_t)sizeof(buffer));
+ if (chunk_data_size < (off_t)sizeof(buffer)) {
+ return ERROR_MALFORMED;
+ }
+
if (mDataSource->readAt(
data_offset, buffer, 8) < 8) {
return ERROR_IO;
@@ -696,7 +715,10 @@
return err;
}
}
- CHECK_EQ(*offset, stop_offset);
+
+ if (*offset != stop_offset) {
+ return ERROR_MALFORMED;
+ }
break;
}
@@ -748,7 +770,10 @@
return err;
}
}
- CHECK_EQ(*offset, stop_offset);
+
+ if (*offset != stop_offset) {
+ return ERROR_MALFORMED;
+ }
break;
}
@@ -792,7 +817,10 @@
return err;
}
}
- CHECK_EQ(*offset, stop_offset);
+
+ if (*offset != stop_offset) {
+ return ERROR_MALFORMED;
+ }
break;
}
@@ -942,7 +970,10 @@
case FOURCC('m', 'e', 't', 'a'):
{
uint8_t buffer[4];
- CHECK(chunk_data_size >= (off_t)sizeof(buffer));
+ if (chunk_data_size < (off_t)sizeof(buffer)) {
+ return ERROR_MALFORMED;
+ }
+
if (mDataSource->readAt(
data_offset, buffer, 4) < 4) {
return ERROR_IO;
@@ -961,7 +992,10 @@
return err;
}
}
- CHECK_EQ(*offset, stop_offset);
+
+ if (*offset != stop_offset) {
+ return ERROR_MALFORMED;
+ }
break;
}
@@ -995,8 +1029,9 @@
int64_t creationTime;
if (header[0] == 1) {
creationTime = U64_AT(&header[4]);
+ } else if (header[0] != 0) {
+ return ERROR_MALFORMED;
} else {
- CHECK_EQ(header[0], 0);
creationTime = U32_AT(&header[4]);
}
@@ -1174,6 +1209,30 @@
track->meta, mDataSource, track->timescale, track->sampleTable);
}
+// static
+status_t MPEG4Extractor::verifyTrack(Track *track) {
+ const char *mime;
+ CHECK(track->meta->findCString(kKeyMIMEType, &mime));
+
+ uint32_t type;
+ const void *data;
+ size_t size;
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
+ if (!track->meta->findData(kKeyAVCC, &type, &data, &size)
+ || type != kTypeAVCC) {
+ return ERROR_MALFORMED;
+ }
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4)
+ || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) {
+ if (!track->meta->findData(kKeyESDS, &type, &data, &size)
+ || type != kTypeESDS) {
+ return ERROR_MALFORMED;
+ }
+ }
+
+ return OK;
+}
+
status_t MPEG4Extractor::updateAudioTrackInfoFromESDS_MPEG4Audio(
const void *esds_data, size_t esds_size) {
ESDS esds(esds_data, esds_size);
@@ -1391,6 +1450,14 @@
&sampleIndex, SampleTable::kSyncSample_Flag);
if (err != OK) {
+ if (err == ERROR_OUT_OF_RANGE) {
+ // An attempt to seek past the end of the stream would
+ // normally cause this ERROR_OUT_OF_RANGE error. Propagating
+ // this all the way to the MediaPlayer would cause abnormal
+ // termination. Legacy behaviour appears to be to behave as if
+ // we had seeked to the end of stream, ending normally.
+ err = ERROR_END_OF_STREAM;
+ }
return err;
}
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 6cf7cff..974413d2 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -298,6 +298,10 @@
quirks |= kRequiresAllocateBufferOnOutputPorts;
}
+ if (!strcmp(componentName, "OMX.TI.Video.Decoder")) {
+ quirks |= kInputBufferSizesAreBogus;
+ }
+
return quirks;
}
@@ -561,7 +565,8 @@
mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
CHECK_EQ(err, OK);
- if (def.nBufferSize < size) {
+ if ((portIndex == kPortIndexInput && (mQuirks & kInputBufferSizesAreBogus))
+ || (def.nBufferSize < size)) {
def.nBufferSize = size;
}
@@ -574,7 +579,12 @@
CHECK_EQ(err, OK);
// Make sure the setting actually stuck.
- CHECK(def.nBufferSize >= size);
+ if (portIndex == kPortIndexInput
+ && (mQuirks & kInputBufferSizesAreBogus)) {
+ CHECK_EQ(def.nBufferSize, size);
+ } else {
+ CHECK(def.nBufferSize >= size);
+ }
}
status_t OMXCodec::setVideoPortFormatType(
@@ -1923,6 +1933,7 @@
CODEC_LOGV("signalling end of input stream.");
flags |= OMX_BUFFERFLAG_EOS;
+ mFinalStatus = err;
mSignalledEOS = true;
} else {
mNoMoreOutputData = false;
@@ -2401,7 +2412,7 @@
}
if (mFilledBuffers.empty()) {
- return ERROR_END_OF_STREAM;
+ return mSignalledEOS ? mFinalStatus : ERROR_END_OF_STREAM;
}
if (mOutputPortSettingsHaveChanged) {
diff --git a/media/libstagefright/Prefetcher.cpp b/media/libstagefright/Prefetcher.cpp
index 76ca77b..9c73f4a 100644
--- a/media/libstagefright/Prefetcher.cpp
+++ b/media/libstagefright/Prefetcher.cpp
@@ -55,6 +55,7 @@
size_t mIndex;
bool mStarted;
bool mReachedEOS;
+ status_t mFinalStatus;
int64_t mSeekTimeUs;
int64_t mCacheDurationUs;
bool mPrefetcherStopped;
@@ -306,7 +307,7 @@
}
if (mCachedBuffers.empty()) {
- return ERROR_END_OF_STREAM;
+ return mReachedEOS ? mFinalStatus : ERROR_END_OF_STREAM;
}
*out = *mCachedBuffers.begin();
@@ -337,26 +338,23 @@
void PrefetchedSource::cacheMore() {
MediaSource::ReadOptions options;
- {
- Mutex::Autolock autoLock(mLock);
+ Mutex::Autolock autoLock(mLock);
- if (!mStarted) {
- return;
- }
+ if (!mStarted) {
+ return;
+ }
- if (mSeekTimeUs >= 0) {
- options.setSeekTo(mSeekTimeUs);
- mSeekTimeUs = -1;
- }
+ if (mSeekTimeUs >= 0) {
+ options.setSeekTo(mSeekTimeUs);
+ mSeekTimeUs = -1;
}
MediaBuffer *buffer;
status_t err = mSource->read(&buffer, &options);
- Mutex::Autolock autoLock(mLock);
-
if (err != OK) {
mReachedEOS = true;
+ mFinalStatus = err;
mCondition.signal();
return;
diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp
index d079e70..6307bc5 100644
--- a/media/libstagefright/TimedEventQueue.cpp
+++ b/media/libstagefright/TimedEventQueue.cpp
@@ -28,6 +28,10 @@
#include <media/stagefright/MediaDebug.h>
+#ifdef ANDROID_SIMULATOR
+#include <jni.h>
+#endif
+
namespace android {
TimedEventQueue::TimedEventQueue()
@@ -183,8 +187,26 @@
// static
void *TimedEventQueue::ThreadWrapper(void *me) {
+
+#ifdef ANDROID_SIMULATOR
+ // The simulator runs everything as one process, so any
+ // Binder calls happen on this thread instead of a thread
+ // in another process. We therefore need to make sure that
+ // this thread can do calls into interpreted code.
+ // On the device this is not an issue because the remote
+ // thread will already be set up correctly for this.
+ JavaVM *vm;
+ int numvms;
+ JNI_GetCreatedJavaVMs(&vm, 1, &numvms);
+ JNIEnv *env;
+ vm->AttachCurrentThread(&env, NULL);
+#endif
+
static_cast<TimedEventQueue *>(me)->threadEntry();
+#ifdef ANDROID_SIMULATOR
+ vm->DetachCurrentThread();
+#endif
return NULL;
}
diff --git a/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp b/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp
index f1f7194..6d6e408 100644
--- a/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp
+++ b/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp
@@ -160,14 +160,25 @@
mConfig->outputFrameSize = buffer->size() / sizeof(int16_t);
mConfig->pOutputBuffer = static_cast<int16_t *>(buffer->data());
- if (pvmp3_framedecoder(mConfig, mDecoderBuf) != NO_DECODING_ERROR) {
- buffer->release();
- buffer = NULL;
+ ERROR_CODE decoderErr;
+ if ((decoderErr = pvmp3_framedecoder(mConfig, mDecoderBuf))
+ != NO_DECODING_ERROR) {
+ LOGV("mp3 decoder returned error %d", decoderErr);
- mInputBuffer->release();
- mInputBuffer = NULL;
+ if (decoderErr != NO_ENOUGH_MAIN_DATA_ERROR) {
+ buffer->release();
+ buffer = NULL;
- return UNKNOWN_ERROR;
+ mInputBuffer->release();
+ mInputBuffer = NULL;
+
+ return UNKNOWN_ERROR;
+ }
+
+ // This is recoverable, just ignore the current frame and
+ // play silence instead.
+ memset(buffer->data(), 0, mConfig->outputFrameSize);
+ mConfig->inputBufferUsedLength = mInputBuffer->range_length();
}
buffer->set_range(
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 114d4c6..3590987 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -92,12 +92,14 @@
FIRST_FRAME = 4,
PREPARING = 8,
PREPARED = 16,
+ AT_EOS = 32,
};
mutable Mutex mLock;
OMXClient mClient;
TimedEventQueue mQueue;
+ bool mQueueStarted;
wp<MediaPlayerBase> mListener;
sp<ISurface> mISurface;
@@ -143,10 +145,11 @@
Condition mPreparedCondition;
bool mIsAsyncPrepare;
status_t mPrepareResult;
+ status_t mStreamDoneStatus;
void postVideoEvent_l(int64_t delayUs = -1);
void postBufferingEvent_l();
- void postStreamDoneEvent_l();
+ void postStreamDoneEvent_l(status_t status);
void postCheckAudioStatusEvent_l();
status_t getPosition_l(int64_t *positionUs);
status_t play_l();
diff --git a/media/libstagefright/include/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h
index 3a63e88..9d35e0c 100644
--- a/media/libstagefright/include/MPEG4Extractor.h
+++ b/media/libstagefright/include/MPEG4Extractor.h
@@ -68,6 +68,8 @@
status_t updateAudioTrackInfoFromESDS_MPEG4Audio(
const void *esds_data, size_t esds_size);
+ static status_t verifyTrack(Track *track);
+
MPEG4Extractor(const MPEG4Extractor &);
MPEG4Extractor &operator=(const MPEG4Extractor &);
};
diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp
index 9ca060d..ff8757d 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -401,6 +401,33 @@
////////////////////////////////////////////////////////////////////////////////
+struct SharedVideoRenderer : public VideoRenderer {
+ SharedVideoRenderer(void *libHandle, VideoRenderer *obj)
+ : mLibHandle(libHandle),
+ mObj(obj) {
+ }
+
+ virtual ~SharedVideoRenderer() {
+ delete mObj;
+ mObj = NULL;
+
+ dlclose(mLibHandle);
+ mLibHandle = NULL;
+ }
+
+ virtual void render(
+ const void *data, size_t size, void *platformPrivate) {
+ return mObj->render(data, size, platformPrivate);
+ }
+
+private:
+ void *mLibHandle;
+ VideoRenderer *mObj;
+
+ SharedVideoRenderer(const SharedVideoRenderer &);
+ SharedVideoRenderer &operator=(const SharedVideoRenderer &);
+};
+
sp<IOMXRenderer> OMX::createRenderer(
const sp<ISurface> &surface,
const char *componentName,
@@ -411,11 +438,7 @@
VideoRenderer *impl = NULL;
- static void *libHandle = NULL;
-
- if (!libHandle) {
- libHandle = dlopen("libstagefrighthw.so", RTLD_NOW);
- }
+ void *libHandle = dlopen("libstagefrighthw.so", RTLD_NOW);
if (libHandle) {
typedef VideoRenderer *(*CreateRendererFunc)(
@@ -434,6 +457,16 @@
if (func) {
impl = (*func)(surface, componentName, colorFormat,
displayWidth, displayHeight, encodedWidth, encodedHeight);
+
+ if (impl) {
+ impl = new SharedVideoRenderer(libHandle, impl);
+ libHandle = NULL;
+ }
+ }
+
+ if (libHandle) {
+ dlclose(libHandle);
+ libHandle = NULL;
}
}
diff --git a/media/sdutils/Android.mk b/media/sdutils/Android.mk
deleted file mode 100644
index 74e1eca..0000000
--- a/media/sdutils/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-ifeq ($(TARGET_ARCH),arm)
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- sdutil.cpp \
-
-LOCAL_SHARED_LIBRARIES := libhardware_legacy libbinder libcutils libutils libc
-
-LOCAL_MODULE:= sdutil
-LOCAL_MODULE_TAGS := debug
-
-include $(BUILD_EXECUTABLE)
-
-endif
diff --git a/media/sdutils/sdutil.cpp b/media/sdutils/sdutil.cpp
deleted file mode 100644
index 6d3e87c..0000000
--- a/media/sdutils/sdutil.cpp
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2007 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.
- */
-
-#include <hardware_legacy/IMountService.h>
-#include <binder/BpBinder.h>
-#include <binder/IServiceManager.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <time.h>
-
-namespace android {
-
-static sp<IMountService> gMountService;
-
-static void init() {
- sp<IServiceManager> sm = defaultServiceManager();
- sp<IBinder> binder = sm->getService(String16("mount"));
- gMountService = interface_cast<IMountService>(binder);
- if (gMountService == 0) {
- fprintf(stderr, "could not get MountService\n");
- exit(1);
- }
-}
-
-static int mount(const char* path) {
- String16 string(path);
- return gMountService->mountVolume(string);
-}
-
-static int share(const char *path, const char *method) {
- String16 sPath(path);
- String16 sMethod(method);
- return gMountService->shareVolume(sPath, sMethod);
-}
-
-static int unshare(const char *path, const char *method) {
- String16 sPath(path);
- String16 sMethod(method);
- return gMountService->unshareVolume(sPath, sMethod);
-}
-
-static bool shared(const char *path, const char *method) {
- String16 sPath(path);
- String16 sMethod(method);
- return gMountService->getVolumeShared(sPath, sMethod);
-}
-
-static int asec_create(const char *id, int sizeMb, const char *fstype,
- const char *key, int ownerUid) {
- String16 sId(id);
- String16 sFstype(fstype);
- String16 sKey(key);
-
- return gMountService->createSecureContainer(
- sId, sizeMb, sFstype, sKey, ownerUid);
-}
-
-static int asec_finalize(const char *id) {
- String16 sId(id);
- return gMountService->finalizeSecureContainer(sId);
-}
-
-static int asec_destroy(const char *id) {
- String16 sId(id);
- return gMountService->destroySecureContainer(sId);
-}
-
-static int asec_mount(const char *id, const char *key, int ownerUid) {
- String16 sId(id);
- String16 sKey(key);
- return gMountService->mountSecureContainer(sId, sKey, ownerUid);
-}
-
-static int asec_unmount(const char *id) {
- String16 sId(id);
- return gMountService->unmountSecureContainer(sId);
-}
-
-static int asec_rename(const char *oldId, const char *newId) {
- String16 sOldId(oldId);
- String16 sNewId(newId);
- return gMountService->renameSecureContainer(sOldId, sNewId);
-}
-
-static int asec_path(const char *id) {
- String16 sId(id);
- gMountService->getSecureContainerPath(sId);
- return 0;
-}
-
-static int unmount(const char* path) {
- String16 string(path);
- return gMountService->unmountVolume(string);
-}
-
-static int format(const char* path) {
- String16 string(path);
- return gMountService->formatVolume(string);
-}
-
-};
-
-static void usage(void);
-
-int main(int argc, char **argv)
-{
- if (argc < 2)
- usage();
-
- android::init();
- int rc = 0;
-
- if (strcmp(argv[1], "mount") == 0) {
- rc = android::mount(argv[2]);
- } else if (strcmp(argv[1], "format") == 0) {
- rc = android::format(argv[2]);
- } else if (strcmp(argv[1], "unmount") == 0) {
- rc = android::unmount(argv[2]);
- } else if (strcmp(argv[1], "share") == 0) {
- if (argc != 3)
- usage();
- rc = android::share(argv[2], argv[3]);
- } else if (strcmp(argv[1], "unshare") == 0) {
- if (argc != 3)
- usage();
- rc = android::unshare(argv[2], argv[3]);
- } else if (strcmp(argv[1], "shared") == 0) {
- if (argc != 3)
- usage();
- fprintf(stdout, "%s\n", (android::shared(argv[2], argv[3]) ? "true" : "false"));
- } else if (!strcmp(argv[1], "asec")) {
- if (argc < 3)
- usage();
-
- if (!strcmp(argv[2], "create")) {
-
- if (argc != 8)
- usage();
- rc = android::asec_create(argv[3], atoi(argv[4]), argv[5], argv[6], atoi(argv[7]));
- } else if (!strcmp(argv[3], "finalize")) {
- rc = android::asec_finalize(argv[3]);
- } else if (!strcmp(argv[3], "destroy")) {
- return android::asec_destroy(argv[3]);
- } else if (!strcmp(argv[3], "mount")) {
- if (argc != 6)
- usage();
- rc = android::asec_mount(argv[3], argv[4], atoi(argv[5]));
- } else if (!strcmp(argv[3], "rename")) {
- if (argc != 5)
- usage();
- rc = android::asec_rename(argv[3], argv[4]);
- } else if (!strcmp(argv[3], "unmount")) {
- rc = android::asec_unmount(argv[3]);
- } else if (!strcmp(argv[3], "path")) {
- rc = android::asec_path(argv[3]);
- }
- }
-
- fprintf(stdout, "Operation completed with code %d\n", rc);
- return rc;
-}
-
-static void usage()
-{
- fprintf(stderr, "usage:\n"
- " sdutil mount <mount path> - mounts the SD card at the given mount point\n"
- " sdutil unmount <mount path> - unmounts the SD card at the given mount point\n"
- " sdutil format <mount path> - formats the SD card at the given mount point\n"
- " sdutil share <path> <method> - shares a volume\n"
- " sdutil unshare <path> <method> - unshares a volume\n"
- " sdutil shared <path> <method> - Queries volume share state\n"
- " sdutil asec create <id> <sizeMb> <fstype> <key> <ownerUid>\n"
- " sdutil asec finalize <id>\n"
- " 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"
- );
- exit(1);
-}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
index 6087268..0e883e6 100755
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
@@ -48,7 +48,7 @@
public static final long PAUSE_WAIT_TIME = 3000;
public static final long WAIT_TIME = 2000;
- public static final long WAIT_LONG = 4000;
+ public static final long WAIT_SNAPSHOT_TIME = 5000;
//Streaming Video
public static final String VIDEO_HTTP3GP = "http://pvs.pv.com/jj/lipsync0.3gp";
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java
index 717f7ba..d7cf069 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java
@@ -1,28 +1,35 @@
package com.android.mediaframeworktest;
import android.media.MediaRecorder;
+import android.media.EncoderCapabilities;
+import android.media.EncoderCapabilities.VideoEncoderCap;
+import android.media.EncoderCapabilities.AudioEncoderCap;
+import android.media.DecoderCapabilities;
+import android.media.DecoderCapabilities.VideoDecoder;
+import android.media.DecoderCapabilities.AudioDecoder;
+
import android.os.SystemProperties;
+import java.util.List;
import java.util.HashMap;
-public class MediaProfileReader {
+public class MediaProfileReader
+{
+ private static final List<VideoDecoder> videoDecoders = DecoderCapabilities.getVideoDecoders();
+ private static final List<AudioDecoder> audioDecoders = DecoderCapabilities.getAudioDecoders();
+ private static final List<VideoEncoderCap> videoEncoders = EncoderCapabilities.getVideoEncoders();
+ private static final List<AudioEncoderCap> audioEncoders = EncoderCapabilities.getAudioEncoders();
+ private static final HashMap<Integer, String> encoderMap = new HashMap<Integer, String>();
- public static final HashMap<String, Integer>
- OUTPUT_FORMAT_TABLE = new HashMap<String, Integer>();
- public static String MEDIA_ENC_VID = "ro.media.enc.vid.";
- public static String MEDIA_AUD_VID = "ro.media.enc.aud.";
- public static String[] VIDEO_ENCODER_PROPERTY = {".width", ".height", ".bps", ".fps",};
- public static String[] AUDIO_ENCODER_PROPERTY = {".bps", ".hz", ".ch",};
+ static {
+ initEncoderMap();
+ };
- public static String getVideoCodecProperty() {
- String s;
- s = SystemProperties.get("ro.media.enc.vid.codec");
- return s;
+ public static List<VideoEncoderCap> getVideoEncoders() {
+ return videoEncoders;
}
- public static String getAudioCodecProperty() {
- String s;
- s = SystemProperties.get("ro.media.enc.aud.codec");
- return s;
+ public static List<AudioEncoderCap> getAudioEncoders() {
+ return audioEncoders;
}
public static String getDeviceType() {
@@ -33,78 +40,56 @@
}
public static boolean getWMAEnable() {
- // push all the property into one big table
- int wmaEnable = 1;
- wmaEnable = SystemProperties.getInt("ro.media.dec.aud.wma.enabled",
- wmaEnable);
- if (wmaEnable == 1) {
- return true;
- } else {
- return false;
+ for (AudioDecoder decoder: audioDecoders) {
+ if (decoder == AudioDecoder.AUDIO_DECODER_WMA) {
+ return true;
+ }
}
+ return false;
}
public static boolean getWMVEnable(){
- int wmvEnable = 1;
- wmvEnable = SystemProperties.getInt("ro.media.dec.vid.wmv.enabled",
- wmvEnable);
- if (wmvEnable == 1) {
- return true;
- } else {
- return false;
- }
- }
-
- public static void createVideoProfileTable() {
- // push all the property into one big table
- String encoderType = getVideoCodecProperty();
- if (encoderType.length() != 0) {
- String encoder[] = encoderType.split(",");
- for (int i = 0; i < encoder.length; i++) {
- for (int j = 0; j < VIDEO_ENCODER_PROPERTY.length; j++) {
- String propertyName = MEDIA_ENC_VID + encoder[i] + VIDEO_ENCODER_PROPERTY[j];
- String prop = SystemProperties.get(propertyName);
- // push to the table
- String propRange[] = prop.split(",");
- OUTPUT_FORMAT_TABLE.put((encoder[i] + VIDEO_ENCODER_PROPERTY[j] + "_low"),
- Integer.parseInt(propRange[0]));
- OUTPUT_FORMAT_TABLE.put((encoder[i] + VIDEO_ENCODER_PROPERTY[j] + "_high"),
- Integer.parseInt(propRange[1]));
- }
-
+ for (VideoDecoder decoder: videoDecoders) {
+ if (decoder == VideoDecoder.VIDEO_DECODER_WMV) {
+ return true;
}
}
+ return false;
}
- public static void createAudioProfileTable() {
- // push all the property into one big table
- String audioType = getAudioCodecProperty();
- String encoder[] = audioType.split(",");
- if (audioType.length() != 0) {
- for (int i = 0; i < encoder.length; i++) {
- for (int j = 0; j < AUDIO_ENCODER_PROPERTY.length; j++) {
- String propertyName = MEDIA_AUD_VID + encoder[i] + AUDIO_ENCODER_PROPERTY[j];
- String prop = SystemProperties.get(propertyName);
- // push to the table
- String propRange[] = prop.split(",");
- OUTPUT_FORMAT_TABLE.put((encoder[i] + AUDIO_ENCODER_PROPERTY[j] + "_low"),
- Integer.parseInt(propRange[0]));
- OUTPUT_FORMAT_TABLE.put((encoder[i] + AUDIO_ENCODER_PROPERTY[j] + "_high"),
- Integer.parseInt(propRange[1]));
- }
- }
+ public static String getVideoCodecName(int videoEncoder) {
+ if (videoEncoder != MediaRecorder.VideoEncoder.H263 &&
+ videoEncoder != MediaRecorder.VideoEncoder.H264 &&
+ videoEncoder != MediaRecorder.VideoEncoder.MPEG_4_SP) {
+ throw new IllegalArgumentException("Unsupported video encoder " + videoEncoder);
}
+ return encoderMap.get(videoEncoder);
}
- public static void createEncoderTable(){
- OUTPUT_FORMAT_TABLE.put("h263", MediaRecorder.VideoEncoder.H263);
- OUTPUT_FORMAT_TABLE.put("h264", MediaRecorder.VideoEncoder.H264);
- OUTPUT_FORMAT_TABLE.put("m4v", MediaRecorder.VideoEncoder.MPEG_4_SP);
- OUTPUT_FORMAT_TABLE.put("amrnb", MediaRecorder.AudioEncoder.AMR_NB);
- OUTPUT_FORMAT_TABLE.put("amrwb", MediaRecorder.AudioEncoder.AMR_WB);
- OUTPUT_FORMAT_TABLE.put("aac", MediaRecorder.AudioEncoder.AAC);
- OUTPUT_FORMAT_TABLE.put("aacplus", MediaRecorder.AudioEncoder.AAC_PLUS);
- OUTPUT_FORMAT_TABLE.put("eaacplus",
- MediaRecorder.AudioEncoder.EAAC_PLUS);
+ public static String getAudioCodecName(int audioEncoder) {
+ if (audioEncoder != MediaRecorder.AudioEncoder.AMR_NB &&
+ audioEncoder != MediaRecorder.AudioEncoder.AMR_WB &&
+ audioEncoder != MediaRecorder.AudioEncoder.AAC &&
+ audioEncoder != MediaRecorder.AudioEncoder.AAC_PLUS &&
+ audioEncoder != MediaRecorder.AudioEncoder.EAAC_PLUS) {
+ throw new IllegalArgumentException("Unsupported audio encodeer " + audioEncoder);
+ }
+ return encoderMap.get(audioEncoder);
+ }
+
+ private MediaProfileReader() {} // Don't call me
+
+ private static void initEncoderMap() {
+ // video encoders
+ encoderMap.put(MediaRecorder.VideoEncoder.H263, "h263");
+ encoderMap.put(MediaRecorder.VideoEncoder.H264, "h264");
+ encoderMap.put(MediaRecorder.VideoEncoder.MPEG_4_SP, "m4v");
+
+ // audio encoders
+ encoderMap.put(MediaRecorder.AudioEncoder.AMR_NB, "amrnb");
+ encoderMap.put(MediaRecorder.AudioEncoder.AMR_WB, "amrwb");
+ encoderMap.put(MediaRecorder.AudioEncoder.AAC, "aac");
+ encoderMap.put(MediaRecorder.AudioEncoder.AAC_PLUS, "aacplus");
+ encoderMap.put(MediaRecorder.AudioEncoder.EAAC_PLUS, "eaacplus");
}
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
index fa0986a..784db97 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
@@ -192,7 +192,7 @@
}
mCamera.setPreviewCallback(null);
mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
- Thread.sleep(MediaNames.WAIT_LONG);
+ Thread.sleep(MediaNames.WAIT_SNAPSHOT_TIME);
} catch (Exception e) {
Log.v(TAG, e.toString());
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java
index fdc5970..39caccd 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java
@@ -25,6 +25,9 @@
import android.hardware.Camera;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
+import android.media.EncoderCapabilities;
+import android.media.EncoderCapabilities.VideoEncoderCap;
+import android.media.EncoderCapabilities.AudioEncoderCap;
import android.test.ActivityInstrumentationTestCase;
import android.util.Log;
import android.view.SurfaceHolder;
@@ -33,6 +36,7 @@
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.Suppress;
+import java.util.List;
/**
@@ -99,31 +103,29 @@
}
}
- private boolean recordVideoWithPara(String encoder, String audio, String quality){
+ private boolean recordVideoWithPara(VideoEncoderCap videoCap, AudioEncoderCap audioCap, boolean highQuality){
boolean recordSuccess = false;
- int videoEncoder = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(encoder);
- int audioEncoder = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(audio);
- int videoWidth = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(encoder + ".width_" + quality);
- int videoHeight =
- MediaProfileReader.OUTPUT_FORMAT_TABLE.get(encoder + ".height_" + quality);
- int videoFps = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(encoder + ".fps_" + quality);
- int videoBitrate = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(encoder + ".bps_" + quality);
- int audioBitrate = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(audio + ".bps_" + quality);
- int audioChannels = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(audio + ".ch_" + quality);
- int audioSamplingRate =
- MediaProfileReader.OUTPUT_FORMAT_TABLE.get(audio + ".hz_" + quality);
+ int videoEncoder = videoCap.mCodec;
+ int audioEncoder = audioCap.mCodec;
+ int videoWidth = highQuality? videoCap.mMaxFrameWidth: videoCap.mMinFrameWidth;
+ int videoHeight = highQuality? videoCap.mMaxFrameHeight: videoCap.mMinFrameHeight;
+ int videoFps = highQuality? videoCap.mMaxFrameRate: videoCap.mMinFrameRate;
+ int videoBitrate = highQuality? videoCap.mMaxBitRate: videoCap.mMinBitRate;
+ int audioBitrate = highQuality? audioCap.mMaxBitRate: audioCap.mMinBitRate;
+ int audioChannels = highQuality? audioCap.mMaxChannels: audioCap.mMinChannels ;
+ int audioSamplingRate = highQuality? audioCap.mMaxSampleRate: audioCap.mMinSampleRate;
if (videoFps < MIN_VIDEO_FPS) {
videoFps = MIN_VIDEO_FPS;
}
mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
- String filename = ("/sdcard/" + encoder + "_" + audio + "_" + quality + ".3gp");
+ String filename = ("/sdcard/" + videoEncoder + "_" + audioEncoder + "_" + highQuality + ".3gp");
try {
Log.v(TAG, "video encoder :" + videoEncoder);
Log.v(TAG, "audio encoder :" + audioEncoder);
- Log.v(TAG, "quality : " + quality);
- Log.v(TAG, "encoder : " + encoder);
- Log.v(TAG, "audio : " + audio);
+ Log.v(TAG, "quality : " + (highQuality?"high": "low"));
+ Log.v(TAG, "encoder : " + MediaProfileReader.getVideoCodecName(videoEncoder));
+ Log.v(TAG, "audio : " + MediaProfileReader.getAudioCodecName(audioEncoder));
Log.v(TAG, "videoWidth : " + videoWidth);
Log.v(TAG, "videoHeight : " + videoHeight);
Log.v(TAG, "videoFPS : " + videoFps);
@@ -434,35 +436,26 @@
boolean recordSuccess = false;
String deviceType = MediaProfileReader.getDeviceType();
Log.v(TAG, "deviceType = " + deviceType);
- // Test cases are device specified
- MediaProfileReader.createVideoProfileTable();
- MediaProfileReader.createAudioProfileTable();
- MediaProfileReader.createEncoderTable();
- String encoderType = MediaProfileReader.getVideoCodecProperty();
- String audioType = MediaProfileReader.getAudioCodecProperty();
- if ((encoderType.length() != 0) || (audioType.length() != 0)) {
- String audio[] = audioType.split(",");
- String encoder[] = encoderType.split(",");
- for (int k = 0; k < 2; k++) {
- for (int i = 0; i < encoder.length; i++) {
- for (int j = 0; j < audio.length; j++) {
- if (k == 0) {
- recordSuccess = recordVideoWithPara(encoder[i], audio[j], "high");
- } else {
- recordSuccess = recordVideoWithPara(encoder[i], audio[j], "low");
- }
- if (!recordSuccess) {
- Log.v(TAG, "testDeviceSpecificCodec failed");
- Log.v(TAG, "Encoder = " + encoder[i] + "Audio Encoder = " + audio[j]);
- noOfFailure++;
- }
- // assertTrue((encoder[i] + audio[j]), recordSuccess);
+ List<VideoEncoderCap> videoEncoders = MediaProfileReader.getVideoEncoders();
+ List<AudioEncoderCap> audioEncoders = MediaProfileReader.getAudioEncoders();
+ for (int k = 0; k < 2; k++) {
+ for (VideoEncoderCap videoEncoder: videoEncoders) {
+ for (AudioEncoderCap audioEncoder: audioEncoders) {
+ if (k == 0) {
+ recordSuccess = recordVideoWithPara(videoEncoder, audioEncoder, true);
+ } else {
+ recordSuccess = recordVideoWithPara(videoEncoder, audioEncoder, false);
+ }
+ if (!recordSuccess) {
+ Log.v(TAG, "testDeviceSpecificCodec failed");
+ Log.v(TAG, "Encoder = " + videoEncoder.mCodec + "Audio Encoder = " + audioEncoder.mCodec);
+ noOfFailure++;
}
}
}
- if (noOfFailure != 0) {
- assertTrue("testDeviceSpecificCodec", false);
- }
+ }
+ if (noOfFailure != 0) {
+ assertTrue("testDeviceSpecificCodec", false);
}
}
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
index 8750098..4c259f1 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
@@ -39,9 +39,8 @@
public static void testAlbumArt() throws Exception {
Log.v(TAG, "testAlbumArt starts.");
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
- MediaProfileReader reader = new MediaProfileReader();
- boolean supportWMA = reader.getWMAEnable();
- boolean supportWMV = reader.getWMVEnable();
+ boolean supportWMA = MediaProfileReader.getWMAEnable();
+ boolean supportWMV = MediaProfileReader.getWMVEnable();
retriever.setMode(MediaMetadataRetriever.MODE_GET_METADATA_ONLY);
for (int i = 0, n = MediaNames.ALBUMART_TEST_FILES.length; i < n; ++i) {
try {
@@ -74,9 +73,8 @@
@LargeTest
public static void testThumbnailCapture() throws Exception {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
- MediaProfileReader reader = new MediaProfileReader();
- boolean supportWMA = reader.getWMAEnable();
- boolean supportWMV = reader.getWMVEnable();
+ boolean supportWMA = MediaProfileReader.getWMAEnable();
+ boolean supportWMV = MediaProfileReader.getWMVEnable();
Log.v(TAG, "Thumbnail processing starts");
long startedAt = System.currentTimeMillis();
for(int i = 0, n = MediaNames.THUMBNAIL_CAPTURE_TEST_FILES.length; i < n; ++i) {
@@ -110,9 +108,8 @@
@LargeTest
public static void testMetadataRetrieval() throws Exception {
- MediaProfileReader reader = new MediaProfileReader();
- boolean supportWMA = reader.getWMAEnable();
- boolean supportWMV = reader.getWMVEnable();
+ boolean supportWMA = MediaProfileReader.getWMAEnable();
+ boolean supportWMV = MediaProfileReader.getWMVEnable();
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setMode(MediaMetadataRetriever.MODE_GET_METADATA_ONLY);
for(int i = 0, n = MediaNames.METADATA_RETRIEVAL_TEST_FILES.length; i < n; ++i) {
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 933a7e5..a79f0cd 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -39,7 +39,7 @@
*/
public class DefaultContainerService extends IntentService {
private static final String TAG = "DefContainer";
- private static final boolean localLOGV = false;
+ private static final boolean localLOGV = true;
private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
/*
@@ -102,7 +102,11 @@
File sourceFile = new File(archiveFilePath);
DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
- PackageParser.Package pkg = packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0);
+ PackageParser.Package pkg = packageParser.parsePackage(sourceFile,
+ archiveFilePath, metrics, 0);
+ // Nuke the parser reference right away and force a gc
+ Runtime.getRuntime().gc();
+ packageParser = null;
if (pkg == null) {
Log.w(TAG, "Failed to parse package");
return PackageHelper.RECOMMEND_FAILED_INVALID_APK;
@@ -115,7 +119,7 @@
return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
} else {
// Implies install on internal storage.
- return 0;
+ return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
}
}
};
@@ -207,6 +211,8 @@
if (PackageHelper.isContainerMounted(newCid)) {
if (localLOGV) Log.i(TAG, "Unmounting " + newCid +
" at path " + newCachePath + " after " + errMsg);
+ // Force a gc to avoid being killed.
+ Runtime.getRuntime().gc();
PackageHelper.unMountSdDir(newCid);
} else {
if (localLOGV) Log.i(TAG, "Container " + newCid + " not mounted");
@@ -324,8 +330,8 @@
boolean auto = true;
// To make final copy
long reqInstallSize = pkgLen;
- // For dex files
- long reqInternalSize = 1 * pkgLen;
+ // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now.
+ long reqInternalSize = 0;
boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD);
boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalFlashSize);
boolean fitsOnSd = (reqInstallSize < availSDSize) && intThresholdOk &&
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 654ca32..1e9c312 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -60,14 +60,14 @@
<!-- user interface sound effects -->
<integer name="def_power_sounds_enabled">1</integer>
- <string name="def_low_battery_sound">/system/media/ui/LowBattery.ogg</string>
- <integer name="def_dock_sounds_enabled">1</integer>
- <string name="def_desk_dock_sound">/system/media/audio/ui/dock.ogg</string>
- <string name="def_desk_undock_sound">/system/media/audio/ui/undock.ogg</string>
- <string name="def_car_dock_sound">/system/media/audio/ui/Dock.ogg</string>
- <string name="def_car_undock_sound">/system/media/audio/ui/Undock.ogg</string>
- <integer name="def_lockscreen_sounds_enabled">1</integer>
- <string name="def_lock_sound">/system/media/audio/ui/Lock.ogg</string>
- <string name="def_unlock_sound">/system/media/audio/ui/Unlock.ogg</string>
+ <string name="def_low_battery_sound" translatable="false">/system/media/ui/LowBattery.ogg</string>
+ <integer name="def_dock_sounds_enabled">0</integer>
+ <string name="def_desk_dock_sound" translatable="false">/system/media/audio/ui/Dock.ogg</string>
+ <string name="def_desk_undock_sound" translatable="false">/system/media/audio/ui/Undock.ogg</string>
+ <string name="def_car_dock_sound" translatable="false">/system/media/audio/ui/Dock.ogg</string>
+ <string name="def_car_undock_sound" translatable="false">/system/media/audio/ui/Undock.ogg</string>
+ <integer name="def_lockscreen_sounds_enabled">0</integer>
+ <string name="def_lock_sound" translatable="false">/system/media/audio/ui/Lock.ogg</string>
+ <string name="def_unlock_sound" translatable="false">/system/media/audio/ui/Unlock.ogg</string>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index cf34d4e..8036e52 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -76,7 +76,7 @@
// database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
// is properly propagated through your change. Not doing so will result in a loss of user
// settings.
- private static final int DATABASE_VERSION = 50;
+ private static final int DATABASE_VERSION = 51;
private Context mContext;
@@ -618,18 +618,9 @@
if (upgradeVersion == 48) {
/*
- * Adding a new setting for which voice recognition service to use.
+ * Default recognition service no longer initialized here,
+ * moved to RecognitionManagerService.
*/
- db.beginTransaction();
- try {
- SQLiteStatement stmt = db.compileStatement("INSERT OR IGNORE INTO secure(name,value)"
- + " VALUES(?,?);");
- loadVoiceRecognitionServiceSetting(stmt);
- stmt.close();
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
upgradeVersion = 49;
}
@@ -651,6 +642,25 @@
upgradeVersion = 50;
}
+ if (upgradeVersion == 50) {
+ /*
+ * New settings for set install location UI.
+ */
+ db.beginTransaction();
+ try {
+ SQLiteStatement stmt = db.compileStatement("INSERT INTO system(name,value)"
+ + " VALUES(?,?);");
+ loadBooleanSetting(stmt, Settings.System.SET_INSTALL_LOCATION,
+ R.bool.set_install_location);
+ stmt.close();
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ upgradeVersion = 51;
+ }
+
if (upgradeVersion != currentVersion) {
Log.w(TAG, "Got stuck trying to upgrade from version " + upgradeVersion
+ ", must wipe the settings provider");
@@ -1028,8 +1038,6 @@
loadBooleanSetting(stmt, Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED,
R.bool.def_mount_ums_notify_enabled);
- loadVoiceRecognitionServiceSetting(stmt);
-
stmt.close();
}
@@ -1041,32 +1049,6 @@
R.string.def_backup_transport);
}
- /**
- * Introduced in database version 49.
- */
- private void loadVoiceRecognitionServiceSetting(SQLiteStatement stmt) {
- String selectedService = null;
- List<ResolveInfo> availableRecognitionServices =
- mContext.getPackageManager().queryIntentServices(
- new Intent(RecognitionService.SERVICE_INTERFACE), 0);
- int numAvailable = availableRecognitionServices.size();
-
- if (numAvailable == 0) {
- Log.w(TAG, "no available voice recognition services found");
- } else {
- if (numAvailable > 1) {
- Log.w(TAG, "more than one voice recognition service found, picking first");
- }
-
- ServiceInfo serviceInfo = availableRecognitionServices.get(0).serviceInfo;
- selectedService =
- new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToString();
- }
-
- loadSetting(stmt, Settings.Secure.VOICE_RECOGNITION_SERVICE,
- selectedService == null ? "" : selectedService);
- }
-
private void loadSetting(SQLiteStatement stmt, String key, Object value) {
stmt.bindString(1, key);
stmt.bindString(2, value.toString());
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 4080a6a..db802d3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -20,7 +20,6 @@
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
-import java.util.Random;
import android.backup.BackupManager;
import android.content.ContentProvider;
@@ -30,14 +29,11 @@
import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
-import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
-import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.DrmStore;
import android.provider.MediaStore;
diff --git a/preloaded-classes b/preloaded-classes
index 092b539c..aaae440 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -1,850 +1,1040 @@
# Classes which are preloaded by com.android.internal.os.ZygoteInit.
+# Automatically generated by frameworks/base/tools/preload/WritePreloadedClassFile.java.
+# MIN_LOAD_TIME_MICROS=1250
android.R$styleable
+android.accounts.AccountManager
+android.accounts.AccountManager$4
+android.accounts.AccountManager$6
+android.accounts.AccountManager$AmsTask
+android.accounts.AccountManager$BaseFutureTask
+android.accounts.AccountManager$Future2Task
+android.accounts.AuthenticatorDescription
+android.accounts.IAccountAuthenticatorResponse$Stub
+android.accounts.IAccountManager$Stub
+android.accounts.IAccountManagerResponse$Stub
android.app.Activity
android.app.ActivityGroup
-android.app.ActivityManager$MemoryInfo$1
+android.app.ActivityManager$RunningAppProcessInfo
+android.app.ActivityManager$RunningServiceInfo
android.app.ActivityManagerNative
android.app.ActivityManagerProxy
android.app.ActivityThread
-android.app.ActivityThread$ActivityRecord
-android.app.ActivityThread$AppBindData
android.app.ActivityThread$ApplicationThread
-android.app.ActivityThread$ContextCleanupInfo
-android.app.ActivityThread$GcIdler
android.app.ActivityThread$H
-android.app.ActivityThread$Idler
-android.app.ActivityThread$PackageInfo
-android.app.ActivityThread$PackageInfo$ReceiverDispatcher
-android.app.ActivityThread$PackageInfo$ReceiverDispatcher$InnerReceiver
-android.app.ActivityThread$PackageInfo$ServiceDispatcher
-android.app.ActivityThread$PackageInfo$ServiceDispatcher$InnerConnection
-android.app.ActivityThread$ProviderRecord
-android.app.ActivityThread$ProviderRefCount
android.app.AlertDialog
-android.app.Application
-android.app.ApplicationLoaders
android.app.ApplicationThreadNative
android.app.ContextImpl
-android.app.ContextImpl$ApplicationContentResolver
android.app.ContextImpl$ApplicationPackageManager
-android.app.ContextImpl$ApplicationPackageManager$PackageRemovedReceiver
-android.app.ContextImpl$ApplicationPackageManager$ResourceName
-android.app.ContextImpl$SharedPreferencesImpl
+android.app.DatePickerDialog
android.app.Dialog
android.app.ExpandableListActivity
android.app.IActivityManager
-android.app.IActivityManager$ContentProviderHolder$1
+android.app.IActivityManager$ContentProviderHolder
android.app.IAlarmManager$Stub
-android.app.IAlarmManager$Stub$Proxy
-android.app.IApplicationThread
-android.app.INotificationManager$Stub
-android.app.INotificationManager$Stub$Proxy
-android.app.ISearchManager
-android.app.ISearchManager$Stub
-android.app.ISearchManager$Stub$Proxy
+android.app.IDevicePolicyManager$Stub
+android.app.IStatusBar$Stub
+android.app.ITransientNotification$Stub
android.app.Instrumentation
-android.app.IntentReceiverLeaked
+android.app.IntentService
android.app.ListActivity
-android.app.ListActivity$1
-android.app.ListActivity$2
android.app.LocalActivityManager
android.app.Notification
-android.app.NotificationManager
android.app.PendingIntent
-android.app.PendingIntent$1
android.app.ProgressDialog
-android.app.ReceiverRestrictedContext
android.app.ResultInfo
-android.app.ResultInfo$1
android.app.SearchDialog
android.app.SearchDialog$SearchAutoComplete
+android.app.SearchDialog$SearchBar
+android.app.SearchableInfo
android.app.Service
-android.app.ServiceConnectionLeaked
+android.app.SuggestionsAdapter
+android.app.SuperNotCalledException
android.app.TabActivity
-android.content.BroadcastReceiver
-android.content.ComponentCallbacks
+android.app.TimePickerDialog
+android.appwidget.AppWidgetHost
+android.appwidget.AppWidgetHostView
+android.appwidget.AppWidgetHostView$ParcelableSparseArray
+android.appwidget.AppWidgetManager
+android.appwidget.AppWidgetProvider
+android.appwidget.AppWidgetProviderInfo
+android.backup.BackupDataInput
+android.backup.BackupDataInput$EntityHeader
+android.backup.BackupDataOutput
+android.backup.BackupHelperAgent
+android.backup.BackupHelperDispatcher
+android.backup.BackupHelperDispatcher$Header
+android.backup.FileBackupHelperBase
+android.backup.IBackupManager$Stub
+android.backup.RestoreSet
+android.bluetooth.BluetoothAdapter
+android.bluetooth.BluetoothAudioGateway
+android.bluetooth.BluetoothSocket
+android.bluetooth.BluetoothUuid
+android.bluetooth.HeadsetBase
+android.bluetooth.IBluetooth
+android.bluetooth.IBluetooth$Stub
+android.bluetooth.IBluetoothA2dp
+android.bluetooth.IBluetoothA2dp$Stub
+android.bluetooth.IBluetoothHeadset$Stub
+android.bluetooth.ScoSocket
android.content.ComponentName
-android.content.ComponentName$1
android.content.ContentProvider$Transport
-android.content.ContentProviderProxy
-android.content.ContentQueryMap
-android.content.ContentQueryMap$1
+android.content.ContentProviderOperation
+android.content.ContentProviderResult
android.content.ContentResolver
-android.content.ContentResolver$CursorWrapperInner
android.content.ContentValues
android.content.Context
android.content.ContextWrapper
-android.content.DialogInterface
-android.content.DialogInterface$OnCancelListener
-android.content.DialogInterface$OnDismissListener
-android.content.IContentProvider
-android.content.IContentService
android.content.IContentService$Stub
+android.content.ISyncContext$Stub
android.content.Intent
-android.content.Intent$1
android.content.IntentFilter
+android.content.IntentSender
android.content.SearchRecentSuggestionsProvider
android.content.SyncResult
android.content.SyncStats
android.content.UriMatcher
android.content.pm.ActivityInfo
-android.content.pm.ActivityInfo$1
android.content.pm.ApplicationInfo
-android.content.pm.ApplicationInfo$1
-android.content.pm.ComponentInfo
-android.content.pm.IPackageManager
+android.content.pm.ConfigurationInfo
+android.content.pm.IPackageDataObserver$Stub
android.content.pm.IPackageManager$Stub
android.content.pm.IPackageManager$Stub$Proxy
+android.content.pm.IPackageStatsObserver$Stub
android.content.pm.InstrumentationInfo
-android.content.pm.InstrumentationInfo$1
-android.content.pm.PackageItemInfo
+android.content.pm.PackageInfo
android.content.pm.PackageManager
android.content.pm.PackageManager$NameNotFoundException
+android.content.pm.PackageStats
android.content.pm.PermissionInfo
android.content.pm.ProviderInfo
-android.content.pm.ProviderInfo$1
-android.content.pm.ResolveInfo$1
-android.content.pm.ServiceInfo$1
+android.content.pm.ResolveInfo
+android.content.pm.ResolveInfo$DisplayNameComparator
+android.content.pm.Signature
+android.content.res.AssetFileDescriptor
+android.content.res.AssetFileDescriptor$1
android.content.res.AssetManager
android.content.res.AssetManager$AssetInputStream
android.content.res.ColorStateList
android.content.res.ColorStateList$1
+android.content.res.CompatibilityInfo
+android.content.res.CompatibilityInfo$1
android.content.res.Configuration
+android.content.res.Configuration$1
android.content.res.Resources
-android.content.res.Resources$Theme
+android.content.res.Resources$1
android.content.res.StringBlock
android.content.res.TypedArray
android.content.res.XmlBlock
android.content.res.XmlBlock$Parser
+android.content.res.XmlResourceParser
android.database.AbstractCursor
-android.database.AbstractCursor$SelfContentObserver
android.database.AbstractWindowedCursor
-android.database.BulkCursorNative
-android.database.BulkCursorProxy
android.database.BulkCursorToCursorAdaptor
-android.database.ContentObservable
-android.database.ContentObserver$Transport
-android.database.Cursor
+android.database.CharArrayBuffer
android.database.CursorToBulkCursorAdaptor
-android.database.CursorToBulkCursorAdaptor$ContentObserverProxy
android.database.CursorWindow
+android.database.CursorWindow$1
android.database.CursorWrapper
-android.database.DataSetObservable
-android.database.IContentObserver$Stub$Proxy
-android.database.MergeCursor
+android.database.MatrixCursor
+android.database.sqlite.SQLiteClosable
+android.database.sqlite.SQLiteCompiledSql
+android.database.sqlite.SQLiteContentHelper
android.database.sqlite.SQLiteCursor
android.database.sqlite.SQLiteDatabase
-android.database.sqlite.SQLiteDatabase$CursorFactory
-android.database.sqlite.SQLiteDirectCursorDriver
+android.database.sqlite.SQLiteDebug
+android.database.sqlite.SQLiteDebug$PagerStats
+android.database.sqlite.SQLiteProgram
android.database.sqlite.SQLiteQuery
+android.database.sqlite.SQLiteQueryBuilder
android.database.sqlite.SQLiteStatement
-android.ddm.DdmHandleAppName
+android.database.sqlite.SqliteWrapper
android.ddm.DdmHandleExit
android.ddm.DdmHandleHeap
android.ddm.DdmHandleHello
android.ddm.DdmHandleNativeHeap
+android.ddm.DdmHandleProfiling
android.ddm.DdmHandleThread
android.ddm.DdmRegister
+android.debug.JNITest
+android.emoji.EmojiFactory
+android.graphics.AvoidXfermode
android.graphics.Bitmap
+android.graphics.Bitmap$1
+android.graphics.Bitmap$CompressFormat
+android.graphics.Bitmap$Config
+android.graphics.BitmapFactory
+android.graphics.BitmapFactory$Options
android.graphics.BitmapShader
+android.graphics.BlurMaskFilter
+android.graphics.Camera
android.graphics.Canvas
-android.graphics.Canvas$EdgeType
+android.graphics.Canvas$VertexMode
android.graphics.Color
+android.graphics.ColorFilter
+android.graphics.ColorMatrixColorFilter
+android.graphics.ComposePathEffect
+android.graphics.ComposeShader
+android.graphics.CornerPathEffect
+android.graphics.DashPathEffect
+android.graphics.DiscretePathEffect
+android.graphics.DrawFilter
+android.graphics.EmbossMaskFilter
android.graphics.Interpolator
+android.graphics.LayerRasterizer
+android.graphics.LightingColorFilter
android.graphics.LinearGradient
+android.graphics.MaskFilter
android.graphics.Matrix
-android.graphics.Matrix$ScaleToFit
+android.graphics.Movie
android.graphics.NinePatch
android.graphics.Paint
+android.graphics.Paint$Align
+android.graphics.Paint$Cap
+android.graphics.Paint$FontMetrics
+android.graphics.Paint$FontMetricsInt
+android.graphics.Paint$Join
+android.graphics.Paint$Style
android.graphics.PaintFlagsDrawFilter
android.graphics.Path
-android.graphics.Path$Direction
+android.graphics.Path$FillType
+android.graphics.PathDashPathEffect
+android.graphics.PathEffect
+android.graphics.PathMeasure
android.graphics.Picture
-android.graphics.PorterDuff
+android.graphics.PixelFormat
+android.graphics.PixelXorXfermode
+android.graphics.Point
+android.graphics.PointF
android.graphics.PorterDuff$Mode
+android.graphics.PorterDuffColorFilter
android.graphics.PorterDuffXfermode
+android.graphics.RadialGradient
+android.graphics.Rasterizer
android.graphics.Rect
+android.graphics.Rect$1
android.graphics.RectF
+android.graphics.RectF$1
android.graphics.Region
+android.graphics.Region$1
android.graphics.Region$Op
+android.graphics.RegionIterator
android.graphics.Shader
android.graphics.Shader$TileMode
+android.graphics.SumPathEffect
+android.graphics.SweepGradient
+android.graphics.TableMaskFilter
android.graphics.Typeface
android.graphics.Xfermode
+android.graphics.YuvImage
+android.graphics.drawable.Animatable
+android.graphics.drawable.AnimatedRotateDrawable
+android.graphics.drawable.AnimatedRotateDrawable$AnimatedRotateState
android.graphics.drawable.AnimationDrawable
+android.graphics.drawable.AnimationDrawable$AnimationState
android.graphics.drawable.BitmapDrawable
android.graphics.drawable.BitmapDrawable$BitmapState
+android.graphics.drawable.ClipDrawable
+android.graphics.drawable.ClipDrawable$ClipState
android.graphics.drawable.ColorDrawable
android.graphics.drawable.ColorDrawable$ColorState
android.graphics.drawable.Drawable
+android.graphics.drawable.Drawable$Callback
+android.graphics.drawable.Drawable$ConstantState
android.graphics.drawable.DrawableContainer
+android.graphics.drawable.DrawableContainer$DrawableContainerState
android.graphics.drawable.GradientDrawable
+android.graphics.drawable.GradientDrawable$GradientState
+android.graphics.drawable.GradientDrawable$Orientation
android.graphics.drawable.LayerDrawable
android.graphics.drawable.LayerDrawable$ChildDrawable
android.graphics.drawable.LayerDrawable$LayerState
android.graphics.drawable.NinePatchDrawable
android.graphics.drawable.NinePatchDrawable$NinePatchState
-android.graphics.drawable.PaintDrawable
-android.graphics.drawable.RotateDrawable
-android.graphics.drawable.RotateDrawable$RotateState
-android.graphics.drawable.ScaleDrawable
-android.graphics.drawable.ScaleDrawable$ScaleState
android.graphics.drawable.ShapeDrawable
-android.graphics.drawable.ShapeDrawable$ShapeState
android.graphics.drawable.StateListDrawable
android.graphics.drawable.StateListDrawable$StateListState
android.graphics.drawable.TransitionDrawable
android.graphics.drawable.TransitionDrawable$TransitionState
-android.graphics.drawable.shapes.RoundRectShape
+android.graphics.utils.BoundaryPatch
+android.hardware.Camera
+android.hardware.Camera$Parameters
+android.hardware.GeomagneticField
android.hardware.SensorManager
-android.inputmethodservice.KeyboardView
+android.location.Address
+android.location.Criteria
+android.location.GeocoderParams
+android.location.IGpsStatusListener$Stub
android.location.ILocationManager$Stub
+android.location.ILocationManager$Stub$Proxy
android.location.Location
+android.location.LocationManager
+android.location.LocationProviderInterface
+android.media.AudioFormat
android.media.AudioManager
+android.media.AudioRecord
+android.media.AudioSystem
+android.media.AudioTrack
+android.media.ExifInterface
android.media.IAudioService$Stub
-android.media.IAudioService$Stub$Proxy
+android.media.JetPlayer
+android.media.MediaFile
+android.media.MediaMetadataRetriever
+android.media.MediaPlayer
+android.media.MediaScanner
+android.media.Metadata
+android.media.MiniThumbFile
+android.media.ThumbnailUtils
+android.media.ToneGenerator
+android.net.ConnectivityManager
+android.net.Credentials
+android.net.DhcpInfo
+android.net.DhcpInfo$1
+android.net.Downloads
+android.net.Downloads$ByUri
+android.net.IConnectivityManager$Stub
+android.net.LocalServerSocket
android.net.LocalSocket
-android.net.LocalSocketAddress
-android.net.LocalSocketAddress$Namespace
android.net.LocalSocketImpl
android.net.LocalSocketImpl$SocketInputStream
android.net.LocalSocketImpl$SocketOutputStream
android.net.NetworkInfo
android.net.NetworkInfo$DetailedState
+android.net.NetworkUtils
android.net.SSLCertificateSocketFactory
android.net.TrafficStats
android.net.Uri
-android.net.Uri$1
-android.net.Uri$AbstractHierarchicalUri
-android.net.Uri$AbstractPart
android.net.Uri$HierarchicalUri
android.net.Uri$OpaqueUri
android.net.Uri$Part
-android.net.Uri$Part$EmptyPart
-android.net.Uri$PathPart
-android.net.Uri$PathSegments
-android.net.Uri$StringUri
android.net.WebAddress
-android.net.http.CertificateChainValidator
+android.net.http.AndroidHttpClientConnection
android.net.http.EventHandler
+android.net.http.Headers
android.net.http.HttpsConnection
+android.net.http.Request
android.net.http.RequestQueue
+android.net.http.SslCertificate
android.net.http.SslError
android.net.wifi.IWifiManager$Stub
+android.net.wifi.ScanResult
android.net.wifi.SupplicantState
android.net.wifi.WifiConfiguration
android.net.wifi.WifiInfo
-android.opengl.Material
+android.net.wifi.WifiManager
+android.net.wifi.WifiNative
+android.opengl.ETC1
+android.opengl.GLES10
+android.opengl.GLES10Ext
+android.opengl.GLES11
+android.opengl.GLES11Ext
+android.opengl.GLES20
+android.opengl.GLSurfaceView
+android.opengl.GLSurfaceView$ComponentSizeChooser
+android.opengl.GLUtils
+android.opengl.Matrix
+android.opengl.Visibility
android.os.Binder
android.os.BinderProxy
android.os.Build
+android.os.Build$VERSION
android.os.Bundle
-android.os.Bundle$1
+android.os.Debug
+android.os.Debug$MemoryInfo
+android.os.Debug$MemoryInfo$1
+android.os.DropBoxManager$Entry
android.os.Environment
+android.os.FileObserver$ObserverThread
android.os.FileUtils
+android.os.FileUtils$FileStatus
android.os.Handler
-android.os.HandlerThread
android.os.IBinder
+android.os.IInterface
android.os.IPowerManager$Stub
-android.os.IPowerManager$Stub$Proxy
-android.os.IServiceManager
-android.os.IVibratorService$Stub
-android.os.IVibratorService$Stub$Proxy
android.os.Looper
+android.os.MemoryFile
android.os.Message
-android.os.Message$1
-android.os.MessageQueue
-android.os.MessageQueue$IdleHandler
android.os.Parcel
-android.os.PatternMatcher
-android.os.PatternMatcher$1
-android.os.PowerManager
-android.os.PowerManager$WakeLock
-android.os.PowerManager$WakeLock$1
+android.os.Parcel$1
+android.os.ParcelFileDescriptor
+android.os.ParcelFileDescriptor$1
+android.os.ParcelUuid
+android.os.Parcelable
+android.os.Parcelable$Creator
+android.os.Power
android.os.Process
-android.os.ServiceManager
-android.os.ServiceManagerNative
-android.os.ServiceManagerProxy
-android.os.Vibrator
-android.preference.CheckBoxPreference
+android.os.RecoverySystem
+android.os.ResultReceiver
+android.os.StatFs
+android.os.SystemClock
+android.os.SystemProperties
+android.os.UEventObserver
+android.os.storage.IMountService$Stub
+android.os.storage.IMountService$Stub$Proxy
+android.pim.EventRecurrence
+android.pim.RecurrenceSet
+android.preference.CheckBoxPreference$SavedState
android.preference.DialogPreference
-android.preference.EditTextPreference
android.preference.ListPreference
android.preference.Preference
android.preference.PreferenceActivity
android.preference.PreferenceGroup
android.preference.PreferenceGroupAdapter
+android.preference.PreferenceInflater
android.preference.PreferenceManager
android.preference.PreferenceScreen
android.preference.RingtonePreference
-android.sax.RootElement
+android.preference.VolumePreference
+android.preference.VolumePreference$SeekBarVolumizer
+android.provider.Browser
+android.provider.Calendar
+android.provider.Calendar$Attendees
+android.provider.Calendar$CalendarAlerts
+android.provider.Calendar$Calendars
+android.provider.Calendar$EventDays
+android.provider.Calendar$Events
+android.provider.Calendar$Reminders
+android.provider.Contacts
+android.provider.Contacts$ContactMethods
+android.provider.ContactsContract
+android.provider.ContactsContract$CommonDataKinds$Email
+android.provider.ContactsContract$CommonDataKinds$Phone
+android.provider.ContactsContract$CommonDataKinds$StructuredPostal
+android.provider.ContactsContract$Contacts
+android.provider.ContactsContract$Data
+android.provider.ContactsContract$DataColumnsWithJoins
+android.provider.ContactsContract$PhoneLookup
+android.provider.ContactsContract$RawContacts
+android.provider.ContactsContract$RawContacts$EntityIteratorImpl
+android.provider.ContactsContract$RawContactsEntity
+android.provider.Downloads
+android.provider.Downloads$Impl
+android.provider.MediaStore
+android.provider.MediaStore$Audio$Artists
+android.provider.MediaStore$Audio$Media
+android.provider.MediaStore$Images$Media
+android.provider.MediaStore$Images$Thumbnails
+android.provider.MediaStore$Video$Media
+android.provider.SearchRecentSuggestions
+android.provider.Settings$Secure
+android.provider.Settings$System
+android.provider.UserDictionary$Words
+android.security.KeyStore
+android.security.Md5MessageDigest
+android.security.MessageDigest
+android.security.Sha1MessageDigest
+android.server.BluetoothA2dpService
+android.server.BluetoothEventLoop
+android.server.BluetoothService
+android.speech.tts.ITts$Stub
+android.speech.tts.ITts$Stub$Proxy
+android.speech.tts.ITtsCallback$Stub
+android.speech.tts.TextToSpeech
android.telephony.PhoneNumberUtils
-android.telephony.PhoneStateListener
android.telephony.ServiceState
-android.telephony.TelephonyManager
-android.telephony.SmsManager
+android.telephony.SignalStrength
android.telephony.SmsMessage
-android.text.AutoText
+android.telephony.SmsMessage$MessageClass
+android.telephony.TelephonyManager
+android.text.AndroidCharacter
android.text.BoringLayout
-android.text.BoringLayout$Metrics
android.text.DynamicLayout
-android.text.DynamicLayout$ChangeWatcher
-android.text.Editable
-android.text.Editable$Factory
-android.text.GetChars
-android.text.GraphicsOperations
android.text.Html$HtmlParser
-android.text.InputFilter
+android.text.HtmlToSpannedConverter
android.text.Layout
-android.text.Layout$Alignment
-android.text.Layout$Directions
-android.text.Layout$Ellipsizer
-android.text.NoCopySpan
-android.text.NoCopySpan$Concrete
-android.text.PackedIntVector
-android.text.PackedObjectVector
-android.text.ParcelableSpan
android.text.Selection
-android.text.Selection$END
-android.text.Selection$START
-android.text.SpanWatcher
-android.text.Spannable
-android.text.Spannable$Factory
-android.text.SpannableString
android.text.SpannableStringBuilder
-android.text.SpannableStringInternal
-android.text.Spanned
android.text.SpannedString
-android.text.StaticLayout
-android.text.Styled
-android.text.TextPaint
android.text.TextUtils
-android.text.TextUtils$1
-android.text.TextUtils$EllipsizeCallback
-android.text.TextUtils$SimpleStringSplitter
-android.text.TextUtils$TruncateAt
-android.text.TextWatcher
android.text.format.DateUtils
+android.text.format.Formatter
android.text.format.Time
android.text.method.ArrowKeyMovementMethod
android.text.method.BaseKeyListener
-android.text.method.KeyListener
+android.text.method.DigitsKeyListener
+android.text.method.LinkMovementMethod
android.text.method.MetaKeyKeyListener
-android.text.method.MovementMethod
android.text.method.QwertyKeyListener
-android.text.method.ReplacementTransformationMethod
android.text.method.ReplacementTransformationMethod$SpannedReplacementCharSequence
android.text.method.SingleLineTransformationMethod
android.text.method.TextKeyListener
android.text.method.TextKeyListener$Capitalize
-android.text.method.TextKeyListener$SettingsObserver
-android.text.method.TransformationMethod
-android.text.style.AlignmentSpan
-android.text.style.CharacterStyle
-android.text.style.ForegroundColorSpan
-android.text.style.LeadingMarginSpan
-android.text.style.LineBackgroundSpan
-android.text.style.LineHeightSpan
-android.text.style.MetricAffectingSpan
-android.text.style.ParagraphStyle
-android.text.style.ReplacementSpan
+android.text.style.ImageSpan
+android.text.style.RelativeSizeSpan
+android.text.style.ScaleXSpan
android.text.style.StyleSpan
-android.text.style.URLSpan
-android.text.style.UpdateAppearance
-android.text.style.UpdateLayout
-android.text.style.WrapTogetherSpan
+android.text.style.TextAppearanceSpan
android.text.util.Linkify
android.util.AttributeSet
android.util.DisplayMetrics
+android.util.EventLog
+android.util.EventLog$Event
android.util.FloatMath
+android.util.Log
+android.util.LongSparseArray
+android.util.MonthDisplayHelper
android.util.SparseArray
+android.util.StateSet
android.util.TypedValue
-android.util.Xml$XmlSerializerFactory
+android.util.Xml
+android.util.Xml$Encoding
+android.util.base64.Base64$Encoder
android.view.AbsSavedState
-android.view.ContextMenu
-android.view.ContextMenu$ContextMenuInfo
android.view.ContextThemeWrapper
android.view.Display
android.view.FocusFinder
-android.view.FocusFinder$1
-android.view.GestureDetector$SimpleOnGestureListener
-android.view.Gravity
-android.view.IWindow
+android.view.GestureDetector
android.view.IWindow$Stub
-android.view.IWindowManager
android.view.IWindowManager$Stub
android.view.IWindowManager$Stub$Proxy
-android.view.IWindowSession
android.view.IWindowSession$Stub
-android.view.IWindowSession$Stub$Proxy
android.view.KeyCharacterMap
+android.view.KeyCharacterMap$KeyData
android.view.KeyEvent
-android.view.KeyEvent$1
-android.view.KeyEvent$Callback
-android.view.LayoutInflater
-android.view.LayoutInflater$Factory
-android.view.Menu
-android.view.MenuInflater
-android.view.MenuItem
android.view.MotionEvent
-android.view.MotionEvent$1
+android.view.ScaleGestureDetector
android.view.Surface
-android.view.SurfaceHolder
+android.view.Surface$1
+android.view.SurfaceSession
android.view.SurfaceView
-android.view.TouchDelegate
+android.view.SurfaceView$MyWindow
android.view.VelocityTracker
android.view.View
-android.view.View$AttachInfo
android.view.View$AttachInfo$Callbacks
+android.view.View$AttachInfo$InvalidateInfo
android.view.View$BaseSavedState
-android.view.View$BaseSavedState$1
-android.view.View$MeasureSpec
-android.view.View$OnCreateContextMenuListener
-android.view.View$ScrollabilityCache
android.view.ViewConfiguration
android.view.ViewGroup
-android.view.ViewGroup$LayoutParams
-android.view.ViewGroup$MarginLayoutParams
-android.view.ViewManager
+android.view.ViewParent
android.view.ViewRoot
-android.view.ViewRoot$1
-android.view.ViewRoot$InputMethodCallback
-android.view.ViewRoot$RunQueue
-android.view.ViewRoot$TrackballAxis
android.view.ViewRoot$W
android.view.ViewStub
-android.view.ViewTreeObserver
-android.view.ViewTreeObserver$InternalInsetsInfo
-android.view.ViewTreeObserver$OnPreDrawListener
android.view.Window
-android.view.Window$Callback
-android.view.Window$LocalWindowManager
-android.view.WindowLeaked
-android.view.WindowManager
android.view.WindowManager$LayoutParams
-android.view.WindowManager$LayoutParams$1
android.view.WindowManagerImpl
-android.view.animation.AccelerateDecelerateInterpolator
-android.view.animation.AlphaAnimation
+android.view.accessibility.AccessibilityEvent
android.view.animation.Animation
android.view.animation.AnimationSet
-android.view.animation.LinearInterpolator
-android.view.animation.Transformation
android.view.inputmethod.BaseInputConnection
android.view.inputmethod.CompletionInfo
-android.view.inputmethod.CompletionInfo$1
-
android.view.inputmethod.EditorInfo
-android.view.inputmethod.EditorInfo$1
-
android.view.inputmethod.ExtractedText
-android.view.inputmethod.ExtractedText$1
-
-android.view.inputmethod.ExtractedTextRequest
-android.view.inputmethod.ExtractedTextRequest$1
-
-android.view.inputmethod.InputBinding
-android.view.inputmethod.InputBinding$1
-android.view.inputmethod.InputConnection
-android.view.inputmethod.InputMethod
-android.view.inputmethod.InputMethod$SessionCallback
-
-android.view.inputmethod.InputMethodInfo
-android.view.inputmethod.InputMethodInfo$1
android.view.inputmethod.InputMethodManager
-android.view.inputmethod.InputMethodManager$1
-android.view.inputmethod.InputMethodManager$2
-android.view.inputmethod.InputMethodManager$ControlledInputConnectionWrapper
-android.view.inputmethod.InputMethodManager$H
-
-android.view.inputmethod.InputMethodSession
-android.view.inputmethod.InputMethodSession$EventCallback
android.webkit.BrowserFrame
android.webkit.CacheManager
android.webkit.CallbackProxy
+android.webkit.ConsoleMessage$MessageLevel
android.webkit.CookieManager
android.webkit.CookieSyncManager
+android.webkit.DownloadListener
+android.webkit.FileLoader
+android.webkit.GeolocationPermissions
+android.webkit.GeolocationService
+android.webkit.HTML5VideoViewProxy
android.webkit.JWebCoreJavaBridge
android.webkit.LoadListener
-android.webkit.MimeTypeMap
+android.webkit.PluginManager
android.webkit.URLUtil
-android.webkit.WebBackForwardList
-android.webkit.WebHistoryItem
-android.webkit.WebIconDatabase
-android.webkit.WebIconDatabase$EventHandler
-android.webkit.WebIconDatabase$EventHandler$1
android.webkit.WebIconDatabase$EventHandler$IconResult
+android.webkit.WebIconDatabase$IconListener
android.webkit.WebSettings
-android.webkit.WebSettings$EventHandler
-android.webkit.WebSettings$EventHandler$1
-android.webkit.WebSettings$LayoutAlgorithm
-android.webkit.WebSettings$RenderPriority
android.webkit.WebSettings$TextSize
-android.webkit.WebSyncManager
-android.webkit.WebSyncManager$SyncHandler
+android.webkit.WebStorage
android.webkit.WebTextView
android.webkit.WebView
-android.webkit.WebView$ExtendedZoomControls
-android.webkit.WebView$PrivateHandler
+android.webkit.WebView$DragTrackerHandler
+android.webkit.WebView$ScaleDetectorListener
android.webkit.WebViewCore
-android.webkit.WebViewCore$CursorData
-android.webkit.WebViewCore$EventHub
-android.webkit.WebViewCore$EventHub$1
-android.webkit.WebViewCore$WebCoreThread
-android.webkit.WebViewCore$WebCoreThread$1
+android.webkit.WebViewCore$4
+android.webkit.WebViewCore$TextSelectionData
+android.webkit.WebViewCore$TouchEventData
+android.webkit.WebViewCore$TouchUpData
android.webkit.WebViewDatabase
android.widget.AbsListView
-android.widget.AbsListView$CheckForLongPress
-android.widget.AbsListView$CheckForTap
-android.widget.AbsListView$LayoutParams
+android.widget.AbsListView$3
+android.widget.AbsListView$CheckForKeyLongPress
android.widget.AbsListView$PerformClick
-android.widget.AbsListView$RecycleBin
android.widget.AbsListView$SavedState
-android.widget.AbsListView$SavedState$1
android.widget.AbsSeekBar
android.widget.AbsSpinner
+android.widget.AbsSpinner$SavedState
android.widget.AbsoluteLayout
-android.widget.AbsoluteLayout$LayoutParams
android.widget.AdapterView
-android.widget.AdapterView$AdapterDataSetObserver
android.widget.ArrayAdapter
android.widget.AutoCompleteTextView
android.widget.AutoCompleteTextView$DropDownItemClickListener
android.widget.AutoCompleteTextView$DropDownListView
android.widget.BaseAdapter
-android.widget.Button
+android.widget.BaseExpandableListAdapter
android.widget.CheckBox
-android.widget.Checkable
-android.widget.CheckedTextView
android.widget.CompoundButton
+android.widget.CompoundButton$SavedState
android.widget.CursorAdapter
-android.widget.CursorAdapter$ChangeObserver
-android.widget.CursorAdapter$MyDataSetObserver
android.widget.CursorTreeAdapter
+android.widget.DatePicker
android.widget.EditText
+android.widget.ExpandableListConnector
android.widget.ExpandableListView
android.widget.FrameLayout
-android.widget.FrameLayout$LayoutParams
-android.widget.Gallery
+android.widget.GridView
android.widget.HeaderViewListAdapter
android.widget.ImageView
android.widget.ImageView$ScaleType
android.widget.LinearLayout
-android.widget.LinearLayout$LayoutParams
android.widget.ListView
-android.widget.ListView$ArrowScrollFocusResult
android.widget.ListView$SavedState
-android.widget.ListView$SavedState$1
+android.widget.MediaController
+android.widget.MediaController$4
+android.widget.MultiAutoCompleteTextView
+android.widget.NumberPicker
android.widget.PopupWindow
+android.widget.PopupWindow$PopupViewContainer
android.widget.ProgressBar
-android.widget.RadioGroup
+android.widget.ProgressBar$SavedState
+android.widget.QuickContactBadge
android.widget.RatingBar
android.widget.RelativeLayout
-android.widget.RelativeLayout$LayoutParams
+android.widget.RelativeLayout$DependencyGraph$Node
android.widget.RemoteViews
+android.widget.ResourceCursorAdapter
android.widget.ScrollBarDrawable
android.widget.ScrollView
-android.widget.Scroller
android.widget.SeekBar
android.widget.SimpleCursorAdapter
android.widget.SlidingDrawer
android.widget.Spinner
-android.widget.Spinner$DropDownAdapter
android.widget.TabHost
android.widget.TabWidget
android.widget.TableLayout
android.widget.TableRow
android.widget.TextView
-android.widget.TextView$1
-android.widget.TextView$Blink
-android.widget.TextView$BufferType
-android.widget.TextView$ChangeWatcher
-android.widget.TextView$CharWrapper
-android.widget.TextView$Drawables
-android.widget.TextView$InputContentType
-android.widget.TextView$InputMethodState
+android.widget.TextView$CommitSelectionReceiver
android.widget.TextView$Marquee
-android.widget.TextView$MenuHandler
-android.widget.TextView$SavedState
-android.widget.TextView$SavedState$1
-android.widget.ToggleButton
+android.widget.TimePicker
android.widget.TwoLineListItem
+android.widget.VideoView
android.widget.ViewAnimator
android.widget.ViewSwitcher
android.widget.ZoomButton
+android.widget.ZoomButtonsController
android.widget.ZoomControls
-com.android.common.ArrayListCursor
+com.android.common.AndroidHttpClient
+com.android.common.DomainNameValidator
com.android.common.FastXmlSerializer
-com.android.common.NetworkConnectivityListener
-com.android.common.NetworkConnectivityListener$State
-com.android.common.XmlUtils
-com.android.internal.database.SortCursor
+com.android.common.HttpDateTime
+com.android.common.Patterns
+com.android.common.Rfc822Validator
+com.android.common.userhappiness.UserHappinessSignals
+com.android.internal.R$styleable
+com.android.internal.app.AlertActivity
+com.android.internal.app.AlertController
+com.android.internal.app.AlertController$AlertParams
+com.android.internal.app.AlertController$RecycleListView
+com.android.internal.app.ChooserActivity
+com.android.internal.app.ResolverActivity
+com.android.internal.app.ResolverActivity$ResolveListAdapter
com.android.internal.appwidget.IAppWidgetService$Stub
-com.android.internal.http.multipart.FilePart
-com.android.internal.http.multipart.MultipartEntity
-com.android.internal.http.multipart.Part
-com.android.internal.http.multipart.PartSource
-com.android.internal.http.multipart.StringPart
-com.android.internal.logging.AndroidConfig
+com.android.internal.content.SyncStateContentProviderHelper
+com.android.internal.graphics.NativeUtils
+com.android.internal.location.DummyLocationProvider
+com.android.internal.location.GpsLocationProvider
com.android.internal.logging.AndroidHandler
com.android.internal.os.AndroidPrintStream
+com.android.internal.os.BinderInternal
com.android.internal.os.BinderInternal$GcWatcher
+com.android.internal.os.IResultReceiver$Stub
com.android.internal.os.LoggingPrintStream
com.android.internal.os.LoggingPrintStream$1
com.android.internal.os.RuntimeInit
com.android.internal.os.RuntimeInit$1
com.android.internal.os.RuntimeInit$UncaughtHandler
-com.android.internal.os.ZygoteInit$MethodAndArgsCaller
-com.android.internal.policy.IPolicy
+com.android.internal.os.SamplingProfilerIntegration
+com.android.internal.os.ZygoteConnection
+com.android.internal.os.ZygoteConnection$Arguments
+com.android.internal.os.ZygoteInit
com.android.internal.policy.PolicyManager
com.android.internal.policy.impl.PhoneLayoutInflater
com.android.internal.policy.impl.PhoneWindow
-com.android.internal.policy.impl.PhoneWindow$1
-com.android.internal.policy.impl.PhoneWindow$ContextMenuCallback
com.android.internal.policy.impl.PhoneWindow$DecorView
-com.android.internal.policy.impl.PhoneWindow$PanelFeatureState
com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState
-com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState$1
+com.android.internal.policy.impl.PhoneWindowManager
com.android.internal.policy.impl.Policy
-com.android.internal.telephony.Connection$DisconnectCause
-com.android.internal.telephony.Connection$PostDialState
-com.android.internal.telephony.IPhoneStateListener$Stub
-com.android.internal.telephony.ITelephony$Stub
-com.android.internal.telephony.Phone
-com.android.internal.telephony.Phone$DataActivityState
-com.android.internal.telephony.Phone$DataState
-com.android.internal.telephony.Phone$State
-com.android.internal.telephony.Phone$SuppService
-com.android.internal.telephony.PhoneBase
-com.android.internal.telephony.PhoneStateIntentReceiver
-com.android.internal.telephony.IccCard$State
-com.android.internal.telephony.BaseCommands
-com.android.internal.telephony.CallForwardInfo
-com.android.internal.telephony.CommandsInterface
-com.android.internal.telephony.DriverCall
-com.android.internal.telephony.DriverCall$State
-com.android.internal.telephony.gsm.GsmConnection
-com.android.internal.telephony.gsm.GSMPhone
com.android.internal.telephony.GsmAlphabet
-com.android.internal.telephony.gsm.GsmMmiCode
-com.android.internal.telephony.gsm.SimCard
-com.android.internal.telephony.ISms$Stub
-com.android.internal.telephony.RIL
-com.android.internal.telephony.ServiceStateTracker
-
-com.android.internal.telephony.gsm.stk.ComprehensionTlvTag
-com.android.internal.telephony.gsm.stk.ResultCode
+com.android.internal.telephony.ITelephony$Stub
+com.android.internal.telephony.ITelephony$Stub$Proxy
+com.android.internal.telephony.ITelephonyRegistry$Stub
+com.android.internal.telephony.IccCard$State
+com.android.internal.telephony.Phone$State
+com.android.internal.telephony.SmsAddress
+com.android.internal.telephony.SmsMessageBase
+com.android.internal.telephony.gsm.GsmSmsAddress
+com.android.internal.telephony.gsm.SmsMessage
+com.android.internal.telephony.gsm.SmsMessage$PduParser
+com.android.internal.util.ArrayUtils
+com.android.internal.util.FastMath
+com.android.internal.util.HanziToPinyin
com.android.internal.view.IInputConnectionWrapper
-com.android.internal.view.IInputConnectionWrapper$MyHandler
-com.android.internal.view.IInputConnectionWrapper$SomeArgs
-
-com.android.internal.view.IInputContext
com.android.internal.view.IInputContext$Stub
-com.android.internal.view.IInputContext$Stub$Proxy
-
-com.android.internal.view.IInputContextCallback
-com.android.internal.view.IInputContextCallback$Stub
-com.android.internal.view.IInputContextCallback$Stub$Proxy
-
-com.android.internal.view.IInputMethod
-com.android.internal.view.IInputMethod$Stub
-com.android.internal.view.IInputMethod$Stub$Proxy
-
-com.android.internal.view.IInputMethodCallback
-com.android.internal.view.IInputMethodCallback$Stub
-com.android.internal.view.IInputMethodCallback$Stub$Proxy
-
-com.android.internal.view.IInputMethodClient
-com.android.internal.view.IInputMethodClient$Stub
-com.android.internal.view.IInputMethodClient$Stub$Proxy
-
-com.android.internal.view.IInputMethodManager
com.android.internal.view.IInputMethodManager$Stub
-com.android.internal.view.IInputMethodManager$Stub$Proxy
-
-com.android.internal.view.IInputMethodSession
-com.android.internal.view.IInputMethodSession$Stub
-com.android.internal.view.IInputMethodSession$Stub$Proxy
-
-com.android.internal.view.InputBindResult
-com.android.internal.view.InputBindResult$1
-
-com.android.internal.view.InputConnectionWrapper
-com.android.internal.view.InputConnectionWrapper$InputContextCallback
-com.android.internal.view.menu.ExpandedMenuView
+com.android.internal.view.menu.ContextMenuBuilder
com.android.internal.view.menu.IconMenuItemView
com.android.internal.view.menu.IconMenuView
+com.android.internal.view.menu.IconMenuView$SavedState
com.android.internal.view.menu.ListMenuItemView
com.android.internal.view.menu.MenuBuilder
-com.android.internal.view.menu.MenuBuilder$Callback
-com.android.internal.view.menu.MenuDialogHelper
com.android.internal.view.menu.MenuItemImpl
com.android.internal.view.menu.SubMenuBuilder
-com.android.internal.widget.RotarySelector
-com.android.internal.widget.Smileys
-com.google.android.gles_jni.EGLDisplayImpl
+com.android.internal.widget.ContactHeaderWidget
+com.android.internal.widget.DialogTitle
+com.android.internal.widget.EditableInputConnection
+com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
+com.android.internal.widget.LockPatternUtils
+com.android.internal.widget.LockPatternView
+com.android.internal.widget.LockPatternView$Cell
com.google.android.gles_jni.EGLImpl
com.google.android.gles_jni.GLImpl
com.ibm.icu4jni.charset.CharsetDecoderICU
com.ibm.icu4jni.charset.CharsetEncoderICU
com.ibm.icu4jni.charset.CharsetICU
-com.ibm.icu4jni.text.CollationAttribute
+com.ibm.icu4jni.charset.CharsetProviderICU
+com.ibm.icu4jni.charset.NativeConverter
+com.ibm.icu4jni.common.ErrorCode
+com.ibm.icu4jni.lang.UCharacter
+com.ibm.icu4jni.regex.NativeRegEx
+com.ibm.icu4jni.text.Collator
+com.ibm.icu4jni.text.NativeBreakIterator
+com.ibm.icu4jni.text.NativeCollation
com.ibm.icu4jni.text.NativeDecimalFormat
com.ibm.icu4jni.text.RuleBasedCollator
+com.ibm.icu4jni.text.RuleBasedNumberFormat
+com.ibm.icu4jni.util.Resources
com.ibm.icu4jni.util.Resources$DefaultTimeZones
-dalvik.system.DexFile
+dalvik.system.DalvikLogHandler
+dalvik.system.DalvikLogging
+dalvik.system.NativeStart
dalvik.system.PathClassLoader
+dalvik.system.SamplingProfiler
+dalvik.system.TouchDex
+dalvik.system.VMDebug
+dalvik.system.VMRuntime
+dalvik.system.VMStack
+dalvik.system.Zygote
java.beans.PropertyChangeEvent
java.beans.PropertyChangeListener
java.beans.PropertyChangeSupport
java.io.BufferedInputStream
+java.io.BufferedReader
java.io.ByteArrayInputStream
-java.io.ByteArrayOutputStream
+java.io.Closeable
+java.io.DataInput
+java.io.DataOutput
+java.io.DataOutputStream
java.io.File
java.io.FileDescriptor
java.io.FileInputStream
java.io.FileInputStream$RepositioningLock
java.io.FileNotFoundException
+java.io.FileOutputStream
java.io.FilterInputStream
+java.io.FilterOutputStream
+java.io.Flushable
java.io.IOException
+java.io.InputStream
+java.io.InputStreamReader
+java.io.InterruptedIOException
+java.io.ObjectInput
+java.io.ObjectInputStream
+java.io.ObjectOutput
+java.io.ObjectOutputStream
java.io.ObjectStreamClass
+java.io.ObjectStreamClass$OSCThreadLocalCache
+java.io.ObjectStreamConstants
+java.io.ObjectStreamException
+java.io.ObjectStreamField
+java.io.OutputStream
+java.io.OutputStreamWriter
+java.io.PrintStream
java.io.PrintWriter
+java.io.PushbackReader
java.io.RandomAccessFile
java.io.RandomAccessFile$RepositionLock
-java.io.StringWriter
-java.io.Writer
+java.io.Reader
+java.io.Serializable
+java.io.StreamCorruptedException
+java.lang.AbstractStringBuilder
+java.lang.Appendable
+java.lang.ArrayIndexOutOfBoundsException
+java.lang.Boolean
+java.lang.BootClassLoader
+java.lang.Byte
+java.lang.CharSequence
+java.lang.Character
+java.lang.Character$UnicodeBlock
java.lang.Class
java.lang.ClassCache
-java.lang.ClassNotFoundException
+java.lang.ClassCache$EnumComparator
+java.lang.ClassLoader
+java.lang.ClassLoader$SystemClassLoader
+java.lang.Cloneable
+java.lang.Comparable
+java.lang.Double
+java.lang.Enum
+java.lang.Error
+java.lang.Exception
+java.lang.Float
java.lang.IllegalArgumentException
-java.lang.IllegalStateException
+java.lang.IndexOutOfBoundsException
java.lang.Integer
+java.lang.InternalError
+java.lang.InterruptedException
+java.lang.Iterable
+java.lang.LangAccessImpl
java.lang.LinkageError
java.lang.Long
+java.lang.Math
java.lang.NoClassDefFoundError
+java.lang.NoSuchMethodError
+java.lang.Number
java.lang.NumberFormatException
java.lang.Object
+java.lang.OutOfMemoryError
+java.lang.Readable
+java.lang.Runnable
java.lang.Runtime
java.lang.RuntimeException
+java.lang.RuntimePermission
+java.lang.SecurityException
+java.lang.Short
+java.lang.StackOverflowError
+java.lang.StackTraceElement
+java.lang.StrictMath
java.lang.String
+java.lang.String$CaseInsensitiveComparator
java.lang.StringBuffer
java.lang.StringBuilder
+java.lang.System
+java.lang.SystemProperties
java.lang.Thread
+java.lang.Thread$State
+java.lang.Thread$UncaughtExceptionHandler
+java.lang.ThreadGroup
+java.lang.ThreadGroup$ChildrenGroupsLock
+java.lang.ThreadGroup$ChildrenThreadsLock
java.lang.ThreadLocal
java.lang.ThreadLocal$Values
java.lang.Throwable
+java.lang.UnsatisfiedLinkError
+java.lang.UnsupportedOperationException
+java.lang.VMClassLoader
java.lang.VMThread
+java.lang.VirtualMachineError
+java.lang.Void
+java.lang.annotation.Annotation
+java.lang.ref.PhantomReference
+java.lang.ref.Reference
java.lang.ref.ReferenceQueue
java.lang.ref.SoftReference
java.lang.ref.WeakReference
+java.lang.reflect.AccessibleObject
+java.lang.reflect.AnnotatedElement
+java.lang.reflect.Array
java.lang.reflect.Constructor
+java.lang.reflect.Field
+java.lang.reflect.GenericDeclaration
+java.lang.reflect.InvocationHandler
+java.lang.reflect.Member
java.lang.reflect.Method
java.lang.reflect.Modifier
+java.lang.reflect.Proxy
+java.lang.reflect.ReflectionAccessImpl
+java.lang.reflect.Type
java.math.BigDecimal
java.math.BigInt
java.math.BigInteger
java.math.Multiplication
+java.net.AddressCache
+java.net.AddressCache$1
+java.net.ConnectException
java.net.ContentHandler
+java.net.DatagramPacket
+java.net.Inet4Address
java.net.InetAddress
+java.net.InetAddress$1
+java.net.InetAddress$2
java.net.InetAddress$WaitReachable
+java.net.InetSocketAddress
java.net.JarURLConnection
java.net.NetPermission
-java.net.ProxySelectorImpl
-java.net.Socket$ConnectLock
+java.net.NetworkInterface
+java.net.ServerSocket
+java.net.Socket
+java.net.SocketException
+java.net.SocketImpl
+java.net.SocketOptions
java.net.URI
java.net.URL
java.net.URLConnection
java.net.URLConnection$DefaultContentHandler
java.net.URLStreamHandler
+java.nio.BaseByteBuffer
+java.nio.Buffer
+java.nio.BufferFactory
+java.nio.ByteBuffer
java.nio.ByteOrder
+java.nio.CharArrayBuffer
+java.nio.CharBuffer
java.nio.CharSequenceAdapter
+java.nio.CharToByteBufferAdapter
java.nio.DirectByteBuffer
+java.nio.FloatToByteBufferAdapter
+java.nio.HeapByteBuffer
+java.nio.IntToByteBufferAdapter
+java.nio.LongBuffer
+java.nio.LongToByteBufferAdapter
+java.nio.NIOAccess
+java.nio.ReadWriteCharArrayBuffer
java.nio.ReadWriteDirectByteBuffer
-java.nio.ReadWriteIntArrayBuffer
-java.nio.ReadWriteShortArrayBuffer
-java.nio.ShortBuffer
+java.nio.ReadWriteHeapByteBuffer
java.nio.ShortToByteBufferAdapter
+java.nio.channels.ByteChannel
+java.nio.channels.Channel
+java.nio.channels.FileChannel
+java.nio.channels.GatheringByteChannel
+java.nio.channels.InterruptibleChannel
+java.nio.channels.ReadableByteChannel
+java.nio.channels.ScatteringByteChannel
+java.nio.channels.WritableByteChannel
+java.nio.channels.spi.AbstractInterruptibleChannel
+java.nio.channels.spi.AbstractInterruptibleChannel$1
+java.nio.channels.spi.AbstractInterruptibleChannel$2
+java.nio.charset.Charset
+java.nio.charset.Charset$1
+java.nio.charset.CharsetDecoder
java.nio.charset.CharsetEncoder
+java.nio.charset.CoderResult
+java.nio.charset.CodingErrorAction
+java.nio.charset.spi.CharsetProvider
java.security.AccessControlContext
-java.security.GeneralSecurityException
+java.security.AccessController
+java.security.BasicPermission
+java.security.Guard
java.security.KeyStore
java.security.MessageDigest
+java.security.Permission
+java.security.PrivilegedAction
+java.security.PrivilegedExceptionAction
java.security.ProtectionDomain
java.security.Provider
-java.security.SecureRandom
java.security.Security
-java.security.cert.CertPathValidator
-java.security.cert.CertificateFactory
-java.security.cert.PKIXParameters
-java.security.cert.TrustAnchor
-java.security.cert.X509CertSelector
java.security.cert.X509Certificate
+java.text.AttributedCharacterIterator$Attribute
java.text.Collator
+java.text.Collator$1
java.text.DateFormat
java.text.DateFormat$Field
java.text.DecimalFormat
java.text.DecimalFormatSymbols
-java.text.MessageFormat
+java.text.Format
java.text.NumberFormat
-java.text.RuleBasedCollator
java.text.SimpleDateFormat
-java.util.AbstractList$FullListIterator
-java.util.AbstractList$SimpleListIterator
+java.util.AbstractCollection
+java.util.AbstractList
+java.util.AbstractMap
+java.util.AbstractSet
java.util.ArrayList
+java.util.ArrayList$ArrayListIterator
java.util.Arrays
java.util.Arrays$ArrayList
+java.util.BitSet
java.util.Calendar
-java.util.Collections$SynchronizedCollection
-java.util.Collections$UnmodifiableList
-java.util.Collections$UnmodifiableMap
-java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$1
+java.util.Collection
+java.util.Collections
+java.util.Collections$EmptyList
+java.util.Collections$EmptyMap
+java.util.Collections$EmptySet
+java.util.Collections$SingletonSet
+java.util.Collections$UnmodifiableCollection
+java.util.Collections$UnmodifiableCollection$1
+java.util.Collections$UnmodifiableRandomAccessList
+java.util.Collections$UnmodifiableSet
+java.util.Comparator
java.util.Date
+java.util.Dictionary
java.util.EnumMap
+java.util.EnumSet
+java.util.Enumeration
java.util.EventListener
java.util.EventObject
java.util.Formatter
java.util.GregorianCalendar
java.util.HashMap
+java.util.HashMap$HashIterator
+java.util.HashMap$HashMapEntry
+java.util.HashMap$KeyIterator
+java.util.HashMap$KeySet
+java.util.HashMap$Values
java.util.HashSet
java.util.Hashtable
+java.util.Hashtable$HashIterator
+java.util.Hashtable$HashtableEntry
+java.util.Hashtable$KeyEnumeration
+java.util.Hashtable$ValueIterator
+java.util.Hashtable$Values
java.util.IdentityHashMap
+java.util.Iterator
java.util.LinkedHashMap
+java.util.LinkedHashMap$LinkedEntry
+java.util.LinkedHashMap$LinkedHashIterator
+java.util.LinkedHashMap$ValueIterator
java.util.LinkedList
-java.util.LinkedList$Link
java.util.List
+java.util.ListIterator
java.util.Locale
+java.util.Map
+java.util.Map$Entry
+java.util.MiniEnumSet
java.util.Properties
-java.util.Random
+java.util.PropertyPermission
+java.util.RandomAccess
java.util.ResourceBundle
+java.util.Set
java.util.SimpleTimeZone
+java.util.SortedMap
+java.util.SortedSet
+java.util.SpecialAccess
+java.util.Stack
+java.util.StringTokenizer
java.util.TimeZone
java.util.TreeMap
-java.util.TreeMap$MapEntry
java.util.TreeSet
+java.util.UUID
java.util.Vector
java.util.WeakHashMap
java.util.WeakHashMap$Entry
java.util.concurrent.ConcurrentHashMap
java.util.concurrent.ConcurrentLinkedQueue
-java.util.concurrent.DelayQueue
+java.util.concurrent.ConcurrentLinkedQueue$Node
+java.util.concurrent.CopyOnWriteArrayList
+java.util.concurrent.CopyOnWriteArrayList$COWIterator
+java.util.concurrent.Executors$DelegatedExecutorService
+java.util.concurrent.FutureTask
java.util.concurrent.LinkedBlockingQueue
-java.util.concurrent.ScheduledThreadPoolExecutor
-java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue
+java.util.concurrent.Semaphore
+java.util.concurrent.ThreadPoolExecutor
java.util.concurrent.TimeUnit
java.util.concurrent.atomic.AtomicBoolean
java.util.concurrent.atomic.AtomicInteger
+java.util.concurrent.atomic.AtomicLong
+java.util.concurrent.atomic.AtomicReference
java.util.concurrent.atomic.UnsafeAccess
+java.util.concurrent.locks.AbstractOwnableSynchronizer
java.util.concurrent.locks.AbstractQueuedSynchronizer
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject
java.util.concurrent.locks.AbstractQueuedSynchronizer$Node
java.util.concurrent.locks.Lock
-java.util.concurrent.locks.LockSupport
java.util.concurrent.locks.ReentrantLock
-java.util.concurrent.locks.ReentrantLock$FairSync
java.util.concurrent.locks.ReentrantLock$NonfairSync
java.util.concurrent.locks.ReentrantLock$Sync
-java.util.concurrent.locks.ReentrantReadWriteLock
-java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync
java.util.concurrent.locks.UnsafeAccess
java.util.jar.Attributes
java.util.jar.Attributes$Name
@@ -852,25 +1042,25 @@
java.util.jar.JarEntry
java.util.jar.JarFile
java.util.jar.JarFile$1JarFileEnumerator
-java.util.jar.JarFile$JarFileInputStream
java.util.jar.JarVerifier
java.util.jar.Manifest
-java.util.logging.ErrorManager
-java.util.logging.Formatter
java.util.logging.Handler
java.util.logging.Level
java.util.logging.LogManager
java.util.logging.LogManager$1
java.util.logging.LogManager$2
java.util.logging.LogManager$2$1
-java.util.logging.LogManager$3
-java.util.logging.LogRecord
+java.util.logging.LogManager$4
java.util.logging.Logger
+java.util.logging.Logger$1
java.util.logging.LoggingPermission
-java.util.logging.SimpleFormatter
+java.util.regex.MatchResult
java.util.regex.Matcher
java.util.regex.Pattern
-java.util.zip.DeflaterOutputStream
+java.util.zip.Adler32
+java.util.zip.CRC32
+java.util.zip.Checksum
+java.util.zip.Deflater
java.util.zip.Inflater
java.util.zip.InflaterInputStream
java.util.zip.ZipConstants
@@ -879,63 +1069,90 @@
java.util.zip.ZipFile
java.util.zip.ZipFile$2
java.util.zip.ZipFile$RAFStream
-javax.microedition.khronos.egl.EGLContext
+java.util.zip.ZipFile$ZipInflaterInputStream
+javax.crypto.Cipher
+javax.crypto.Mac
+javax.crypto.spec.IvParameterSpec
+javax.microedition.khronos.egl.EGL
+javax.microedition.khronos.egl.EGL10
+javax.microedition.khronos.opengles.GL
+javax.microedition.khronos.opengles.GL10
+javax.microedition.khronos.opengles.GL10Ext
+javax.microedition.khronos.opengles.GL11
+javax.microedition.khronos.opengles.GL11Ext
+javax.microedition.khronos.opengles.GL11ExtensionPack
+javax.net.ssl.DefaultHostnameVerifier
javax.net.ssl.HttpsURLConnection
-javax.net.ssl.SSLHandshakeException
+javax.net.ssl.SSLServerSocket
+javax.net.ssl.SSLSession
+javax.net.ssl.SSLSocket
+javax.net.ssl.SSLSocketFactory
javax.security.auth.x500.X500Principal
javax.security.cert.X509Certificate
-javax.security.cert.X509Certificate$2
junit.framework.Assert
-org.apache.commons.codec.binary.Base64
-org.apache.commons.codec.binary.Hex
org.apache.commons.logging.LogFactory
-org.apache.commons.logging.impl.Jdk14Logger
org.apache.harmony.archive.util.Util
-org.apache.harmony.dalvik.ddmc.Chunk
+org.apache.harmony.dalvik.NativeTestTarget
org.apache.harmony.dalvik.ddmc.ChunkHandler
org.apache.harmony.dalvik.ddmc.DdmServer
-org.apache.harmony.dalvik.ddmc.DdmVmInternal
+org.apache.harmony.kernel.vm.LangAccess
+org.apache.harmony.kernel.vm.ReflectionAccess
+org.apache.harmony.lang.annotation.AnnotationFactory
+org.apache.harmony.lang.annotation.AnnotationMember
org.apache.harmony.luni.internal.net.www.protocol.file.FileURLConnection
org.apache.harmony.luni.internal.net.www.protocol.file.Handler
org.apache.harmony.luni.internal.net.www.protocol.http.Handler
-org.apache.harmony.luni.internal.net.www.protocol.https.Handler
org.apache.harmony.luni.internal.net.www.protocol.jar.Handler
org.apache.harmony.luni.internal.net.www.protocol.jar.JarURLConnectionImpl
-org.apache.harmony.luni.internal.net.www.protocol.jar.JarURLConnectionImpl$1
org.apache.harmony.luni.internal.net.www.protocol.jar.JarURLConnectionImpl$JarURLConnectionInputStream
org.apache.harmony.luni.internal.util.TimezoneGetter
-org.apache.harmony.luni.internal.util.ZoneInfo
org.apache.harmony.luni.internal.util.ZoneInfoDB
+org.apache.harmony.luni.net.GenericIPMreq
org.apache.harmony.luni.net.PlainSocketImpl
+org.apache.harmony.luni.platform.Endianness
+org.apache.harmony.luni.platform.ICommonDataTypes
+org.apache.harmony.luni.platform.IFileSystem
+org.apache.harmony.luni.platform.IMemorySystem
+org.apache.harmony.luni.platform.INetworkSystem
+org.apache.harmony.luni.platform.OSFileSystem
+org.apache.harmony.luni.platform.OSMemory
+org.apache.harmony.luni.platform.OSNetworkSystem
+org.apache.harmony.luni.platform.Platform
org.apache.harmony.luni.platform.PlatformAddress
-org.apache.harmony.luni.util.TwoKeyHashMap
+org.apache.harmony.luni.platform.PlatformAddressFactory
+org.apache.harmony.luni.util.FloatingPointParser
+org.apache.harmony.luni.util.InputStreamHelper
+org.apache.harmony.luni.util.InputStreamHelper$1
+org.apache.harmony.luni.util.InputStreamHelper$ExposedByteArrayInputStream
+org.apache.harmony.luni.util.LocaleCache
+org.apache.harmony.luni.util.Msg
+org.apache.harmony.luni.util.NumberConverter
+org.apache.harmony.luni.util.PriviAction
+org.apache.harmony.luni.util.ThreadLocalCache
+org.apache.harmony.luni.util.ThreadLocalCache$1
+org.apache.harmony.luni.util.ThreadLocalCache$2
+org.apache.harmony.luni.util.ThreadLocalCache$3
+org.apache.harmony.luni.util.ThreadLocalCache$4
+org.apache.harmony.luni.util.ThreadLocalCache$5
+org.apache.harmony.luni.util.Util
+org.apache.harmony.nio.FileChannelFactory
+org.apache.harmony.nio.internal.DirectBuffer
+org.apache.harmony.nio.internal.FileChannelImpl
org.apache.harmony.nio.internal.FileChannelImpl$RepositioningLock
+org.apache.harmony.nio.internal.FileLockImpl
org.apache.harmony.nio.internal.LockManager
org.apache.harmony.nio.internal.LockManager$1
-org.apache.harmony.nio.internal.ReadOnlyFileChannel
-org.apache.harmony.security.asn1.ASN1BitString
-org.apache.harmony.security.asn1.ASN1BitString$ASN1NamedBitList
-org.apache.harmony.security.asn1.ASN1Boolean
-org.apache.harmony.security.asn1.ASN1Explicit
+org.apache.harmony.nio.internal.WriteOnlyFileChannel
org.apache.harmony.security.asn1.ASN1GeneralizedTime
-org.apache.harmony.security.asn1.ASN1Implicit
-org.apache.harmony.security.asn1.ASN1Integer
-org.apache.harmony.security.asn1.ASN1OctetString
-org.apache.harmony.security.asn1.ASN1SetOf
+org.apache.harmony.security.asn1.ASN1Oid
org.apache.harmony.security.asn1.ASN1StringType
-org.apache.harmony.security.asn1.ASN1StringType$1
-org.apache.harmony.security.asn1.ASN1StringType$2
-org.apache.harmony.security.asn1.ASN1StringType$3
-org.apache.harmony.security.asn1.ASN1StringType$4
-org.apache.harmony.security.asn1.ASN1StringType$5
-org.apache.harmony.security.asn1.ASN1StringType$6
-org.apache.harmony.security.asn1.ASN1StringType$7
-org.apache.harmony.security.asn1.ASN1UTCTime
-org.apache.harmony.security.asn1.BitString
+org.apache.harmony.security.asn1.DerInputStream
+org.apache.harmony.security.asn1.DerOutputStream
org.apache.harmony.security.fortress.Engine
org.apache.harmony.security.fortress.SecurityUtils
org.apache.harmony.security.fortress.Services
org.apache.harmony.security.pkcs7.ContentInfo
+org.apache.harmony.security.provider.cert.DRLCertFactory
org.apache.harmony.security.provider.cert.X509CertFactoryImpl
org.apache.harmony.security.provider.cert.X509CertImpl
org.apache.harmony.security.provider.cert.X509CertPathImpl
@@ -943,183 +1160,111 @@
org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl
org.apache.harmony.security.utils.AlgNameMapper
org.apache.harmony.security.x501.AttributeTypeAndValue
-org.apache.harmony.security.x501.AttributeValue
org.apache.harmony.security.x501.DirectoryString
-org.apache.harmony.security.x501.DirectoryString$1
org.apache.harmony.security.x501.Name
-org.apache.harmony.security.x501.Name$1
org.apache.harmony.security.x509.AlgorithmIdentifier
-org.apache.harmony.security.x509.AlgorithmIdentifier$1
org.apache.harmony.security.x509.BasicConstraints
-org.apache.harmony.security.x509.BasicConstraints$1
org.apache.harmony.security.x509.Certificate
-org.apache.harmony.security.x509.Certificate$1
+org.apache.harmony.security.x509.EDIPartyName
org.apache.harmony.security.x509.Extension
-org.apache.harmony.security.x509.Extension$1
-org.apache.harmony.security.x509.Extension$2
org.apache.harmony.security.x509.Extensions
-org.apache.harmony.security.x509.Extensions$1
org.apache.harmony.security.x509.GeneralName
org.apache.harmony.security.x509.GeneralNames
org.apache.harmony.security.x509.KeyUsage
org.apache.harmony.security.x509.ORAddress
+org.apache.harmony.security.x509.OtherName
org.apache.harmony.security.x509.SubjectPublicKeyInfo
-org.apache.harmony.security.x509.SubjectPublicKeyInfo$1
org.apache.harmony.security.x509.TBSCertificate
-org.apache.harmony.security.x509.TBSCertificate$1
org.apache.harmony.security.x509.Time
-org.apache.harmony.security.x509.Time$1
org.apache.harmony.security.x509.Validity
-org.apache.harmony.security.x509.Validity$1
+org.apache.harmony.text.BidiWrapper
+org.apache.harmony.xml.ExpatAttributes
org.apache.harmony.xml.ExpatParser
org.apache.harmony.xml.ExpatPullParser
-org.apache.harmony.xml.ExpatReader
-org.apache.harmony.xnet.provider.jsse.ClientSessionContext
+org.apache.harmony.xml.parsers.SAXParserFactoryImpl
+org.apache.harmony.xnet.provider.jsse.FileClientSessionCache
+org.apache.harmony.xnet.provider.jsse.NativeCrypto
+org.apache.harmony.xnet.provider.jsse.OpenSSLServerSocketImpl
org.apache.harmony.xnet.provider.jsse.OpenSSLSessionImpl
org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl
org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$Finalizer
-org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLInputStream
-org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLOutputStream
+org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$LoggerHolder
+org.apache.harmony.xnet.provider.jsse.ProtocolVersion
org.apache.harmony.xnet.provider.jsse.SSLContextImpl
org.apache.harmony.xnet.provider.jsse.SSLParameters
-org.apache.harmony.xnet.provider.jsse.TrustManagerFactoryImpl
-org.apache.harmony.xnet.provider.jsse.TrustManagerImpl
-org.apache.http.HttpHost
-org.apache.http.HttpRequestInterceptor
+org.apache.harmony.xnet.provider.jsse.ServerSessionContext
org.apache.http.HttpVersion
-org.apache.http.auth.AuthSchemeRegistry
-org.apache.http.client.HttpClient
-org.apache.http.client.RequestDirector
+org.apache.http.client.methods.HttpEntityEnclosingRequestBase
+org.apache.http.client.methods.HttpGet
+org.apache.http.client.methods.HttpPost
org.apache.http.client.methods.HttpRequestBase
-org.apache.http.client.protocol.RequestAddCookies
-org.apache.http.client.protocol.RequestDefaultHeaders
-org.apache.http.client.protocol.RequestProxyAuthentication
-org.apache.http.client.protocol.RequestTargetAuthentication
-org.apache.http.client.protocol.ResponseProcessCookies
-org.apache.http.conn.params.ConnManagerParams$1
+org.apache.http.conn.BasicManagedEntity
+org.apache.http.conn.params.ConnManagerParams
org.apache.http.conn.params.ConnRouteParams
org.apache.http.conn.routing.HttpRoute
-org.apache.http.conn.routing.RouteInfo$LayerType
-org.apache.http.conn.routing.RouteInfo$TunnelType
-org.apache.http.conn.routing.RouteTracker
-org.apache.http.conn.scheme.PlainSocketFactory
-org.apache.http.conn.scheme.Scheme
-org.apache.http.conn.scheme.SchemeRegistry
-org.apache.http.conn.ssl.AllowAllHostnameVerifier
-org.apache.http.conn.ssl.BrowserCompatHostnameVerifier
+org.apache.http.conn.ssl.AbstractVerifier
org.apache.http.conn.ssl.SSLSocketFactory
-org.apache.http.conn.ssl.StrictHostnameVerifier
org.apache.http.conn.util.InetAddressUtils
-org.apache.http.cookie.CookieSpecRegistry
-org.apache.http.impl.DefaultConnectionReuseStrategy
-org.apache.http.impl.DefaultHttpResponseFactory
+org.apache.http.impl.AbstractHttpClientConnection
org.apache.http.impl.EnglishReasonPhraseCatalog
-org.apache.http.impl.HttpConnectionMetricsImpl
org.apache.http.impl.SocketHttpClientConnection
-org.apache.http.impl.auth.BasicSchemeFactory
-org.apache.http.impl.auth.DigestSchemeFactory
org.apache.http.impl.client.AbstractAuthenticationHandler
org.apache.http.impl.client.AbstractHttpClient
-org.apache.http.impl.client.BasicCredentialsProvider
+org.apache.http.impl.client.BasicCookieStore
org.apache.http.impl.client.DefaultHttpClient
-org.apache.http.impl.client.DefaultHttpRequestRetryHandler
-org.apache.http.impl.client.DefaultProxyAuthenticationHandler
-org.apache.http.impl.client.DefaultRedirectHandler
-org.apache.http.impl.client.DefaultTargetAuthenticationHandler
-org.apache.http.impl.client.DefaultUserTokenHandler
org.apache.http.impl.client.EntityEnclosingRequestWrapper
org.apache.http.impl.conn.AbstractClientConnAdapter
+org.apache.http.impl.conn.AbstractPooledConnAdapter
org.apache.http.impl.conn.DefaultClientConnection
-org.apache.http.impl.conn.DefaultClientConnectionOperator
-org.apache.http.impl.conn.DefaultHttpRoutePlanner
-org.apache.http.impl.conn.DefaultResponseParser
-org.apache.http.impl.conn.IdleConnectionHandler
-org.apache.http.impl.conn.tsccm.BasicPoolEntry
-org.apache.http.impl.conn.tsccm.BasicPoolEntryRef
+org.apache.http.impl.conn.SingleClientConnManager
org.apache.http.impl.conn.tsccm.ConnPoolByRoute
-org.apache.http.impl.conn.tsccm.RefQueueWorker
-org.apache.http.impl.conn.tsccm.RouteSpecificPool
org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager
org.apache.http.impl.cookie.BasicClientCookie
-org.apache.http.impl.cookie.BestMatchSpecFactory
-org.apache.http.impl.cookie.BrowserCompatSpecFactory
+org.apache.http.impl.cookie.BrowserCompatSpec
org.apache.http.impl.cookie.DateUtils
-org.apache.http.impl.cookie.NetscapeDraftSpecFactory
-org.apache.http.impl.cookie.RFC2109SpecFactory
-org.apache.http.impl.cookie.RFC2965SpecFactory
-org.apache.http.impl.entity.EntityDeserializer
-org.apache.http.impl.entity.EntitySerializer
-org.apache.http.impl.entity.LaxContentLengthStrategy
-org.apache.http.impl.entity.StrictContentLengthStrategy
-org.apache.http.impl.io.HttpRequestWriter
-org.apache.http.impl.io.HttpTransportMetricsImpl
+org.apache.http.impl.cookie.DateUtils$DateFormatHolder
+org.apache.http.impl.cookie.RFC2109Spec
org.apache.http.impl.io.SocketInputBuffer
-org.apache.http.impl.io.SocketOutputBuffer
-org.apache.http.message.BasicHeaderValueParser
org.apache.http.message.BasicHttpEntityEnclosingRequest
+org.apache.http.message.BasicHttpRequest
org.apache.http.message.BasicHttpResponse
-org.apache.http.message.BasicLineFormatter
org.apache.http.message.BasicLineParser
+org.apache.http.message.BasicNameValuePair
+org.apache.http.message.BasicTokenIterator
org.apache.http.params.BasicHttpParams
org.apache.http.protocol.BasicHttpProcessor
org.apache.http.protocol.HTTP
-org.apache.http.protocol.HttpRequestExecutor
-org.apache.http.protocol.HttpRequestInterceptorList
-org.apache.http.protocol.HttpResponseInterceptorList
-org.apache.http.protocol.RequestConnControl
-org.apache.http.protocol.RequestContent
-org.apache.http.protocol.RequestExpectContinue
-org.apache.http.protocol.RequestTargetHost
-org.apache.http.protocol.RequestUserAgent
-org.apache.http.util.ByteArrayBuffer
-org.apache.http.util.CharArrayBuffer
-org.apache.http.util.EntityUtils
-org.apache.http.util.VersionInfo
-org.bouncycastle.asn1.DERBitString
-org.bouncycastle.asn1.DERIA5String
-org.bouncycastle.asn1.DERInteger
+org.bouncycastle.asn1.DERNull
org.bouncycastle.asn1.DERObject
org.bouncycastle.asn1.DERObjectIdentifier
-org.bouncycastle.asn1.DEROctetString
-org.bouncycastle.asn1.DERPrintableString
-org.bouncycastle.asn1.DERSequence
-org.bouncycastle.asn1.DERSet
-org.bouncycastle.asn1.DERTaggedObject
-org.bouncycastle.asn1.DERUTCTime
-org.bouncycastle.asn1.DERUTF8String
-org.bouncycastle.asn1.OrderedTable
+org.bouncycastle.asn1.iana.IANAObjectIdentifiers
org.bouncycastle.asn1.nist.NISTObjectIdentifiers
+org.bouncycastle.asn1.oiw.OIWObjectIdentifiers
org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
-org.bouncycastle.asn1.x509.AlgorithmIdentifier
-org.bouncycastle.asn1.x509.RSAPublicKeyStructure
-org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
-org.bouncycastle.asn1.x509.TBSCertificateStructure
-org.bouncycastle.asn1.x509.Time
-org.bouncycastle.asn1.x509.X509CertificateStructure
-org.bouncycastle.asn1.x509.X509Extension
org.bouncycastle.asn1.x509.X509Extensions
org.bouncycastle.asn1.x509.X509Name
-org.bouncycastle.asn1.x509.X509NameElementList
-org.bouncycastle.asn1.x9.X9ObjectIdentifiers
+org.bouncycastle.crypto.digests.SHA1Digest
org.bouncycastle.crypto.engines.AESFastEngine
+org.bouncycastle.crypto.macs.HMac
+org.bouncycastle.jce.provider.BouncyCastleProvider
org.bouncycastle.jce.provider.CertPathValidatorUtilities
-org.bouncycastle.jce.provider.JCEBlockCipher$AES
-org.bouncycastle.jce.provider.JCERSAPublicKey
-org.bouncycastle.jce.provider.JDKKeyFactory$RSA
+org.bouncycastle.jce.provider.JCEBlockCipher
org.bouncycastle.jce.provider.JDKKeyStore
-org.bouncycastle.jce.provider.JDKKeyStore$StoreEntry
+org.bouncycastle.jce.provider.JDKX509CertificateFactory
org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi
-org.bouncycastle.jce.provider.RSAUtil
+org.bouncycastle.jce.provider.WrapCipherSpi
org.bouncycastle.jce.provider.X509CertificateObject
org.ccil.cowan.tagsoup.HTMLScanner
+org.ccil.cowan.tagsoup.HTMLSchema
org.ccil.cowan.tagsoup.Parser
-org.json.JSONArray
org.json.JSONObject
-org.json.JSONStringer
org.kxml2.io.KXmlParser
org.kxml2.io.KXmlSerializer
+org.openssl.NativeBN
+org.xml.sax.Attributes
+org.xml.sax.InputSource
+org.xml.sax.helpers.AttributesImpl
org.xml.sax.helpers.DefaultHandler
-org.xml.sax.helpers.NewInstance
+org.xmlpull.v1.XmlPullParser
org.xmlpull.v1.XmlPullParserFactory
-org.xmlpull.v1.sax2.Driver
sun.misc.Unsafe
diff --git a/services/java/com/android/server/AccessibilityManagerService.java b/services/java/com/android/server/AccessibilityManagerService.java
index 407983d..c55dcb3 100644
--- a/services/java/com/android/server/AccessibilityManagerService.java
+++ b/services/java/com/android/server/AccessibilityManagerService.java
@@ -16,8 +16,7 @@
package com.android.server;
-import static android.util.Config.LOGV;
-
+import com.android.internal.content.PackageMonitor;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.HandlerCaller.SomeArgs;
@@ -47,6 +46,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
+import android.util.Config;
import android.util.Log;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityEvent;
@@ -57,6 +57,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -137,10 +138,6 @@
registerPackageChangeAndBootCompletedBroadcastReceiver();
registerSettingsContentObservers();
-
- synchronized (mLock) {
- populateAccessibilityServiceListLocked();
- }
}
/**
@@ -150,39 +147,83 @@
private void registerPackageChangeAndBootCompletedBroadcastReceiver() {
Context context = mContext;
- BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ PackageMonitor monitor = new PackageMonitor() {
@Override
- public void onReceive(Context context, Intent intent) {
+ public void onSomePackagesChanged() {
synchronized (mLock) {
populateAccessibilityServiceListLocked();
manageServicesLocked();
+ }
+ }
+
+ @Override
+ public boolean onHandleForceStop(Intent intent, String[] packages,
+ int uid, boolean doit) {
+ synchronized (mLock) {
+ boolean changed = false;
+ Iterator<ComponentName> it = mEnabledServices.iterator();
+ while (it.hasNext()) {
+ ComponentName comp = it.next();
+ String compPkg = comp.getPackageName();
+ for (String pkg : packages) {
+ if (compPkg.equals(pkg)) {
+ if (!doit) {
+ return true;
+ }
+ it.remove();
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ it = mEnabledServices.iterator();
+ StringBuilder str = new StringBuilder();
+ while (it.hasNext()) {
+ if (str.length() > 0) {
+ str.append(':');
+ }
+ str.append(it.next().flattenToShortString());
+ }
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ str.toString());
+ manageServicesLocked();
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction() == Intent.ACTION_BOOT_COMPLETED) {
+ synchronized (mLock) {
+ populateAccessibilityServiceListLocked();
- if (intent.getAction() == Intent.ACTION_BOOT_COMPLETED) {
+ // get the accessibility enabled setting on boot
mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
- updateClientsLocked();
+
+ // if accessibility is enabled inform our clients we are on
+ if (mIsEnabled) {
+ updateClientsLocked();
+ }
+
+ manageServicesLocked();
}
+
+ return;
}
+
+ super.onReceive(context, intent);
}
};
// package changes
- IntentFilter packageFilter = new IntentFilter();
- packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
- packageFilter.addDataScheme("package");
- context.registerReceiver(broadcastReceiver, packageFilter);
- // Register for events related to sdcard installation.
- IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- mContext.registerReceiver(broadcastReceiver, sdFilter);
+ monitor.register(context, true);
// boot completed
IntentFilter bootFiler = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
- mContext.registerReceiver(broadcastReceiver, bootFiler);
+ mContext.registerReceiver(monitor, bootFiler);
}
/**
@@ -409,7 +450,7 @@
try {
listener.onAccessibilityEvent(event);
- if (LOGV) {
+ if (Config.DEBUG) {
Log.i(LOG_TAG, "Event " + event + " sent to " + listener);
}
} catch (RemoteException re) {
@@ -434,7 +475,7 @@
mServices.remove(service);
mHandler.removeMessages(service.mId);
- if (LOGV) {
+ if (Config.DEBUG) {
Log.i(LOG_TAG, "Dead service " + service.mService + " removed");
}
@@ -529,8 +570,14 @@
TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
splitter.setString(servicesValue);
while (splitter.hasNext()) {
- ComponentName enabledService = ComponentName.unflattenFromString(splitter.next());
- enabledServices.add(enabledService);
+ String str = splitter.next();
+ if (str == null || str.length() <= 0) {
+ continue;
+ }
+ ComponentName enabledService = ComponentName.unflattenFromString(str);
+ if (enabledService != null) {
+ enabledServices.add(enabledService);
+ }
}
}
}
@@ -547,6 +594,7 @@
Map<ComponentName, Service> componentNameToServiceMap = mComponentNameToServiceMap;
List<Service> services = mServices;
+ boolean isEnabled = mIsEnabled;
for (int i = 0, count = installedServices.size(); i < count; i++) {
ServiceInfo intalledService = installedServices.get(i);
@@ -554,7 +602,7 @@
intalledService.name);
Service service = componentNameToServiceMap.get(componentName);
- if (enabledServices.contains(componentName)) {
+ if (isEnabled && enabledServices.contains(componentName)) {
if (service == null) {
new Service(componentName).bind();
}
@@ -578,6 +626,7 @@
} catch (RemoteException re) {
mClients.remove(i);
count--;
+ i--;
}
}
}
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index 44cc0bb..f480209 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.app.Activity;
import android.app.ActivityManagerNative;
import android.app.AlarmManager;
import android.app.IAlarmManager;
@@ -344,6 +345,22 @@
}
}
+ public boolean lookForPackageLocked(String packageName) {
+ return lookForPackageLocked(mRtcWakeupAlarms, packageName)
+ || lookForPackageLocked(mRtcAlarms, packageName)
+ || lookForPackageLocked(mElapsedRealtimeWakeupAlarms, packageName)
+ || lookForPackageLocked(mElapsedRealtimeAlarms, packageName);
+ }
+
+ private boolean lookForPackageLocked(ArrayList<Alarm> alarmList, String packageName) {
+ for (int i=alarmList.size()-1; i>=0; i--) {
+ if (alarmList.get(i).operation.getTargetPackage().equals(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private ArrayList<Alarm> getAlarmList(int type) {
switch (type) {
case AlarmManager.RTC_WAKEUP: return mRtcWakeupAlarms;
@@ -778,6 +795,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
filter.addDataScheme("package");
mContext.registerReceiver(this, filter);
// Register for events related to sdcard installation.
@@ -791,7 +809,16 @@
synchronized (mLock) {
String action = intent.getAction();
String pkgList[] = null;
- if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+ for (String packageName : pkgList) {
+ if (lookForPackageLocked(packageName)) {
+ setResultCode(Activity.RESULT_OK);
+ return;
+ }
+ }
+ return;
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
} else {
Uri data = intent.getData();
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index ea0fc65..d4b28e2 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -17,6 +17,7 @@
package com.android.server;
import android.app.ActivityManagerNative;
+import android.app.ActivityThread;
import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.IApplicationThread;
@@ -34,6 +35,7 @@
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
@@ -113,6 +115,7 @@
private Context mContext;
private PackageManager mPackageManager;
+ IPackageManager mPackageManagerBinder;
private IActivityManager mActivityManager;
private PowerManager mPowerManager;
private AlarmManager mAlarmManager;
@@ -179,13 +182,15 @@
public IRestoreObserver observer;
public long token;
public PackageInfo pkgInfo;
+ public int pmToken; // in post-install restore, the PM's token for this transaction
RestoreParams(IBackupTransport _transport, IRestoreObserver _obs,
- long _token, PackageInfo _pkg) {
+ long _token, PackageInfo _pkg, int _pmToken) {
transport = _transport;
observer = _obs;
token = _token;
pkgInfo = _pkg;
+ pmToken = _pmToken;
}
RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token) {
@@ -193,6 +198,7 @@
observer = _obs;
token = _token;
pkgInfo = null;
+ pmToken = 0;
}
}
@@ -302,7 +308,7 @@
RestoreParams params = (RestoreParams)msg.obj;
Log.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
(new PerformRestoreTask(params.transport, params.observer,
- params.token, params.pkgInfo)).run();
+ params.token, params.pkgInfo, params.pmToken)).run();
break;
}
@@ -349,6 +355,7 @@
public BackupManagerService(Context context) {
mContext = context;
mPackageManager = context.getPackageManager();
+ mPackageManagerBinder = ActivityThread.getPackageManager();
mActivityManager = ActivityManagerNative.getDefault();
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
@@ -1115,6 +1122,18 @@
}
}
+ // Get the restore-set token for the best-available restore set for this package:
+ // the active set if possible, else the ancestral one. Returns zero if none available.
+ long getAvailableRestoreToken(String packageName) {
+ long token = mAncestralToken;
+ synchronized (mQueueLock) {
+ if (mEverStoredApps.contains(packageName)) {
+ token = mCurrentToken;
+ }
+ }
+ return token;
+ }
+
// -----
// Utility methods used by the asynchronous-with-timeout backup/restore operations
boolean waitUntilOperationComplete(int token) {
@@ -1132,12 +1151,14 @@
}
}
mBackupHandler.removeMessages(MSG_TIMEOUT);
- if (DEBUG) Log.v(TAG, "operation " + token + " complete: finalState=" + finalState);
+ if (DEBUG) Log.v(TAG, "operation " + Integer.toHexString(token)
+ + " complete: finalState=" + finalState);
return finalState == OP_ACKNOWLEDGED;
}
void prepareOperationTimeout(int token, long interval) {
- if (DEBUG) Log.v(TAG, "starting timeout: token=" + token + " interval=" + interval);
+ if (DEBUG) Log.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
+ + " interval=" + interval);
mCurrentOperations.put(token, OP_PENDING);
Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0);
mBackupHandler.sendMessageDelayed(msg, interval);
@@ -1476,6 +1497,7 @@
private long mToken;
private PackageInfo mTargetPackage;
private File mStateDir;
+ private int mPmToken;
class RestoreRequest {
public PackageInfo app;
@@ -1488,11 +1510,12 @@
}
PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,
- long restoreSetToken, PackageInfo targetPackage) {
+ long restoreSetToken, PackageInfo targetPackage, int pmToken) {
mTransport = transport;
mObserver = observer;
mToken = restoreSetToken;
mTargetPackage = targetPackage;
+ mPmToken = pmToken;
try {
mStateDir = new File(mBaseStateDir, transport.transportDirName());
@@ -1505,25 +1528,7 @@
long startRealtime = SystemClock.elapsedRealtime();
if (DEBUG) Log.v(TAG, "Beginning restore process mTransport=" + mTransport
+ " mObserver=" + mObserver + " mToken=" + Long.toHexString(mToken)
- + " mTargetPackage=" + mTargetPackage);
- /**
- * Restore sequence:
- *
- * 1. get the restore set description for our identity
- * 2. for each app in the restore set:
- * 2.a. if it's restorable on this device, add it to the restore queue
- * 3. for each app in the restore queue:
- * 3.a. clear the app data
- * 3.b. get the restore data for the app from the transport
- * 3.c. launch the backup agent for the app
- * 3.d. agent.doRestore() with the data from the server
- * 3.e. unbind the agent [and kill the app?]
- * 4. shut down the transport
- *
- * On errors, we try our best to recover and move on to the next
- * application, but if necessary we abort the whole operation --
- * the user is waiting, after all.
- */
+ + " mTargetPackage=" + mTargetPackage + " mPmToken=" + mPmToken);
PackageManagerBackupAgent pmAgent = null;
int error = -1; // assume error
@@ -1609,6 +1614,7 @@
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
return;
} else if (packageName.equals("")) {
+ if (DEBUG) Log.v(TAG, "No next package, finishing restore");
break;
}
@@ -1641,11 +1647,22 @@
}
if (metaInfo.versionCode > packageInfo.versionCode) {
- String message = "Version " + metaInfo.versionCode
- + " > installed version " + packageInfo.versionCode;
- Log.w(TAG, "Package " + packageName + ": " + message);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName, message);
- continue;
+ // Data is from a "newer" version of the app than we have currently
+ // installed. If the app has not declared that it is prepared to
+ // handle this case, we do not attempt the restore.
+ if ((packageInfo.applicationInfo.flags
+ & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) == 0) {
+ String message = "Version " + metaInfo.versionCode
+ + " > installed version " + packageInfo.versionCode;
+ Log.w(TAG, "Package " + packageName + ": " + message);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+ packageName, message);
+ continue;
+ } else {
+ if (DEBUG) Log.v(TAG, "Version " + metaInfo.versionCode
+ + " > installed " + packageInfo.versionCode
+ + " but restoreAnyVersion");
+ }
}
if (!signaturesMatch(metaInfo.signatures, packageInfo)) {
@@ -1689,8 +1706,10 @@
// The agent was probably running with a stub Application object,
// which isn't a valid run mode for the main app logic. Shut
// down the app so that next time it's launched, it gets the
- // usual full initialization.
- if ((packageInfo.applicationInfo.flags
+ // usual full initialization. Note that this is only done for
+ // full-system restores: when a single app has requested a restore,
+ // it is explicitly not killed following that operation.
+ if (mTargetPackage == null && (packageInfo.applicationInfo.flags
& ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0) {
if (DEBUG) Log.d(TAG, "Restore complete, killing host process of "
+ packageInfo.applicationInfo.processName);
@@ -1733,6 +1752,15 @@
writeRestoreTokens();
}
+ // We must under all circumstances tell the Package Manager to
+ // proceed with install notifications if it's waiting for us.
+ if (mPmToken > 0) {
+ if (DEBUG) Log.v(TAG, "finishing PM token " + mPmToken);
+ try {
+ mPackageManagerBinder.finishPackageInstall(mPmToken);
+ } catch (RemoteException e) { /* can't happen */ }
+ }
+
// done; we can finally release the wakelock
mWakelock.release();
}
@@ -2169,7 +2197,7 @@
public String getCurrentTransport() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getCurrentTransport");
- Log.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport);
+ if (DEBUG) Log.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport);
return mCurrentTransport;
}
@@ -2248,6 +2276,45 @@
}
}
+ // An application being installed will need a restore pass, then the Package Manager
+ // will need to be told when the restore is finished.
+ public void restoreAtInstall(String packageName, int token) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ Log.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
+ + " attemping install-time restore");
+ return;
+ }
+
+ long restoreSet = getAvailableRestoreToken(packageName);
+ if (DEBUG) Log.v(TAG, "restoreAtInstall pkg=" + packageName
+ + " token=" + Integer.toHexString(token));
+
+ if (restoreSet != 0) {
+ // okay, we're going to attempt a restore of this package from this restore set.
+ // The eventual message back into the Package Manager to run the post-install
+ // steps for 'token' will be issued from the restore handling code.
+
+ // We can use a synthetic PackageInfo here because:
+ // 1. We know it's valid, since the Package Manager supplied the name
+ // 2. Only the packageName field will be used by the restore code
+ PackageInfo pkg = new PackageInfo();
+ pkg.packageName = packageName;
+
+ mWakelock.acquire();
+ Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
+ msg.obj = new RestoreParams(getTransport(mCurrentTransport), null,
+ restoreSet, pkg, token);
+ mBackupHandler.sendMessage(msg);
+ } else {
+ // No way to attempt a restore; just tell the Package Manager to proceed
+ // with the post-install handling for this package.
+ if (DEBUG) Log.v(TAG, "No restore set -- skipping restore");
+ try {
+ mPackageManagerBinder.finishPackageInstall(token);
+ } catch (RemoteException e) { /* can't happen */ }
+ }
+ }
+
// Hand off a restore session
public IRestoreSession beginRestoreSession(String transport) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "beginRestoreSession");
@@ -2266,7 +2333,7 @@
// completed the given outstanding asynchronous backup/restore operation.
public void opComplete(int token) {
synchronized (mCurrentOpLock) {
- if (DEBUG) Log.v(TAG, "opComplete: " + token);
+ if (DEBUG) Log.v(TAG, "opComplete: " + Integer.toHexString(token));
mCurrentOperations.put(token, OP_ACKNOWLEDGED);
mCurrentOpLock.notifyAll();
}
@@ -2289,6 +2356,7 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getAvailableRestoreSets");
+ long oldId = Binder.clearCallingIdentity();
try {
if (mRestoreTransport == null) {
Log.w(TAG, "Null transport getting restore sets");
@@ -2302,6 +2370,8 @@
} catch (Exception e) {
Log.e(TAG, "Error in getAvailableRestoreSets", e);
return null;
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
}
}
@@ -2360,12 +2430,7 @@
// So far so good; we're allowed to try to restore this package. Now
// check whether there is data for it in the current dataset, falling back
// to the ancestral dataset if not.
- long token = mAncestralToken;
- synchronized (mQueueLock) {
- if (mEverStoredApps.contains(packageName)) {
- token = mCurrentToken;
- }
- }
+ long token = getAvailableRestoreToken(packageName);
// If we didn't come up with a place to look -- no ancestral dataset and
// the app has never been backed up from this device -- there's nothing
@@ -2378,7 +2443,7 @@
long oldId = Binder.clearCallingIdentity();
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
- msg.obj = new RestoreParams(mRestoreTransport, observer, token, app);
+ msg.obj = new RestoreParams(mRestoreTransport, observer, token, app, 0);
mBackupHandler.sendMessage(msg);
Binder.restoreCallingIdentity(oldId);
return 0;
@@ -2391,12 +2456,14 @@
if (DEBUG) Log.d(TAG, "endRestoreSession");
synchronized (this) {
+ long oldId = Binder.clearCallingIdentity();
try {
if (mRestoreTransport != null) mRestoreTransport.finishRestore();
} catch (Exception e) {
Log.e(TAG, "Error in finishRestore", e);
} finally {
mRestoreTransport = null;
+ Binder.restoreCallingIdentity(oldId);
}
}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 108246d..df685ab 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -289,6 +289,7 @@
* the number of different network types is not going
* to change very often.
*/
+ boolean noMobileData = !getMobileDataEnabled();
for (int netType : mPriorityList) {
switch (mNetAttributes[netType].mRadio) {
case ConnectivityManager.TYPE_WIFI:
@@ -306,6 +307,10 @@
mNetTrackers[netType] = new MobileDataStateTracker(context, mHandler,
netType, mNetAttributes[netType].mName);
mNetTrackers[netType].startMonitoring();
+ if (noMobileData) {
+ if (DBG) Log.d(TAG, "tearing down Mobile networks due to setting");
+ mNetTrackers[netType].teardown();
+ }
break;
default:
Log.e(TAG, "Trying to create a DataStateTracker for an unknown radio type " +
@@ -530,6 +535,10 @@
// TODO - move this into the MobileDataStateTracker
int usedNetworkType = networkType;
if(networkType == ConnectivityManager.TYPE_MOBILE) {
+ if (!getMobileDataEnabled()) {
+ if (DBG) Log.d(TAG, "requested special network with data disabled - rejected");
+ return Phone.APN_TYPE_NOT_AVAILABLE;
+ }
if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) {
@@ -767,6 +776,46 @@
mContext.sendBroadcast(broadcast);
}
+ /**
+ * @see ConnectivityManager#getMobileDataEnabled()
+ */
+ public boolean getMobileDataEnabled() {
+ enforceAccessPermission();
+ boolean retVal = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.MOBILE_DATA, 1) == 1;
+ if (DBG) Log.d(TAG, "getMobileDataEnabled returning " + retVal);
+ return retVal;
+ }
+
+ /**
+ * @see ConnectivityManager#setMobileDataEnabled(boolean)
+ */
+ public synchronized void setMobileDataEnabled(boolean enabled) {
+ enforceChangePermission();
+ if (DBG) Log.d(TAG, "setMobileDataEnabled(" + enabled + ")");
+
+ if (getMobileDataEnabled() == enabled) return;
+
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.MOBILE_DATA, enabled ? 1 : 0);
+
+ if (enabled) {
+ if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) {
+ if (DBG) Log.d(TAG, "starting up " + mNetTrackers[ConnectivityManager.TYPE_MOBILE]);
+ mNetTrackers[ConnectivityManager.TYPE_MOBILE].reconnect();
+ }
+ } else {
+ for (NetworkStateTracker nt : mNetTrackers) {
+ if (nt == null) continue;
+ int netType = nt.getNetworkInfo().getType();
+ if (mNetAttributes[netType].mRadio == ConnectivityManager.TYPE_MOBILE) {
+ if (DBG) Log.d(TAG, "tearing down " + nt);
+ nt.teardown();
+ }
+ }
+ }
+ }
+
private int getNumConnectedNetworks() {
int numConnectedNets = 0;
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index a267e0f..ac65aa9 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -17,6 +17,8 @@
package com.android.server;
import com.android.common.FastXmlSerializer;
+import com.android.common.XmlUtils;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.widget.LockPatternUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -34,6 +36,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Binder;
import android.os.IBinder;
import android.os.IPowerManager;
@@ -58,9 +61,10 @@
* Implementation of the device policy APIs.
*/
public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
- private static final String TAG = "DevicePolicyManagerService";
+ static final String TAG = "DevicePolicyManagerService";
- private final Context mContext;
+ final Context mContext;
+ final MyPackageMonitor mMonitor;
IPowerManager mIPowerManager;
@@ -89,6 +93,9 @@
void writeToXml(XmlSerializer out)
throws IllegalArgumentException, IllegalStateException, IOException {
+ out.startTag(null, "policies");
+ info.writePoliciesToXml(out);
+ out.endTag(null, "policies");
if (passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
out.startTag(null, "password-quality");
out.attribute(null, "value", Integer.toString(passwordQuality));
@@ -121,7 +128,9 @@
continue;
}
String tag = parser.getName();
- if ("password-quality".equals(tag)) {
+ if ("policies".equals(tag)) {
+ info.readPoliciesFromXml(parser);
+ } else if ("password-quality".equals(tag)) {
passwordQuality = Integer.parseInt(
parser.getAttributeValue(null, "value"));
} else if ("min-password-length".equals(tag)) {
@@ -133,6 +142,35 @@
} else if ("max-failed-password-wipe".equals(tag)) {
maximumFailedPasswordsForWipe = Integer.parseInt(
parser.getAttributeValue(null, "value"));
+ } else {
+ Log.w(TAG, "Unknown admin tag: " + tag);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ class MyPackageMonitor extends PackageMonitor {
+ public void onSomePackagesChanged() {
+ synchronized (DevicePolicyManagerService.this) {
+ for (int i=mAdminList.size()-1; i>=0; i--) {
+ ActiveAdmin aa = mAdminList.get(i);
+ int change = isPackageDisappearing(aa.info.getPackageName());
+ if (change == PACKAGE_PERMANENT_CHANGE
+ || change == PACKAGE_TEMPORARY_CHANGE) {
+ Log.w(TAG, "Admin unexpectedly uninstalled: "
+ + aa.info.getComponent());
+ mAdminList.remove(i);
+ } else if (isPackageModified(aa.info.getPackageName())) {
+ try {
+ mContext.getPackageManager().getReceiverInfo(
+ aa.info.getComponent(), 0);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Admin package change removed component: "
+ + aa.info.getComponent());
+ mAdminList.remove(i);
+ }
+ }
}
}
}
@@ -143,6 +181,8 @@
*/
public DevicePolicyManagerService(Context context) {
mContext = context;
+ mMonitor = new MyPackageMonitor();
+ mMonitor.register(context, true);
}
private IPowerManager getIPowerManager() {
@@ -336,6 +376,10 @@
} else if ("failed-password-attempts".equals(tag)) {
mFailedPasswordAttempts = Integer.parseInt(
parser.getAttributeValue(null, "value"));
+ XmlUtils.skipCurrentTag(parser);
+ } else {
+ Log.w(TAG, "Unknown tag: " + tag);
+ XmlUtils.skipCurrentTag(parser);
}
}
} catch (NullPointerException e) {
@@ -420,6 +464,18 @@
}
}
+ public boolean packageHasActiveAdmins(String packageName) {
+ synchronized (this) {
+ final int N = mAdminList.size();
+ for (int i=0; i<N; i++) {
+ if (mAdminList.get(i).info.getPackageName().equals(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
public void removeActiveAdmin(ComponentName adminReceiver) {
synchronized (this) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver);
diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java
index b566fb7e..027f35c 100644
--- a/services/java/com/android/server/DockObserver.java
+++ b/services/java/com/android/server/DockObserver.java
@@ -22,8 +22,10 @@
import android.app.IActivityManager;
import android.app.IUiModeManager;
import android.app.KeyguardManager;
-import android.app.StatusBarManager;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.StatusBarManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ActivityNotFoundException;
@@ -39,6 +41,7 @@
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Binder;
+import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
@@ -55,6 +58,8 @@
import android.text.format.Time;
import android.util.Log;
+import com.android.internal.R;
+import com.android.internal.app.DisableCarModeActivity;
import com.android.internal.widget.LockPatternUtils;
import java.io.FileNotFoundException;
@@ -101,6 +106,7 @@
private final Context mContext;
private PowerManagerService mPowerManager;
+ private NotificationManager mNotificationManager;
private KeyguardManager.KeyguardLock mKeyguardLock;
private boolean mKeyguardDisabled;
@@ -125,7 +131,8 @@
// Launch a dock activity
String category;
- if (mCarModeEnabled || mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
+ if (mCarModeEnabled) {
+ // Only launch car home when car mode is enabled.
category = Intent.CATEGORY_CAR_DOCK;
} else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
category = Intent.CATEGORY_DESK_DOCK;
@@ -332,9 +339,13 @@
if (mCarModeEnabled && mDockState != Intent.EXTRA_DOCK_STATE_CAR) {
// Pretend to be in DOCK_STATE_CAR.
intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_CAR);
+ } else if (!mCarModeEnabled && mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
+ // Pretend to be in DOCK_STATE_UNDOCKED.
+ intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
} else {
intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
}
+ intent.putExtra(Intent.EXTRA_PHYSICAL_DOCK_STATE, mDockState);
intent.putExtra(Intent.EXTRA_CAR_MODE_ENABLED, mCarModeEnabled);
// Check if this is Bluetooth Dock
@@ -369,7 +380,10 @@
final Uri soundUri = Uri.parse("file://" + soundPath);
if (soundUri != null) {
final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
- if (sfx != null) sfx.play();
+ if (sfx != null) {
+ sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+ sfx.play();
+ }
}
}
}
@@ -402,7 +416,7 @@
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
LOCATION_UPDATE_MS, LOCATION_UPDATE_DISTANCE_METER, mLocationListener);
retrieveLocation();
- if (mLocation != null) {
+ if (mCarModeEnabled && mLocation != null && mNightMode == MODE_NIGHT_AUTO) {
try {
DockObserver.this.updateTwilight();
} catch (RemoteException e) {
@@ -448,8 +462,8 @@
if (location == null) {
Time currentTime = new Time();
currentTime.set(System.currentTimeMillis());
- double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE * currentTime.gmtoff
- - (currentTime.isDst > 0 ? 3600 : 0);
+ double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE *
+ (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0));
location = new Location("fake");
location.setLongitude(lngOffset);
location.setLatitude(59.95);
@@ -462,6 +476,52 @@
}
};
+ private void adjustStatusBarCarMode() {
+ if (mStatusBarManager == null) {
+ mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
+ }
+
+ // Fear not: StatusBarService manages a list of requests to disable
+ // features of the status bar; these are ORed together to form the
+ // active disabled list. So if (for example) the device is locked and
+ // the status bar should be totally disabled, the calls below will
+ // have no effect until the device is unlocked.
+ if (mStatusBarManager != null) {
+ long ident = Binder.clearCallingIdentity();
+ mStatusBarManager.disable(mCarModeEnabled
+ ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
+ : StatusBarManager.DISABLE_NONE);
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ if (mNotificationManager == null) {
+ mNotificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ if (mNotificationManager != null) {
+ long ident = Binder.clearCallingIdentity();
+ if (mCarModeEnabled) {
+ Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class);
+
+ Notification n = new Notification();
+ n.icon = R.drawable.stat_notify_car_mode;
+ n.defaults = Notification.DEFAULT_LIGHTS;
+ n.flags = Notification.FLAG_ONGOING_EVENT;
+ n.when = 0;
+ n.setLatestEventInfo(
+ mContext,
+ mContext.getString(R.string.car_mode_disable_notification_title),
+ mContext.getString(R.string.car_mode_disable_notification_message),
+ PendingIntent.getActivity(mContext, 0, carModeOffIntent, 0));
+ mNotificationManager.notify(0, n);
+ } else {
+ mNotificationManager.cancel(0);
+ }
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private void setCarMode(boolean enabled) throws RemoteException {
mCarModeEnabled = enabled;
if (enabled) {
@@ -475,23 +535,7 @@
setMode(Configuration.UI_MODE_TYPE_NORMAL,
Configuration.UI_MODE_NIGHT_UNDEFINED);
}
-
- if (mStatusBarManager == null) {
- mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
- }
-
- // Fear not: StatusBarService manages a list of requests to disable
- // features of the status bar; these are ORed together to form the
- // active disabled list. So if (for example) the device is locked and
- // the status bar should be totally disabled, the calls below will
- // have no effect until the device is unlocked.
- if (mStatusBarManager != null) {
- long ident = Binder.clearCallingIdentity();
- mStatusBarManager.disable(enabled
- ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
- : StatusBarManager.DISABLE_NONE);
- Binder.restoreCallingIdentity(ident);
- }
+ adjustStatusBarCarMode();
}
private void setMode(int modeType, int modeNight) throws RemoteException {
@@ -543,20 +587,26 @@
}
// schedule next update
- final int mLastTwilightState = tw.mState;
- // add some extra time to be on the save side.
- long nextUpdate = DateUtils.MINUTE_IN_MILLIS;
- if (currentTime > tw.mSunset) {
- // next update should be on the following day
- tw.calculateTwilight(currentTime
- + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(),
- mLocation.getLongitude());
- }
-
- if (mLastTwilightState == TwilightCalculator.NIGHT) {
- nextUpdate += tw.mSunrise;
+ long nextUpdate = 0;
+ if (tw.mSunrise == -1 || tw.mSunset == -1) {
+ // In the case the day or night never ends the update is scheduled 12 hours later.
+ nextUpdate = currentTime + 12 * DateUtils.HOUR_IN_MILLIS;
} else {
- nextUpdate += tw.mSunset;
+ final int mLastTwilightState = tw.mState;
+ // add some extra time to be on the save side.
+ nextUpdate += DateUtils.MINUTE_IN_MILLIS;
+ if (currentTime > tw.mSunset) {
+ // next update should be on the following day
+ tw.calculateTwilight(currentTime
+ + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(),
+ mLocation.getLongitude());
+ }
+
+ if (mLastTwilightState == TwilightCalculator.NIGHT) {
+ nextUpdate += tw.mSunrise;
+ } else {
+ nextUpdate += tw.mSunset;
+ }
}
Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE);
@@ -565,8 +615,11 @@
mAlarmManager.cancel(pendingIntent);
mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent);
- // set current mode
- setMode(Configuration.UI_MODE_TYPE_CAR, nightMode << 4);
+ // Make sure that we really set the new mode only if we're in car mode and
+ // automatic switching is enables.
+ if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
+ setMode(Configuration.UI_MODE_TYPE_CAR, nightMode << 4);
+ }
}
}
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 5e96a2f..59d4c9b 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.os.HandlerCaller;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
@@ -48,7 +49,6 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.ContentObserver;
-import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -332,51 +332,68 @@
}
}
- class PackageReceiver extends android.content.BroadcastReceiver {
+ class MyPackageMonitor extends PackageMonitor {
+
@Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- String pkgList[] = null;
- if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
- Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
- Uri uri = intent.getData();
- String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
- if (pkg != null) {
- pkgList = new String[] { pkg };
- }
- } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action) ||
- Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- }
- if (pkgList == null || pkgList.length == 0) {
- return;
- }
+ public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
synchronized (mMethodMap) {
- buildInputMethodListLocked(mMethodList, mMethodMap);
-
- InputMethodInfo curIm = null;
- String curInputMethodId = Settings.Secure.getString(context
+ String curInputMethodId = Settings.Secure.getString(mContext
.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
final int N = mMethodList.size();
if (curInputMethodId != null) {
for (int i=0; i<N; i++) {
- if (mMethodList.get(i).getId().equals(curInputMethodId)) {
- curIm = mMethodList.get(i);
+ InputMethodInfo imi = mMethodList.get(i);
+ if (imi.getId().equals(curInputMethodId)) {
+ for (String pkg : packages) {
+ if (imi.getPackageName().equals(pkg)) {
+ if (!doit) {
+ return true;
+ }
+
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD, "");
+ chooseNewDefaultIMELocked();
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onSomePackagesChanged() {
+ synchronized (mMethodMap) {
+ InputMethodInfo curIm = null;
+ String curInputMethodId = Settings.Secure.getString(mContext
+ .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+ final int N = mMethodList.size();
+ if (curInputMethodId != null) {
+ for (int i=0; i<N; i++) {
+ InputMethodInfo imi = mMethodList.get(i);
+ if (imi.getId().equals(curInputMethodId)) {
+ curIm = imi;
+ }
+ int change = isPackageDisappearing(imi.getPackageName());
+ if (change == PACKAGE_TEMPORARY_CHANGE
+ || change == PACKAGE_PERMANENT_CHANGE) {
+ Log.i(TAG, "Input method uninstalled, disabling: "
+ + imi.getComponent());
+ setInputMethodEnabledLocked(imi.getId(), false);
}
}
}
+ buildInputMethodListLocked(mMethodList, mMethodMap);
+
boolean changed = false;
if (curIm != null) {
- boolean foundPkg = false;
- for (String pkg : pkgList) {
- if (curIm.getPackageName().equals(pkg)) {
- foundPkg = true;
- break;
- }
- }
- if (foundPkg) {
+ int change = isPackageDisappearing(curIm.getPackageName());
+ if (change == PACKAGE_TEMPORARY_CHANGE
+ || change == PACKAGE_PERMANENT_CHANGE) {
ServiceInfo si = null;
try {
si = mContext.getPackageManager().getServiceInfo(
@@ -387,7 +404,7 @@
// Uh oh, current input method is no longer around!
// Pick another one...
Log.i(TAG, "Current input method removed: " + curInputMethodId);
- if (!chooseNewDefaultIME()) {
+ if (!chooseNewDefaultIMELocked()) {
changed = true;
curIm = null;
curInputMethodId = "";
@@ -397,16 +414,17 @@
curInputMethodId);
}
}
-
- } else if (curIm == null) {
- // We currently don't have a default input method... is
- // one now available?
- changed = chooseNewDefaultIME();
}
+ }
+
+ if (curIm == null) {
+ // We currently don't have a default input method... is
+ // one now available?
+ changed = chooseNewDefaultIMELocked();
+ }
- if (changed) {
- updateFromSettingsLocked();
- }
+ if (changed) {
+ updateFromSettingsLocked();
}
}
}
@@ -438,19 +456,7 @@
}
});
- PackageReceiver mBroadcastReceiver = new PackageReceiver();
- IntentFilter packageFilt = new IntentFilter();
- packageFilt.addAction(Intent.ACTION_PACKAGE_ADDED);
- packageFilt.addAction(Intent.ACTION_PACKAGE_CHANGED);
- packageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED);
- packageFilt.addAction(Intent.ACTION_PACKAGE_RESTARTED);
- packageFilt.addDataScheme("package");
- mContext.registerReceiver(mBroadcastReceiver, packageFilt);
- // Register for events related to sdcard installation.
- IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- mContext.registerReceiver(mBroadcastReceiver, sdFilter);
+ (new MyPackageMonitor()).register(mContext, true);
IntentFilter screenOnOffFilt = new IntentFilter();
screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON);
@@ -1385,7 +1391,7 @@
& ApplicationInfo.FLAG_SYSTEM) != 0;
}
- private boolean chooseNewDefaultIME() {
+ private boolean chooseNewDefaultIMELocked() {
List<InputMethodInfo> enabled = getEnabledInputMethodListLocked();
if (enabled != null && enabled.size() > 0) {
Settings.Secure.putString(mContext.getContentResolver(),
@@ -1446,7 +1452,7 @@
String defaultIme = Settings.Secure.getString(mContext
.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
if (!map.containsKey(defaultIme)) {
- if (chooseNewDefaultIME()) {
+ if (chooseNewDefaultIMELocked()) {
updateFromSettingsLocked();
}
}
@@ -1557,91 +1563,95 @@
"Requires permission "
+ android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
-
+
long ident = Binder.clearCallingIdentity();
try {
- // Make sure this is a valid input method.
- InputMethodInfo imm = mMethodMap.get(id);
- if (imm == null) {
- if (imm == null) {
- throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
- }
- }
-
- StringBuilder builder = new StringBuilder(256);
-
- boolean removed = false;
- String firstId = null;
-
- // Look through the currently enabled input methods.
- String enabledStr = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS);
- if (enabledStr != null) {
- final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
- splitter.setString(enabledStr);
- while (splitter.hasNext()) {
- String curId = splitter.next();
- if (curId.equals(id)) {
- if (enabled) {
- // We are enabling this input method, but it is
- // already enabled. Nothing to do. The previous
- // state was enabled.
- return true;
- }
- // We are disabling this input method, and it is
- // currently enabled. Skip it to remove from the
- // new list.
- removed = true;
- } else if (!enabled) {
- // We are building a new list of input methods that
- // doesn't contain the given one.
- if (firstId == null) firstId = curId;
- if (builder.length() > 0) builder.append(':');
- builder.append(curId);
- }
- }
- }
-
- if (!enabled) {
- if (!removed) {
- // We are disabling the input method but it is already
- // disabled. Nothing to do. The previous state was
- // disabled.
- return false;
- }
- // Update the setting with the new list of input methods.
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
- // We the disabled input method is currently selected, switch
- // to another one.
- String selId = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD);
- if (id.equals(selId)) {
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD,
- firstId != null ? firstId : "");
- }
- // Previous state was enabled.
- return true;
- }
-
- // Add in the newly enabled input method.
- if (enabledStr == null || enabledStr.length() == 0) {
- enabledStr = id;
- } else {
- enabledStr = enabledStr + ':' + id;
- }
-
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS, enabledStr);
-
- // Previous state was disabled.
- return false;
+ return setInputMethodEnabledLocked(id, enabled);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
+
+ boolean setInputMethodEnabledLocked(String id, boolean enabled) {
+ // Make sure this is a valid input method.
+ InputMethodInfo imm = mMethodMap.get(id);
+ if (imm == null) {
+ if (imm == null) {
+ throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
+ }
+ }
+
+ StringBuilder builder = new StringBuilder(256);
+
+ boolean removed = false;
+ String firstId = null;
+
+ // Look through the currently enabled input methods.
+ String enabledStr = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_INPUT_METHODS);
+ if (enabledStr != null) {
+ final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
+ splitter.setString(enabledStr);
+ while (splitter.hasNext()) {
+ String curId = splitter.next();
+ if (curId.equals(id)) {
+ if (enabled) {
+ // We are enabling this input method, but it is
+ // already enabled. Nothing to do. The previous
+ // state was enabled.
+ return true;
+ }
+ // We are disabling this input method, and it is
+ // currently enabled. Skip it to remove from the
+ // new list.
+ removed = true;
+ } else if (!enabled) {
+ // We are building a new list of input methods that
+ // doesn't contain the given one.
+ if (firstId == null) firstId = curId;
+ if (builder.length() > 0) builder.append(':');
+ builder.append(curId);
+ }
+ }
+ }
+
+ if (!enabled) {
+ if (!removed) {
+ // We are disabling the input method but it is already
+ // disabled. Nothing to do. The previous state was
+ // disabled.
+ return false;
+ }
+ // Update the setting with the new list of input methods.
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
+ // We the disabled input method is currently selected, switch
+ // to another one.
+ String selId = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ if (id.equals(selId)) {
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD,
+ firstId != null ? firstId : "");
+ }
+ // Previous state was enabled.
+ return true;
+ }
+
+ // Add in the newly enabled input method.
+ if (enabledStr == null || enabledStr.length() == 0) {
+ enabledStr = id;
+ } else {
+ enabledStr = enabledStr + ':' + id;
+ }
+
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_INPUT_METHODS, enabledStr);
+
+ // Previous state was disabled.
+ return false;
+ }
// ----------------------------------------------------------------------
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 004fcf13..c0dcdf9 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -27,6 +27,7 @@
import java.util.Observer;
import java.util.Set;
+import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -484,14 +485,17 @@
super();
mContext = context;
- Thread thread = new Thread(null, this, "LocationManagerService");
- thread.start();
-
if (LOCAL_LOGV) {
Log.v(TAG, "Constructed LocationManager Service");
}
}
+ void systemReady() {
+ // we defer starting up the service until the system is ready
+ Thread thread = new Thread(null, this, "LocationManagerService");
+ thread.start();
+ }
+
private void initialize() {
// Create a wake lock, needs to be done before calling loadProviders() below
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -506,6 +510,7 @@
// Register for Package Manager updates
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ intentFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
mContext.registerReceiver(mBroadcastReceiver, intentFilter);
IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mBroadcastReceiver, sdFilter);
@@ -1539,8 +1544,9 @@
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
-
- if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
+ boolean queryRestart = action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART);
+ if (queryRestart
+ || action.equals(Intent.ACTION_PACKAGE_REMOVED)
|| action.equals(Intent.ACTION_PACKAGE_RESTARTED)
|| action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
synchronized (mLock) {
@@ -1560,6 +1566,10 @@
for (int j=i.size()-1; j>=0; j--) {
UpdateRecord ur = i.get(j);
if (ur.mReceiver.isPendingIntent() && ur.mUid == uid) {
+ if (queryRestart) {
+ setResultCode(Activity.RESULT_OK);
+ return;
+ }
if (removedRecs == null) {
removedRecs = new ArrayList<Receiver>();
}
@@ -1572,6 +1582,10 @@
ArrayList<ProximityAlert> removedAlerts = null;
for (ProximityAlert i : mProximityAlerts.values()) {
if (i.mUid == uid) {
+ if (queryRestart) {
+ setResultCode(Activity.RESULT_OK);
+ return;
+ }
if (removedAlerts == null) {
removedAlerts = new ArrayList<ProximityAlert>();
}
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 4e2ffa4..4485c79 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -16,32 +16,29 @@
package com.android.server;
+import com.android.server.am.ActivityManagerService;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.content.res.Resources;
import android.net.Uri;
import android.os.storage.IMountService;
import android.os.storage.IMountServiceListener;
import android.os.storage.StorageResultCode;
+import android.os.Handler;
+import android.os.Message;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.Environment;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.os.UEventObserver;
-import android.os.Handler;
-import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashSet;
-import java.io.File;
-import java.io.FileReader;
-
/**
* MountService implements back-end services for platform storage
* management.
@@ -97,7 +94,7 @@
public static final int OpFailedMediaBlank = 402;
public static final int OpFailedMediaCorrupt = 403;
public static final int OpFailedVolNotMounted = 404;
- public static final int OpFailedVolBusy = 405;
+ public static final int OpFailedStorageBusy = 405;
/*
* 600 series - Unsolicited broadcasts.
@@ -115,14 +112,119 @@
private PackageManagerService mPms;
private boolean mUmsEnabling;
private ArrayList<MountServiceBinderListener> mListeners;
- private boolean mBooted;
- private boolean mReady;
+ private boolean mBooted = false;
+ private boolean mReady = false;
+ private boolean mSendUmsConnectedOnBoot = false;
/**
* Private hash of currently mounted secure containers.
*/
private HashSet<String> mAsecMountSet = new HashSet<String>();
+ private static final int H_UNMOUNT_PM_UPDATE = 1;
+ private static final int H_UNMOUNT_PM_DONE = 2;
+ private static final int H_UNMOUNT_MS = 3;
+ private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
+ private static final int MAX_UNMOUNT_RETRIES = 4;
+
+ private IntentFilter mPmFilter = new IntentFilter(
+ Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ private BroadcastReceiver mPmReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
+ }
+ }
+ };
+
+ class UnmountCallBack {
+ String path;
+ int retries;
+ boolean force;
+
+ UnmountCallBack(String path, boolean force) {
+ retries = 0;
+ this.path = path;
+ this.force = force;
+ }
+ }
+
+ final private Handler mHandler = new Handler() {
+ ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>();
+
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case H_UNMOUNT_PM_UPDATE: {
+ UnmountCallBack ucb = (UnmountCallBack) msg.obj;
+ mForceUnmounts.add(ucb);
+ mContext.registerReceiver(mPmReceiver, mPmFilter);
+ boolean hasExtPkgs = mPms.updateExternalMediaStatus(false);
+ if (!hasExtPkgs) {
+ // Unregister right away
+ mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
+ }
+ break;
+ }
+ case H_UNMOUNT_PM_DONE: {
+ // Unregister receiver
+ mContext.unregisterReceiver(mPmReceiver);
+ UnmountCallBack ucb = mForceUnmounts.get(0);
+ if (ucb == null || ucb.path == null) {
+ // Just ignore
+ return;
+ }
+ String path = ucb.path;
+ boolean done = false;
+ if (!ucb.force) {
+ done = true;
+ } else {
+ int pids[] = getStorageUsers(path);
+ if (pids == null || pids.length == 0) {
+ done = true;
+ } else {
+ // Kill processes holding references first
+ ActivityManagerService ams = (ActivityManagerService)
+ ServiceManager.getService("activity");
+ // Eliminate system process here?
+ boolean ret = ams.killPidsForMemory(pids);
+ if (ret) {
+ // Confirm if file references have been freed.
+ pids = getStorageUsers(path);
+ if (pids == null || pids.length == 0) {
+ done = true;
+ }
+ }
+ }
+ }
+ if (done) {
+ mForceUnmounts.remove(0);
+ mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
+ ucb));
+ } else {
+ if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
+ Log.i(TAG, "Cannot unmount inspite of " +
+ MAX_UNMOUNT_RETRIES + " to unmount media");
+ // Send final broadcast indicating failure to unmount.
+ } else {
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
+ ucb.retries++),
+ RETRY_UNMOUNT_DELAY);
+ }
+ }
+ break;
+ }
+ case H_UNMOUNT_MS : {
+ UnmountCallBack ucb = (UnmountCallBack) msg.obj;
+ String path = ucb.path;
+ doUnmountVolume(path, true);
+ break;
+ }
+ }
+ }
+ };
+
private void waitForReady() {
while (mReady == false) {
for (int retries = 5; retries > 0; retries--) {
@@ -142,13 +244,39 @@
if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
mBooted = true;
- String path = Environment.getExternalStorageDirectory().getPath();
- if (getVolumeState(path).equals(Environment.MEDIA_UNMOUNTED)) {
- int rc = doMountVolume(path);
- if (rc != StorageResultCode.OperationSucceeded) {
- Log.e(TAG, String.format("Boot-time mount failed (%d)", rc));
- }
+ /*
+ * In the simulator, we need to broadcast a volume mounted event
+ * to make the media scanner run.
+ */
+ if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
+ notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, VolumeState.Mounted);
+ return;
}
+ new Thread() {
+ public void run() {
+ try {
+ String path = Environment.getExternalStorageDirectory().getPath();
+ if (getVolumeState(
+ Environment.getExternalStorageDirectory().getPath()).equals(
+ Environment.MEDIA_UNMOUNTED)) {
+ int rc = doMountVolume(path);
+ if (rc != StorageResultCode.OperationSucceeded) {
+ Log.e(TAG, String.format("Boot-time mount failed (%d)", rc));
+ }
+ }
+ /*
+ * If UMS is connected in boot, send the connected event
+ * now that we're up.
+ */
+ if (mSendUmsConnectedOnBoot) {
+ sendUmsIntent(true);
+ mSendUmsConnectedOnBoot = false;
+ }
+ } catch (Exception ex) {
+ Log.e(TAG, "Boot-time mount exception", ex);
+ }
+ }
+ }.start();
}
}
};
@@ -184,7 +312,7 @@
String vs = getVolumeState(path);
if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
mUmsEnabling = enable; // Override for isUsbMassStorageEnabled()
- int rc = doUnmountVolume(path);
+ int rc = doUnmountVolume(path, true);
mUmsEnabling = false; // Clear override
if (rc != StorageResultCode.OperationSucceeded) {
Log.e(TAG, String.format("Failed to unmount before enabling UMS (%d)", rc));
@@ -527,17 +655,32 @@
return rc;
}
- private int doUnmountVolume(String path) {
+ /*
+ * If force is not set, we do not unmount if there are
+ * processes holding references to the volume about to be unmounted.
+ * If force is set, all the processes holding references need to be
+ * killed via the ActivityManager before actually unmounting the volume.
+ * This might even take a while and might be retried after timed delays
+ * to make sure we dont end up in an instable state and kill some core
+ * processes.
+ */
+ private int doUnmountVolume(String path, boolean force) {
if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
return VoldResponseCode.OpFailedVolNotMounted;
}
+ // We unmounted the volume. No of the asec containers are available now.
+ synchronized (mAsecMountSet) {
+ mAsecMountSet.clear();
+ }
// Notify PackageManager of potential media removal and deal with
// return code later on. The caller of this api should be aware or have been
// notified that the applications installed on the media will be killed.
+ // Redundant probably. But no harm in updating state again.
mPms.updateExternalMediaStatus(false);
try {
- mConnector.doCommand(String.format("volume unmount %s", path));
+ mConnector.doCommand(String.format(
+ "volume unmount %s%s", path, (force ? " force" : "")));
return StorageResultCode.OperationSucceeded;
} catch (NativeDaemonConnectorException e) {
// Don't worry about mismatch in PackageManager since the
@@ -545,6 +688,8 @@
int code = e.getCode();
if (code == VoldResponseCode.OpFailedVolNotMounted) {
return StorageResultCode.OperationFailedStorageNotMounted;
+ } else if (code == VoldResponseCode.OpFailedStorageBusy) {
+ return StorageResultCode.OperationFailedStorageBusy;
} else {
return StorageResultCode.OperationFailedInternalError;
}
@@ -615,16 +760,17 @@
}
if (mBooted == true) {
- Intent intent;
- if (avail) {
- intent = new Intent(Intent.ACTION_UMS_CONNECTED);
- } else {
- intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
- }
- mContext.sendBroadcast(intent);
+ sendUmsIntent(avail);
+ } else {
+ mSendUmsConnectedOnBoot = avail;
}
}
+ private void sendUmsIntent(boolean c) {
+ mContext.sendBroadcast(
+ new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)));
+ }
+
private void validatePermission(String perm) {
if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(String.format("Requires %s permission", perm));
@@ -639,15 +785,6 @@
public MountService(Context context) {
mContext = context;
- /*
- * Vold does not run in the simulator, so fake out a mounted
- * event to trigger MediaScanner
- */
- if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
- updatePublicVolumeState("/sdcard", Environment.MEDIA_MOUNTED);
- return;
- }
-
// XXX: This will go away soon in favor of IMountServiceObserver
mPms = (PackageManagerService) ServiceManager.getService("package");
@@ -656,6 +793,16 @@
mListeners = new ArrayList<MountServiceBinderListener>();
+ /*
+ * Vold does not run in the simulator, so pretend the connector thread
+ * ran and did its thing.
+ */
+ if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
+ mReady = true;
+ mUmsEnabling = true;
+ return;
+ }
+
mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
mReady = false;
Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
@@ -733,7 +880,7 @@
/*
* If the media is mounted, then gracefully unmount it.
*/
- if (doUnmountVolume(path) != StorageResultCode.OperationSucceeded) {
+ if (doUnmountVolume(path, true) != StorageResultCode.OperationSucceeded) {
Log.e(TAG, "Failed to unmount media for shutdown");
}
}
@@ -782,11 +929,12 @@
return doMountVolume(path);
}
- public int unmountVolume(String path) {
+ public void unmountVolume(String path, boolean force) {
validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
waitForReady();
- return doUnmountVolume(path);
+ UnmountCallBack ucb = new UnmountCallBack(path, force);
+ mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
}
public int formatVolume(String path) {
@@ -878,16 +1026,21 @@
return rc;
}
- public int destroySecureContainer(String id) {
+ public int destroySecureContainer(String id, boolean force) {
validatePermission(android.Manifest.permission.ASEC_DESTROY);
waitForReady();
warnOnNotMounted();
int rc = StorageResultCode.OperationSucceeded;
try {
- mConnector.doCommand(String.format("asec destroy %s", id));
+ mConnector.doCommand(String.format("asec destroy %s%s", id, (force ? " force" : "")));
} catch (NativeDaemonConnectorException e) {
- rc = StorageResultCode.OperationFailedInternalError;
+ int code = e.getCode();
+ if (code == VoldResponseCode.OpFailedStorageBusy) {
+ rc = StorageResultCode.OperationFailedStorageBusy;
+ } else {
+ rc = StorageResultCode.OperationFailedInternalError;
+ }
}
if (rc == StorageResultCode.OperationSucceeded) {
@@ -928,7 +1081,7 @@
return rc;
}
- public int unmountSecureContainer(String id) {
+ public int unmountSecureContainer(String id, boolean force) {
validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
waitForReady();
warnOnNotMounted();
@@ -940,11 +1093,16 @@
}
int rc = StorageResultCode.OperationSucceeded;
- String cmd = String.format("asec unmount %s", id);
+ String cmd = String.format("asec unmount %s%s", id, (force ? " force" : ""));
try {
mConnector.doCommand(cmd);
} catch (NativeDaemonConnectorException e) {
- rc = StorageResultCode.OperationFailedInternalError;
+ int code = e.getCode();
+ if (code == VoldResponseCode.OpFailedStorageBusy) {
+ rc = StorageResultCode.OperationFailedStorageBusy;
+ } else {
+ rc = StorageResultCode.OperationFailedInternalError;
+ }
}
if (rc == StorageResultCode.OperationSucceeded) {
@@ -971,7 +1129,11 @@
warnOnNotMounted();
synchronized (mAsecMountSet) {
- if (mAsecMountSet.contains(oldId)) {
+ /*
+ * Because a mounted container has active internal state which cannot be
+ * changed while active, we must ensure both ids are not currently mounted.
+ */
+ if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
return StorageResultCode.OperationFailedStorageMounted;
}
}
@@ -983,6 +1145,13 @@
} catch (NativeDaemonConnectorException e) {
rc = StorageResultCode.OperationFailedInternalError;
}
+ if (rc == StorageResultCode.OperationSucceeded) {
+ synchronized (mAsecMountSet) {
+ if (!mAsecMountSet.contains(newId)) {
+ mAsecMountSet.add(newId);
+ }
+ }
+ }
return rc;
}
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index b9bb16f1..7c555e2 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -28,6 +28,7 @@
import android.net.INetworkManagementEventObserver;
import android.os.INetworkManagementService;
import android.os.Handler;
+import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
@@ -87,6 +88,10 @@
mObservers = new ArrayList<INetworkManagementEventObserver>();
+ if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
+ return;
+ }
+
mConnector = new NativeDaemonConnector(
new NetdCallbackReceiver(), "netd", 10, "NetdConnector");
Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
@@ -251,14 +256,14 @@
Log.e(TAG, "Failed to parse netmask", uhe);
cfg.netmask = 0;
}
- cfg.interfaceFlags = st.nextToken("]");
+ cfg.interfaceFlags = st.nextToken("]").trim() +"]";
Log.d(TAG, String.format("flags <%s>", cfg.interfaceFlags));
return cfg;
}
public void setInterfaceConfig(
String iface, InterfaceConfiguration cfg) throws IllegalStateException {
- String cmd = String.format("interface setcfg %s %s %s", iface,
+ String cmd = String.format("interface setcfg %s %s %s %s", iface,
intToIpString(cfg.ipAddr), intToIpString(cfg.netmask), cfg.interfaceFlags);
mConnector.doCommand(cmd);
}
@@ -420,4 +425,35 @@
android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
mConnector.doCommand(String.format("pppd detach %s", tty));
}
+
+ public void startUsbRNDIS() throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+ mConnector.doCommand("usb startrndis");
+ }
+
+ public void stopUsbRNDIS() throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+ mConnector.doCommand("usb stoprndis");
+ }
+
+ public boolean isUsbRNDISStarted() throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
+ ArrayList<String> rsp = mConnector.doCommand("usb rndisstatus");
+
+ for (String line : rsp) {
+ String []tok = line.split(" ");
+ int code = Integer.parseInt(tok[0]);
+ if (code == NetdResponseCode.UsbRNDISStatusResult) {
+ if (tok[2].equals("started"))
+ return true;
+ return false;
+ } else {
+ throw new IllegalStateException(String.format("Unexpected response code %d", code));
+ }
+ }
+ throw new IllegalStateException("Got an empty response");
+ }
}
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 3657133..3c43352 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -307,6 +307,8 @@
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
+ boolean queryRestart = false;
+
if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
boolean batteryCharging = (intent.getIntExtra("plugged", 0) != 0);
int level = intent.getIntExtra("level", -1);
@@ -330,10 +332,13 @@
updateAdbNotification();
} else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
|| action.equals(Intent.ACTION_PACKAGE_RESTARTED)
+ || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
|| action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
String pkgList[] = null;
if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ } else if (queryRestart) {
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
} else {
Uri uri = intent.getData();
if (uri == null) {
@@ -347,7 +352,7 @@
}
if (pkgList != null && (pkgList.length > 0)) {
for (String pkgName : pkgList) {
- cancelAllNotificationsInt(pkgName, 0, 0);
+ cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart);
}
}
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
@@ -436,11 +441,15 @@
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
filter.addAction(Intent.ACTION_UMS_CONNECTED);
filter.addAction(Intent.ACTION_UMS_DISCONNECTED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mIntentReceiver, filter);
+ IntentFilter pkgFilter = new IntentFilter();
+ pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
+ pkgFilter.addDataScheme("package");
+ mContext.registerReceiver(mIntentReceiver, pkgFilter);
IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mIntentReceiver, sdFilter);
@@ -920,8 +929,8 @@
* Cancels all notifications from a given package that have all of the
* {@code mustHaveFlags}.
*/
- void cancelAllNotificationsInt(String pkg, int mustHaveFlags,
- int mustNotHaveFlags) {
+ boolean cancelAllNotificationsInt(String pkg, int mustHaveFlags,
+ int mustNotHaveFlags, boolean doit) {
EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL_ALL, pkg, mustHaveFlags);
synchronized (mNotificationList) {
@@ -938,13 +947,17 @@
if (!r.pkg.equals(pkg)) {
continue;
}
+ canceledSomething = true;
+ if (!doit) {
+ return true;
+ }
mNotificationList.remove(i);
cancelNotificationLocked(r);
- canceledSomething = true;
}
if (canceledSomething) {
updateLightsLocked();
}
+ return canceledSomething;
}
}
@@ -966,7 +979,7 @@
// Calling from user space, don't allow the canceling of actively
// running foreground services.
- cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE);
+ cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true);
}
void checkIncomingCall(String pkg) {
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 4326efc..a23fac4 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -28,7 +28,10 @@
import org.xmlpull.v1.XmlSerializer;
import android.app.ActivityManagerNative;
+import android.app.DevicePolicyManager;
import android.app.IActivityManager;
+import android.app.IDevicePolicyManager;
+import android.backup.IBackupManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -44,6 +47,7 @@
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageManager;
+import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
@@ -122,6 +126,7 @@
private static final boolean DEBUG_SETTINGS = false;
private static final boolean DEBUG_PREFERRED = false;
private static final boolean DEBUG_UPGRADE = false;
+ private static final boolean DEBUG_INSTALL = false;
private static final boolean MULTIPLE_APPLICATION_UIDS = true;
private static final int RADIO_UID = Process.PHONE_UID;
@@ -142,6 +147,9 @@
FileObserver.CLOSE_WRITE /*| FileObserver.CREATE*/ | FileObserver.MOVED_TO;
private static final int OBSERVER_EVENTS = REMOVE_EVENTS | ADD_EVENTS;
+ // Suffix used during package installation when copying/moving
+ // package apks to install directory.
+ private static final String INSTALL_PACKAGE_SUFFIX = "-";
static final int SCAN_MONITOR = 1<<0;
static final int SCAN_NO_DEX = 1<<1;
@@ -306,6 +314,7 @@
static final int MCS_UNBIND = 6;
static final int START_CLEANING_PACKAGE = 7;
static final int FIND_INSTALL_LOC = 8;
+ static final int POST_INSTALL = 9;
// Delay time in millisecs
static final int BROADCAST_DELAY = 10 * 1000;
private ServiceConnection mDefContainerConn = new ServiceConnection() {
@@ -320,9 +329,23 @@
}
};
+ // Recordkeeping of restore-after-install operations that are currently in flight
+ // between the Package Manager and the Backup Manager
+ class PostInstallData {
+ public InstallArgs args;
+ public PackageInstalledInfo res;
+
+ PostInstallData(InstallArgs _a, PackageInstalledInfo _r) {
+ args = _a;
+ res = _r;
+ }
+ };
+ final SparseArray<PostInstallData> mRunningInstalls = new SparseArray<PostInstallData>();
+ int mNextInstallToken = 1; // nonzero; will be wrapped back to 1 when ++ overflows
+
class PackageHandler extends Handler {
- final ArrayList<InstallParams> mPendingInstalls =
- new ArrayList<InstallParams>();
+ final ArrayList<HandlerParams> mPendingInstalls =
+ new ArrayList<HandlerParams>();
// Service Connection to remote media container service to copy
// package uri's from external media onto secure containers
// or internal storage.
@@ -334,20 +357,19 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
- InstallParams params = (InstallParams) msg.obj;
+ HandlerParams params = (HandlerParams) msg.obj;
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
if (mContainerService != null) {
// No need to add to pending list. Use remote stub directly
- handleStartCopy(params);
+ params.handleStartCopy(mContainerService);
} else {
if (mContext.bindService(service, mDefContainerConn,
Context.BIND_AUTO_CREATE)) {
mPendingInstalls.add(params);
} else {
Log.e(TAG, "Failed to bind to media container service");
- // Indicate install failure TODO add new error code
- processPendingInstall(createInstallArgs(params),
- PackageManager.INSTALL_FAILED_INTERNAL_ERROR);
+ // Indicate service bind error
+ params.handleServiceError();
}
}
break;
@@ -358,9 +380,9 @@
mContainerService = (IMediaContainerService) msg.obj;
}
if (mPendingInstalls.size() > 0) {
- InstallParams params = mPendingInstalls.remove(0);
+ HandlerParams params = mPendingInstalls.remove(0);
if (params != null) {
- handleStartCopy(params);
+ params.handleStartCopy(mContainerService);
}
}
break;
@@ -419,55 +441,52 @@
}
startCleaningPackages();
} break;
- }
- }
+ case POST_INSTALL: {
+ if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1);
+ PostInstallData data = mRunningInstalls.get(msg.arg1);
+ mRunningInstalls.delete(msg.arg1);
- // Utility method to initiate copying apk via media
- // container service.
- private void handleStartCopy(InstallParams params) {
- int ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
- if (mContainerService != null) {
- // Remote call to find out default install location
- int loc = params.getInstallLocation(mContainerService);
- // Use install location to create InstallArgs and temporary
- // install location
- if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE){
- ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
- ret = PackageManager.INSTALL_FAILED_INVALID_APK;
- } else {
- if ((params.flags & PackageManager.INSTALL_EXTERNAL) == 0){
- if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
- // Set the flag to install on external media.
- params.flags |= PackageManager.INSTALL_EXTERNAL;
- } else {
- // Make sure the flag for installing on external
- // media is unset
- params.flags &= ~PackageManager.INSTALL_EXTERNAL;
+ if (data != null) {
+ InstallArgs args = data.args;
+ PackageInstalledInfo res = data.res;
+
+ if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
+ res.removedInfo.sendBroadcast(false, true);
+ Bundle extras = new Bundle(1);
+ extras.putInt(Intent.EXTRA_UID, res.uid);
+ final boolean update = res.removedInfo.removedPackage != null;
+ if (update) {
+ extras.putBoolean(Intent.EXTRA_REPLACING, true);
+ }
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
+ res.pkg.applicationInfo.packageName,
+ extras);
+ if (update) {
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
+ res.pkg.applicationInfo.packageName,
+ extras);
+ }
+ if (res.removedInfo.args != null) {
+ // Remove the replaced package's older resources safely now
+ synchronized (mInstallLock) {
+ res.removedInfo.args.doPostDeleteLI(true);
+ }
+ }
}
- }
- // Disable forward locked apps on sdcard.
- if ((params.flags & PackageManager.INSTALL_FORWARD_LOCK) != 0 &&
- (params.flags & PackageManager.INSTALL_EXTERNAL) != 0) {
- // Make sure forward locked apps can only be installed
- // on internal storage
- Log.w(TAG, "Cannot install protected apps on sdcard");
- ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+ Runtime.getRuntime().gc();
+
+ if (args.observer != null) {
+ try {
+ args.observer.packageInstalled(res.name, res.returnCode);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Observer no longer exists.");
+ }
+ }
} else {
- ret = PackageManager.INSTALL_SUCCEEDED;
+ Log.e(TAG, "Bogus post-install token " + msg.arg1);
}
- }
+ } break;
}
- mHandler.sendEmptyMessage(MCS_UNBIND);
- // Create the file args now.
- InstallArgs args = createInstallArgs(params);
- if (ret == PackageManager.INSTALL_SUCCEEDED) {
- // Create copy only if we are not in an erroneous state.
- args.createCopyFile();
- // Remote call to initiate copy
- ret = args.copyApk(mContainerService);
- }
- processPendingInstall(args, ret);
}
}
@@ -2180,8 +2199,8 @@
int i;
for (i=0; i<files.length; i++) {
File file = new File(dir, files[i]);
- if (files[i] != null && files[i].endsWith(".zip")) {
- // Public resource for forward locked package. Ignore
+ if (!isPackageFilename(files[i])) {
+ // Ignore entries which are not apk's
continue;
}
PackageParser.Package pkg = scanPackageLI(file,
@@ -2936,11 +2955,6 @@
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST;
}
- // We don't expect installation to fail beyond this point,
- if ((scanMode&SCAN_MONITOR) != 0) {
- 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.
@@ -2950,6 +2964,10 @@
}
synchronized (mPackages) {
+ // We don't expect installation to fail beyond this point,
+ if ((scanMode&SCAN_MONITOR) != 0) {
+ mAppDirs.put(pkg.mPath, pkg);
+ }
// Add the new setting to mSettings
mSettings.insertPackageSettingLP(pkgSetting, pkg);
// Add the new setting to mPackages
@@ -4224,19 +4242,24 @@
return;
}
+ // Ignore packages that are being installed or
+ // have just been installed.
+ if (ignoreCodePath(fullPathStr)) {
+ return;
+ }
+ PackageParser.Package p = null;
+ synchronized (mPackages) {
+ p = mAppDirs.get(fullPathStr);
+ }
if ((event&REMOVE_EVENTS) != 0) {
- synchronized (mInstallLock) {
- PackageParser.Package p = mAppDirs.get(fullPathStr);
- if (p != null) {
- removePackageLI(p, true);
- removedPackage = p.applicationInfo.packageName;
- removedUid = p.applicationInfo.uid;
- }
+ if (p != null) {
+ removePackageLI(p, true);
+ removedPackage = p.applicationInfo.packageName;
+ removedUid = p.applicationInfo.uid;
}
}
if ((event&ADD_EVENTS) != 0) {
- PackageParser.Package p = mAppDirs.get(fullPathStr);
if (p == null) {
p = scanPackageLI(fullPath,
(mIsRom ? PackageParser.PARSE_IS_SYSTEM : 0) |
@@ -4294,6 +4317,12 @@
mHandler.sendMessage(msg);
}
+ public void finishPackageInstall(int token) {
+ if (DEBUG_INSTALL) Log.v(TAG, "BM finishing package install for " + token);
+ Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
+ mHandler.sendMessage(msg);
+ }
+
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() {
@@ -4312,48 +4341,73 @@
}
args.doPostInstall(res.returnCode);
}
- if (args.observer != null) {
- try {
- args.observer.packageInstalled(res.name, res.returnCode);
- } catch (RemoteException e) {
- Log.i(TAG, "Observer no longer exists.");
- }
- }
- // There appears to be a subtle deadlock condition if the sendPackageBroadcast
- // call appears in the synchronized block above.
- if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
- res.removedInfo.sendBroadcast(false, true);
- Bundle extras = new Bundle(1);
- extras.putInt(Intent.EXTRA_UID, res.uid);
- final boolean update = res.removedInfo.removedPackage != null;
- if (update) {
- extras.putBoolean(Intent.EXTRA_REPLACING, true);
- }
- sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
- res.pkg.applicationInfo.packageName,
- extras);
- if (update) {
- sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
- res.pkg.applicationInfo.packageName,
- extras);
- }
- if (res.removedInfo.args != null) {
- // Remove the replaced package's older resources safely now
- synchronized (mInstallLock) {
- res.removedInfo.args.doPostDeleteLI(true);
+
+ // A restore should be performed at this point if (a) the install
+ // succeeded, (b) the operation is not an update, and (c) the new
+ // package has a backupAgent defined.
+ final boolean update = res.removedInfo.removedPackage != null;
+ boolean doRestore = (!update
+ && res.pkg != null
+ && res.pkg.applicationInfo.backupAgentName != null);
+
+ // Set up the post-install work request bookkeeping. This will be used
+ // and cleaned up by the post-install event handling regardless of whether
+ // there's a restore pass performed. Token values are >= 1.
+ int token;
+ if (mNextInstallToken < 0) mNextInstallToken = 1;
+ token = mNextInstallToken++;
+
+ PostInstallData data = new PostInstallData(args, res);
+ mRunningInstalls.put(token, data);
+ if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
+
+ if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
+ // Pass responsibility to the Backup Manager. It will perform a
+ // restore if appropriate, then pass responsibility back to the
+ // Package Manager to run the post-install observer callbacks
+ // and broadcasts.
+ IBackupManager bm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ if (bm != null) {
+ if (DEBUG_INSTALL) Log.v(TAG, "token " + token
+ + " to BM for possible restore");
+ try {
+ bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token);
+ } catch (RemoteException e) {
+ // can't happen; the backup manager is local
+ } catch (Exception e) {
+ Log.e(TAG, "Exception trying to enqueue restore", e);
+ doRestore = false;
}
+ } else {
+ Log.e(TAG, "Backup Manager not found!");
+ doRestore = false;
}
}
- Runtime.getRuntime().gc();
+
+ if (!doRestore) {
+ // No restore possible, or the Backup Manager was mysteriously not
+ // available -- just fire the post-install work request directly.
+ if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
+ Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
+ mHandler.sendMessage(msg);
+ }
}
});
}
- static final class InstallParams {
+ interface HandlerParams {
+ void handleStartCopy(IMediaContainerService imcs);
+ void handleServiceError();
+ }
+
+ class InstallParams implements HandlerParams {
final IPackageInstallObserver observer;
int flags;
final Uri packageURI;
final String installerPackageName;
+ private InstallArgs mArgs;
+ private int mRet;
InstallParams(Uri packageURI,
IPackageInstallObserver observer, int flags,
String installerPackageName) {
@@ -4363,13 +4417,118 @@
this.installerPackageName = installerPackageName;
}
- public int getInstallLocation(IMediaContainerService imcs) {
+ private int getInstallLocation(IMediaContainerService imcs) {
try {
return imcs.getRecommendedInstallLocation(packageURI);
} catch (RemoteException e) {
}
return -1;
}
+
+ public void handleStartCopy(IMediaContainerService imcs) {
+ int ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+ if (imcs != null) {
+ // Remote call to find out default install location
+ int loc = getInstallLocation(imcs);
+ // Use install location to create InstallArgs and temporary
+ // install location
+ if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE){
+ ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
+ ret = PackageManager.INSTALL_FAILED_INVALID_APK;
+ } else {
+ if ((flags & PackageManager.INSTALL_EXTERNAL) == 0){
+ if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
+ // Set the flag to install on external media.
+ flags |= PackageManager.INSTALL_EXTERNAL;
+ } else {
+ // Make sure the flag for installing on external
+ // media is unset
+ flags &= ~PackageManager.INSTALL_EXTERNAL;
+ }
+ }
+ // Disable forward locked apps on sdcard.
+ if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0 &&
+ (flags & PackageManager.INSTALL_EXTERNAL) != 0) {
+ // Make sure forward locked apps can only be installed
+ // on internal storage
+ Log.w(TAG, "Cannot install protected apps on sdcard");
+ ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+ } else {
+ ret = PackageManager.INSTALL_SUCCEEDED;
+ }
+ }
+ }
+ // Create the file args now.
+ mArgs = createInstallArgs(this);
+ if (ret == PackageManager.INSTALL_SUCCEEDED) {
+ // Create copy only if we are not in an erroneous state.
+ // Remote call to initiate copy using temporary file
+ ret = mArgs.copyApk(imcs, true);
+ }
+ mRet = ret;
+ mHandler.sendEmptyMessage(MCS_UNBIND);
+ handleReturnCode();
+ }
+
+ void handleReturnCode() {
+ processPendingInstall(mArgs, mRet);
+ }
+
+ public void handleServiceError() {
+ mArgs = createInstallArgs(this);
+ mRet = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+ handleReturnCode();
+ }
+ };
+
+ /*
+ * Utility class used in movePackage api.
+ * srcArgs and targetArgs are not set for invalid flags and make
+ * sure to do null checks when invoking methods on them.
+ * We probably want to return ErrorPrams for both failed installs
+ * and moves.
+ */
+ class MoveParams implements HandlerParams {
+ final IPackageMoveObserver observer;
+ final int flags;
+ final String packageName;
+ final InstallArgs srcArgs;
+ final InstallArgs targetArgs;
+ int mRet;
+ MoveParams(InstallArgs srcArgs,
+ IPackageMoveObserver observer,
+ int flags, String packageName) {
+ this.srcArgs = srcArgs;
+ this.observer = observer;
+ this.flags = flags;
+ this.packageName = packageName;
+ if (srcArgs != null) {
+ Uri packageUri = Uri.fromFile(new File(srcArgs.getCodePath()));
+ targetArgs = createInstallArgs(packageUri, flags, packageName);
+ } else {
+ targetArgs = null;
+ }
+ }
+
+ public void handleStartCopy(IMediaContainerService imcs) {
+ // Create the file args now.
+ mRet = targetArgs.copyApk(imcs, false);
+ targetArgs.doPreInstall(mRet);
+ mHandler.sendEmptyMessage(MCS_UNBIND);
+ handleReturnCode();
+ }
+
+ void handleReturnCode() {
+ targetArgs.doPostInstall(mRet);
+ // TODO invoke pending move
+ processPendingMove(this, mRet);
+ }
+
+ public void handleServiceError() {
+ mRet = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+ handleReturnCode();
+ }
};
private InstallArgs createInstallArgs(InstallParams params) {
@@ -4388,8 +4547,18 @@
}
}
+ private InstallArgs createInstallArgs(Uri packageURI, int flags, String pkgName) {
+ if (installOnSd(flags)) {
+ String cid = getNextCodePath(null, pkgName, "/" + SdInstallArgs.RES_FILE_NAME);
+ return new SdInstallArgs(packageURI, cid);
+ } else {
+ return new FileInstallArgs(packageURI, pkgName);
+ }
+ }
+
static abstract class InstallArgs {
final IPackageInstallObserver observer;
+ // Always refers to PackageManager flags only
final int flags;
final Uri packageURI;
final String installerPackageName;
@@ -4404,7 +4573,7 @@
}
abstract void createCopyFile();
- abstract int copyApk(IMediaContainerService imcs);
+ abstract int copyApk(IMediaContainerService imcs, boolean temp);
abstract int doPreInstall(int status);
abstract boolean doRename(int status, String pkgName, String oldCodePath);
abstract int doPostInstall(int status);
@@ -4419,6 +4588,7 @@
File installDir;
String codeFileName;
String resourceFileName;
+ boolean created = false;
FileInstallArgs(InstallParams params) {
super(params.packageURI, params.observer,
@@ -4433,6 +4603,15 @@
resourceFileName = fullResourcePath;
}
+ FileInstallArgs(Uri packageURI, String pkgName) {
+ super(packageURI, null, 0, null);
+ boolean fwdLocked = isFwdLocked(flags);
+ installDir = fwdLocked ? mDrmAppPrivateInstallDir : mAppInstallDir;
+ String apkName = getNextCodePath(null, pkgName, ".apk");
+ codeFileName = new File(installDir, apkName + ".apk").getPath();
+ resourceFileName = getResourcePathFromCodePath();
+ }
+
String getCodePath() {
return codeFileName;
}
@@ -4442,11 +4621,29 @@
installDir = fwdLocked ? mDrmAppPrivateInstallDir : mAppInstallDir;
codeFileName = createTempPackageFile(installDir).getPath();
resourceFileName = getResourcePathFromCodePath();
+ created = true;
}
- int copyApk(IMediaContainerService imcs) {
+ int copyApk(IMediaContainerService imcs, boolean temp) {
+ if (temp) {
+ // Generate temp file name
+ createCopyFile();
+ }
// Get a ParcelFileDescriptor to write to the output file
File codeFile = new File(codeFileName);
+ if (!created) {
+ try {
+ codeFile.createNewFile();
+ // Set permissions
+ if (!setPermissions()) {
+ // Failed setting permissions.
+ return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to create file " + codeFile);
+ return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ }
+ }
ParcelFileDescriptor out = null;
try {
out = ParcelFileDescriptor.open(codeFile,
@@ -4491,7 +4688,7 @@
codeFileName = desFile.getPath();
resourceFileName = getResourcePathFromCodePath();
// Set permissions
- if (!setPermissions(pkgName)) {
+ if (!setPermissions()) {
// Failed setting permissions.
return false;
}
@@ -4558,7 +4755,7 @@
}
}
- private boolean setPermissions(String pkgName) {
+ private boolean setPermissions() {
// TODO Do this in a more elegant way later on. for now just a hack
if (!isFwdLocked(flags)) {
final int filePermissions =
@@ -4594,7 +4791,7 @@
}
SdInstallArgs(String fullCodePath, String fullResourcePath) {
- super(null, null, ApplicationInfo.FLAG_ON_SDCARD, null);
+ super(null, null, PackageManager.INSTALL_EXTERNAL, null);
// Extract cid from fullCodePath
int eidx = fullCodePath.lastIndexOf("/");
String subStr1 = fullCodePath.substring(0, eidx);
@@ -4604,7 +4801,12 @@
}
SdInstallArgs(String cid) {
- super(null, null, ApplicationInfo.FLAG_ON_SDCARD, null);
+ super(null, null, PackageManager.INSTALL_EXTERNAL, null);
+ this.cid = cid;
+ }
+
+ SdInstallArgs(Uri packageURI, String cid) {
+ super(packageURI, null, PackageManager.INSTALL_EXTERNAL, null);
this.cid = cid;
}
@@ -4612,7 +4814,10 @@
cid = getTempContainerId();
}
- int copyApk(IMediaContainerService imcs) {
+ int copyApk(IMediaContainerService imcs, boolean temp) {
+ if (temp) {
+ createCopyFile();
+ }
try {
cachePath = imcs.copyResourceToContainer(
packageURI, cid,
@@ -4653,42 +4858,67 @@
String oldCodePath) {
String newCacheId = getNextCodePath(oldCodePath, pkgName, "/" + RES_FILE_NAME);
String newCachePath = null;
- final int RENAME_FAILED = 1;
- final int MOUNT_FAILED = 2;
- final int PASS = 4;
- int errCode = RENAME_FAILED;
- String errMsg = "RENAME_FAILED";
- boolean mounted = PackageHelper.isContainerMounted(cid);
- if (mounted) {
- // Unmount the container
- if (!PackageHelper.unMountSdDir(cid)) {
- Log.i(TAG, "Failed to unmount " + cid + " before renaming");
+ boolean enableRename = false;
+ if (enableRename) {
+ if (PackageHelper.isContainerMounted(cid)) {
+ // Unmount the container
+ if (!PackageHelper.unMountSdDir(cid)) {
+ Log.i(TAG, "Failed to unmount " + cid + " before renaming");
+ return false;
+ }
+ }
+ if (!PackageHelper.renameSdDir(cid, newCacheId)) {
+ Log.e(TAG, "Failed to rename " + cid + " to " + newCacheId);
return false;
}
- mounted = false;
- }
- if (PackageHelper.renameSdDir(cid, newCacheId)) {
- errCode = MOUNT_FAILED;
- errMsg = "MOUNT_FAILED";
- if ((newCachePath = PackageHelper.mountSdDir(newCacheId,
- getEncryptKey(), Process.SYSTEM_UID)) != null) {
- errCode = PASS;
- errMsg = "PASS";
+ if (!PackageHelper.isContainerMounted(newCacheId)) {
+ Log.w(TAG, "Mounting container " + newCacheId);
+ newCachePath = PackageHelper.mountSdDir(newCacheId,
+ getEncryptKey(), Process.SYSTEM_UID);
+ } else {
+ newCachePath = PackageHelper.getSdDir(newCacheId);
}
- }
- if (errCode != PASS) {
- Log.i(TAG, "Failed to rename " + cid + " to " + newCacheId +
- " at path: " + cachePath + " to new path: " + newCachePath +
- "err = " + errMsg);
+ if (newCachePath == null) {
+ Log.w(TAG, "Failed to get cache path for " + newCacheId);
+ return false;
+ }
// Mount old container?
- return false;
+ Log.i(TAG, "Succesfully renamed " + cid +
+ " at path: " + cachePath + " to " + newCacheId +
+ " at new path: " + newCachePath);
+ cid = newCacheId;
+ cachePath = newCachePath;
+ return true;
} else {
- Log.i(TAG, "Succesfully renamed " + cid + " to " + newCacheId +
- " at path: " + cachePath + " to new path: " + newCachePath);
+ // STOPSHIP work around for rename
+ Log.i(TAG, "Copying instead of renaming");
+ File srcFile = new File(getCodePath());
+ // Create new container
+ newCachePath = PackageHelper.createSdDir(srcFile, newCacheId,
+ getEncryptKey(), Process.SYSTEM_UID);
+ Log.i(TAG, "Created rename container " + newCacheId);
+ File destFile = new File(newCachePath + "/" + RES_FILE_NAME);
+ if (!FileUtils.copyFile(srcFile, destFile)) {
+ Log.e(TAG, "Failed to copy " + srcFile + " to " + destFile);
+ return false;
+ }
+ Log.i(TAG, "Successfully copied resource to " + newCachePath);
+ if (!PackageHelper.finalizeSdDir(newCacheId)) {
+ Log.e(TAG, "Failed to finalize " + newCacheId);
+ PackageHelper.destroySdDir(newCacheId);
+ return false;
+ }
+ Log.i(TAG, "Finalized " + newCacheId);
+ Runtime.getRuntime().gc();
+ // Unmount first
+ PackageHelper.unMountSdDir(cid);
+ // Delete old container
+ PackageHelper.destroySdDir(cid);
+ // Dont have to mount. Already mounted.
+ cid = newCacheId;
+ cachePath = newCachePath;
+ return true;
}
- cid = newCacheId;
- cachePath = newCachePath;
- return true;
}
int doPostInstall(int status) {
@@ -4771,8 +5001,8 @@
if (sidx != -1) {
subStr = subStr.substring(sidx + prefix.length());
if (subStr != null) {
- if (subStr.startsWith("-")) {
- subStr = subStr.substring(1);
+ if (subStr.startsWith(INSTALL_PACKAGE_SUFFIX)) {
+ subStr = subStr.substring(INSTALL_PACKAGE_SUFFIX.length());
}
try {
idx = Integer.parseInt(subStr);
@@ -4786,10 +5016,26 @@
}
}
}
- idxStr = "-" + Integer.toString(idx);
+ idxStr = INSTALL_PACKAGE_SUFFIX + Integer.toString(idx);
return prefix + idxStr;
}
+ // Utility method used to ignore ADD/REMOVE events
+ // by directory observer.
+ private static boolean ignoreCodePath(String fullPathStr) {
+ String apkName = getApkName(fullPathStr);
+ int idx = apkName.lastIndexOf(INSTALL_PACKAGE_SUFFIX);
+ if (idx != -1 && ((idx+1) < apkName.length())) {
+ // Make sure the package ends with a numeral
+ String version = apkName.substring(idx+1);
+ try {
+ Integer.parseInt(version);
+ return true;
+ } catch (NumberFormatException e) {}
+ }
+ return false;
+ }
+
// 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.
@@ -5055,6 +5301,19 @@
}
}
+ // Utility method used to move dex files during install.
+ private int moveDexFiles(PackageParser.Package newPackage) {
+ int retCode;
+ if ((newPackage.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
+ retCode = mInstaller.movedex(newPackage.mScanPath, newPackage.mPath);
+ if (retCode != 0) {
+ Log.e(TAG, "Couldn't rename dex file: " + newPackage.mPath);
+ return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ }
+ }
+ return PackageManager.INSTALL_SUCCEEDED;
+ }
+
private void updateSettingsLI(PackageParser.Package newPackage,
String installerPackageName, PackageInstalledInfo res) {
String pkgName = newPackage.packageName;
@@ -5066,27 +5325,20 @@
mSettings.writeLP();
}
- int retCode = 0;
- if ((newPackage.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
- retCode = mInstaller.movedex(newPackage.mScanPath, newPackage.mPath);
- if (retCode != 0) {
- Log.e(TAG, "Couldn't rename dex file: " + newPackage.mPath);
- res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- return;
- }
+ if ((res.returnCode = moveDexFiles(newPackage))
+ != PackageManager.INSTALL_SUCCEEDED) {
+ // Discontinue if moving dex files failed.
+ return;
}
- res.returnCode = setPermissionsLI(newPackage);
- if(res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
+ if((res.returnCode = setPermissionsLI(newPackage))
+ != PackageManager.INSTALL_SUCCEEDED) {
+ if (mInstaller != null) {
+ mInstaller.rmdex(newPackage.mScanPath);
+ }
return;
} else {
Log.d(TAG, "New package installed in " + newPackage.mPath);
}
- if(res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
- if (mInstaller != null) {
- mInstaller.rmdex(newPackage.mScanPath);
- }
- }
-
synchronized (mPackages) {
grantPermissionsLP(newPackage, true);
res.name = pkgName;
@@ -5176,6 +5428,7 @@
res.returnCode = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
return;
}
+
if (!args.doRename(res.returnCode, pkgName, oldCodePath)) {
res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
return;
@@ -5369,6 +5622,16 @@
PackageRemovedInfo info = new PackageRemovedInfo();
boolean res;
+ IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
+ ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
+ try {
+ if (dpm != null && dpm.packageHasActiveAdmins(packageName)) {
+ Log.w(TAG, "Not removing package " + packageName + ": has active device admin");
+ return false;
+ }
+ } catch (RemoteException e) {
+ }
+
synchronized (mInstallLock) {
res = deletePackageLI(packageName, deleteCodeAndResources, flags, info);
}
@@ -5467,6 +5730,18 @@
// remove permissions associated with package
mSettings.updateSharedUserPermsLP(deletedPs, mGlobalGids);
}
+ if (deletedPs != null) {
+ // remove from preferred activities.
+ ArrayList<PreferredActivity> removed = new ArrayList<PreferredActivity>();
+ for (PreferredActivity pa : mSettings.mPreferredActivities.filterSet()) {
+ if (pa.mActivity.getPackageName().equals(deletedPs.name)) {
+ removed.add(pa);
+ }
+ }
+ for (PreferredActivity pa : removed) {
+ mSettings.mPreferredActivities.removeFilter(pa);
+ }
+ }
// Save settings now
mSettings.writeLP();
}
@@ -6765,9 +7040,13 @@
HashSet<String> loadedPermissions = new HashSet<String>();
GrantedPermissions(int pkgFlags) {
+ setFlags(pkgFlags);
+ }
+
+ void setFlags(int pkgFlags) {
this.pkgFlags = (pkgFlags & ApplicationInfo.FLAG_SYSTEM) |
- (pkgFlags & ApplicationInfo.FLAG_FORWARD_LOCK) |
- (pkgFlags & ApplicationInfo.FLAG_ON_SDCARD);
+ (pkgFlags & ApplicationInfo.FLAG_FORWARD_LOCK) |
+ (pkgFlags & ApplicationInfo.FLAG_ON_SDCARD);
}
}
@@ -7220,9 +7499,9 @@
Log.w(TAG, "Trying to update system app code path from " +
p.codePathString + " to " + codePath.toString());
} else {
- // Let the app continue with previous uid if code path changes.
- reportSettingsProblem(Log.WARN,
- "Package " + name + " codePath changed from " + p.codePath
+ // Just a change in the code path is not an issue, but
+ // let's log a message about it.
+ Log.i(TAG, "Package " + name + " codePath changed from " + p.codePath
+ " to " + codePath + "; Retaining data and using new");
}
}
@@ -8556,7 +8835,7 @@
}
static String getTempContainerId() {
- String prefix = "smdl1tmp";
+ String prefix = "smdl2tmp";
int tmpIdx = 1;
String list[] = PackageHelper.getSecureContainerList();
if (list != null) {
@@ -8598,30 +8877,56 @@
return prefix + tmpIdx;
}
- public void updateExternalMediaStatus(final boolean mediaStatus) {
+ /*
+ * Return true if PackageManager does have packages to be updated.
+ */
+ public boolean updateExternalMediaStatus(final boolean mediaStatus) {
synchronized (mPackages) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" +
mediaStatus+", mMediaMounted=" + mMediaMounted);
if (mediaStatus == mMediaMounted) {
- return;
+ return false;
}
mMediaMounted = mediaStatus;
+ boolean ret = false;
+ synchronized (mPackages) {
+ Set<String> appList = mSettings.findPackagesWithFlag(ApplicationInfo.FLAG_ON_SDCARD);
+ ret = appList != null && appList.size() > 0;
+ }
+ if (!ret) {
+ // No packages will be effected by the sdcard update. Just return.
+ return false;
+ }
// Queue up an async operation since the package installation may take a little while.
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
- updateExternalMediaStatusInner(mediaStatus);
+ // If we are up here that means there are packages to be
+ // enabled or disabled.
+ final HashMap<SdInstallArgs, String> processCids =
+ new HashMap<SdInstallArgs, String>();
+ final int[] uidArr = getExternalMediaPackages(mediaStatus, processCids);
+ if (mediaStatus) {
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages");
+ loadMediaPackages(processCids, uidArr);
+ startCleaningPackages();
+ } else {
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading packages");
+ unloadMediaPackages(processCids, uidArr);
+ }
}
});
+ return true;
}
}
- void updateExternalMediaStatusInner(boolean mediaStatus) {
+ private int[] getExternalMediaPackages(boolean mediaStatus,
+ Map<SdInstallArgs, String> processCids) {
final String list[] = PackageHelper.getSecureContainerList();
if (list == null || list.length == 0) {
- return;
+ return null;
}
- HashMap<SdInstallArgs, String> processCids = new HashMap<SdInstallArgs, String>();
+
int uidList[] = new int[list.length];
int num = 0;
synchronized (mPackages) {
@@ -8662,20 +8967,8 @@
uidArr[di++] = uidList[i];
}
}
- if (true) {
- for (int j = 0; j < num; j++) {
- Log.i(TAG, "uidArr[" + j + "]=" + uidArr[j]);
- }
- }
}
- if (mediaStatus) {
- if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages");
- loadMediaPackages(processCids, uidArr);
- startCleaningPackages();
- } else {
- if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading packages");
- unloadMediaPackages(processCids, uidArr);
- }
+ return uidArr;
}
private void sendResourcesChangedBroadcast(boolean mediaStatus,
@@ -8695,7 +8988,7 @@
}
}
- void loadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[]) {
+ private void loadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[]) {
ArrayList<String> pkgList = new ArrayList<String>();
Set<SdInstallArgs> keys = processCids.keySet();
for (SdInstallArgs args : keys) {
@@ -8737,15 +9030,15 @@
}
args.doPostInstall(retCode);
}
- // Send broadcasts first
+ // Send a broadcast to let everyone know we are done processing
+ sendResourcesChangedBroadcast(true, pkgList, uidArr);
if (pkgList.size() > 0) {
- sendResourcesChangedBroadcast(true, pkgList, uidArr);
Runtime.getRuntime().gc();
// If something failed do we clean up here or next install?
}
}
- void unloadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[]) {
+ private void unloadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[]) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "unloading media packages");
ArrayList<String> pkgList = new ArrayList<String>();
ArrayList<SdInstallArgs> failedList = new ArrayList<SdInstallArgs>();
@@ -8767,9 +9060,9 @@
}
}
}
+ sendResourcesChangedBroadcast(false, pkgList, uidArr);
// Send broadcasts
if (pkgList.size() > 0) {
- sendResourcesChangedBroadcast(false, pkgList, uidArr);
Runtime.getRuntime().gc();
}
// Do clean up. Just unmount
@@ -8779,4 +9072,145 @@
}
}
}
+
+ public void movePackage(final String packageName,
+ final IPackageMoveObserver observer, final int flags) {
+ if (packageName == null) {
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MOVE_PACKAGE, null);
+ int returnCode = PackageManager.MOVE_SUCCEEDED;
+ int currFlags = 0;
+ int newFlags = 0;
+ synchronized (mPackages) {
+ PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST;
+ }
+ // Disable moving fwd locked apps and system packages
+ if (pkg.applicationInfo != null &&
+ (pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ Log.w(TAG, "Cannot move system application");
+ returnCode = PackageManager.MOVE_FAILED_SYSTEM_PACKAGE;
+ } else if (pkg.applicationInfo != null &&
+ (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0) {
+ Log.w(TAG, "Cannot move forward locked app.");
+ returnCode = PackageManager.MOVE_FAILED_FORWARD_LOCKED;
+ } else {
+ // Find install location first
+ if ((flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 &&
+ (flags & PackageManager.MOVE_INTERNAL) != 0) {
+ Log.w(TAG, "Ambigous flags specified for move location.");
+ returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION;
+ } else {
+ newFlags = (flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 ?
+ PackageManager.INSTALL_EXTERNAL : 0;
+ currFlags = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0 ?
+ PackageManager.INSTALL_EXTERNAL : 0;
+ if (newFlags == currFlags) {
+ Log.w(TAG, "No move required. Trying to move to same location");
+ returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION;
+ }
+ }
+ }
+ if (returnCode != PackageManager.MOVE_SUCCEEDED) {
+ processPendingMove(new MoveParams(null, observer, 0, null), returnCode);
+ } else {
+ Message msg = mHandler.obtainMessage(INIT_COPY);
+ InstallArgs srcArgs = createInstallArgs(currFlags, pkg.applicationInfo.sourceDir,
+ pkg.applicationInfo.publicSourceDir);
+ MoveParams mp = new MoveParams(srcArgs, observer, newFlags,
+ packageName);
+ msg.obj = mp;
+ mHandler.sendMessage(msg);
+ }
+ }
+ }
+
+ private void processPendingMove(final MoveParams mp, final int currentStatus) {
+ // Queue up an async operation since the package deletion may take a little while.
+ mHandler.post(new Runnable() {
+ public void run() {
+ mHandler.removeCallbacks(this);
+ int returnCode = currentStatus;
+ boolean moveSucceeded = (returnCode == PackageManager.MOVE_SUCCEEDED);
+ if (moveSucceeded) {
+ int uid = -1;
+ synchronized (mPackages) {
+ uid = mPackages.get(mp.packageName).applicationInfo.uid;
+ }
+ ArrayList<String> pkgList = new ArrayList<String>();
+ pkgList.add(mp.packageName);
+ int uidArr[] = new int[] { uid };
+ // Send resources unavailable broadcast
+ sendResourcesChangedBroadcast(false, pkgList, uidArr);
+
+ // Update package code and resource paths
+ synchronized (mPackages) {
+ PackageParser.Package pkg = mPackages.get(mp.packageName);
+ if (pkg != null) {
+ String oldCodePath = pkg.mPath;
+ String newCodePath = mp.targetArgs.getCodePath();
+ String newResPath = mp.targetArgs.getResourcePath();
+ pkg.mPath = newCodePath;
+ // Move dex files around
+ if (moveDexFiles(pkg)
+ != PackageManager.INSTALL_SUCCEEDED) {
+ // Moving of dex files failed. Set
+ // error code and abort move.
+ pkg.mPath = pkg.mScanPath;
+ returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
+ moveSucceeded = false;
+ } else {
+ pkg.mScanPath = newCodePath;
+ pkg.applicationInfo.sourceDir = newCodePath;
+ pkg.applicationInfo.publicSourceDir = newResPath;
+ PackageSetting ps = (PackageSetting) pkg.mExtras;
+ ps.codePath = new File(pkg.applicationInfo.sourceDir);
+ ps.codePathString = ps.codePath.getPath();
+ ps.resourcePath = new File(pkg.applicationInfo.publicSourceDir);
+ ps.resourcePathString = ps.resourcePath.getPath();
+ // Set the application info flag correctly.
+ if ((mp.flags & PackageManager.INSTALL_EXTERNAL) != 0) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_ON_SDCARD;
+ } else {
+ pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_ON_SDCARD;
+ }
+ ps.setFlags(pkg.applicationInfo.flags);
+ mAppDirs.remove(oldCodePath);
+ mAppDirs.put(newCodePath, pkg);
+ // Persist settings
+ mSettings.writeLP();
+ }
+ }
+ }
+ if (moveSucceeded) {
+ // Delete older code
+ synchronized (mInstallLock) {
+ mp.srcArgs.cleanUpResourcesLI();
+ }
+ // Send resources available broadcast
+ sendResourcesChangedBroadcast(true, pkgList, uidArr);
+ Runtime.getRuntime().gc();
+ }
+ }
+ if (!moveSucceeded){
+ // Clean up failed installation
+ if (mp.targetArgs != null) {
+ mp.targetArgs.doPostInstall(
+ PackageManager.INSTALL_FAILED_INTERNAL_ERROR);
+ }
+ }
+ IPackageMoveObserver observer = mp.observer;
+ if (observer != null) {
+ try {
+ observer.packageMoved(mp.packageName, returnCode);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Observer no longer exists.");
+ }
+ }
+ }
+ });
+ }
}
diff --git a/services/java/com/android/server/RecognitionManagerService.java b/services/java/com/android/server/RecognitionManagerService.java
new file mode 100644
index 0000000..7305b07
--- /dev/null
+++ b/services/java/com/android/server/RecognitionManagerService.java
@@ -0,0 +1,130 @@
+/*
+ * 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.server;
+
+import com.android.internal.content.PackageMonitor;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+import android.provider.Settings;
+import android.speech.RecognitionService;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+
+public class RecognitionManagerService extends Binder {
+ final static String TAG = "RecognitionManagerService";
+
+ final Context mContext;
+ final MyPackageMonitor mMonitor;
+
+ class MyPackageMonitor extends PackageMonitor {
+ public void onSomePackagesChanged() {
+ ComponentName comp = getCurRecognizer();
+ if (comp == null) {
+ if (anyPackagesAppearing()) {
+ comp = findAvailRecognizer(null);
+ if (comp != null) {
+ setCurRecognizer(comp);
+ }
+ }
+ return;
+ }
+
+ int change = isPackageDisappearing(comp.getPackageName());
+ if (change == PACKAGE_PERMANENT_CHANGE
+ || change == PACKAGE_TEMPORARY_CHANGE) {
+ setCurRecognizer(findAvailRecognizer(null));
+
+ } else if (isPackageModified(comp.getPackageName())) {
+ setCurRecognizer(findAvailRecognizer(comp.getPackageName()));
+ }
+ }
+ }
+
+ RecognitionManagerService(Context context) {
+ mContext = context;
+ mMonitor = new MyPackageMonitor();
+ mMonitor.register(context, true);
+ }
+
+ public void systemReady() {
+ ComponentName comp = getCurRecognizer();
+ if (comp != null) {
+ // See if the current recognizer is no longer available.
+ try {
+ mContext.getPackageManager().getServiceInfo(comp, 0);
+ } catch (NameNotFoundException e) {
+ setCurRecognizer(null);
+ }
+ } else {
+ comp = findAvailRecognizer(null);
+ if (comp != null) {
+ setCurRecognizer(comp);
+ }
+ }
+ }
+
+ ComponentName findAvailRecognizer(String prefPackage) {
+ List<ResolveInfo> available =
+ mContext.getPackageManager().queryIntentServices(
+ new Intent(RecognitionService.SERVICE_INTERFACE), 0);
+ int numAvailable = available.size();
+
+ if (numAvailable == 0) {
+ Log.w(TAG, "no available voice recognition services found");
+ return null;
+ } else {
+ if (prefPackage != null) {
+ for (int i=0; i<numAvailable; i++) {
+ ServiceInfo serviceInfo = available.get(i).serviceInfo;
+ if (prefPackage.equals(serviceInfo.packageName)) {
+ return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ }
+ }
+ }
+ if (numAvailable > 1) {
+ Log.w(TAG, "more than one voice recognition service found, picking first");
+ }
+
+ ServiceInfo serviceInfo = available.get(0).serviceInfo;
+ return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ }
+ }
+
+ ComponentName getCurRecognizer() {
+ String curRecognizer = Settings.Secure.getString(
+ mContext.getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE);
+ if (TextUtils.isEmpty(curRecognizer)) {
+ return null;
+ }
+ return ComponentName.unflattenFromString(curRecognizer);
+ }
+
+ void setCurRecognizer(ComponentName comp) {
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE,
+ comp != null ? comp.flattenToShortString() : "");
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a09896a..1f46faf 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -97,6 +97,7 @@
BluetoothA2dpService bluetoothA2dp = null;
HeadsetObserver headset = null;
DockObserver dock = null;
+ RecognitionManagerService recognition = null;
// Critical services...
try {
@@ -208,6 +209,7 @@
AppWidgetService appWidget = null;
NotificationManagerService notification = null;
WallpaperManagerService wallpaper = null;
+ LocationManagerService location = null;
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
try {
@@ -302,8 +304,8 @@
try {
Log.i(TAG, "Location Manager");
- ServiceManager.addService(Context.LOCATION_SERVICE,
- new LocationManagerService(context));
+ location = new LocationManagerService(context);
+ ServiceManager.addService(Context.LOCATION_SERVICE, location);
} catch (Throwable e) {
Log.e(TAG, "Failure starting Location Manager", e);
}
@@ -377,6 +379,13 @@
}
try {
+ Log.i(TAG, "Recognition Service");
+ recognition = new RecognitionManagerService(context);
+ } catch (Throwable e) {
+ Log.e(TAG, "Failure starting Recognition Service", e);
+ }
+
+ try {
com.android.server.status.StatusBarPolicy.installIcons(context, statusBar);
} catch (Throwable e) {
Log.e(TAG, "Failure installing status bar icons", e);
@@ -435,6 +444,8 @@
final AppWidgetService appWidgetF = appWidget;
final WallpaperManagerService wallpaperF = wallpaper;
final InputMethodManagerService immF = imm;
+ final RecognitionManagerService recognitionF = recognition;
+ final LocationManagerService locationF = location;
// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
@@ -449,6 +460,7 @@
if (batteryF != null) batteryF.systemReady();
if (connectivityF != null) connectivityF.systemReady();
if (dockF != null) dockF.systemReady();
+ if (recognitionF != null) recognitionF.systemReady();
Watchdog.getInstance().start();
// It is now okay to let the various system services start their
@@ -457,6 +469,7 @@
if (appWidgetF != null) appWidgetF.systemReady(safeMode);
if (wallpaperF != null) wallpaperF.systemReady();
if (immF != null) immF.systemReady();
+ if (locationF != null) locationF.systemReady();
}
});
diff --git a/services/java/com/android/server/TwilightCalculator.java b/services/java/com/android/server/TwilightCalculator.java
index a8f67d8..a5c93b5 100644
--- a/services/java/com/android/server/TwilightCalculator.java
+++ b/services/java/com/android/server/TwilightCalculator.java
@@ -46,10 +46,16 @@
// Java time on Jan 1, 2000 12:00 UTC.
private static final long UTC_2000 = 946728000000L;
- /** Time of sunset (civil twilight) in milliseconds. */
+ /**
+ * Time of sunset (civil twilight) in milliseconds or -1 in the case the day
+ * or night never ends.
+ */
public long mSunset;
- /** Time of sunrise (civil twilight) in milliseconds. */
+ /**
+ * Time of sunrise (civil twilight) in milliseconds or -1 in the case the
+ * day or night never ends.
+ */
public long mSunrise;
/** Current state */
@@ -85,10 +91,24 @@
double solarDec = Math.asin(FloatMath.sin(solarLng) * FloatMath.sin(OBLIQUITY));
final double latRad = latiude * DEGREES_TO_RADIANS;
- float hourAngle = (float) (Math
- .acos((FloatMath.sin(ALTIDUTE_CORRECTION_CIVIL_TWILIGHT) - Math.sin(latRad)
- * Math.sin(solarDec))
- / (Math.cos(latRad) * Math.cos(solarDec))) / (2 * Math.PI));
+
+ double cosHourAngle = (FloatMath.sin(ALTIDUTE_CORRECTION_CIVIL_TWILIGHT) - Math.sin(latRad)
+ * Math.sin(solarDec)) / (Math.cos(latRad) * Math.cos(solarDec));
+ // The day or night never ends for the given date and location, if this value is out of
+ // range.
+ if (cosHourAngle >= 1) {
+ mState = NIGHT;
+ mSunset = -1;
+ mSunrise = -1;
+ return;
+ } else if (cosHourAngle <= -1) {
+ mState = DAY;
+ mSunset = -1;
+ mSunrise = -1;
+ return;
+ }
+
+ float hourAngle = (float) (Math.acos(cosHourAngle) / (2 * Math.PI));
mSunset = Math.round((solarTransitJ2000 + hourAngle) * DateUtils.DAY_IN_MILLIS) + UTC_2000;
mSunrise = Math.round((solarTransitJ2000 - hourAngle) * DateUtils.DAY_IN_MILLIS) + UTC_2000;
diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java
index 81255ee..481e6a4 100644
--- a/services/java/com/android/server/WallpaperManagerService.java
+++ b/services/java/com/android/server/WallpaperManagerService.java
@@ -65,7 +65,10 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.service.wallpaper.ImageWallpaper;
+import com.android.server.DevicePolicyManagerService.ActiveAdmin;
+import com.android.server.DevicePolicyManagerService.MyPackageMonitor;
import com.android.common.FastXmlSerializer;
class WallpaperManagerService extends IWallpaperManager.Stub {
@@ -122,6 +125,7 @@
final Context mContext;
final IWindowManager mIWindowManager;
+ final MyPackageMonitor mMonitor;
int mWidth = -1;
int mHeight = -1;
@@ -150,6 +154,7 @@
WallpaperConnection mWallpaperConnection;
long mLastDiedTime;
+ boolean mWallpaperUpdating;
class WallpaperConnection extends IWallpaperConnection.Stub
implements ServiceConnection {
@@ -165,6 +170,7 @@
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
if (mWallpaperConnection == this) {
+ mLastDiedTime = SystemClock.uptimeMillis();
mService = IWallpaperService.Stub.asInterface(service);
attachServiceLocked(this);
// XXX should probably do saveSettingsLocked() later
@@ -182,8 +188,8 @@
mEngine = null;
if (mWallpaperConnection == this) {
Log.w(TAG, "Wallpaper service gone: " + mWallpaperComponent);
- if ((mLastDiedTime+MIN_WALLPAPER_CRASH_TIME)
- < SystemClock.uptimeMillis()) {
+ if (!mWallpaperUpdating && (mLastDiedTime+MIN_WALLPAPER_CRASH_TIME)
+ > SystemClock.uptimeMillis()) {
Log.w(TAG, "Reverting to built-in wallpaper!");
bindWallpaperComponentLocked(null);
}
@@ -205,11 +211,92 @@
}
}
+ class MyPackageMonitor extends PackageMonitor {
+ @Override
+ public void onPackageUpdateFinished(String packageName, int uid) {
+ synchronized (mLock) {
+ if (mWallpaperComponent != null &&
+ mWallpaperComponent.getPackageName().equals(packageName)) {
+ mWallpaperUpdating = false;
+ ComponentName comp = mWallpaperComponent;
+ clearWallpaperComponentLocked();
+ bindWallpaperComponentLocked(comp);
+ }
+ }
+ }
+
+ @Override
+ public void onPackageUpdateStarted(String packageName, int uid) {
+ synchronized (mLock) {
+ if (mWallpaperComponent != null &&
+ mWallpaperComponent.getPackageName().equals(packageName)) {
+ mWallpaperUpdating = true;
+ }
+ }
+ }
+
+ @Override
+ public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+ return doPackagesChanged(doit);
+ }
+
+ @Override
+ public void onSomePackagesChanged() {
+ doPackagesChanged(true);
+ }
+
+ boolean doPackagesChanged(boolean doit) {
+ boolean changed = false;
+ synchronized (mLock) {
+ if (mWallpaperComponent != null) {
+ int change = isPackageDisappearing(mWallpaperComponent.getPackageName());
+ if (change == PACKAGE_PERMANENT_CHANGE
+ || change == PACKAGE_TEMPORARY_CHANGE) {
+ changed = true;
+ if (doit) {
+ Log.w(TAG, "Wallpaper uninstalled, removing: " + mWallpaperComponent);
+ clearWallpaperLocked();
+ }
+ }
+ }
+ if (mNextWallpaperComponent != null) {
+ int change = isPackageDisappearing(mNextWallpaperComponent.getPackageName());
+ if (change == PACKAGE_PERMANENT_CHANGE
+ || change == PACKAGE_TEMPORARY_CHANGE) {
+ mNextWallpaperComponent = null;
+ }
+ }
+ if (mWallpaperComponent != null
+ && isPackageModified(mWallpaperComponent.getPackageName())) {
+ try {
+ mContext.getPackageManager().getServiceInfo(
+ mWallpaperComponent, 0);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Wallpaper component gone, removing: " + mWallpaperComponent);
+ clearWallpaperLocked();
+ }
+ }
+ if (mNextWallpaperComponent != null
+ && isPackageModified(mNextWallpaperComponent.getPackageName())) {
+ try {
+ mContext.getPackageManager().getServiceInfo(
+ mNextWallpaperComponent, 0);
+ } catch (NameNotFoundException e) {
+ mNextWallpaperComponent = null;
+ }
+ }
+ }
+ return changed;
+ }
+ }
+
public WallpaperManagerService(Context context) {
if (DEBUG) Log.v(TAG, "WallpaperService startup");
mContext = context;
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
+ mMonitor = new MyPackageMonitor();
+ mMonitor.register(context, true);
WALLPAPER_DIR.mkdirs();
loadSettingsLocked();
mWallpaperObserver.startWatching();
@@ -241,16 +328,20 @@
public void clearWallpaper() {
if (DEBUG) Log.v(TAG, "clearWallpaper");
synchronized (mLock) {
- File f = WALLPAPER_FILE;
- if (f.exists()) {
- f.delete();
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- bindWallpaperComponentLocked(null);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ clearWallpaperLocked();
+ }
+ }
+
+ public void clearWallpaperLocked() {
+ File f = WALLPAPER_FILE;
+ if (f.exists()) {
+ f.delete();
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ bindWallpaperComponentLocked(null);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -499,7 +590,9 @@
mWidth, mHeight);
} catch (RemoteException e) {
Log.w(TAG, "Failed attaching wallpaper; clearing", e);
- bindWallpaperComponentLocked(null);
+ if (!mWallpaperUpdating) {
+ bindWallpaperComponentLocked(null);
+ }
}
}
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 7fcf900..a481036e 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -373,10 +373,15 @@
boolean mLayoutNeeded = true;
boolean mAnimationPending = false;
boolean mDisplayFrozen = false;
+ boolean mWaitingForConfig = false;
boolean mWindowsFreezingScreen = false;
long mFreezeGcPending = 0;
int mAppsFreezingScreen = 0;
+ int mLayoutSeq = 0;
+
+ Configuration mCurConfiguration = new Configuration();
+
// This is held as long as we have the screen frozen, to give us time to
// perform a rotation animation when turning off shows the lock screen which
// changes the orientation.
@@ -1828,7 +1833,7 @@
}
if (attrs.type >= FIRST_SUB_WINDOW && attrs.type <= LAST_SUB_WINDOW) {
- attachedWindow = windowForClientLocked(null, attrs.token);
+ attachedWindow = windowForClientLocked(null, attrs.token, false);
if (attachedWindow == null) {
Log.w(TAG, "Attempted to add window with token that is not a window: "
+ attrs.token + ". Aborting.");
@@ -1988,6 +1993,10 @@
if (localLOGV) Log.v(
TAG, "New client " + client.asBinder()
+ ": window=" + win);
+
+ if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked()) {
+ reportNewConfig = true;
+ }
}
// sendNewConfiguration() checks caller permissions so we must call it with
@@ -1997,14 +2006,6 @@
final long origId = Binder.clearCallingIdentity();
if (reportNewConfig) {
sendNewConfiguration();
- } else {
- // Update Orientation after adding a window, only if the window needs to be
- // displayed right away
- if (win.isVisibleOrAdding()) {
- if (updateOrientationFromAppTokensUnchecked(null, null) != null) {
- sendNewConfiguration();
- }
- }
}
Binder.restoreCallingIdentity(origId);
@@ -2013,7 +2014,7 @@
public void removeWindow(Session session, IWindow client) {
synchronized(mWindowMap) {
- WindowState win = windowForClientLocked(session, client);
+ WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
return;
}
@@ -2081,8 +2082,9 @@
// Removing a visible window will effect the computed orientation
// So just update orientation if needed.
if (wasVisible && computeForcedAppOrientationLocked()
- != mForcedAppOrientation) {
- mH.sendMessage(mH.obtainMessage(H.COMPUTE_AND_SEND_NEW_CONFIGURATION));
+ != mForcedAppOrientation
+ && updateOrientationFromAppTokensLocked()) {
+ mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
}
updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL);
Binder.restoreCallingIdentity(origId);
@@ -2177,7 +2179,7 @@
long origId = Binder.clearCallingIdentity();
try {
synchronized (mWindowMap) {
- WindowState w = windowForClientLocked(session, client);
+ WindowState w = windowForClientLocked(session, client, false);
if ((w != null) && (w.mSurface != null)) {
if (SHOW_TRANSACTIONS) Log.i(TAG, ">>> OPEN TRANSACTION");
Surface.openTransaction();
@@ -2203,7 +2205,7 @@
long origId = Binder.clearCallingIdentity();
try {
synchronized (mWindowMap) {
- WindowState w = windowForClientLocked(session, client);
+ WindowState w = windowForClientLocked(session, client, false);
if (w != null) {
w.mGivenInsetsPending = false;
w.mGivenContentInsets.set(contentInsets);
@@ -2221,7 +2223,7 @@
public void getWindowDisplayFrame(Session session, IWindow client,
Rect outDisplayFrame) {
synchronized(mWindowMap) {
- WindowState win = windowForClientLocked(session, client);
+ WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
outDisplayFrame.setEmpty();
return;
@@ -2291,11 +2293,11 @@
Surface outSurface) {
boolean displayed = false;
boolean inTouchMode;
- Configuration newConfig = null;
+ boolean configChanged;
long origId = Binder.clearCallingIdentity();
synchronized(mWindowMap) {
- WindowState win = windowForClientLocked(session, client);
+ WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
return 0;
}
@@ -2507,7 +2509,7 @@
if (assignLayers) {
assignLayersLocked();
}
- newConfig = updateOrientationFromAppTokensLocked(null, null);
+ configChanged = updateOrientationFromAppTokensLocked();
performLayoutAndPlaceSurfacesLocked();
if (displayed && win.mIsWallpaper) {
updateWallpaperOffsetLocked(win, mDisplay.getWidth(),
@@ -2533,7 +2535,7 @@
inTouchMode = mInTouchMode;
}
- if (newConfig != null) {
+ if (configChanged) {
sendNewConfiguration();
}
@@ -2546,7 +2548,7 @@
public void finishDrawingWindow(Session session, IWindow client) {
final long origId = Binder.clearCallingIdentity();
synchronized(mWindowMap) {
- WindowState win = windowForClientLocked(session, client);
+ WindowState win = windowForClientLocked(session, client, false);
if (win != null && win.finishDrawingLocked()) {
if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
adjustWallpaperWindowsLocked();
@@ -2988,62 +2990,62 @@
}
public int getOrientationFromAppTokensLocked() {
- int pos = mAppTokens.size() - 1;
- int curGroup = 0;
- int lastOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
- boolean findingBehind = false;
- boolean haveGroup = false;
- boolean lastFullscreen = false;
- while (pos >= 0) {
- AppWindowToken wtoken = mAppTokens.get(pos);
- pos--;
- // if we're about to tear down this window and not seek for
- // the behind activity, don't use it for orientation
- if (!findingBehind
- && (!wtoken.hidden && wtoken.hiddenRequested)) {
+ int pos = mAppTokens.size() - 1;
+ int curGroup = 0;
+ int lastOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ boolean findingBehind = false;
+ boolean haveGroup = false;
+ boolean lastFullscreen = false;
+ while (pos >= 0) {
+ AppWindowToken wtoken = mAppTokens.get(pos);
+ pos--;
+ // if we're about to tear down this window and not seek for
+ // the behind activity, don't use it for orientation
+ if (!findingBehind
+ && (!wtoken.hidden && wtoken.hiddenRequested)) {
+ continue;
+ }
+
+ if (!haveGroup) {
+ // We ignore any hidden applications on the top.
+ if (wtoken.hiddenRequested || wtoken.willBeHidden) {
continue;
}
-
- if (!haveGroup) {
- // We ignore any hidden applications on the top.
- if (wtoken.hiddenRequested || wtoken.willBeHidden) {
- continue;
- }
- haveGroup = true;
- curGroup = wtoken.groupId;
- lastOrientation = wtoken.requestedOrientation;
- } else if (curGroup != wtoken.groupId) {
- // If we have hit a new application group, and the bottom
- // of the previous group didn't explicitly say to use
- // the orientation behind it, and the last app was
- // full screen, then we'll stick with the
- // user's orientation.
- if (lastOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND
- && lastFullscreen) {
- return lastOrientation;
- }
+ haveGroup = true;
+ curGroup = wtoken.groupId;
+ lastOrientation = wtoken.requestedOrientation;
+ } else if (curGroup != wtoken.groupId) {
+ // If we have hit a new application group, and the bottom
+ // of the previous group didn't explicitly say to use
+ // the orientation behind it, and the last app was
+ // full screen, then we'll stick with the
+ // user's orientation.
+ if (lastOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND
+ && lastFullscreen) {
+ return lastOrientation;
}
- int or = wtoken.requestedOrientation;
- // If this application is fullscreen, and didn't explicitly say
- // to use the orientation behind it, then just take whatever
- // orientation it has and ignores whatever is under it.
- lastFullscreen = wtoken.appFullscreen;
- if (lastFullscreen
- && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
- return or;
- }
- // If this application has requested an explicit orientation,
- // then use it.
- if (or == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ||
- or == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT ||
- or == ActivityInfo.SCREEN_ORIENTATION_SENSOR ||
- or == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR ||
- or == ActivityInfo.SCREEN_ORIENTATION_USER) {
- return or;
- }
- findingBehind |= (or == ActivityInfo.SCREEN_ORIENTATION_BEHIND);
}
- return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ int or = wtoken.requestedOrientation;
+ // If this application is fullscreen, and didn't explicitly say
+ // to use the orientation behind it, then just take whatever
+ // orientation it has and ignores whatever is under it.
+ lastFullscreen = wtoken.appFullscreen;
+ if (lastFullscreen
+ && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
+ return or;
+ }
+ // If this application has requested an explicit orientation,
+ // then use it.
+ if (or == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ||
+ or == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT ||
+ or == ActivityInfo.SCREEN_ORIENTATION_SENSOR ||
+ or == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR ||
+ or == ActivityInfo.SCREEN_ORIENTATION_USER) {
+ return or;
+ }
+ findingBehind |= (or == ActivityInfo.SCREEN_ORIENTATION_BEHIND);
+ }
+ return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
public Configuration updateOrientationFromAppTokens(
@@ -3053,81 +3055,75 @@
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
- Configuration config;
+ Configuration config = null;
long ident = Binder.clearCallingIdentity();
- config = updateOrientationFromAppTokensUnchecked(currentConfig,
- freezeThisOneIfNeeded);
+
+ synchronized(mWindowMap) {
+ if (updateOrientationFromAppTokensLocked()) {
+ if (freezeThisOneIfNeeded != null) {
+ AppWindowToken wtoken = findAppWindowToken(
+ freezeThisOneIfNeeded);
+ if (wtoken != null) {
+ startAppFreezingScreenLocked(wtoken,
+ ActivityInfo.CONFIG_ORIENTATION);
+ }
+ }
+ config = computeNewConfigurationLocked();
+
+ } else if (currentConfig != null) {
+ // No obvious action we need to take, but if our current
+ // state mismatches the activity maanager's, update it
+ mTempConfiguration.setToDefaults();
+ if (computeNewConfigurationLocked(mTempConfiguration)) {
+ if (currentConfig.diff(mTempConfiguration) != 0) {
+ mWaitingForConfig = true;
+ mLayoutNeeded = true;
+ startFreezingDisplayLocked();
+ config = new Configuration(mTempConfiguration);
+ }
+ }
+ }
+ }
+
Binder.restoreCallingIdentity(ident);
return config;
}
- Configuration updateOrientationFromAppTokensUnchecked(
- Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
- Configuration config;
- synchronized(mWindowMap) {
- config = updateOrientationFromAppTokensLocked(currentConfig, freezeThisOneIfNeeded);
- if (config != null) {
- mLayoutNeeded = true;
- performLayoutAndPlaceSurfacesLocked();
- }
- }
- return config;
- }
-
/*
+ * Determine the new desired orientation of the display, returning
+ * a non-null new Configuration if it has changed from the current
+ * orientation. IF TRUE IS RETURNED SOMEONE MUST CALL
+ * setNewConfiguration() TO TELL THE WINDOW MANAGER IT CAN UNFREEZE THE
+ * SCREEN. This will typically be done for you if you call
+ * sendNewConfiguration().
+ *
* The orientation is computed from non-application windows first. If none of
* the non-application windows specify orientation, the orientation is computed from
* application tokens.
* @see android.view.IWindowManager#updateOrientationFromAppTokens(
* android.os.IBinder)
*/
- Configuration updateOrientationFromAppTokensLocked(
- Configuration appConfig, IBinder freezeThisOneIfNeeded) {
+ boolean updateOrientationFromAppTokensLocked() {
boolean changed = false;
long ident = Binder.clearCallingIdentity();
try {
int req = computeForcedAppOrientationLocked();
if (req != mForcedAppOrientation) {
- changed = true;
mForcedAppOrientation = req;
//send a message to Policy indicating orientation change to take
//action like disabling/enabling sensors etc.,
mPolicy.setCurrentOrientationLw(req);
- }
-
- if (changed) {
- changed = setRotationUncheckedLocked(
- WindowManagerPolicy.USE_LAST_ROTATION,
- mLastRotationFlags & (~Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE));
- if (changed) {
- if (freezeThisOneIfNeeded != null) {
- AppWindowToken wtoken = findAppWindowToken(
- freezeThisOneIfNeeded);
- if (wtoken != null) {
- startAppFreezingScreenLocked(wtoken,
- ActivityInfo.CONFIG_ORIENTATION);
- }
- }
- return computeNewConfigurationLocked();
+ if (setRotationUncheckedLocked(WindowManagerPolicy.USE_LAST_ROTATION,
+ mLastRotationFlags | Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE)) {
+ changed = true;
}
}
- // No obvious action we need to take, but if our current
- // state mismatches the activity maanager's, update it
- if (appConfig != null) {
- mTempConfiguration.setToDefaults();
- if (computeNewConfigurationLocked(mTempConfiguration)) {
- if (appConfig.diff(mTempConfiguration) != 0) {
- return new Configuration(mTempConfiguration);
- }
- }
- }
+ return changed;
} finally {
Binder.restoreCallingIdentity(ident);
}
-
- return null;
}
int computeForcedAppOrientationLocked() {
@@ -3138,6 +3134,19 @@
return req;
}
+ public void setNewConfiguration(Configuration config) {
+ if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+ "setNewConfiguration()")) {
+ throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+ }
+
+ synchronized(mWindowMap) {
+ mCurConfiguration = new Configuration(config);
+ mWaitingForConfig = false;
+ performLayoutAndPlaceSurfacesLocked();
+ }
+ }
+
public void setAppOrientation(IApplicationToken token, int requestedOrientation) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"setAppOrientation()")) {
@@ -3648,9 +3657,7 @@
mLayoutNeeded = true;
performLayoutAndPlaceSurfacesLocked();
}
- if (mAppsFreezingScreen == 0 && !mWindowsFreezingScreen) {
- stopFreezingDisplayLocked();
- }
+ stopFreezingDisplayLocked();
}
}
}
@@ -4403,20 +4410,21 @@
changed = setRotationUncheckedLocked(rotation, animFlags);
}
- if (changed) {
- sendNewConfiguration();
- synchronized(mWindowMap) {
- mLayoutNeeded = true;
- performLayoutAndPlaceSurfacesLocked();
- }
- } else if (alwaysSendConfiguration) {
- //update configuration ignoring orientation change
+ if (changed || alwaysSendConfiguration) {
sendNewConfiguration();
}
Binder.restoreCallingIdentity(origId);
}
+ /**
+ * Apply a new rotation to the screen, respecting the requests of
+ * applications. Use WindowManagerPolicy.USE_LAST_ROTATION to simply
+ * re-evaluate the desired rotation.
+ *
+ * Returns null if the rotation has been changed. In this case YOU
+ * MUST CALL setNewConfiguration() TO UNFREEZE THE SCREEN.
+ */
public boolean setRotationUncheckedLocked(int rotation, int animFlags) {
boolean changed;
if (rotation == WindowManagerPolicy.USE_LAST_ROTATION) {
@@ -4442,6 +4450,8 @@
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
mH.sendMessageDelayed(mH.obtainMessage(H.WINDOW_FREEZE_TIMEOUT),
2000);
+ mWaitingForConfig = true;
+ mLayoutNeeded = true;
startFreezingDisplayLocked();
Log.i(TAG, "Setting rotation to " + rotation + ", animFlags=" + animFlags);
mQueue.setOrientation(rotation);
@@ -6707,7 +6717,8 @@
long ident = Binder.clearCallingIdentity();
try {
return mPolicy.performHapticFeedbackLw(
- windowForClientLocked(this, window), effectId, always);
+ windowForClientLocked(this, window, true),
+ effectId, always);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -6718,7 +6729,8 @@
synchronized(mWindowMap) {
long ident = Binder.clearCallingIdentity();
try {
- setWindowWallpaperPositionLocked(windowForClientLocked(this, window),
+ setWindowWallpaperPositionLocked(
+ windowForClientLocked(this, window, true),
x, y, xStep, yStep);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -6736,7 +6748,7 @@
long ident = Binder.clearCallingIdentity();
try {
return sendWindowWallpaperCommandLocked(
- windowForClientLocked(this, window),
+ windowForClientLocked(this, window, true),
action, x, y, z, extras, sync);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -6852,6 +6864,10 @@
WindowState mNextOutsideTouch;
+ int mLayoutSeq = -1;
+
+ Configuration mConfiguration = null;
+
// Actual frame shown on-screen (may be modified by animation)
final Rect mShownFrame = new Rect();
final Rect mLastShownFrame = new Rect();
@@ -7980,7 +7996,7 @@
public void binderDied() {
try {
synchronized(mWindowMap) {
- WindowState win = windowForClientLocked(mSession, mClient);
+ WindowState win = windowForClientLocked(mSession, mClient, false);
Log.i(TAG, "WIN DEATH: " + win);
if (win != null) {
removeWindowLocked(mSession, win);
@@ -8056,8 +8072,6 @@
}
void dump(PrintWriter pw, String prefix) {
- StringBuilder sb = new StringBuilder(64);
-
pw.print(prefix); pw.print("mSession="); pw.print(mSession);
pw.print(" mClient="); pw.println(mClient.asBinder());
pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs);
@@ -8105,7 +8119,8 @@
pw.print(prefix); pw.print("mRelayoutCalled="); pw.println(mRelayoutCalled);
}
pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth);
- pw.print(" h="); pw.println(mRequestedHeight);
+ pw.print(" h="); pw.print(mRequestedHeight);
+ pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
if (mXOffset != 0 || mYOffset != 0) {
pw.print(prefix); pw.print("Offsets x="); pw.print(mXOffset);
pw.print(" y="); pw.println(mYOffset);
@@ -8119,6 +8134,7 @@
pw.print(prefix); pw.print("mTouchableInsets="); pw.print(mTouchableInsets);
pw.print(" mGivenInsetsPending="); pw.println(mGivenInsetsPending);
}
+ pw.print(prefix); pw.print("mConfiguration="); pw.println(mConfiguration);
pw.print(prefix); pw.print("mShownFrame=");
mShownFrame.printShortString(pw);
pw.print(" last="); mLastShownFrame.printShortString(pw);
@@ -8693,7 +8709,7 @@
public static final int FORCE_GC = 15;
public static final int ENABLE_SCREEN = 16;
public static final int APP_FREEZE_TIMEOUT = 17;
- public static final int COMPUTE_AND_SEND_NEW_CONFIGURATION = 18;
+ public static final int SEND_NEW_CONFIGURATION = 18;
private Session mLastReportedHold;
@@ -9019,10 +9035,9 @@
break;
}
- case COMPUTE_AND_SEND_NEW_CONFIGURATION: {
- if (updateOrientationFromAppTokensUnchecked(null, null) != null) {
- sendNewConfiguration();
- }
+ case SEND_NEW_CONFIGURATION: {
+ removeMessages(SEND_NEW_CONFIGURATION);
+ sendNewConfiguration();
break;
}
@@ -9064,23 +9079,33 @@
// Internals
// -------------------------------------------------------------
- final WindowState windowForClientLocked(Session session, IWindow client) {
- return windowForClientLocked(session, client.asBinder());
+ final WindowState windowForClientLocked(Session session, IWindow client,
+ boolean throwOnError) {
+ return windowForClientLocked(session, client.asBinder(), throwOnError);
}
- final WindowState windowForClientLocked(Session session, IBinder client) {
+ final WindowState windowForClientLocked(Session session, IBinder client,
+ boolean throwOnError) {
WindowState win = mWindowMap.get(client);
if (localLOGV) Log.v(
TAG, "Looking up client " + client + ": " + win);
if (win == null) {
- RuntimeException ex = new RuntimeException();
- Log.w(TAG, "Requested window " + client + " does not exist", ex);
+ RuntimeException ex = new IllegalArgumentException(
+ "Requested window " + client + " does not exist");
+ if (throwOnError) {
+ throw ex;
+ }
+ Log.w(TAG, "Failed looking up window", ex);
return null;
}
if (session != null && win.mSession != session) {
- RuntimeException ex = new RuntimeException();
- Log.w(TAG, "Requested window " + client + " is in session " +
- win.mSession + ", not " + session, ex);
+ RuntimeException ex = new IllegalArgumentException(
+ "Requested window " + client + " is in session " +
+ win.mSession + ", not " + session);
+ if (throwOnError) {
+ throw ex;
+ }
+ Log.w(TAG, "Failed looking up window", ex);
return null;
}
@@ -9183,6 +9208,13 @@
return;
}
+ if (mWaitingForConfig) {
+ // Our configuration has changed (most likely rotation), but we
+ // don't yet have the complete configuration to report to
+ // applications. Don't do any window layout until we have it.
+ return;
+ }
+
boolean recoveringMemory = false;
if (mForceRemoves != null) {
recoveringMemory = true;
@@ -9249,6 +9281,10 @@
while (mLayoutNeeded) {
mPolicy.beginLayoutLw(dw, dh);
+ int seq = mLayoutSeq+1;
+ if (seq < 0) seq = 0;
+ mLayoutSeq = seq;
+
// First perform layout of any root windows (not attached
// to another window).
int topAttached = -1;
@@ -9266,7 +9302,7 @@
|| win.mAttachedHidden
|| win.mExiting || win.mDestroying;
- if (win.mLayoutAttached) {
+ if (!win.mLayoutAttached) {
if (DEBUG_LAYOUT) Log.v(TAG, "First pass " + win
+ ": gone=" + gone + " mHaveFrame=" + win.mHaveFrame
+ " mLayoutAttached=" + win.mLayoutAttached);
@@ -9286,6 +9322,7 @@
if (!gone || !win.mHaveFrame) {
if (!win.mLayoutAttached) {
mPolicy.layoutWindowLw(win, win.mAttrs, null);
+ win.mLayoutSeq = seq;
if (DEBUG_LAYOUT) Log.v(TAG, "-> mFrame="
+ win.mFrame + " mContainingFrame="
+ win.mContainingFrame + " mDisplayFrame="
@@ -9316,6 +9353,7 @@
if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled)
|| !win.mHaveFrame) {
mPolicy.layoutWindowLw(win, win.mAttrs, win.mAttachedWindow);
+ win.mLayoutSeq = seq;
if (DEBUG_LAYOUT) Log.v(TAG, "-> mFrame="
+ win.mFrame + " mContainingFrame="
+ win.mContainingFrame + " mDisplayFrame="
@@ -9336,11 +9374,8 @@
Log.w(TAG, "Layout repeat aborted after too many iterations");
mLayoutNeeded = false;
if ((changes&WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) {
- Configuration newConfig = updateOrientationFromAppTokensLocked(
- null, null);
- if (newConfig != null) {
- mLayoutNeeded = true;
- mH.sendEmptyMessage(H.COMPUTE_AND_SEND_NEW_CONFIGURATION);
+ if (updateOrientationFromAppTokensLocked()) {
+ mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
}
}
} else {
@@ -9349,10 +9384,8 @@
repeats++;
if ((changes&WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) {
if (DEBUG_LAYOUT) Log.v(TAG, "Computing new config from layout");
- Configuration newConfig = updateOrientationFromAppTokensLocked(
- null, null);
- if (newConfig != null) {
- mH.sendEmptyMessage(H.COMPUTE_AND_SEND_NEW_CONFIGURATION);
+ if (updateOrientationFromAppTokensLocked()) {
+ mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
}
}
}
@@ -9996,14 +10029,22 @@
}
}
}
- if (!w.mAppFreezing) {
+ if (!w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) {
w.mContentInsetsChanged =
!w.mLastContentInsets.equals(w.mContentInsets);
w.mVisibleInsetsChanged =
!w.mLastVisibleInsets.equals(w.mVisibleInsets);
+ boolean configChanged =
+ w.mConfiguration != mCurConfiguration
+ && (w.mConfiguration == null
+ || mCurConfiguration.diff(w.mConfiguration) != 0);
+ if (localLOGV) Log.v(TAG, "Resizing " + w
+ + ": configChanged=" + configChanged
+ + " last=" + w.mLastFrame + " frame=" + w.mFrame);
if (!w.mLastFrame.equals(w.mFrame)
|| w.mContentInsetsChanged
- || w.mVisibleInsetsChanged) {
+ || w.mVisibleInsetsChanged
+ || configChanged) {
w.mLastFrame.set(w.mFrame);
w.mLastContentInsets.set(w.mContentInsets);
w.mLastVisibleInsets.set(w.mVisibleInsets);
@@ -10014,7 +10055,7 @@
if (DEBUG_ORIENTATION) Log.v(TAG,
"Resizing while display frozen: " + w);
w.mOrientationChanging = true;
- if (mWindowsFreezingScreen) {
+ if (!mWindowsFreezingScreen) {
mWindowsFreezingScreen = true;
// XXX should probably keep timeout from
// when we first froze the display.
@@ -10327,9 +10368,7 @@
mWindowsFreezingScreen = false;
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
}
- if (mAppsFreezingScreen == 0) {
- stopFreezingDisplayLocked();
- }
+ stopFreezingDisplayLocked();
}
i = mResizingWindows.size();
@@ -10340,9 +10379,20 @@
try {
if (DEBUG_ORIENTATION) Log.v(TAG, "Reporting new frame to "
+ win + ": " + win.mFrame);
+ boolean configChanged =
+ win.mConfiguration != mCurConfiguration
+ && (win.mConfiguration == null
+ || mCurConfiguration.diff(win.mConfiguration) != 0);
+ win.mConfiguration = mCurConfiguration;
+ if (DEBUG_ORIENTATION && configChanged) {
+ Log.i(TAG, "Sending new config to window " + win + ": "
+ + win.mFrame.width() + "x" + win.mFrame.height()
+ + " / " + win.mConfiguration);
+ }
win.mClient.resized(win.mFrame.width(),
win.mFrame.height(), win.mLastContentInsets,
- win.mLastVisibleInsets, win.mDrawPending);
+ win.mLastVisibleInsets, win.mDrawPending,
+ configChanged ? win.mConfiguration : null);
win.mContentInsetsChanged = false;
win.mVisibleInsetsChanged = false;
} catch (RemoteException e) {
@@ -10732,6 +10782,10 @@
return;
}
+ if (mWaitingForConfig || mAppsFreezingScreen > 0 || mWindowsFreezingScreen) {
+ return;
+ }
+
mDisplayFrozen = false;
mH.removeMessages(H.APP_FREEZE_TIMEOUT);
if (PROFILE_ORIENTATION) {
@@ -10921,7 +10975,9 @@
pw.print(" mLowerWallpaperTarget="); pw.println(mLowerWallpaperTarget);
pw.print(" mUpperWallpaperTarget="); pw.println(mUpperWallpaperTarget);
}
- pw.print(" mInTouchMode="); pw.println(mInTouchMode);
+ pw.print(" mCurConfiguration="); pw.println(this.mCurConfiguration);
+ pw.print(" mInTouchMode="); pw.print(mInTouchMode);
+ pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
pw.print(" mSystemBooted="); pw.print(mSystemBooted);
pw.print(" mDisplayEnabled="); pw.println(mDisplayEnabled);
pw.print(" mLayoutNeeded="); pw.print(mLayoutNeeded);
@@ -10939,7 +10995,8 @@
pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY);
pw.print(" mDisplayFrozen="); pw.print(mDisplayFrozen);
pw.print(" mWindowsFreezingScreen="); pw.print(mWindowsFreezingScreen);
- pw.print(" mAppsFreezingScreen="); pw.println(mAppsFreezingScreen);
+ pw.print(" mAppsFreezingScreen="); pw.print(mAppsFreezingScreen);
+ pw.print(" mWaitingForConfig="); pw.println(mWaitingForConfig);
pw.print(" mRotation="); pw.print(mRotation);
pw.print(", mForcedAppOrientation="); pw.print(mForcedAppOrientation);
pw.print(", mRequestedRotation="); pw.println(mRequestedRotation);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 47a58cf..7b64704 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -47,6 +47,7 @@
import android.app.Service;
import android.backup.IBackupManager;
import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -93,7 +94,6 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
-import android.text.TextUtils;
import android.util.Config;
import android.util.EventLog;
import android.util.Log;
@@ -107,7 +107,6 @@
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
@@ -136,6 +135,7 @@
static final boolean DEBUG_VISBILITY = localLOGV || false;
static final boolean DEBUG_PROCESSES = localLOGV || false;
static final boolean DEBUG_PROVIDER = localLOGV || false;
+ static final boolean DEBUG_URI_PERMISSION = localLOGV || false;
static final boolean DEBUG_USER_LEAVING = localLOGV || false;
static final boolean DEBUG_RESULTS = localLOGV || false;
static final boolean DEBUG_BACKUP = localLOGV || false;
@@ -296,12 +296,6 @@
// Memory pages are 4K.
static final int PAGE_SIZE = 4*1024;
- // System property defining error report receiver for system apps
- static final String SYSTEM_APPS_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.system.apps";
-
- // System property defining default error report receiver
- static final String DEFAULT_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.default";
-
// Corresponding memory levels for above adjustments.
static final int EMPTY_APP_MEM;
static final int HIDDEN_APP_MEM;
@@ -766,6 +760,12 @@
Configuration mConfiguration = new Configuration();
/**
+ * Current sequencing integer of the configuration, for skipping old
+ * configurations.
+ */
+ int mConfigurationSeq = 0;
+
+ /**
* Hardware-reported OpenGLES version.
*/
final int GL_ES_VERSION;
@@ -969,6 +969,7 @@
static final int PROC_START_TIMEOUT_MSG = 20;
static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 21;
static final int KILL_APPLICATION_MSG = 22;
+ static final int FINALIZE_PENDING_INTENT_MSG = 23;
AlertDialog mUidAlert;
@@ -1187,9 +1188,12 @@
int uid = msg.arg1;
boolean restart = (msg.arg2 == 1);
String pkg = (String) msg.obj;
- forceStopPackageLocked(pkg, uid, restart, false);
+ forceStopPackageLocked(pkg, uid, restart, false, true);
}
} break;
+ case FINALIZE_PENDING_INTENT_MSG: {
+ ((PendingIntentRecord)msg.obj).completeFinalize();
+ } break;
}
}
};
@@ -1390,7 +1394,8 @@
GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
- mConfiguration.makeDefault();
+ mConfiguration.setToDefaults();
+ mConfiguration.locale = Locale.getDefault();
mProcessStats.init();
// Add ourself to the Watchdog monitors.
@@ -2662,20 +2667,6 @@
mConfiguration,
next.mayFreezeScreenLocked(next.app) ? next : null);
if (config != null) {
- /*
- * Explicitly restore the locale to the one from the
- * old configuration, since the one that comes back from
- * the window manager has the default (boot) locale.
- *
- * It looks like previously the locale picker only worked
- * by coincidence: usually it would do its setting of
- * the locale after the activity transition, so it didn't
- * matter that this lost it. With the synchronized
- * block now keeping them from happening at the same time,
- * this one always would happen second and undo what the
- * locale picker had just done.
- */
- config.locale = mConfiguration.locale;
next.frozenBeforeDestroy = true;
}
updated = updateConfigurationLocked(config, next);
@@ -3613,10 +3604,18 @@
}
synchronized(this) {
+ int callingPid;
+ int callingUid;
+ if (caller == null) {
+ callingPid = Binder.getCallingPid();
+ callingUid = Binder.getCallingUid();
+ } else {
+ callingPid = callingUid = -1;
+ }
final long origId = Binder.clearCallingIdentity();
int res = startActivityLocked(caller, intent, resolvedType,
grantedUriPermissions, grantedMode, aInfo,
- resultTo, resultWho, requestCode, -1, -1,
+ resultTo, resultWho, requestCode, callingPid, callingUid,
onlyIfNeeded, componentSpecified);
Binder.restoreCallingIdentity(origId);
return res;
@@ -4854,7 +4853,7 @@
return;
}
killPackageProcessesLocked(packageName, pkgUid,
- SECONDARY_SERVER_ADJ, false);
+ SECONDARY_SERVER_ADJ, false, true);
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -4996,7 +4995,7 @@
}
private void forceStopPackageLocked(final String packageName, int uid) {
- forceStopPackageLocked(packageName, uid, false, false);
+ forceStopPackageLocked(packageName, uid, false, false, true);
Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
Uri.fromParts("package", packageName, null));
intent.putExtra(Intent.EXTRA_UID, uid);
@@ -5005,8 +5004,8 @@
false, false, MY_PID, Process.SYSTEM_UID);
}
- private final void killPackageProcessesLocked(String packageName, int uid,
- int minOomAdj, boolean callerWillRestart) {
+ private final boolean killPackageProcessesLocked(String packageName, int uid,
+ int minOomAdj, boolean callerWillRestart, boolean doit) {
ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>();
// Remove all processes this package may have touched: all with the
@@ -5018,11 +5017,16 @@
for (int ia=0; ia<NA; ia++) {
ProcessRecord app = apps.valueAt(ia);
if (app.removed) {
- procs.add(app);
+ if (doit) {
+ procs.add(app);
+ }
} else if ((uid > 0 && uid != Process.SYSTEM_UID && app.info.uid == uid)
|| app.processName.equals(packageName)
|| app.processName.startsWith(procNamePrefix)) {
if (app.setAdj >= minOomAdj) {
+ if (!doit) {
+ return true;
+ }
app.removed = true;
procs.add(app);
}
@@ -5034,10 +5038,11 @@
for (int i=0; i<N; i++) {
removeProcessLocked(procs.get(i), callerWillRestart);
}
+ return N > 0;
}
- private final void forceStopPackageLocked(String name, int uid,
- boolean callerWillRestart, boolean purgeCache) {
+ private final boolean forceStopPackageLocked(String name, int uid,
+ boolean callerWillRestart, boolean purgeCache, boolean doit) {
int i, N;
if (uid < 0) {
@@ -5047,21 +5052,28 @@
}
}
- Log.i(TAG, "Force stopping package " + name + " uid=" + uid);
+ if (doit) {
+ Log.i(TAG, "Force stopping package " + name + " uid=" + uid);
- Iterator<SparseArray<Long>> badApps = mProcessCrashTimes.getMap().values().iterator();
- while (badApps.hasNext()) {
- SparseArray<Long> ba = badApps.next();
- if (ba.get(uid) != null) {
- badApps.remove();
+ Iterator<SparseArray<Long>> badApps = mProcessCrashTimes.getMap().values().iterator();
+ while (badApps.hasNext()) {
+ SparseArray<Long> ba = badApps.next();
+ if (ba.get(uid) != null) {
+ badApps.remove();
+ }
}
}
-
- killPackageProcessesLocked(name, uid, -100, callerWillRestart);
+
+ boolean didSomething = killPackageProcessesLocked(name, uid, -100,
+ callerWillRestart, doit);
for (i=mHistory.size()-1; i>=0; i--) {
HistoryRecord r = (HistoryRecord)mHistory.get(i);
if (r.packageName.equals(name)) {
+ if (!doit) {
+ return true;
+ }
+ didSomething = true;
Log.i(TAG, " Force finishing activity " + r);
if (r.app != null) {
r.app.removed = true;
@@ -5074,6 +5086,10 @@
ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
for (ServiceRecord service : mServices.values()) {
if (service.packageName.equals(name)) {
+ if (!doit) {
+ return true;
+ }
+ didSomething = true;
Log.i(TAG, " Force stopping service " + service);
if (service.app != null) {
service.app.removed = true;
@@ -5088,13 +5104,17 @@
bringDownServiceLocked(services.get(i), true);
}
- resumeTopActivityLocked(null);
- if (purgeCache) {
- AttributeCache ac = AttributeCache.instance();
- if (ac != null) {
- ac.removePackage(name);
+ if (doit) {
+ if (purgeCache) {
+ AttributeCache ac = AttributeCache.instance();
+ if (ac != null) {
+ ac.removePackage(name);
+ }
}
+ resumeTopActivityLocked(null);
}
+
+ return didSomething;
}
private final boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart) {
@@ -5591,19 +5611,38 @@
}
final void finishBooting() {
- // Ensure that any processes we had put on hold are now started
- // up.
- final int NP = mProcessesOnHold.size();
- if (NP > 0) {
- ArrayList<ProcessRecord> procs =
- new ArrayList<ProcessRecord>(mProcessesOnHold);
- for (int ip=0; ip<NP; ip++) {
- this.startProcessLocked(procs.get(ip), "on-hold", null);
+ IntentFilter pkgFilter = new IntentFilter();
+ pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
+ pkgFilter.addDataScheme("package");
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+ if (pkgs != null) {
+ for (String pkg : pkgs) {
+ if (forceStopPackageLocked(pkg, -1, false, false, false)) {
+ setResultCode(Activity.RESULT_OK);
+ return;
+ }
+ }
+ }
}
- }
- if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
- // Tell anyone interested that we are done booting!
- synchronized (this) {
+ }, pkgFilter);
+
+ synchronized (this) {
+ // Ensure that any processes we had put on hold are now started
+ // up.
+ final int NP = mProcessesOnHold.size();
+ if (NP > 0) {
+ ArrayList<ProcessRecord> procs =
+ new ArrayList<ProcessRecord>(mProcessesOnHold);
+ for (int ip=0; ip<NP; ip++) {
+ this.startProcessLocked(procs.get(ip), "on-hold", null);
+ }
+ }
+
+ if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
+ // Tell anyone interested that we are done booting!
broadcastIntentLocked(null, null,
new Intent(Intent.ACTION_BOOT_COMPLETED, null),
null, null, 0, null, null,
@@ -6140,10 +6179,15 @@
return;
}
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Requested grant " + targetPkg + " permission to " + uri);
+
final IPackageManager pm = ActivityThread.getPackageManager();
// If this is not a content: uri, we can't do anything with it.
if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Can't grant URI permission for non-content URI: " + uri);
return;
}
@@ -6169,6 +6213,8 @@
try {
targetUid = pm.getPackageUid(targetPkg);
if (targetUid < 0) {
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Can't grant URI permission no uid for: " + targetPkg);
return;
}
} catch (RemoteException ex) {
@@ -6178,17 +6224,12 @@
// First... does the target actually need this permission?
if (checkHoldingPermissionsLocked(pm, pi, targetUid, modeFlags)) {
// No need to grant the target this permission.
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Target " + targetPkg + " already has full permission to " + uri);
return;
}
- // Second... maybe someone else has already granted the
- // permission?
- if (checkUriPermissionLocked(uri, targetUid, modeFlags)) {
- // No need to grant the target this permission.
- return;
- }
-
- // Third... is the provider allowing granting of URI permissions?
+ // Second... is the provider allowing granting of URI permissions?
if (!pi.grantUriPermissions) {
throw new SecurityException("Provider " + pi.packageName
+ "/" + pi.name
@@ -6213,7 +6254,7 @@
}
}
- // Fourth... does the caller itself have permission to access
+ // Third... does the caller itself have permission to access
// this uri?
if (!checkHoldingPermissionsLocked(pm, pi, callingUid, modeFlags)) {
if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
@@ -6226,6 +6267,9 @@
// to the uri, and the target doesn't. Let's now give this to
// the target.
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Granting " + targetPkg + " permission to " + uri);
+
HashMap<Uri, UriPermission> targetUris
= mGrantedUriPermissions.get(targetUid);
if (targetUris == null) {
@@ -6299,6 +6343,8 @@
HashMap<Uri, UriPermission> perms
= mGrantedUriPermissions.get(perm.uid);
if (perms != null) {
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Removing " + perm.uid + " permission to " + perm.uri);
perms.remove(perm.uri);
if (perms.size() == 0) {
mGrantedUriPermissions.remove(perm.uid);
@@ -6338,6 +6384,9 @@
return;
}
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Revoking all granted permissions to " + uri);
+
final IPackageManager pm = ActivityThread.getPackageManager();
final String authority = uri.getAuthority();
@@ -6396,6 +6445,8 @@
continue toploop;
}
}
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Revoking " + perm.uid + " permission to " + perm.uri);
perm.clearModes(modeFlags);
if (perm.modeFlags == 0) {
it.remove();
@@ -7564,6 +7615,15 @@
? cpi.readPermission : cpi.writePermission);
}
+ if (!mSystemReady && !mDidUpdate && !mWaitingUpdate
+ && !cpi.processName.equals("system")) {
+ // If this content provider does not run in the system
+ // process, and the system is not yet ready to run other
+ // processes, then fail fast instead of hanging.
+ throw new IllegalArgumentException(
+ "Attempt to launch content provider before system ready");
+ }
+
cpr = (ContentProviderRecord)mProvidersByClass.get(cpi.name);
final boolean firstClass = cpr == null;
if (firstClass) {
@@ -7811,6 +7871,16 @@
public static final void installSystemProviders() {
ProcessRecord app = mSelf.mProcessNames.get("system", Process.SYSTEM_UID);
List providers = mSelf.generateApplicationProvidersLocked(app);
+ if (providers != null) {
+ for (int i=providers.size()-1; i>=0; i--) {
+ ProviderInfo pi = (ProviderInfo)providers.get(i);
+ if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
+ Log.w(TAG, "Not installing system proc provider " + pi.name
+ + ": not system .apk");
+ providers.remove(i);
+ }
+ }
+ }
mSystemThread.installSystemProviders(providers);
}
@@ -8058,7 +8128,7 @@
mDebugTransient = !persistent;
if (packageName != null) {
final long origId = Binder.clearCallingIdentity();
- forceStopPackageLocked(packageName, -1, false, false);
+ forceStopPackageLocked(packageName, -1, false, false, true);
Binder.restoreCallingIdentity(origId);
}
}
@@ -8348,6 +8418,7 @@
// This happens before any activities are started, so we can
// change mConfiguration in-place.
mConfiguration.updateFrom(configuration);
+ mConfigurationSeq = mConfiguration.seq = 1;
if (DEBUG_CONFIGURATION) Log.v(TAG, "Initial config: " + mConfiguration);
}
}
@@ -8543,73 +8614,6 @@
return handleAppCrashLocked(app);
}
- private ComponentName getErrorReportReceiver(ProcessRecord app) {
- // check if error reporting is enabled in secure settings
- int enabled = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SEND_ACTION_APP_ERROR, 0);
- if (enabled == 0) {
- return null;
- }
-
- IPackageManager pm = ActivityThread.getPackageManager();
-
- try {
- // look for receiver in the installer package
- String candidate = pm.getInstallerPackageName(app.info.packageName);
- ComponentName result = getErrorReportReceiver(pm, app.info.packageName, candidate);
- if (result != null) {
- return result;
- }
-
- // if the error app is on the system image, look for system apps
- // error receiver
- if ((app.info.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
- candidate = SystemProperties.get(SYSTEM_APPS_ERROR_RECEIVER_PROPERTY);
- result = getErrorReportReceiver(pm, app.info.packageName, candidate);
- if (result != null) {
- return result;
- }
- }
-
- // if there is a default receiver, try that
- candidate = SystemProperties.get(DEFAULT_ERROR_RECEIVER_PROPERTY);
- return getErrorReportReceiver(pm, app.info.packageName, candidate);
- } catch (RemoteException e) {
- // should not happen
- Log.e(TAG, "error talking to PackageManager", e);
- return null;
- }
- }
-
- /**
- * Return activity in receiverPackage that handles ACTION_APP_ERROR.
- *
- * @param pm PackageManager isntance
- * @param errorPackage package which caused the error
- * @param receiverPackage candidate package to receive the error
- * @return activity component within receiverPackage which handles
- * ACTION_APP_ERROR, or null if not found
- */
- private ComponentName getErrorReportReceiver(IPackageManager pm, String errorPackage,
- String receiverPackage) throws RemoteException {
- if (receiverPackage == null || receiverPackage.length() == 0) {
- return null;
- }
-
- // break the loop if it's the error report receiver package that crashed
- if (receiverPackage.equals(errorPackage)) {
- return null;
- }
-
- Intent intent = new Intent(Intent.ACTION_APP_ERROR);
- intent.setPackage(receiverPackage);
- ResolveInfo info = pm.resolveIntent(intent, null, 0);
- if (info == null || info.activityInfo == null) {
- return null;
- }
- return new ComponentName(receiverPackage, info.activityInfo.name);
- }
-
private void makeAppNotRespondingLocked(ProcessRecord app,
String activity, String shortMsg, String longMsg) {
app.notResponding = true;
@@ -8723,7 +8727,8 @@
}
void startAppProblemLocked(ProcessRecord app) {
- app.errorReportReceiver = getErrorReportReceiver(app);
+ app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
+ mContext, app.info.packageName, app.info.flags);
skipCurrentReceiverLocked(app);
}
@@ -11957,7 +11962,7 @@
String list[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
if (list != null && (list.length > 0)) {
for (String pkg : list) {
- forceStopPackageLocked(pkg, -1, false, true);
+ forceStopPackageLocked(pkg, -1, false, true, true);
}
}
} else {
@@ -11966,7 +11971,7 @@
if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
if (!intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false)) {
forceStopPackageLocked(ssp,
- intent.getIntExtra(Intent.EXTRA_UID, -1), false, true);
+ intent.getIntExtra(Intent.EXTRA_UID, -1), false, true, true);
}
}
}
@@ -12937,7 +12942,7 @@
}
final long origId = Binder.clearCallingIdentity();
- forceStopPackageLocked(ii.targetPackage, -1, true, false);
+ forceStopPackageLocked(ii.targetPackage, -1, true, false, true);
ProcessRecord app = addAppLocked(ai);
app.instrumentationClass = className;
app.instrumentationInfo = ai;
@@ -12992,7 +12997,7 @@
app.instrumentationProfileFile = null;
app.instrumentationArguments = null;
- forceStopPackageLocked(app.processName, -1, false, false);
+ forceStopPackageLocked(app.processName, -1, false, false, true);
}
public void finishInstrumentation(IApplicationThread target,
@@ -13090,6 +13095,11 @@
values.userSetLocale);
}
+ mConfigurationSeq++;
+ if (mConfigurationSeq <= 0) {
+ mConfigurationSeq = 1;
+ }
+ newConfig.seq = mConfigurationSeq;
mConfiguration = newConfig;
Log.i(TAG, "Config changed: " + newConfig);
@@ -13146,6 +13156,10 @@
}
}
+ if (values != null && mWindowManager != null) {
+ mWindowManager.setNewConfiguration(mConfiguration);
+ }
+
return kept;
}
diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java
index b3086d5..fac47d7 100644
--- a/services/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/java/com/android/server/am/PendingIntentRecord.java
@@ -145,8 +145,9 @@
public String toString() {
return "Key{" + typeName() + " pkg=" + packageName
- + " intent=" + requestIntent.toShortString(true, false) + " flags=0x"
- + Integer.toHexString(flags) + "}";
+ + " intent="
+ + (requestIntent != null ? requestIntent.toShortString(true, false) : "<null>")
+ + " flags=0x" + Integer.toHexString(flags) + "}";
}
String typeName() {
@@ -262,17 +263,26 @@
}
protected void finalize() throws Throwable {
- if (!canceled) {
- synchronized(owner) {
- WeakReference<PendingIntentRecord> current =
- owner.mIntentSenderRecords.get(key);
- if (current == ref) {
- owner.mIntentSenderRecords.remove(key);
- }
+ try {
+ if (!canceled) {
+ owner.mHandler.sendMessage(owner.mHandler.obtainMessage(
+ ActivityManagerService.FINALIZE_PENDING_INTENT_MSG, this));
}
+ } finally {
+ super.finalize();
}
}
+ public void completeFinalize() {
+ synchronized(owner) {
+ WeakReference<PendingIntentRecord> current =
+ owner.mIntentSenderRecords.get(key);
+ if (current == ref) {
+ owner.mIntentSenderRecords.remove(key);
+ }
+ }
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("uid="); pw.print(uid);
pw.print(" packageName="); pw.print(key.packageName);
@@ -286,8 +296,10 @@
pw.print(prefix); pw.print("requestCode="); pw.print(key.requestCode);
pw.print(" requestResolvedType="); pw.println(key.requestResolvedType);
}
- pw.print(prefix); pw.print("requestIntent=");
- pw.println(key.requestIntent.toShortString(true, true));
+ if (key.requestIntent != null) {
+ pw.print(prefix); pw.print("requestIntent=");
+ pw.println(key.requestIntent.toShortString(true, true));
+ }
if (sent || canceled) {
pw.print(prefix); pw.print("sent="); pw.print(sent);
pw.print(" canceled="); pw.println(canceled);
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index 89761a8..5a02c40 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -29,6 +29,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.util.Log;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -267,6 +268,13 @@
int[] outId = new int[1];
inm.enqueueNotification(localPackageName, localForegroundId,
localForegroundNoti, outId);
+ } catch (RuntimeException e) {
+ Log.w(ActivityManagerService.TAG,
+ "Error showing notification for service", e);
+ // If it gave us a garbage notification, it doesn't
+ // get to be foreground.
+ ams.setServiceForeground(name, ServiceRecord.this,
+ localForegroundId, null, true);
} catch (RemoteException e) {
}
}
@@ -288,6 +296,9 @@
}
try {
inm.cancelNotification(localPackageName, localForegroundId);
+ } catch (RuntimeException e) {
+ Log.w(ActivityManagerService.TAG,
+ "Error canceling notification for service", e);
} catch (RemoteException e) {
}
}
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 1d20074..16638bc 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -27,6 +27,7 @@
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.ConnectivityManager;
+import android.net.InterfaceConfiguration;
import android.net.IConnectivityManager;
import android.net.INetworkManagementEventObserver;
import android.net.NetworkInfo;
@@ -56,6 +57,7 @@
*
* TODO - look for parent classes and code sharing
*/
+
public class Tethering extends INetworkManagementEventObserver.Stub {
private Notification mTetheringNotification;
@@ -74,12 +76,23 @@
private BroadcastReceiver mStateReceiver;
+ private static final String USB_NEAR_IFACE_ADDR = "169.254.2.1";
+
private String[] mDhcpRange;
+ private static final String DHCP_DEFAULT_RANGE_START = "169.254.2.10";
+ private static final String DHCP_DEFAULT_RANGE_STOP = "169.254.2.64";
private String[] mDnsServers;
+ private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8";
+ private static final String DNS_DEFAULT_SERVER2 = "4.2.2.2";
private String mUpstreamIfaceName;
+ // turning on/off RNDIS resets the interface generating and extra discon/conn cycle
+ // count how many to ignore.. Self correcting if you plug/unplug a bunch of times.
+ // TODO - brittle - maybe don't need?
+ private int mUsbResetExpected = 0;
+
HierarchicalStateMachine mTetherMasterSM;
public Tethering(Context context) {
@@ -113,8 +126,8 @@
com.android.internal.R.array.config_tether_dhcp_range);
if (mDhcpRange.length == 0) {
mDhcpRange = new String[2];
- mDhcpRange[0] = new String("169.254.2.1");
- mDhcpRange[1] = new String("169.254.2.64");
+ mDhcpRange[0] = DHCP_DEFAULT_RANGE_START;
+ mDhcpRange[1] = DHCP_DEFAULT_RANGE_STOP;
} else if(mDhcpRange.length == 1) {
String[] tmp = new String[2];
tmp[0] = mDhcpRange[0];
@@ -127,26 +140,17 @@
mTetherableWifiRegexs = context.getResources().getStringArray(
com.android.internal.R.array.config_tether_wifi_regexs);
- String[] ifaces = new String[0];
- try {
- ifaces = service.listInterfaces();
- } catch (Exception e) {
- Log.e(TAG, "Error listing Interfaces :" + e);
- }
- for (String iface : ifaces) {
- interfaceAdded(iface);
- }
-
// TODO - remove and rely on real notifications of the current iface
mDnsServers = new String[2];
- mDnsServers[0] = "8.8.8.8";
- mDnsServers[1] = "4.2.2.2";
+ mDnsServers[0] = DNS_DEFAULT_SERVER1;
+ mDnsServers[1] = DNS_DEFAULT_SERVER2;
mUpstreamIfaceName = "rmnet0";
}
public void interfaceLinkStatusChanged(String iface, boolean link) {
Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link);
boolean found = false;
+ boolean usb = false;
for (String regex : mTetherableWifiRegexs) {
if (iface.matches(regex)) {
found = true;
@@ -156,6 +160,7 @@
for (String regex: mTetherableUsbRegexs) {
if (iface.matches(regex)) {
found = true;
+ usb = true;
break;
}
}
@@ -165,7 +170,7 @@
TetherInterfaceSM sm = mIfaces.get(iface);
if (link) {
if (sm == null) {
- sm = new TetherInterfaceSM(iface);
+ sm = new TetherInterfaceSM(iface, usb);
mIfaces.put(iface, sm);
sm.start();
}
@@ -179,7 +184,10 @@
}
public void interfaceAdded(String iface) {
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
boolean found = false;
+ boolean usb = false;
for (String regex : mTetherableWifiRegexs) {
if (iface.matches(regex)) {
found = true;
@@ -189,6 +197,7 @@
for (String regex : mTetherableUsbRegexs) {
if (iface.matches(regex)) {
found = true;
+ usb = true;
break;
}
}
@@ -196,13 +205,14 @@
Log.d(TAG, iface + " is not a tetherable iface, ignoring");
return;
}
+
synchronized (mIfaces) {
TetherInterfaceSM sm = mIfaces.get(iface);
if (sm != null) {
Log.e(TAG, "active iface (" + iface + ") reported as added, ignoring");
return;
}
- sm = new TetherInterfaceSM(iface);
+ sm = new TetherInterfaceSM(iface, usb);
mIfaces.put(iface, sm);
sm.start();
}
@@ -297,7 +307,8 @@
broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER,
erroredList);
mContext.sendStickyBroadcast(broadcast);
-
+ Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " +
+ activeList.size() + ", " + erroredList.size());
// check if we need to send a USB notification
// Check if the user wants to be bothered
boolean tellUser = (Settings.Secure.getInt(mContext.getContentResolver(),
@@ -314,8 +325,8 @@
if (tellUser) {
for (Object o : availableList) {
String s = (String)o;
- for (Object matchObject : mTetherableUsbRegexs) {
- if (s.matches((String)matchObject)) {
+ for (String match : mTetherableUsbRegexs) {
+ if (s.matches(match)) {
showTetherAvailableNotification();
return;
}
@@ -414,20 +425,32 @@
}
}
-
-
-
private class StateReceiver extends BroadcastReceiver {
public void onReceive(Context content, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_UMS_CONNECTED)) {
- Tethering.this.handleTtyConnect();
+ Log.w(TAG, "got UMS connected");
+ synchronized (Tethering.this) {
+ if(mUsbResetExpected != 0) {
+ Log.w(TAG, "mUsbResetExpected == " + mUsbResetExpected + ", ignored");
+ mUsbResetExpected--;
+ return;
+ }
+ }
+ Tethering.this.enableUsbIfaces(true); // add them
} else if (action.equals(Intent.ACTION_UMS_DISCONNECTED)) {
- Tethering.this.handleTtyDisconnect();
+ Log.w(TAG, "got UMS disconneded broadcast");
+ synchronized (Tethering.this) {
+ if(mUsbResetExpected != 0) {
+ Log.w(TAG, "mUsbResetExpected == " + mUsbResetExpected + ", ignored");
+ mUsbResetExpected--;
+ return;
+ }
+ }
+ Tethering.this.enableUsbIfaces(false); // remove them
} else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager service =
- IConnectivityManager.Stub.asInterface(b);
+ IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
try {
NetworkInfo info = service.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_DUN);
int msg;
@@ -442,6 +465,103 @@
}
}
+ // used on cable insert/remove
+ private void enableUsbIfaces(boolean enable) {
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+ String[] ifaces = new String[0];
+ try {
+ ifaces = service.listInterfaces();
+ } catch (Exception e) {
+ Log.e(TAG, "Error listing Interfaces :" + e);
+ return;
+ }
+ for (String iface : ifaces) {
+ for (String regex : mTetherableUsbRegexs) {
+ if (iface.matches(regex)) {
+ if (enable) {
+ interfaceAdded(iface);
+ } else {
+ interfaceRemoved(iface);
+ }
+ }
+ }
+ }
+ }
+
+ // toggled when we enter/leave the fully teathered state
+ private boolean enableUsbRndis(boolean enabled) {
+ Log.d(TAG, "enableUsbRndis(" + enabled + ")");
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+
+ try {
+ if (enabled) {
+ // turning this on will reset USB and generate two bogus events - ignore them
+ synchronized (this) {
+ if (!service.isUsbRNDISStarted()) {
+ mUsbResetExpected += 2;
+ service.startUsbRNDIS();
+ }
+ }
+ } else {
+ if (service.isUsbRNDISStarted()) {
+ service.stopUsbRNDIS();
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error toggling usb RNDIS :" + e);
+ return false;
+ }
+ return true;
+ }
+
+ // configured when we start tethering and unconfig'd on error or conclusion
+ private boolean configureUsbIface(boolean enabled) {
+ Log.d(TAG, "configureUsbIface(" + enabled + ")");
+
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+
+ // bring toggle the interfaces
+ String[] ifaces = new String[0];
+ try {
+ ifaces = service.listInterfaces();
+ } catch (Exception e) {
+ Log.e(TAG, "Error listing Interfaces :" + e);
+ return false;
+ }
+ for (String iface : ifaces) {
+ for (String regex : mTetherableUsbRegexs) {
+ if (iface.matches(regex)) {
+ InterfaceConfiguration ifcg = null;
+ try {
+ ifcg = service.getInterfaceConfig(iface);
+ if (ifcg != null) {
+ ifcg.ipAddr = (169 << 24) + (254 << 16) + (2 << 8) + 1;
+ ifcg.netmask = (255 << 24) + (255 << 16) + (255 << 8) + 0;
+ if (enabled) {
+ ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up");
+ } else {
+ ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down");
+ // TODO - clean this up - maybe a better regex?
+ ifcg.interfaceFlags = ifcg.interfaceFlags.replace(" running", "");
+ ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running ","");
+ ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running","");
+ }
+ service.setInterfaceConfig(iface, ifcg);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error configuring interface " + iface + ", :" + e);
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
private void handleTtyConnect() {
Log.d(TAG, "handleTtyConnect");
// for each of the available Tty not already supported by a ppp session,
@@ -609,6 +729,7 @@
private HierarchicalState mUntetherInterfaceErrorState;
private HierarchicalState mEnableNatErrorState;
private HierarchicalState mDisableNatErrorState;
+ private HierarchicalState mUsbConfigurationErrorState;
private HierarchicalState mUnavailableState;
@@ -617,10 +738,12 @@
private boolean mTethered;
String mIfaceName;
+ boolean mUsb;
- TetherInterfaceSM(String name) {
+ TetherInterfaceSM(String name, boolean usb) {
super(name);
mIfaceName = name;
+ mUsb = usb;
mInitialState = new InitialState();
addState(mInitialState);
@@ -638,6 +761,8 @@
addState(mEnableNatErrorState);
mDisableNatErrorState = new DisableNatErrorState();
addState(mDisableNatErrorState);
+ mUsbConfigurationErrorState = new UsbConfigurationErrorState();
+ addState(mUsbConfigurationErrorState);
mUnavailableState = new UnavailableState();
addState(mUnavailableState);
@@ -656,6 +781,7 @@
if (current == mUntetherInterfaceErrorState) res += "UntetherInterfaceErrorState";
if (current == mEnableNatErrorState) res += "EnableNatErrorState";
if (current == mDisableNatErrorState) res += "DisableNatErrorState";
+ if (current == mUsbConfigurationErrorState) res += "UsbConfigurationErrorState";
if (current == mUnavailableState) res += "UnavailableState";
if (mAvailable) res += " - Available";
if (mTethered) res += " - Tethered";
@@ -686,8 +812,15 @@
return mErrored;
}
- private synchronized void setErrored(boolean errored) {
- mErrored = errored;
+ private void setErrored(boolean errored) {
+ synchronized (this) {
+ mErrored = errored;
+ }
+ if (errored && mUsb) {
+ // note everything's been unwound by this point so nothing to do on
+ // further error..
+ Tethering.this.configureUsbIface(false);
+ }
}
class InitialState extends HierarchicalState {
@@ -726,6 +859,19 @@
@Override
public void enter() {
setAvailable(false);
+ if (mUsb) {
+ if (!Tethering.this.configureUsbIface(true)) {
+ Message m = mTetherMasterSM.obtainMessage(
+ TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED);
+ m.obj = TetherInterfaceSM.this;
+ mTetherMasterSM.sendMessage(m);
+
+ m = obtainMessage(CMD_TRANSITION_TO_ERROR);
+ m.obj = mUsbConfigurationErrorState;
+ sendMessageAtFrontOfQueue(m);
+ return;
+ }
+ }
sendTetherStateChangedBroadcast();
}
@Override
@@ -739,6 +885,12 @@
TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED);
m.obj = TetherInterfaceSM.this;
mTetherMasterSM.sendMessage(m);
+ if (mUsb) {
+ if (!Tethering.this.configureUsbIface(false)) {
+ transitionTo(mUsbConfigurationErrorState);
+ break;
+ }
+ }
transitionTo(mInitialState);
break;
case CMD_TETHER_MODE_ALIVE:
@@ -759,6 +911,10 @@
mTetherMasterSM.sendMessage(m);
transitionTo(mUnavailableState);
break;
+ case CMD_TRANSITION_TO_ERROR:
+ HierarchicalState s = (HierarchicalState)(message.obj);
+ transitionTo(s);
+ break;
default:
retValue = false;
}
@@ -788,12 +944,17 @@
sendMessageAtFrontOfQueue(m);
return;
}
+ if (mUsb) Tethering.this.enableUsbRndis(true);
Log.d(TAG, "Tethered " + mIfaceName);
setAvailable(false);
setTethered(true);
sendTetherStateChangedBroadcast();
}
@Override
+ public void exit() {
+ if (mUsb) Tethering.this.enableUsbRndis(false);
+ }
+ @Override
public boolean processMessage(Message message) {
Log.d(TAG, "TetheredState.processMessage what=" + message.what);
boolean retValue = true;
@@ -821,12 +982,19 @@
m.obj = TetherInterfaceSM.this;
mTetherMasterSM.sendMessage(m);
if (message.what == CMD_TETHER_UNREQUESTED) {
- transitionTo(mInitialState);
+ if (mUsb) {
+ if (!Tethering.this.configureUsbIface(false)) {
+ transitionTo(mUsbConfigurationErrorState);
+ } else {
+ transitionTo(mInitialState);
+ }
+ } else {
+ transitionTo(mInitialState);
+ }
} else if (message.what == CMD_INTERFACE_DOWN) {
transitionTo(mUnavailableState);
}
Log.d(TAG, "Untethered " + mIfaceName);
- sendTetherStateChangedBroadcast();
break;
case CMD_CELL_DUN_ERROR:
case CMD_IP_FORWARDING_ENABLE_ERROR:
@@ -857,6 +1025,12 @@
}
Log.d(TAG, "Tether lost upstream connection " + mIfaceName);
sendTetherStateChangedBroadcast();
+ if (mUsb) {
+ if (!Tethering.this.configureUsbIface(false)) {
+ transitionTo(mUsbConfigurationErrorState);
+ break;
+ }
+ }
transitionTo(mInitialState);
break;
case CMD_TRANSITION_TO_ERROR:
@@ -974,6 +1148,15 @@
sendTetherStateChangedBroadcast();
}
}
+
+ class UsbConfigurationErrorState extends ErrorState {
+ @Override
+ public void enter() {
+ Log.e(TAG, "Error trying to configure USB " + mIfaceName);
+ setAvailable(false);
+ setErrored(true);
+ }
+ }
}
class TetherMasterSM extends HierarchicalStateMachine {
@@ -1000,7 +1183,6 @@
private HierarchicalState mCellDunRequestedState;
private HierarchicalState mCellDunAliveState;
private HierarchicalState mTetherModeAliveState;
- private HierarchicalState mCellDunUnRequestedState;
private HierarchicalState mCellDunErrorState;
private HierarchicalState mSetIpForwardingEnabledErrorState;
@@ -1028,8 +1210,6 @@
addState(mCellDunAliveState);
mTetherModeAliveState = new TetherModeAliveState();
addState(mTetherModeAliveState);
- mCellDunUnRequestedState = new CellDunUnRequestedState();
- addState(mCellDunUnRequestedState);
mCellDunErrorState = new CellDunErrorState();
addState(mCellDunErrorState);
@@ -1049,8 +1229,81 @@
setInitialState(mInitialState);
}
+ class TetherMasterUtilState extends HierarchicalState {
+ @Override
+ public boolean processMessage(Message m) {
+ return false;
+ }
+ public int turnOnMobileDun() {
+ IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+ IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
+ int retValue = Phone.APN_REQUEST_FAILED;
+ try {
+ retValue = service.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+ Phone.FEATURE_ENABLE_DUN, new Binder());
+ } catch (Exception e) {
+ }
+ return retValue;
+ }
+ public boolean turnOffMobileDun() {
+ IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+ IConnectivityManager service =
+ IConnectivityManager.Stub.asInterface(b);
+ try {
+ service.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+ Phone.FEATURE_ENABLE_DUN);
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
+ public boolean turnOnMasterTetherSettings() {
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ INetworkManagementService service =
+ INetworkManagementService.Stub.asInterface(b);
+ try {
+ service.setIpForwardingEnabled(true);
+ } catch (Exception e) {
+ transitionTo(mSetIpForwardingEnabledErrorState);
+ return false;
+ }
+ try {
+ service.startTethering(mDhcpRange[0], mDhcpRange[1]);
+ } catch (Exception e) {
+ transitionTo(mStartTetheringErrorState);
+ return false;
+ }
+ try {
+ service.setDnsForwarders(mDnsServers);
+ } catch (Exception e) {
+ transitionTo(mSetDnsForwardersErrorState);
+ return false;
+ }
+ transitionTo(mTetherModeAliveState);
+ return true;
+ }
+ public boolean turnOffMasterTetherSettings() {
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ INetworkManagementService service =
+ INetworkManagementService.Stub.asInterface(b);
+ try {
+ service.stopTethering();
+ } catch (Exception e) {
+ transitionTo(mStopTetheringErrorState);
+ return false;
+ }
+ try {
+ service.setIpForwardingEnabled(false);
+ } catch (Exception e) {
+ transitionTo(mSetIpForwardingDisabledErrorState);
+ return false;
+ }
+ transitionTo(mInitialState);
+ return true;
+ }
+ }
- class InitialState extends HierarchicalState {
+ class InitialState extends TetherMasterUtilState {
@Override
public boolean processMessage(Message message) {
Log.d(TAG, "MasterInitialState.processMessage what=" + message.what);
@@ -1080,20 +1333,11 @@
return retValue;
}
}
- class CellDunRequestedState extends HierarchicalState {
+ class CellDunRequestedState extends TetherMasterUtilState {
@Override
public void enter() {
++mSequenceNumber;
- IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager service =
- IConnectivityManager.Stub.asInterface(b);
- int result;
- try {
- result = service.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
- Phone.FEATURE_ENABLE_DUN, new Binder());
- } catch (Exception e) {
- result = Phone.APN_REQUEST_FAILED;
- }
+ int result = turnOnMobileDun();
switch (result) {
case Phone.APN_ALREADY_ACTIVE:
Log.d(TAG, "Dun already active");
@@ -1132,34 +1376,13 @@
if (index != -1) {
mNotifyList.remove(index);
if (mNotifyList.isEmpty()) {
- transitionTo(mCellDunUnRequestedState);
+ turnOffMobileDun();
+ transitionTo(mInitialState);
}
}
break;
case CMD_CELL_DUN_ENABLED:
- IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- INetworkManagementService service =
- INetworkManagementService.Stub.asInterface(b);
-
- try {
- service.setIpForwardingEnabled(true);
- } catch (Exception e) {
- transitionTo(mSetIpForwardingEnabledErrorState);
- break;
- }
- try {
- service.startTethering(mDhcpRange[0], mDhcpRange[1]);
- } catch (Exception e) {
- transitionTo(mStartTetheringErrorState);
- break;
- }
- try {
- service.setDnsForwarders(mDnsServers);
- } catch (Exception e) {
- transitionTo(mSetDnsForwardersErrorState);
- break;
- }
- transitionTo(mTetherModeAliveState);
+ turnOnMasterTetherSettings();
break;
case CMD_CELL_DUN_DISABLED:
break;
@@ -1176,9 +1399,10 @@
}
}
- class CellDunAliveState extends HierarchicalState {
+ class CellDunAliveState extends TetherMasterUtilState {
@Override
public void enter() {
+ Log.d(TAG, "renewing Dun in " + CELL_DUN_RENEW_MS + "ms");
sendMessageDelayed(obtainMessage(CMD_CELL_DUN_RENEW), CELL_DUN_RENEW_MS);
}
@@ -1190,29 +1414,7 @@
case CMD_TETHER_MODE_REQUESTED:
TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
mNotifyList.add(who);
- IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- INetworkManagementService service =
- INetworkManagementService.Stub.asInterface(b);
-
- try {
- service.setIpForwardingEnabled(true);
- } catch (Exception e) {
- transitionTo(mSetIpForwardingEnabledErrorState);
- break;
- }
- try {
- service.startTethering(mDhcpRange[0], mDhcpRange[1]);
- } catch (Exception e) {
- transitionTo(mStartTetheringErrorState);
- break;
- }
- try {
- service.setDnsForwarders(mDnsServers);
- } catch (Exception e) {
- transitionTo(mSetDnsForwardersErrorState);
- break;
- }
- transitionTo(mTetherModeAliveState);
+ turnOnMasterTetherSettings();
break;
case CMD_TETHER_MODE_UNREQUESTED:
who = (TetherInterfaceSM)message.obj;
@@ -1220,7 +1422,8 @@
if (index != -1) {
mNotifyList.remove(index);
if (mNotifyList.isEmpty()) {
- transitionTo(mCellDunUnRequestedState);
+ turnOffMobileDun();
+ transitionTo(mInitialState);
}
}
break;
@@ -1228,13 +1431,9 @@
transitionTo(mInitialState);
break;
case CMD_CELL_DUN_RENEW:
- b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager cservice = IConnectivityManager.Stub.asInterface(b);
- try {
- cservice.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
- Phone.FEATURE_ENABLE_DUN, new Binder());
- } catch (Exception e) {
- }
+ Log.d(TAG, "renewing dun connection - requeuing for another " +
+ CELL_DUN_RENEW_MS + "ms");
+ turnOnMobileDun();
sendMessageDelayed(obtainMessage(CMD_CELL_DUN_RENEW), CELL_DUN_RENEW_MS);
break;
default:
@@ -1245,9 +1444,11 @@
}
}
- class TetherModeAliveState extends HierarchicalState {
+ class TetherModeAliveState extends TetherMasterUtilState {
@Override
public void enter() {
+ Log.d(TAG, "renewing Dun in " + CELL_DUN_RENEW_MS + "ms");
+ sendMessageDelayed(obtainMessage(CMD_CELL_DUN_RENEW), CELL_DUN_RENEW_MS);
for (Object o : mNotifyList) {
TetherInterfaceSM sm = (TetherInterfaceSM)o;
sm.sendMessage(sm.obtainMessage(TetherInterfaceSM.CMD_TETHER_MODE_ALIVE));
@@ -1269,7 +1470,8 @@
if (index != -1) {
mNotifyList.remove(index);
if (mNotifyList.isEmpty()) {
- transitionTo(mCellDunUnRequestedState);
+ turnOffMobileDun();
+ turnOffMasterTetherSettings(); // transitions appropriately
}
}
break;
@@ -1281,22 +1483,13 @@
sm.sendMessage(sm.obtainMessage(
TetherInterfaceSM.CMD_TETHER_MODE_DEAD));
}
- IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- INetworkManagementService service =
- INetworkManagementService.Stub.asInterface(b);
- try {
- service.stopTethering();
- } catch (Exception e) {
- transitionTo(mStopTetheringErrorState);
- break;
- }
- try {
- service.setIpForwardingEnabled(false);
- } catch (Exception e) {
- transitionTo(mSetIpForwardingDisabledErrorState);
- break;
- }
- transitionTo(mInitialState);
+ turnOffMasterTetherSettings(); // transitions appropriately
+ break;
+ case CMD_CELL_DUN_RENEW:
+ Log.d(TAG, "renewing dun connection - requeuing for another " +
+ CELL_DUN_RENEW_MS + "ms");
+ turnOnMobileDun();
+ sendMessageDelayed(obtainMessage(CMD_CELL_DUN_RENEW), CELL_DUN_RENEW_MS);
break;
default:
retValue = false;
@@ -1306,58 +1499,6 @@
}
}
- class CellDunUnRequestedState extends HierarchicalState {
- @Override
- public void enter() {
- IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager service =
- IConnectivityManager.Stub.asInterface(b);
- NetworkInfo dunInfo = null;
- try {
- dunInfo = service.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_DUN);
- } catch (Exception e) {}
- if (dunInfo != null && !dunInfo.isConnectedOrConnecting()) {
- sendMessage(obtainMessage(CMD_CELL_DUN_DISABLED));
- return;
- }
- try {
- service.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
- Phone.FEATURE_ENABLE_DUN);
- } catch (Exception e) {}
- Message m = obtainMessage(CMD_CELL_DUN_TIMEOUT);
- m.arg1 = ++mSequenceNumber;
- // use a short timeout - this will often be a no-op and
- // we just want this request to get into the queue before we
- // try again.
- sendMessageDelayed(m, CELL_DISABLE_DUN_TIMEOUT_MS);
- }
- @Override
- public boolean processMessage(Message message) {
- Log.d(TAG, "CellDunUnRequestedState.processMessage what=" + message.what);
- boolean retValue = true;
- switch (message.what) {
- case CMD_TETHER_MODE_REQUESTED:
- case CMD_TETHER_MODE_UNREQUESTED:
- deferMessage(message);
- break;
- case CMD_CELL_DUN_DISABLED:
- transitionTo(mInitialState);
- break;
- case CMD_CELL_DUN_TIMEOUT:
- // if we aren't using a sep apn, we won't get a disconnect broadcast..
- // just go back to initial after our short pause
- if (message.arg1 == mSequenceNumber) {
- transitionTo(mInitialState);
- }
- break;
- default:
- retValue = false;
- break;
- }
- return retValue;
- }
- }
-
class ErrorState extends HierarchicalState {
int mErrorNotification;
@Override
diff --git a/services/java/com/android/server/status/ExpandedView.java b/services/java/com/android/server/status/ExpandedView.java
index d0f14cb..33ac8c1 100644
--- a/services/java/com/android/server/status/ExpandedView.java
+++ b/services/java/com/android/server/status/ExpandedView.java
@@ -11,17 +11,11 @@
public class ExpandedView extends LinearLayout {
- final Display mDisplay;
StatusBarService mService;
- boolean mTracking;
- int mStartX, mStartY;
- int mMaxHeight = 0;
int mPrevHeight = -1;
public ExpandedView(Context context, AttributeSet attrs) {
super(context, attrs);
- mDisplay = ((WindowManager)context.getSystemService(
- Context.WINDOW_SERVICE)).getDefaultDisplay();
}
@Override
@@ -36,12 +30,6 @@
}
@Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec,
- MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.AT_MOST));
- }
-
- @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int height = bottom - top;
@@ -51,11 +39,4 @@
mService.updateExpandedViewPos(StatusBarService.EXPANDED_LEAVE_ALONE);
}
}
-
- void setMaxHeight(int h) {
- if (h != mMaxHeight) {
- mMaxHeight = h;
- requestLayout();
- }
- }
}
diff --git a/services/java/com/android/server/status/StatusBarPolicy.java b/services/java/com/android/server/status/StatusBarPolicy.java
index d13f9d3..f1ccb9b 100644
--- a/services/java/com/android/server/status/StatusBarPolicy.java
+++ b/services/java/com/android/server/status/StatusBarPolicy.java
@@ -826,7 +826,10 @@
final Uri soundUri = Uri.parse("file://" + soundPath);
if (soundUri != null) {
final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
- if (sfx != null) sfx.play();
+ if (sfx != null) {
+ sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+ sfx.play();
+ }
}
}
}
diff --git a/services/java/com/android/server/status/StatusBarService.java b/services/java/com/android/server/status/StatusBarService.java
index c7c3335..3f77291 100644
--- a/services/java/com/android/server/status/StatusBarService.java
+++ b/services/java/com/android/server/status/StatusBarService.java
@@ -187,8 +187,9 @@
TextView mSpnLabel;
TextView mPlmnLabel;
TextView mClearButton;
+ View mExpandedContents;
CloseDragHandle mCloseView;
- int[] mCloseLocation = new int[2];
+ int[] mPositionTmp = new int[2];
boolean mExpanded;
boolean mExpandedVisible;
@@ -198,7 +199,7 @@
// the tracker view
TrackingView mTrackingView;
WindowManager.LayoutParams mTrackingParams;
- int mTrackingPosition;
+ int mTrackingPosition; // the position of the top of the tracking view.
// ticker
private Ticker mTicker;
@@ -206,6 +207,7 @@
private boolean mTicking;
// Tracking finger for opening/closing.
+ int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore
boolean mTracking;
VelocityTracker mVelocityTracker;
@@ -273,6 +275,7 @@
mExpandedDialog = new ExpandedDialog(context);
mExpandedView = expanded;
+ mExpandedContents = expanded.findViewById(R.id.notificationLinearLayout);
mOngoingTitle = (TextView)expanded.findViewById(R.id.ongoingTitle);
mOngoingItems = (LinearLayout)expanded.findViewById(R.id.ongoingItems);
mLatestTitle = (TextView)expanded.findViewById(R.id.latestTitle);
@@ -299,6 +302,8 @@
mCloseView = (CloseDragHandle)mTrackingView.findViewById(R.id.close);
mCloseView.mService = this;
+ mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
+
// add the more icon for the notifications
IconData moreData = IconData.makeIcon(null, context.getPackageName(),
R.drawable.stat_notify_more, 0, 42);
@@ -972,7 +977,7 @@
return;
}
- prepareTracking(0);
+ prepareTracking(0, true);
performFling(0, 2000.0f, true);
}
@@ -980,7 +985,9 @@
if (SPEW) {
Log.d(TAG, "animateCollapse(): mExpanded=" + mExpanded
+ " mExpandedVisible=" + mExpandedVisible
+ + " mExpanded=" + mExpanded
+ " mAnimating=" + mAnimating
+ + " mAnimY=" + mAnimY
+ " mAnimVel=" + mAnimVel);
}
@@ -988,12 +995,16 @@
return;
}
+ int y;
if (mAnimating) {
- return;
+ y = (int)mAnimY;
+ } else {
+ y = mDisplay.getHeight()-1;
}
-
- int y = mDisplay.getHeight()-1;
- prepareTracking(y);
+ // Let the fling think that we're open so it goes in the right direction
+ // and doesn't try to re-open the windowshade.
+ mExpanded = true;
+ prepareTracking(y, false);
performFling(y, -2000.0f, true);
}
@@ -1108,10 +1119,9 @@
}
}
- void prepareTracking(int y) {
+ void prepareTracking(int y, boolean opening) {
mTracking = true;
mVelocityTracker = VelocityTracker.obtain();
- boolean opening = !mExpanded;
if (opening) {
mAnimAccel = 2000.0f;
mAnimVel = 200;
@@ -1199,16 +1209,19 @@
}
boolean interceptTouchEvent(MotionEvent event) {
- if (SPEW) Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event);
+ if (SPEW) {
+ Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled="
+ + mDisabled);
+ }
if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
- return true;
+ return false;
}
final int statusBarSize = mStatusBarView.getHeight();
final int hitSize = statusBarSize*2;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
- int y = (int)event.getRawY();
+ final int y = (int)event.getRawY();
if (!mExpanded) {
mViewDelta = statusBarSize - y;
@@ -1218,8 +1231,16 @@
}
if ((!mExpanded && y < hitSize) ||
(mExpanded && y > (mDisplay.getHeight()-hitSize))) {
- prepareTracking(y);
- mVelocityTracker.addMovement(event);
+
+ // We drop events at the edge of the screen to make the windowshade come
+ // down by accident less, especially when pushing open a device with a keyboard
+ // that rotates (like g1 and droid)
+ int x = (int)event.getRawX();
+ final int edgeBorder = mEdgeBorder;
+ if (x >= edgeBorder && x < mDisplay.getWidth() - edgeBorder) {
+ prepareTracking(y, !mExpanded);// opening if we're not already fully visible
+ mVelocityTracker.addMovement(event);
+ }
}
} else if (mTracking) {
mVelocityTracker.addMovement(event);
@@ -1511,20 +1532,11 @@
/// ---------- Expanded View --------------
pixelFormat = PixelFormat.TRANSLUCENT;
- bg = mExpandedView.getBackground();
- if (bg != null) {
- pixelFormat = bg.getOpacity();
- if (pixelFormat != PixelFormat.TRANSLUCENT) {
- // we want good-looking gradients, so we force a 8-bits per
- // pixel format.
- pixelFormat = PixelFormat.RGBX_8888;
- }
- }
final int disph = mDisplay.getHeight();
lp = mExpandedDialog.getWindow().getAttributes();
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
- lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ lp.height = getExpandedHeight();
lp.x = 0;
mTrackingPosition = lp.y = -disph; // sufficiently large negative
lp.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
@@ -1543,10 +1555,10 @@
mExpandedDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
mExpandedDialog.setContentView(mExpandedView,
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ mExpandedDialog.getWindow().setBackgroundDrawable(null);
mExpandedDialog.show();
FrameLayout hack = (FrameLayout)mExpandedView.getParent();
- hack.setForeground(null);
}
void setDateViewVisibility(boolean visible, int anim) {
@@ -1611,11 +1623,15 @@
mTrackingParams.height = disph-h;
WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams);
- mCloseView.getLocationInWindow(mCloseLocation);
-
if (mExpandedParams != null) {
+ mCloseView.getLocationInWindow(mPositionTmp);
+ final int closePos = mPositionTmp[1];
+
+ mExpandedContents.getLocationInWindow(mPositionTmp);
+ final int contentsBottom = mPositionTmp[1] + mExpandedContents.getHeight();
+
mExpandedParams.y = pos + mTrackingView.getHeight()
- - (mTrackingParams.height-mCloseLocation[1]) - mExpandedView.getHeight();
+ - (mTrackingParams.height-closePos) - contentsBottom;
int max = h;
if (mExpandedParams.y > max) {
mExpandedParams.y = max;
@@ -1625,13 +1641,13 @@
mExpandedParams.y = min;
}
- /*
- Log.d(TAG, "mTrackingPosition=" + mTrackingPosition
- + " mTrackingView.height=" + mTrackingView.getHeight()
- + " diff=" + (mTrackingPosition + mTrackingView.getHeight())
- + " h=" + h);
- */
- panelSlightlyVisible((mTrackingPosition + mTrackingView.getHeight()) > h);
+ boolean visible = (mTrackingPosition + mTrackingView.getHeight()) > h;
+ if (!visible) {
+ // if the contents aren't visible, move the expanded view way off screen
+ // because the window itself extends below the content view.
+ mExpandedParams.y = -disph;
+ }
+ panelSlightlyVisible(visible);
mExpandedDialog.getWindow().setAttributes(mExpandedParams);
}
@@ -1639,16 +1655,19 @@
Log.d(TAG, "updateExpandedViewPos after expandedPosition=" + expandedPosition
+ " mTrackingParams.y=" + mTrackingParams.y
+ " mTrackingPosition=" + mTrackingPosition
- + " mExpandedParams.y=" + mExpandedParams.y);
+ + " mExpandedParams.y=" + mExpandedParams.y
+ + " mExpandedParams.height=" + mExpandedParams.height);
}
}
- void updateAvailableHeight() {
+ int getExpandedHeight() {
+ return mDisplay.getHeight() - mStatusBarView.getHeight() - mCloseView.getHeight();
+ }
+
+ void updateExpandedHeight() {
if (mExpandedView != null) {
- int disph = mDisplay.getHeight();
- int h = mStatusBarView.getHeight();
- int max = disph - (mCloseView.getHeight() + h);
- mExpandedView.setMaxHeight(max);
+ mExpandedParams.height = getExpandedHeight();
+ mExpandedDialog.getWindow().setAttributes(mExpandedParams);
}
}
@@ -1679,9 +1698,7 @@
if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
if ((net & StatusBarManager.DISABLE_EXPAND) != 0) {
Log.d(TAG, "DISABLE_EXPAND: yes");
- mAnimating = false;
- updateExpandedViewPos(0);
- performCollapse();
+ animateCollapse();
}
}
if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
@@ -1767,10 +1784,15 @@
* meantime, just update the things that we know change.
*/
void updateResources() {
+ Resources res = mContext.getResources();
+
mClearButton.setText(mContext.getText(R.string.status_bar_clear_all_button));
mOngoingTitle.setText(mContext.getText(R.string.status_bar_ongoing_events_title));
mLatestTitle.setText(mContext.getText(R.string.status_bar_latest_events_title));
mNoNotificationsTitle.setText(mContext.getText(R.string.status_bar_no_notifications_title));
+
+ mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
+
if (false) Log.v(TAG, "updateResources");
}
diff --git a/services/java/com/android/server/status/StorageNotification.java b/services/java/com/android/server/status/StorageNotification.java
index 3b79049..e0b288d 100644
--- a/services/java/com/android/server/status/StorageNotification.java
+++ b/services/java/com/android/server/status/StorageNotification.java
@@ -36,6 +36,7 @@
import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager;
import android.os.storage.StorageResultCode;
+import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
@@ -46,6 +47,8 @@
public class StorageNotification extends StorageEventListener {
private static final String TAG = "StorageNotification";
+ private static final boolean POP_UMS_ACTIVITY_ON_CONNECT = true;
+
/**
* Binder context for this service
*/
@@ -239,12 +242,28 @@
Intent intent = new Intent();
intent.setClass(mContext, com.android.server.status.UsbStorageActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ final boolean adbOn = 1 == Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ADB_ENABLED,
+ 0);
+
PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
setUsbStorageNotification(
com.android.internal.R.string.usb_storage_notification_title,
com.android.internal.R.string.usb_storage_notification_message,
com.android.internal.R.drawable.stat_sys_data_usb,
false, true, pi);
+
+ if (POP_UMS_ACTIVITY_ON_CONNECT && !adbOn) {
+ // We assume that developers don't want to enable UMS every
+ // time they attach a device to a USB host. The average user,
+ // however, is looking to charge the phone (in which case this
+ // is harmless) or transfer files (in which case this coaches
+ // the user about how to complete that task and saves several
+ // steps).
+ mContext.startActivity(intent);
+ }
} else {
setUsbStorageNotification(0, 0, 0, false, false, null);
}
diff --git a/services/java/com/android/server/status/TrackingView.java b/services/java/com/android/server/status/TrackingView.java
index 722d10c..886d66d 100644
--- a/services/java/com/android/server/status/TrackingView.java
+++ b/services/java/com/android/server/status/TrackingView.java
@@ -23,7 +23,7 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- mService.updateAvailableHeight();
+ mService.updateExpandedHeight();
}
@Override
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index 52c8b1f..cab7b81 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -440,10 +440,11 @@
return Phone.APN_REQUEST_FAILED;
}
- if(DBG) Log.d(LOG_TAG, "enableApnType("+type+"), isApnTypeActive = "
+ if (DBG) Log.d(LOG_TAG, "enableApnType("+type+"), isApnTypeActive = "
+ isApnTypeActive(type) + " and state = " + state);
if (!isApnTypeAvailable(type)) {
+ if (DBG) Log.d(LOG_TAG, "type not available");
return Phone.APN_TYPE_NOT_AVAILABLE;
}
diff --git a/telephony/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java b/telephony/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
index 31cf6a7..9f8e57f 100644
--- a/telephony/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
@@ -22,10 +22,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.ServiceManager;
-import android.telephony.PhoneNumberUtils;
-import android.util.Log;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -37,7 +34,7 @@
protected PhoneBase phone;
protected AdnRecordCache adnCache;
- protected Object mLock = new Object();
+ protected final Object mLock = new Object();
protected int recordSize[];
protected boolean success;
protected List<AdnRecord> records;
@@ -80,8 +77,7 @@
ar = (AsyncResult)msg.obj;
synchronized (mLock) {
if (ar.exception == null) {
- records = (List<AdnRecord>)
- ((ArrayList<AdnRecord>) ar.result);
+ records = (List<AdnRecord>) ar.result;
} else {
if(DBG) logd("Cannot load ADN records");
if (records != null) {
diff --git a/telephony/java/com/android/internal/telephony/PhoneSubInfo.java b/telephony/java/com/android/internal/telephony/PhoneSubInfo.java
index 19900c8..1ac2da3 100644
--- a/telephony/java/com/android/internal/telephony/PhoneSubInfo.java
+++ b/telephony/java/com/android/internal/telephony/PhoneSubInfo.java
@@ -39,6 +39,11 @@
}
protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
Log.d(LOG_TAG, "PhoneSubInfo finalized");
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index fbb3c4c..d5f18e0 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -23,6 +23,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncResult;
@@ -188,8 +190,16 @@
// and 2) whether the RIL will setup the baseband to auto-PS attach.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(phone.getContext());
+ boolean dataEnabledSetting = true;
+ try {
+ dataEnabledSetting = IConnectivityManager.Stub.asInterface(ServiceManager.
+ getService(Context.CONNECTIVITY_SERVICE)).getMobileDataEnabled();
+ } catch (Exception e) {
+ // nothing to do - use the old behavior and leave data on
+ }
dataEnabled[APN_DEFAULT_ID] =
- !sp.getBoolean(CDMAPhone.DATA_DISABLED_ON_BOOT_KEY, false);
+ !sp.getBoolean(CDMAPhone.DATA_DISABLED_ON_BOOT_KEY, false) &&
+ dataEnabledSetting;
if (dataEnabled[APN_DEFAULT_ID]) {
enabledCount++;
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimPhoneBookInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimPhoneBookInterfaceManager.java
index 78e89d5..6e12f24a 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimPhoneBookInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimPhoneBookInterfaceManager.java
@@ -16,22 +16,10 @@
package com.android.internal.telephony.cdma;
-import android.content.pm.PackageManager;
-import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
-import android.os.ServiceManager;
-import android.telephony.PhoneNumberUtils;
import android.util.Log;
-import com.android.internal.telephony.AdnRecord;
-import com.android.internal.telephony.AdnRecordCache;
import com.android.internal.telephony.IccPhoneBookInterfaceManager;
-import com.android.internal.telephony.PhoneProxy;
-
-import java.util.ArrayList;
-import java.util.List;
/**
* RuimPhoneBookInterfaceManager to provide an inter-process communication to
@@ -42,20 +30,6 @@
public class RuimPhoneBookInterfaceManager extends IccPhoneBookInterfaceManager {
static final String LOG_TAG = "CDMA";
-
- Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- AsyncResult ar;
-
- switch(msg.what) {
- default:
- mBaseHandler.handleMessage(msg);
- break;
- }
- }
- };
-
public RuimPhoneBookInterfaceManager(CDMAPhone phone) {
super(phone);
adnCache = phone.mRuimRecords.getAdnCache();
@@ -67,6 +41,11 @@
}
protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
if(DBG) Log.d(LOG_TAG, "RuimPhoneBookInterfaceManager finalized");
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
index 9439359..cfcfd98 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
@@ -30,6 +30,7 @@
import com.android.internal.telephony.SmsRawData;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import static android.telephony.SmsManager.STATUS_ON_ICC_FREE;
@@ -89,6 +90,11 @@
}
protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
if(DBG) Log.d(LOG_TAG, "RuimSmsInterfaceManager finalized");
}
@@ -143,7 +149,7 @@
public boolean copyMessageToIccEf(int status, byte[] pdu, byte[] smsc) {
//NOTE smsc not used in RUIM
if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
- "pdu=("+ pdu + ")");
+ "pdu=("+ Arrays.toString(pdu) + ")");
enforceReceiveAndSend("Copying message to RUIM");
synchronized(mLock) {
mSuccess = false;
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 1fd6be8..30beaaa 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -27,6 +27,8 @@
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.database.Cursor;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.WifiManager;
@@ -243,7 +245,15 @@
// This preference tells us 1) initial condition for "dataEnabled",
// and 2) whether the RIL will setup the baseband to auto-PS attach.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(phone.getContext());
- dataEnabled[APN_DEFAULT_ID] = !sp.getBoolean(GSMPhone.DATA_DISABLED_ON_BOOT_KEY, false);
+ boolean dataEnabledSetting = true;
+ try {
+ dataEnabledSetting = IConnectivityManager.Stub.asInterface(ServiceManager.
+ getService(Context.CONNECTIVITY_SERVICE)).getMobileDataEnabled();
+ } catch (Exception e) {
+ // nothing to do - use the old behavior and leave data on
+ }
+ dataEnabled[APN_DEFAULT_ID] = !sp.getBoolean(GSMPhone.DATA_DISABLED_ON_BOOT_KEY, false) &&
+ dataEnabledSetting;
if (dataEnabled[APN_DEFAULT_ID]) {
enabledCount++;
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java
index 076da6b..feb508a 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java
@@ -16,22 +16,10 @@
package com.android.internal.telephony.gsm;
-import android.content.pm.PackageManager;
-import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
-import android.os.ServiceManager;
-import android.telephony.PhoneNumberUtils;
import android.util.Log;
-import com.android.internal.telephony.AdnRecord;
-import com.android.internal.telephony.AdnRecordCache;
import com.android.internal.telephony.IccPhoneBookInterfaceManager;
-import com.android.internal.telephony.PhoneProxy;
-
-import java.util.ArrayList;
-import java.util.List;
/**
* SimPhoneBookInterfaceManager to provide an inter-process communication to
@@ -42,20 +30,6 @@
public class SimPhoneBookInterfaceManager extends IccPhoneBookInterfaceManager {
static final String LOG_TAG = "GSM";
-
- Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- AsyncResult ar;
-
- switch(msg.what) {
- default:
- mBaseHandler.handleMessage(msg);
- break;
- }
- }
- };
-
public SimPhoneBookInterfaceManager(GSMPhone phone) {
super(phone);
adnCache = phone.mSIMRecords.getAdnCache();
@@ -67,6 +41,11 @@
}
protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
if(DBG) Log.d(LOG_TAG, "SimPhoneBookInterfaceManager finalized");
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
index 875d8d0..2028ca4 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
@@ -25,10 +25,10 @@
import com.android.internal.telephony.IccConstants;
import com.android.internal.telephony.IccSmsInterfaceManager;
import com.android.internal.telephony.IccUtils;
-import com.android.internal.telephony.PhoneProxy;
import com.android.internal.telephony.SmsRawData;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import static android.telephony.SmsManager.STATUS_ON_ICC_FREE;
@@ -65,8 +65,7 @@
ar = (AsyncResult)msg.obj;
synchronized (mLock) {
if (ar.exception == null) {
- mSms = (List<SmsRawData>)
- buildValidRawData((ArrayList<byte[]>) ar.result);
+ mSms = buildValidRawData((ArrayList<byte[]>) ar.result);
} else {
if(DBG) log("Cannot load Sms records");
if (mSms != null)
@@ -88,6 +87,11 @@
}
protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
if(DBG) Log.d(LOG_TAG, "SimSmsInterfaceManager finalized");
}
@@ -106,7 +110,7 @@
updateMessageOnIccEf(int index, int status, byte[] pdu) {
if (DBG) log("updateMessageOnIccEf: index=" + index +
" status=" + status + " ==> " +
- "("+ pdu + ")");
+ "("+ Arrays.toString(pdu) + ")");
enforceReceiveAndSend("Updating message on SIM");
synchronized(mLock) {
mSuccess = false;
@@ -118,7 +122,7 @@
mPhone.mCM.deleteSmsOnSim(index, response);
} else {
byte[] record = makeSmsRecordData(status, pdu);
- ((SIMFileHandler)mPhone.getIccFileHandler()).updateEFLinearFixed(
+ mPhone.getIccFileHandler().updateEFLinearFixed(
IccConstants.EF_SMS,
index, record, null, response);
}
@@ -142,7 +146,8 @@
*/
public boolean copyMessageToIccEf(int status, byte[] pdu, byte[] smsc) {
if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
- "pdu=("+ pdu + "), smsm=(" + smsc +")");
+ "pdu=("+ Arrays.toString(pdu) +
+ "), smsm=(" + Arrays.toString(smsc) +")");
enforceReceiveAndSend("Copying message to SIM");
synchronized(mLock) {
mSuccess = false;
@@ -175,8 +180,7 @@
"Reading messages from SIM");
synchronized(mLock) {
Message response = mHandler.obtainMessage(EVENT_LOAD_DONE);
- ((SIMFileHandler)mPhone.getIccFileHandler()).loadEFLinearFixedAll(IccConstants.EF_SMS,
- response);
+ mPhone.getIccFileHandler().loadEFLinearFixedAll(IccConstants.EF_SMS, response);
try {
mLock.wait();
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index 42167c7..962e96c 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -18,10 +18,13 @@
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES := core framework
LOCAL_MODULE:= android.test.runner
include $(BUILD_JAVA_LIBRARY)
+
+# additionally, build unit tests in a separate .apk
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/test-runner/android/test/ActivityInstrumentationTestCase.java b/test-runner/src/android/test/ActivityInstrumentationTestCase.java
similarity index 100%
rename from test-runner/android/test/ActivityInstrumentationTestCase.java
rename to test-runner/src/android/test/ActivityInstrumentationTestCase.java
diff --git a/test-runner/android/test/ActivityInstrumentationTestCase2.java b/test-runner/src/android/test/ActivityInstrumentationTestCase2.java
similarity index 100%
rename from test-runner/android/test/ActivityInstrumentationTestCase2.java
rename to test-runner/src/android/test/ActivityInstrumentationTestCase2.java
diff --git a/test-runner/android/test/ActivityTestCase.java b/test-runner/src/android/test/ActivityTestCase.java
similarity index 100%
rename from test-runner/android/test/ActivityTestCase.java
rename to test-runner/src/android/test/ActivityTestCase.java
diff --git a/test-runner/android/test/ActivityUnitTestCase.java b/test-runner/src/android/test/ActivityUnitTestCase.java
similarity index 100%
rename from test-runner/android/test/ActivityUnitTestCase.java
rename to test-runner/src/android/test/ActivityUnitTestCase.java
diff --git a/test-runner/android/test/AndroidTestRunner.java b/test-runner/src/android/test/AndroidTestRunner.java
similarity index 100%
rename from test-runner/android/test/AndroidTestRunner.java
rename to test-runner/src/android/test/AndroidTestRunner.java
diff --git a/test-runner/android/test/ApplicationTestCase.java b/test-runner/src/android/test/ApplicationTestCase.java
similarity index 100%
rename from test-runner/android/test/ApplicationTestCase.java
rename to test-runner/src/android/test/ApplicationTestCase.java
diff --git a/test-runner/android/test/AssertionFailedError.java b/test-runner/src/android/test/AssertionFailedError.java
similarity index 100%
rename from test-runner/android/test/AssertionFailedError.java
rename to test-runner/src/android/test/AssertionFailedError.java
diff --git a/test-runner/android/test/BundlePrinter.java b/test-runner/src/android/test/BundlePrinter.java
similarity index 100%
rename from test-runner/android/test/BundlePrinter.java
rename to test-runner/src/android/test/BundlePrinter.java
diff --git a/test-runner/android/test/BundleTestListener.java b/test-runner/src/android/test/BundleTestListener.java
similarity index 100%
rename from test-runner/android/test/BundleTestListener.java
rename to test-runner/src/android/test/BundleTestListener.java
diff --git a/test-runner/android/test/ClassPathPackageInfo.java b/test-runner/src/android/test/ClassPathPackageInfo.java
similarity index 100%
rename from test-runner/android/test/ClassPathPackageInfo.java
rename to test-runner/src/android/test/ClassPathPackageInfo.java
diff --git a/test-runner/android/test/ClassPathPackageInfoSource.java b/test-runner/src/android/test/ClassPathPackageInfoSource.java
similarity index 100%
rename from test-runner/android/test/ClassPathPackageInfoSource.java
rename to test-runner/src/android/test/ClassPathPackageInfoSource.java
diff --git a/test-runner/android/test/ComparisonFailure.java b/test-runner/src/android/test/ComparisonFailure.java
similarity index 100%
rename from test-runner/android/test/ComparisonFailure.java
rename to test-runner/src/android/test/ComparisonFailure.java
diff --git a/test-runner/android/test/DatabaseTestUtils.java b/test-runner/src/android/test/DatabaseTestUtils.java
similarity index 100%
rename from test-runner/android/test/DatabaseTestUtils.java
rename to test-runner/src/android/test/DatabaseTestUtils.java
diff --git a/test-runner/android/test/InstrumentationCoreTestRunner.java b/test-runner/src/android/test/InstrumentationCoreTestRunner.java
similarity index 100%
rename from test-runner/android/test/InstrumentationCoreTestRunner.java
rename to test-runner/src/android/test/InstrumentationCoreTestRunner.java
diff --git a/test-runner/android/test/InstrumentationTestRunner.java b/test-runner/src/android/test/InstrumentationTestRunner.java
similarity index 88%
rename from test-runner/android/test/InstrumentationTestRunner.java
rename to test-runner/src/android/test/InstrumentationTestRunner.java
index 3e9cd9f..ee6b89c 100644
--- a/test-runner/android/test/InstrumentationTestRunner.java
+++ b/test-runner/src/android/test/InstrumentationTestRunner.java
@@ -19,6 +19,7 @@
import static android.test.suitebuilder.TestPredicates.REJECT_PERFORMANCE;
import com.android.internal.util.Predicate;
+import com.android.internal.util.Predicates;
import android.app.Activity;
import android.app.Instrumentation;
@@ -31,11 +32,14 @@
import android.test.suitebuilder.TestMethod;
import android.test.suitebuilder.TestPredicates;
import android.test.suitebuilder.TestSuiteBuilder;
+import android.test.suitebuilder.annotation.HasAnnotation;
+import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
+import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -93,6 +97,18 @@
* -e size large
* com.android.foo/android.test.InstrumentationTestRunner
* <p/>
+ * <b>Filter test run to tests with given annotation:</b> adb shell am instrument -w
+ * -e annotation com.android.foo.MyAnnotation
+ * com.android.foo/android.test.InstrumentationTestRunner
+ * <p/>
+ * If used with other options, the resulting test run will contain the union of the two options.
+ * e.g. "-e size large -e annotation com.android.foo.MyAnnotation" will run only tests with both
+ * the {@link LargeTest} and "com.android.foo.MyAnnotation" annotations.
+ * <p/>
+ * <b>Filter test run to tests <i>without</i> given annotation:</b> adb shell am instrument -w
+ * -e notAnnotation com.android.foo.MyAnnotation
+ * com.android.foo/android.test.InstrumentationTestRunner
+ * <p/>
* <b>Running a single testcase:</b> adb shell am instrument -w
* -e class com.android.foo.FooTest
* com.android.foo/android.test.InstrumentationTestRunner
@@ -161,6 +177,10 @@
private static final String LARGE_SUITE = "large";
private static final String ARGUMENT_LOG_ONLY = "log";
+ /** @hide */
+ static final String ARGUMENT_ANNOTATION = "annotation";
+ /** @hide */
+ static final String ARGUMENT_NOT_ANNOTATION = "notAnnotation";
/**
* This constant defines the maximum allowed runtime (in ms) for a test included in the "small"
@@ -274,6 +294,8 @@
ClassPathPackageInfoSource.setApkPaths(apkPaths);
Predicate<TestMethod> testSizePredicate = null;
+ Predicate<TestMethod> testAnnotationPredicate = null;
+ Predicate<TestMethod> testNotAnnotationPredicate = null;
boolean includePerformance = false;
String testClassesArg = null;
boolean logOnly = false;
@@ -287,6 +309,11 @@
mPackageOfTests = arguments.getString(ARGUMENT_TEST_PACKAGE);
testSizePredicate = getSizePredicateFromArg(
arguments.getString(ARGUMENT_TEST_SIZE_PREDICATE));
+ testAnnotationPredicate = getAnnotationPredicate(
+ arguments.getString(ARGUMENT_ANNOTATION));
+ testNotAnnotationPredicate = getNotAnnotationPredicate(
+ arguments.getString(ARGUMENT_NOT_ANNOTATION));
+
includePerformance = getBooleanArgument(arguments, ARGUMENT_INCLUDE_PERF);
logOnly = getBooleanArgument(arguments, ARGUMENT_LOG_ONLY);
mCoverage = getBooleanArgument(arguments, "coverage");
@@ -306,6 +333,12 @@
if (testSizePredicate != null) {
testSuiteBuilder.addRequirements(testSizePredicate);
}
+ if (testAnnotationPredicate != null) {
+ testSuiteBuilder.addRequirements(testAnnotationPredicate);
+ }
+ if (testNotAnnotationPredicate != null) {
+ testSuiteBuilder.addRequirements(testNotAnnotationPredicate);
+ }
if (!includePerformance) {
testSuiteBuilder.addRequirements(REJECT_PERFORMANCE);
}
@@ -406,6 +439,59 @@
}
}
+ /**
+ * Returns the test predicate object, corresponding to the annotation class value provided via
+ * the {@link ARGUMENT_ANNOTATION} argument.
+ *
+ * @return the predicate or <code>null</code>
+ */
+ private Predicate<TestMethod> getAnnotationPredicate(String annotationClassName) {
+ Class<? extends Annotation> annotationClass = getAnnotationClass(annotationClassName);
+ if (annotationClass != null) {
+ return new HasAnnotation(annotationClass);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the negative test predicate object, corresponding to the annotation class value
+ * provided via the {@link ARGUMENT_NOT_ANNOTATION} argument.
+ *
+ * @return the predicate or <code>null</code>
+ */
+ private Predicate<TestMethod> getNotAnnotationPredicate(String annotationClassName) {
+ Class<? extends Annotation> annotationClass = getAnnotationClass(annotationClassName);
+ if (annotationClass != null) {
+ return Predicates.not(new HasAnnotation(annotationClass));
+ }
+ return null;
+ }
+
+ /**
+ * Helper method to return the annotation class with specified name
+ *
+ * @param annotationClassName the fully qualified name of the class
+ * @return the annotation class or <code>null</code>
+ */
+ private Class<? extends Annotation> getAnnotationClass(String annotationClassName) {
+ if (annotationClassName == null) {
+ return null;
+ }
+ try {
+ Class<?> annotationClass = Class.forName(annotationClassName);
+ if (annotationClass.isAnnotation()) {
+ return (Class<? extends Annotation>)annotationClass;
+ } else {
+ Log.e(LOG_TAG, String.format("Provided annotation value %s is not an Annotation",
+ annotationClassName));
+ }
+ } catch (ClassNotFoundException e) {
+ Log.e(LOG_TAG, String.format("Could not find class for specified annotation %s",
+ annotationClassName));
+ }
+ return null;
+ }
+
@Override
public void onStart() {
Looper.prepare();
@@ -471,7 +557,7 @@
String coverageFilePath = getCoverageFilePath();
java.io.File coverageFile = new java.io.File(coverageFilePath);
try {
- Class emmaRTClass = Class.forName("com.vladium.emma.rt.RT");
+ Class<?> emmaRTClass = Class.forName("com.vladium.emma.rt.RT");
Method dumpCoverageMethod = emmaRTClass.getMethod("dumpCoverageData",
coverageFile.getClass(), boolean.class, boolean.class);
@@ -480,8 +566,11 @@
// needed
mResults.putString(REPORT_KEY_COVERAGE_PATH, coverageFilePath);
// also output a more user friendly msg
+ final String currentStream = mResults.getString(
+ Instrumentation.REPORT_KEY_STREAMRESULT);
mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
- String.format("Generated code coverage data to %s", coverageFilePath));
+ String.format("%s\nGenerated code coverage data to %s", currentStream,
+ coverageFilePath));
} catch (ClassNotFoundException e) {
reportEmmaError("Is emma jar on classpath?", e);
} catch (SecurityException e) {
diff --git a/test-runner/android/test/InstrumentationUtils.java b/test-runner/src/android/test/InstrumentationUtils.java
similarity index 100%
rename from test-runner/android/test/InstrumentationUtils.java
rename to test-runner/src/android/test/InstrumentationUtils.java
diff --git a/test-runner/android/test/IsolatedContext.java b/test-runner/src/android/test/IsolatedContext.java
similarity index 100%
rename from test-runner/android/test/IsolatedContext.java
rename to test-runner/src/android/test/IsolatedContext.java
diff --git a/test-runner/android/test/LaunchPerformanceBase.java b/test-runner/src/android/test/LaunchPerformanceBase.java
similarity index 100%
rename from test-runner/android/test/LaunchPerformanceBase.java
rename to test-runner/src/android/test/LaunchPerformanceBase.java
diff --git a/test-runner/android/test/MoreAsserts.java b/test-runner/src/android/test/MoreAsserts.java
similarity index 100%
rename from test-runner/android/test/MoreAsserts.java
rename to test-runner/src/android/test/MoreAsserts.java
diff --git a/test-runner/android/test/NoExecTestResult.java b/test-runner/src/android/test/NoExecTestResult.java
similarity index 100%
rename from test-runner/android/test/NoExecTestResult.java
rename to test-runner/src/android/test/NoExecTestResult.java
diff --git a/test-runner/android/test/PackageInfoSources.java b/test-runner/src/android/test/PackageInfoSources.java
similarity index 100%
rename from test-runner/android/test/PackageInfoSources.java
rename to test-runner/src/android/test/PackageInfoSources.java
diff --git a/test-runner/android/test/PerformanceCollectorTestCase.java b/test-runner/src/android/test/PerformanceCollectorTestCase.java
similarity index 100%
rename from test-runner/android/test/PerformanceCollectorTestCase.java
rename to test-runner/src/android/test/PerformanceCollectorTestCase.java
diff --git a/test-runner/android/test/PerformanceTestBase.java b/test-runner/src/android/test/PerformanceTestBase.java
similarity index 100%
rename from test-runner/android/test/PerformanceTestBase.java
rename to test-runner/src/android/test/PerformanceTestBase.java
diff --git a/test-runner/android/test/ProviderTestCase.java b/test-runner/src/android/test/ProviderTestCase.java
similarity index 100%
rename from test-runner/android/test/ProviderTestCase.java
rename to test-runner/src/android/test/ProviderTestCase.java
diff --git a/test-runner/android/test/ProviderTestCase2.java b/test-runner/src/android/test/ProviderTestCase2.java
similarity index 100%
rename from test-runner/android/test/ProviderTestCase2.java
rename to test-runner/src/android/test/ProviderTestCase2.java
diff --git a/test-runner/android/test/RenamingDelegatingContext.java b/test-runner/src/android/test/RenamingDelegatingContext.java
similarity index 100%
rename from test-runner/android/test/RenamingDelegatingContext.java
rename to test-runner/src/android/test/RenamingDelegatingContext.java
diff --git a/test-runner/android/test/ServiceLocator.java b/test-runner/src/android/test/ServiceLocator.java
similarity index 100%
rename from test-runner/android/test/ServiceLocator.java
rename to test-runner/src/android/test/ServiceLocator.java
diff --git a/test-runner/android/test/ServiceTestCase.java b/test-runner/src/android/test/ServiceTestCase.java
similarity index 100%
rename from test-runner/android/test/ServiceTestCase.java
rename to test-runner/src/android/test/ServiceTestCase.java
diff --git a/test-runner/android/test/SimpleCache.java b/test-runner/src/android/test/SimpleCache.java
similarity index 100%
rename from test-runner/android/test/SimpleCache.java
rename to test-runner/src/android/test/SimpleCache.java
diff --git a/test-runner/android/test/SingleLaunchActivityTestCase.java b/test-runner/src/android/test/SingleLaunchActivityTestCase.java
similarity index 100%
rename from test-runner/android/test/SingleLaunchActivityTestCase.java
rename to test-runner/src/android/test/SingleLaunchActivityTestCase.java
diff --git a/test-runner/android/test/SyncBaseInstrumentation.java b/test-runner/src/android/test/SyncBaseInstrumentation.java
similarity index 100%
rename from test-runner/android/test/SyncBaseInstrumentation.java
rename to test-runner/src/android/test/SyncBaseInstrumentation.java
diff --git a/test-runner/android/test/TestBrowserActivity.java b/test-runner/src/android/test/TestBrowserActivity.java
similarity index 100%
rename from test-runner/android/test/TestBrowserActivity.java
rename to test-runner/src/android/test/TestBrowserActivity.java
diff --git a/test-runner/android/test/TestBrowserController.java b/test-runner/src/android/test/TestBrowserController.java
similarity index 100%
rename from test-runner/android/test/TestBrowserController.java
rename to test-runner/src/android/test/TestBrowserController.java
diff --git a/test-runner/android/test/TestBrowserControllerImpl.java b/test-runner/src/android/test/TestBrowserControllerImpl.java
similarity index 100%
rename from test-runner/android/test/TestBrowserControllerImpl.java
rename to test-runner/src/android/test/TestBrowserControllerImpl.java
diff --git a/test-runner/android/test/TestBrowserView.java b/test-runner/src/android/test/TestBrowserView.java
similarity index 100%
rename from test-runner/android/test/TestBrowserView.java
rename to test-runner/src/android/test/TestBrowserView.java
diff --git a/test-runner/android/test/TestCase.java b/test-runner/src/android/test/TestCase.java
similarity index 100%
rename from test-runner/android/test/TestCase.java
rename to test-runner/src/android/test/TestCase.java
diff --git a/test-runner/android/test/TestCaseUtil.java b/test-runner/src/android/test/TestCaseUtil.java
similarity index 100%
rename from test-runner/android/test/TestCaseUtil.java
rename to test-runner/src/android/test/TestCaseUtil.java
diff --git a/test-runner/android/test/TestListActivity.java b/test-runner/src/android/test/TestListActivity.java
similarity index 100%
rename from test-runner/android/test/TestListActivity.java
rename to test-runner/src/android/test/TestListActivity.java
diff --git a/test-runner/android/test/TestPrinter.java b/test-runner/src/android/test/TestPrinter.java
similarity index 100%
rename from test-runner/android/test/TestPrinter.java
rename to test-runner/src/android/test/TestPrinter.java
diff --git a/test-runner/android/test/TestRecorder.java b/test-runner/src/android/test/TestRecorder.java
similarity index 100%
rename from test-runner/android/test/TestRecorder.java
rename to test-runner/src/android/test/TestRecorder.java
diff --git a/test-runner/android/test/TestRunner.java b/test-runner/src/android/test/TestRunner.java
similarity index 100%
rename from test-runner/android/test/TestRunner.java
rename to test-runner/src/android/test/TestRunner.java
diff --git a/test-runner/android/test/TestRunnerView.java b/test-runner/src/android/test/TestRunnerView.java
similarity index 100%
rename from test-runner/android/test/TestRunnerView.java
rename to test-runner/src/android/test/TestRunnerView.java
diff --git a/test-runner/android/test/TestSuiteProvider.java b/test-runner/src/android/test/TestSuiteProvider.java
similarity index 100%
rename from test-runner/android/test/TestSuiteProvider.java
rename to test-runner/src/android/test/TestSuiteProvider.java
diff --git a/test-runner/android/test/TimedTest.java b/test-runner/src/android/test/TimedTest.java
similarity index 100%
rename from test-runner/android/test/TimedTest.java
rename to test-runner/src/android/test/TimedTest.java
diff --git a/test-runner/android/test/TouchUtils.java b/test-runner/src/android/test/TouchUtils.java
similarity index 100%
rename from test-runner/android/test/TouchUtils.java
rename to test-runner/src/android/test/TouchUtils.java
diff --git a/test-runner/android/test/ViewAsserts.java b/test-runner/src/android/test/ViewAsserts.java
similarity index 100%
rename from test-runner/android/test/ViewAsserts.java
rename to test-runner/src/android/test/ViewAsserts.java
diff --git a/test-runner/android/test/mock/MockApplication.java b/test-runner/src/android/test/mock/MockApplication.java
similarity index 100%
rename from test-runner/android/test/mock/MockApplication.java
rename to test-runner/src/android/test/mock/MockApplication.java
diff --git a/test-runner/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java
similarity index 100%
rename from test-runner/android/test/mock/MockContentProvider.java
rename to test-runner/src/android/test/mock/MockContentProvider.java
diff --git a/test-runner/android/test/mock/MockContentResolver.java b/test-runner/src/android/test/mock/MockContentResolver.java
similarity index 100%
rename from test-runner/android/test/mock/MockContentResolver.java
rename to test-runner/src/android/test/mock/MockContentResolver.java
diff --git a/test-runner/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
similarity index 100%
rename from test-runner/android/test/mock/MockContext.java
rename to test-runner/src/android/test/mock/MockContext.java
diff --git a/test-runner/android/test/mock/MockCursor.java b/test-runner/src/android/test/mock/MockCursor.java
similarity index 100%
rename from test-runner/android/test/mock/MockCursor.java
rename to test-runner/src/android/test/mock/MockCursor.java
diff --git a/test-runner/android/test/mock/MockDialogInterface.java b/test-runner/src/android/test/mock/MockDialogInterface.java
similarity index 100%
rename from test-runner/android/test/mock/MockDialogInterface.java
rename to test-runner/src/android/test/mock/MockDialogInterface.java
diff --git a/test-runner/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java
similarity index 100%
rename from test-runner/android/test/mock/MockIContentProvider.java
rename to test-runner/src/android/test/mock/MockIContentProvider.java
diff --git a/test-runner/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
similarity index 97%
rename from test-runner/android/test/mock/MockPackageManager.java
rename to test-runner/src/android/test/mock/MockPackageManager.java
index f1ba44a..2ccc9bb 100644
--- a/test-runner/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -26,6 +26,7 @@
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageInstallObserver;
+import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
@@ -308,6 +309,14 @@
int flags, String installerPackageName) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * @hide - to match hiding in superclass
+ */
+ @Override
+ public void movePackage(String packageName, IPackageMoveObserver observer, int flags) {
+ throw new UnsupportedOperationException();
+ }
@Override
public String getInstallerPackageName(String packageName) {
diff --git a/test-runner/android/test/mock/MockResources.java b/test-runner/src/android/test/mock/MockResources.java
similarity index 100%
rename from test-runner/android/test/mock/MockResources.java
rename to test-runner/src/android/test/mock/MockResources.java
diff --git a/test-runner/android/test/mock/package.html b/test-runner/src/android/test/mock/package.html
similarity index 100%
rename from test-runner/android/test/mock/package.html
rename to test-runner/src/android/test/mock/package.html
diff --git a/test-runner/android/test/suitebuilder/AssignableFrom.java b/test-runner/src/android/test/suitebuilder/AssignableFrom.java
similarity index 100%
rename from test-runner/android/test/suitebuilder/AssignableFrom.java
rename to test-runner/src/android/test/suitebuilder/AssignableFrom.java
diff --git a/test-runner/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java b/test-runner/src/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java
similarity index 100%
rename from test-runner/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java
rename to test-runner/src/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java
diff --git a/test-runner/android/test/suitebuilder/SmokeTestSuiteBuilder.java b/test-runner/src/android/test/suitebuilder/SmokeTestSuiteBuilder.java
similarity index 100%
rename from test-runner/android/test/suitebuilder/SmokeTestSuiteBuilder.java
rename to test-runner/src/android/test/suitebuilder/SmokeTestSuiteBuilder.java
diff --git a/test-runner/android/test/suitebuilder/TestGrouping.java b/test-runner/src/android/test/suitebuilder/TestGrouping.java
similarity index 100%
rename from test-runner/android/test/suitebuilder/TestGrouping.java
rename to test-runner/src/android/test/suitebuilder/TestGrouping.java
diff --git a/test-runner/android/test/suitebuilder/TestMethod.java b/test-runner/src/android/test/suitebuilder/TestMethod.java
similarity index 100%
rename from test-runner/android/test/suitebuilder/TestMethod.java
rename to test-runner/src/android/test/suitebuilder/TestMethod.java
diff --git a/test-runner/android/test/suitebuilder/TestPredicates.java b/test-runner/src/android/test/suitebuilder/TestPredicates.java
similarity index 100%
rename from test-runner/android/test/suitebuilder/TestPredicates.java
rename to test-runner/src/android/test/suitebuilder/TestPredicates.java
diff --git a/test-runner/android/test/suitebuilder/TestSuiteBuilder.java b/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
similarity index 100%
rename from test-runner/android/test/suitebuilder/TestSuiteBuilder.java
rename to test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
diff --git a/test-runner/android/test/suitebuilder/UnitTestSuiteBuilder.java b/test-runner/src/android/test/suitebuilder/UnitTestSuiteBuilder.java
similarity index 100%
rename from test-runner/android/test/suitebuilder/UnitTestSuiteBuilder.java
rename to test-runner/src/android/test/suitebuilder/UnitTestSuiteBuilder.java
diff --git a/test-runner/android/test/suitebuilder/annotation/HasAnnotation.java b/test-runner/src/android/test/suitebuilder/annotation/HasAnnotation.java
similarity index 100%
rename from test-runner/android/test/suitebuilder/annotation/HasAnnotation.java
rename to test-runner/src/android/test/suitebuilder/annotation/HasAnnotation.java
diff --git a/test-runner/android/test/suitebuilder/annotation/HasClassAnnotation.java b/test-runner/src/android/test/suitebuilder/annotation/HasClassAnnotation.java
similarity index 100%
rename from test-runner/android/test/suitebuilder/annotation/HasClassAnnotation.java
rename to test-runner/src/android/test/suitebuilder/annotation/HasClassAnnotation.java
diff --git a/test-runner/android/test/suitebuilder/annotation/HasMethodAnnotation.java b/test-runner/src/android/test/suitebuilder/annotation/HasMethodAnnotation.java
similarity index 100%
rename from test-runner/android/test/suitebuilder/annotation/HasMethodAnnotation.java
rename to test-runner/src/android/test/suitebuilder/annotation/HasMethodAnnotation.java
diff --git a/test-runner/android/test/suitebuilder/annotation/package.html b/test-runner/src/android/test/suitebuilder/annotation/package.html
similarity index 100%
rename from test-runner/android/test/suitebuilder/annotation/package.html
rename to test-runner/src/android/test/suitebuilder/annotation/package.html
diff --git a/test-runner/android/test/suitebuilder/package.html b/test-runner/src/android/test/suitebuilder/package.html
similarity index 100%
rename from test-runner/android/test/suitebuilder/package.html
rename to test-runner/src/android/test/suitebuilder/package.html
diff --git a/test-runner/junit/MODULE_LICENSE_CPL b/test-runner/src/junit/MODULE_LICENSE_CPL
similarity index 100%
rename from test-runner/junit/MODULE_LICENSE_CPL
rename to test-runner/src/junit/MODULE_LICENSE_CPL
diff --git a/test-runner/junit/runner/BaseTestRunner.java b/test-runner/src/junit/runner/BaseTestRunner.java
similarity index 100%
rename from test-runner/junit/runner/BaseTestRunner.java
rename to test-runner/src/junit/runner/BaseTestRunner.java
diff --git a/test-runner/junit/runner/ClassPathTestCollector.java b/test-runner/src/junit/runner/ClassPathTestCollector.java
similarity index 100%
rename from test-runner/junit/runner/ClassPathTestCollector.java
rename to test-runner/src/junit/runner/ClassPathTestCollector.java
diff --git a/test-runner/junit/runner/FailureDetailView.java b/test-runner/src/junit/runner/FailureDetailView.java
similarity index 100%
rename from test-runner/junit/runner/FailureDetailView.java
rename to test-runner/src/junit/runner/FailureDetailView.java
diff --git a/test-runner/junit/runner/LoadingTestCollector.java b/test-runner/src/junit/runner/LoadingTestCollector.java
similarity index 100%
rename from test-runner/junit/runner/LoadingTestCollector.java
rename to test-runner/src/junit/runner/LoadingTestCollector.java
diff --git a/test-runner/junit/runner/ReloadingTestSuiteLoader.java b/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java
similarity index 100%
rename from test-runner/junit/runner/ReloadingTestSuiteLoader.java
rename to test-runner/src/junit/runner/ReloadingTestSuiteLoader.java
diff --git a/test-runner/junit/runner/SimpleTestCollector.java b/test-runner/src/junit/runner/SimpleTestCollector.java
similarity index 100%
rename from test-runner/junit/runner/SimpleTestCollector.java
rename to test-runner/src/junit/runner/SimpleTestCollector.java
diff --git a/test-runner/junit/runner/Sorter.java b/test-runner/src/junit/runner/Sorter.java
similarity index 100%
rename from test-runner/junit/runner/Sorter.java
rename to test-runner/src/junit/runner/Sorter.java
diff --git a/test-runner/junit/runner/StandardTestSuiteLoader.java b/test-runner/src/junit/runner/StandardTestSuiteLoader.java
similarity index 100%
rename from test-runner/junit/runner/StandardTestSuiteLoader.java
rename to test-runner/src/junit/runner/StandardTestSuiteLoader.java
diff --git a/test-runner/junit/runner/TestCaseClassLoader.java b/test-runner/src/junit/runner/TestCaseClassLoader.java
similarity index 100%
rename from test-runner/junit/runner/TestCaseClassLoader.java
rename to test-runner/src/junit/runner/TestCaseClassLoader.java
diff --git a/test-runner/junit/runner/TestCollector.java b/test-runner/src/junit/runner/TestCollector.java
similarity index 100%
rename from test-runner/junit/runner/TestCollector.java
rename to test-runner/src/junit/runner/TestCollector.java
diff --git a/test-runner/junit/runner/TestRunListener.java b/test-runner/src/junit/runner/TestRunListener.java
similarity index 100%
rename from test-runner/junit/runner/TestRunListener.java
rename to test-runner/src/junit/runner/TestRunListener.java
diff --git a/test-runner/junit/runner/TestSuiteLoader.java b/test-runner/src/junit/runner/TestSuiteLoader.java
similarity index 100%
rename from test-runner/junit/runner/TestSuiteLoader.java
rename to test-runner/src/junit/runner/TestSuiteLoader.java
diff --git a/test-runner/junit/runner/Version.java b/test-runner/src/junit/runner/Version.java
similarity index 100%
rename from test-runner/junit/runner/Version.java
rename to test-runner/src/junit/runner/Version.java
diff --git a/test-runner/junit/runner/excluded.properties b/test-runner/src/junit/runner/excluded.properties
similarity index 100%
rename from test-runner/junit/runner/excluded.properties
rename to test-runner/src/junit/runner/excluded.properties
diff --git a/test-runner/junit/runner/logo.gif b/test-runner/src/junit/runner/logo.gif
similarity index 100%
rename from test-runner/junit/runner/logo.gif
rename to test-runner/src/junit/runner/logo.gif
Binary files differ
diff --git a/test-runner/junit/runner/package.html b/test-runner/src/junit/runner/package.html
similarity index 100%
rename from test-runner/junit/runner/package.html
rename to test-runner/src/junit/runner/package.html
diff --git a/test-runner/junit/runner/smalllogo.gif b/test-runner/src/junit/runner/smalllogo.gif
similarity index 100%
rename from test-runner/junit/runner/smalllogo.gif
rename to test-runner/src/junit/runner/smalllogo.gif
Binary files differ
diff --git a/test-runner/junit/textui/ResultPrinter.java b/test-runner/src/junit/textui/ResultPrinter.java
similarity index 100%
rename from test-runner/junit/textui/ResultPrinter.java
rename to test-runner/src/junit/textui/ResultPrinter.java
diff --git a/test-runner/junit/textui/TestRunner.java b/test-runner/src/junit/textui/TestRunner.java
similarity index 100%
rename from test-runner/junit/textui/TestRunner.java
rename to test-runner/src/junit/textui/TestRunner.java
diff --git a/test-runner/junit/textui/package.html b/test-runner/src/junit/textui/package.html
similarity index 100%
rename from test-runner/junit/textui/package.html
rename to test-runner/src/junit/textui/package.html
diff --git a/test-runner/tests/Android.mk b/test-runner/tests/Android.mk
new file mode 100644
index 0000000..d1efe7b
--- /dev/null
+++ b/test-runner/tests/Android.mk
@@ -0,0 +1,29 @@
+# Copyright 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := FrameworkTestRunnerTests
+
+include $(BUILD_PACKAGE)
+
diff --git a/test-runner/tests/AndroidManifest.xml b/test-runner/tests/AndroidManifest.xml
new file mode 100644
index 0000000..4f32392
--- /dev/null
+++ b/test-runner/tests/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.testrunner.tests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.test.StubTestBrowserActivity"
+ android:label="Stubbed Test Browser">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.FOR_TESTS_ONLY"/>
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.test.TestBrowserTests"
+ android:label="Test Browser Tests">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.UNIT_TEST"/>
+ </intent-filter>
+ </activity>`
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.frameworks.testrunner.tests"
+ android:label="Framework testrunner tests" />
+</manifest>
diff --git a/tests/CoreTests/android/test/AndroidTestRunnerTest.java b/test-runner/tests/src/android/test/AndroidTestRunnerTest.java
similarity index 100%
rename from tests/CoreTests/android/test/AndroidTestRunnerTest.java
rename to test-runner/tests/src/android/test/AndroidTestRunnerTest.java
diff --git a/tests/CoreTests/android/test/InstrumentationTestRunnerTest.java b/test-runner/tests/src/android/test/InstrumentationTestRunnerTest.java
similarity index 84%
rename from tests/CoreTests/android/test/InstrumentationTestRunnerTest.java
rename to test-runner/tests/src/android/test/InstrumentationTestRunnerTest.java
index d9afd54..6db72ad 100644
--- a/tests/CoreTests/android/test/InstrumentationTestRunnerTest.java
+++ b/test-runner/tests/src/android/test/InstrumentationTestRunnerTest.java
@@ -109,6 +109,33 @@
assertTrue(mStubAndroidTestRunner.isRun());
}
+ /**
+ * Test that the -e {@link InstrumentationTestRunner.ARGUMENT_ANNOTATION} parameter properly
+ * selects tests.
+ */
+ public void testAnnotationParameter() throws Exception {
+ String expectedTestClassName = AnnotationTest.class.getName();
+ Bundle args = new Bundle();
+ args.putString(InstrumentationTestRunner.ARGUMENT_TEST_CLASS, expectedTestClassName);
+ args.putString(InstrumentationTestRunner.ARGUMENT_ANNOTATION, FlakyTest.class.getName());
+ mInstrumentationTestRunner.onCreate(args);
+ assertTestRunnerCalledWithExpectedParameters(expectedTestClassName, "testAnnotated");
+ }
+
+ /**
+ * Test that the -e {@link InstrumentationTestRunner.ARGUMENT_NOT_ANNOTATION} parameter
+ * properly excludes tests.
+ */
+ public void testNotAnnotationParameter() throws Exception {
+ String expectedTestClassName = AnnotationTest.class.getName();
+ Bundle args = new Bundle();
+ args.putString(InstrumentationTestRunner.ARGUMENT_TEST_CLASS, expectedTestClassName);
+ args.putString(InstrumentationTestRunner.ARGUMENT_NOT_ANNOTATION,
+ FlakyTest.class.getName());
+ mInstrumentationTestRunner.onCreate(args);
+ assertTestRunnerCalledWithExpectedParameters(expectedTestClassName, "testNotAnnotated");
+ }
+
private void assertContentsInOrder(List<TestDescriptor> actual, TestDescriptor... source) {
TestDescriptor[] clonedSource = source.clone();
assertEquals("Unexpected number of items.", clonedSource.length, actual.size());
@@ -269,4 +296,17 @@
}
}
+
+ /**
+ * Annotated test used for validation.
+ */
+ public static class AnnotationTest extends TestCase {
+
+ public void testNotAnnotated() throws Exception {
+ }
+
+ @FlakyTest
+ public void testAnnotated() throws Exception {
+ }
+ }
}
diff --git a/tests/CoreTests/android/test/StubTestBrowserActivity.java b/test-runner/tests/src/android/test/StubTestBrowserActivity.java
similarity index 100%
rename from tests/CoreTests/android/test/StubTestBrowserActivity.java
rename to test-runner/tests/src/android/test/StubTestBrowserActivity.java
diff --git a/tests/CoreTests/android/test/TestBrowserActivityTest.java b/test-runner/tests/src/android/test/TestBrowserActivityTest.java
similarity index 96%
rename from tests/CoreTests/android/test/TestBrowserActivityTest.java
rename to test-runner/tests/src/android/test/TestBrowserActivityTest.java
index 6afbe37..355409e 100644
--- a/tests/CoreTests/android/test/TestBrowserActivityTest.java
+++ b/test-runner/tests/src/android/test/TestBrowserActivityTest.java
@@ -215,19 +215,24 @@
}
private TestBrowserActivity createActivity() throws RemoteException {
- return launchActivity("android.test", StubTestBrowserActivity.class, null);
+ return launchActivity(getAndroidPackageName(), StubTestBrowserActivity.class, null);
}
private Intent createIntent(TestSuite testSuite) {
Intent intent = new Intent(Intent.ACTION_RUN);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
String className = StubTestBrowserActivity.class.getName();
- String packageName = className.substring(0, className.lastIndexOf("."));
+ String packageName = getAndroidPackageName();
intent.setClassName(packageName, className);
intent.setData(Uri.parse(testSuite.getName()));
return intent;
}
+ private String getAndroidPackageName() {
+ String packageName = getInstrumentation().getTargetContext().getPackageName();
+ return packageName;
+ }
+
private TestBrowserActivity launchTestBrowserActivity(TestSuite testSuite)
throws RemoteException {
getInstrumentation().setInTouchMode(false);
diff --git a/tests/CoreTests/android/test/TestBrowserControllerImplTest.java b/test-runner/tests/src/android/test/TestBrowserControllerImplTest.java
similarity index 100%
rename from tests/CoreTests/android/test/TestBrowserControllerImplTest.java
rename to test-runner/tests/src/android/test/TestBrowserControllerImplTest.java
diff --git a/tests/CoreTests/android/test/TestBrowserTests.java b/test-runner/tests/src/android/test/TestBrowserTests.java
similarity index 100%
rename from tests/CoreTests/android/test/TestBrowserTests.java
rename to test-runner/tests/src/android/test/TestBrowserTests.java
diff --git a/tests/CoreTests/android/test/TestCaseUtilTest.java b/test-runner/tests/src/android/test/TestCaseUtilTest.java
similarity index 100%
rename from tests/CoreTests/android/test/TestCaseUtilTest.java
rename to test-runner/tests/src/android/test/TestCaseUtilTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/AssignableFromTest.java b/test-runner/tests/src/android/test/suitebuilder/AssignableFromTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/AssignableFromTest.java
rename to test-runner/tests/src/android/test/suitebuilder/AssignableFromTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/InstrumentationTestSuiteBuilderTest.java b/test-runner/tests/src/android/test/suitebuilder/InstrumentationTestSuiteBuilderTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/InstrumentationTestSuiteBuilderTest.java
rename to test-runner/tests/src/android/test/suitebuilder/InstrumentationTestSuiteBuilderTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/ListTestCaseNames.java b/test-runner/tests/src/android/test/suitebuilder/ListTestCaseNames.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/ListTestCaseNames.java
rename to test-runner/tests/src/android/test/suitebuilder/ListTestCaseNames.java
diff --git a/tests/CoreTests/android/test/suitebuilder/SmokeTestSuiteBuilderTest.java b/test-runner/tests/src/android/test/suitebuilder/SmokeTestSuiteBuilderTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/SmokeTestSuiteBuilderTest.java
rename to test-runner/tests/src/android/test/suitebuilder/SmokeTestSuiteBuilderTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/TestSuiteBuilderTest.java b/test-runner/tests/src/android/test/suitebuilder/TestSuiteBuilderTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/TestSuiteBuilderTest.java
rename to test-runner/tests/src/android/test/suitebuilder/TestSuiteBuilderTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/UnitTestSuiteBuilderTest.java b/test-runner/tests/src/android/test/suitebuilder/UnitTestSuiteBuilderTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/UnitTestSuiteBuilderTest.java
rename to test-runner/tests/src/android/test/suitebuilder/UnitTestSuiteBuilderTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/annotation/HasAnnotationTest.java b/test-runner/tests/src/android/test/suitebuilder/annotation/HasAnnotationTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/annotation/HasAnnotationTest.java
rename to test-runner/tests/src/android/test/suitebuilder/annotation/HasAnnotationTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/annotation/HasClassAnnotationTest.java b/test-runner/tests/src/android/test/suitebuilder/annotation/HasClassAnnotationTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/annotation/HasClassAnnotationTest.java
rename to test-runner/tests/src/android/test/suitebuilder/annotation/HasClassAnnotationTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/annotation/HasMethodAnnotationTest.java b/test-runner/tests/src/android/test/suitebuilder/annotation/HasMethodAnnotationTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/annotation/HasMethodAnnotationTest.java
rename to test-runner/tests/src/android/test/suitebuilder/annotation/HasMethodAnnotationTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/OuterTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/OuterTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/OuterTest.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/OuterTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/error/ErrorTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/error/ErrorTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/error/ErrorTest.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/error/ErrorTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/error/FailingTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/error/FailingTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/error/FailingTest.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/error/FailingTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/instrumentation/InstrumentationTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/instrumentation/InstrumentationTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/instrumentation/InstrumentationTest.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/instrumentation/InstrumentationTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/nested/Level1Test.java b/test-runner/tests/src/android/test/suitebuilder/examples/nested/Level1Test.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/nested/Level1Test.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/nested/Level1Test.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/nested/nested/Level2Test.java b/test-runner/tests/src/android/test/suitebuilder/examples/nested/nested/Level2Test.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/nested/nested/Level2Test.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/nested/nested/Level2Test.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/simple/AnotherSimpleTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/simple/AnotherSimpleTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/simple/AnotherSimpleTest.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/simple/AnotherSimpleTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/simple/SimpleTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/simple/SimpleTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/simple/SimpleTest.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/simple/SimpleTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/smoke/NonSmokeTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/smoke/NonSmokeTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/smoke/NonSmokeTest.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/smoke/NonSmokeTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/smoke/SmokeTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/smoke/SmokeTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/smoke/SmokeTest.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/smoke/SmokeTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/subclass/SubclassTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/subclass/SubclassTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/subclass/SubclassTest.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/subclass/SubclassTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/subclass/SuperclassTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/subclass/SuperclassTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/subclass/SuperclassTest.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/subclass/SuperclassTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/suppress/PartiallySuppressedTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/suppress/PartiallySuppressedTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/suppress/PartiallySuppressedTest.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/suppress/PartiallySuppressedTest.java
diff --git a/tests/CoreTests/android/test/suitebuilder/examples/suppress/SuppressedTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/suppress/SuppressedTest.java
similarity index 100%
rename from tests/CoreTests/android/test/suitebuilder/examples/suppress/SuppressedTest.java
rename to test-runner/tests/src/android/test/suitebuilder/examples/suppress/SuppressedTest.java
diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml
index 28f1e73..e06c3a8 100644
--- a/tests/AndroidTests/AndroidManifest.xml
+++ b/tests/AndroidTests/AndroidManifest.xml
@@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.DELETE_PACKAGES" />
+ <uses-permission android:name="android.permission.MOVE_PACKAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.ASEC_ACCESS" />
<uses-permission android:name="android.permission.ASEC_CREATE" />
diff --git a/tests/AndroidTests/res/raw/install_loc_auto b/tests/AndroidTests/res/raw/install_loc_auto
index 60dda18..d5d2739 100644
--- a/tests/AndroidTests/res/raw/install_loc_auto
+++ b/tests/AndroidTests/res/raw/install_loc_auto
Binary files differ
diff --git a/tests/AndroidTests/res/raw/install_loc_internal b/tests/AndroidTests/res/raw/install_loc_internal
index 1bc33ca..eb6279a 100644
--- a/tests/AndroidTests/res/raw/install_loc_internal
+++ b/tests/AndroidTests/res/raw/install_loc_internal
Binary files differ
diff --git a/tests/AndroidTests/res/raw/install_loc_sdcard b/tests/AndroidTests/res/raw/install_loc_sdcard
index 6604e35..c774989 100644
--- a/tests/AndroidTests/res/raw/install_loc_sdcard
+++ b/tests/AndroidTests/res/raw/install_loc_sdcard
Binary files differ
diff --git a/tests/AndroidTests/res/raw/install_loc_unspecified b/tests/AndroidTests/res/raw/install_loc_unspecified
index 88bbace..ab226c6 100644
--- a/tests/AndroidTests/res/raw/install_loc_unspecified
+++ b/tests/AndroidTests/res/raw/install_loc_unspecified
Binary files differ
diff --git a/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java b/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java
index 7569d7a..5aaf13b 100755
--- a/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java
@@ -55,6 +55,7 @@
void failStr(String errMsg) {
Log.w(TAG, "errMsg="+errMsg);
}
+
void failStr(Exception e) {
Log.w(TAG, "e.getMessage="+e.getMessage());
Log.w(TAG, "e="+e);
@@ -67,17 +68,86 @@
cleanupContainers();
}
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
+ cleanupContainers();
+ }
+
private void cleanupContainers() throws RemoteException {
IMountService ms = getMs();
String[] containers = ms.getSecureContainerList();
for (int i = 0; i < containers.length; i++) {
if (containers[i].startsWith("com.android.unittests.AsecTests.")) {
- ms.destroySecureContainer(containers[i]);
+ ms.destroySecureContainer(containers[i], true);
}
}
}
+ private boolean containerExists(String localId) throws RemoteException {
+ IMountService ms = getMs();
+ String[] containers = ms.getSecureContainerList();
+ String fullId = "com.android.unittests.AsecTests." + localId;
+
+ for (int i = 0; i < containers.length; i++) {
+ if (containers[i].equals(fullId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int createContainer(String localId, int size, String key) throws RemoteException {
+ Assert.assertTrue(isMediaMounted());
+ String fullId = "com.android.unittests.AsecTests." + localId;
+
+ IMountService ms = getMs();
+ return ms.createSecureContainer(fullId, size, "fat", key, android.os.Process.myUid());
+ }
+
+ private int mountContainer(String localId, String key) throws RemoteException {
+ Assert.assertTrue(isMediaMounted());
+ String fullId = "com.android.unittests.AsecTests." + localId;
+
+ IMountService ms = getMs();
+ return ms.mountSecureContainer(fullId, key, android.os.Process.myUid());
+ }
+
+ private int renameContainer(String localId1, String localId2) throws RemoteException {
+ Assert.assertTrue(isMediaMounted());
+ String fullId1 = "com.android.unittests.AsecTests." + localId1;
+ String fullId2 = "com.android.unittests.AsecTests." + localId2;
+
+ IMountService ms = getMs();
+ return ms.renameSecureContainer(fullId1, fullId2);
+ }
+
+ private int unmountContainer(String localId, boolean force) throws RemoteException {
+ Assert.assertTrue(isMediaMounted());
+ String fullId = "com.android.unittests.AsecTests." + localId;
+
+ IMountService ms = getMs();
+ return ms.unmountSecureContainer(fullId, force);
+ }
+
+ private int destroyContainer(String localId, boolean force) throws RemoteException {
+ Assert.assertTrue(isMediaMounted());
+ String fullId = "com.android.unittests.AsecTests." + localId;
+
+ IMountService ms = getMs();
+ return ms.destroySecureContainer(fullId, force);
+ }
+
+ private boolean isContainerMounted(String localId) throws RemoteException {
+ Assert.assertTrue(isMediaMounted());
+ String fullId = "com.android.unittests.AsecTests." + localId;
+
+ IMountService ms = getMs();
+ return ms.isSecureContainerMounted(fullId);
+ }
+
private IMountService getMs() {
IBinder service = ServiceManager.getService("mount");
if (service != null) {
@@ -100,69 +170,200 @@
}
public void testCreateContainer() {
- Assert.assertTrue(isMediaMounted());
- IMountService ms = getMs();
try {
- int rc = ms.createSecureContainer("com.android.unittests.AsecTests.testCreateContainer", 4, "fat", "none", 1000);
- Assert.assertEquals(StorageResultCode.OperationSucceeded, rc);
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testCreateContainer", 4, "none"));
+ Assert.assertEquals(true, containerExists("testCreateContainer"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testCreateMinSizeContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testCreateContainer", 1, "none"));
+ Assert.assertEquals(true, containerExists("testCreateContainer"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testCreateZeroSizeContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationFailedInternalError,
+ createContainer("testCreateZeroContainer", 0, "none"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testCreateDuplicateContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testCreateDupContainer", 4, "none"));
+
+ Assert.assertEquals(StorageResultCode.OperationFailedInternalError,
+ createContainer("testCreateDupContainer", 4, "none"));
} catch (Exception e) {
failStr(e);
}
}
public void testDestroyContainer() {
- Assert.assertTrue(isMediaMounted());
- IMountService ms = getMs();
try {
- int rc = ms.createSecureContainer("com.android.unittests.AsecTests.testDestroyContainer", 4, "fat", "none", 1000);
- Assert.assertEquals(StorageResultCode.OperationSucceeded, rc);
- rc = ms.destroySecureContainer("com.android.unittests.AsecTests.testDestroyContainer");
- Assert.assertEquals(StorageResultCode.OperationSucceeded, rc);
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testDestroyContainer", 4, "none"));
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ destroyContainer("testDestroyContainer", false));
} catch (Exception e) {
failStr(e);
}
}
public void testMountContainer() {
- Assert.assertTrue(isMediaMounted());
- IMountService ms = getMs();
try {
- int rc = ms.createSecureContainer(
- "com.android.unittests.AsecTests.testMountContainer", 4, "fat", "none", 1000);
- Assert.assertEquals(StorageResultCode.OperationSucceeded, rc);
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testMountContainer", 4, "none"));
- rc = ms.unmountSecureContainer("com.android.unittests.AsecTests.testMountContainer");
- Assert.assertEquals(StorageResultCode.OperationSucceeded, rc);
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ unmountContainer("testMountContainer", false));
- rc = ms.mountSecureContainer("com.android.unittests.AsecTests.testMountContainer", "none", 1000);
- Assert.assertEquals(StorageResultCode.OperationSucceeded, rc);
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ mountContainer("testMountContainer", "none"));
} catch (Exception e) {
failStr(e);
}
}
public void testMountBadKey() {
- Assert.assertTrue(isMediaMounted());
- IMountService ms = getMs();
try {
- int rc = ms.createSecureContainer(
- "com.android.unittests.AsecTests.testMountBadKey", 4, "fat",
- "00000000000000000000000000000000", 1000);
- Assert.assertEquals(StorageResultCode.OperationSucceeded, rc);
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testMountBadKey", 4, "00000000000000000000000000000000"));
- rc = ms.unmountSecureContainer("com.android.unittests.AsecTests.testMountBadKey");
- Assert.assertEquals(StorageResultCode.OperationSucceeded, rc);
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ unmountContainer("testMountBadKey", false));
- rc = ms.mountSecureContainer(
- "com.android.unittests.AsecTests.testMountBadKey",
- "00000000000000000000000000000001", 1001);
- Assert.assertEquals(StorageResultCode.OperationFailedInternalError, rc);
+ Assert.assertEquals(StorageResultCode.OperationFailedInternalError,
+ mountContainer("testMountContainer", "000000000000000000000000000000001"));
- rc = ms.mountSecureContainer(
- "com.android.unittests.AsecTests.testMountBadKey", "none", 1001);
- Assert.assertEquals(StorageResultCode.OperationFailedInternalError, rc);
+ Assert.assertEquals(StorageResultCode.OperationFailedInternalError,
+ mountContainer("testMountContainer", "none"));
} catch (Exception e) {
failStr(e);
}
}
+
+ public void testUnmountBusyContainer() {
+ IMountService ms = getMs();
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testUnmountBusyContainer", 4, "none"));
+
+ String path = ms.getSecureContainerPath("com.android.unittests.AsecTests.testUnmountBusyContainer");
+
+ File f = new File(path, "reference");
+ FileOutputStream fos = new FileOutputStream(f);
+
+ Assert.assertEquals(StorageResultCode.OperationFailedStorageBusy,
+ unmountContainer("testUnmountBusyContainer", false));
+
+ fos.close();
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ unmountContainer("testUnmountBusyContainer", false));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testDestroyBusyContainer() {
+ IMountService ms = getMs();
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testDestroyBusyContainer", 4, "none"));
+
+ String path = ms.getSecureContainerPath("com.android.unittests.AsecTests.testDestroyBusyContainer");
+
+ File f = new File(path, "reference");
+ FileOutputStream fos = new FileOutputStream(f);
+
+ Assert.assertEquals(StorageResultCode.OperationFailedStorageBusy,
+ destroyContainer("testDestroyBusyContainer", false));
+
+ fos.close();
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ destroyContainer("testDestroyBusyContainer", false));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testRenameContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testRenameContainer.1", 4, "none"));
+
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ unmountContainer("testRenameContainer.1", false));
+
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ renameContainer("testRenameContainer.1", "testRenameContainer.2"));
+
+ Assert.assertEquals(false, containerExists("testRenameContainer.1"));
+ Assert.assertEquals(true, containerExists("testRenameContainer.2"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testRenameSrcMountedContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testRenameContainer.1", 4, "none"));
+
+ Assert.assertEquals(StorageResultCode.OperationFailedStorageMounted,
+ renameContainer("testRenameContainer.1", "testRenameContainer.2"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testRenameDstMountedContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testRenameContainer.1", 4, "none"));
+
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ unmountContainer("testRenameContainer.1", false));
+
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testRenameContainer.2", 4, "none"));
+
+ Assert.assertEquals(StorageResultCode.OperationFailedStorageMounted,
+ renameContainer("testRenameContainer.1", "testRenameContainer.2"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testIsContainerMountedAfterRename() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testRenameContainer.1", 4, "none"));
+
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ unmountContainer("testRenameContainer.1", false));
+
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ renameContainer("testRenameContainer.1", "testRenameContainer.2"));
+
+ Assert.assertEquals(false, containerExists("testRenameContainer.1"));
+ Assert.assertEquals(true, containerExists("testRenameContainer.2"));
+ // Check if isContainerMounted returns valid value
+ Assert.assertEquals(true, isContainerMounted("testRenameContainer.2"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
index 8f4d0a1..5e3895a 100755
--- a/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
@@ -37,6 +37,7 @@
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageMoveObserver;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
@@ -56,17 +57,25 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.storage.IMountService;
+import android.os.storage.IMountServiceListener;
+import android.os.storage.StorageEventListener;
+import android.os.storage.StorageManager;
import android.os.storage.StorageResultCode;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StatFs;
import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
public class PackageManagerTests extends AndroidTestCase {
private static final boolean localLOGV = true;
public static final String TAG="PackageManagerTests";
public final long MAX_WAIT_TIME=120*1000;
public final long WAIT_TIME_INCR=20*1000;
+ private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec";
+ private static final int APP_INSTALL_AUTO = 0;
+ private static final int APP_INSTALL_DEVICE = 1;
+ private static final int APP_INSTALL_SDCARD = 2;
void failStr(String errMsg) {
Log.w(TAG, "errMsg="+errMsg);
@@ -159,6 +168,7 @@
PackageInstallObserver observer = new PackageInstallObserver();
final boolean received = false;
mContext.registerReceiver(receiver, receiver.filter);
+ final boolean DEBUG = true;
try {
// Wait on observer
synchronized(observer) {
@@ -173,6 +183,7 @@
throw new Exception("Timed out waiting for packageInstalled callback");
}
if (observer.returnCode != PackageManager.INSTALL_SUCCEEDED) {
+ Log.i(TAG, "Failed to install with error code = " + observer.returnCode);
return false;
}
// Verify we received the broadcast
@@ -237,11 +248,50 @@
File sourceFile = new File(archiveFilePath);
DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
- return packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0);
+ PackageParser.Package pkg = packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0);
+ packageParser = null;
+ return pkg;
}
-
- private void assertInstall(String pkgName, int flags) {
+ private boolean getInstallLoc(int flags, int expInstallLocation) {
+ // Flags explicitly over ride everything else.
+ if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0 ) {
+ return false;
+ } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0 ) {
+ return true;
+ }
+ // Manifest option takes precedence next
+ if (expInstallLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
+ return true;
+ }
+ // TODO Out of memory checks here.
+ boolean checkSd = false;
+ int setLoc = 0;
try {
+ setLoc = Settings.System.getInt(mContext.getContentResolver(), Settings.System.SET_INSTALL_LOCATION);
+ } catch (SettingNotFoundException e) {
+ failStr(e);
+ }
+ if (setLoc == 1) {
+ int userPref = APP_INSTALL_AUTO;
+ try {
+ userPref = Settings.System.getInt(mContext.getContentResolver(), Settings.System.DEFAULT_INSTALL_LOCATION);
+ } catch (SettingNotFoundException e) {
+ failStr(e);
+ }
+ if (userPref == APP_INSTALL_DEVICE) {
+ checkSd = false;
+ } else if (userPref == APP_INSTALL_SDCARD) {
+ checkSd = true;
+ } else if (userPref == APP_INSTALL_AUTO) {
+ // Might be determined dynamically. TODO fix this
+ checkSd = false;
+ }
+ }
+ return checkSd;
+ }
+ private void assertInstall(PackageParser.Package pkg, int flags, int expInstallLocation) {
+ try {
+ String pkgName = pkg.packageName;
ApplicationInfo info = getPm().getApplicationInfo(pkgName, 0);
assertNotNull(info);
assertEquals(pkgName, info.packageName);
@@ -259,15 +309,14 @@
assertEquals(publicSrcPath, appInstallPath);
} else {
assertFalse((info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0);
- if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) {
- assertTrue((info.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0);
- // Hardcoded for now
- assertTrue(srcPath.startsWith("/asec"));
- assertTrue(publicSrcPath.startsWith("/asec"));
- } else {
+ if (!getInstallLoc(flags, expInstallLocation)) {
assertEquals(srcPath, appInstallPath);
assertEquals(publicSrcPath, appInstallPath);
assertFalse((info.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0);
+ } else {
+ assertTrue((info.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0);
+ assertTrue(srcPath.startsWith(SECURE_CONTAINERS_PREFIX));
+ assertTrue(publicSrcPath.startsWith(SECURE_CONTAINERS_PREFIX));
}
}
} catch (NameNotFoundException e) {
@@ -295,26 +344,48 @@
private InstallParams sampleInstallFromRawResource(int flags, boolean cleanUp) {
return installFromRawResource("install.apk", R.raw.install, flags, cleanUp,
- false, -1);
+ false, -1, PackageInfo.INSTALL_LOCATION_AUTO);
}
+
+ public void clearSecureContainersForPkg(String pkgName) {
+ IMountService ms = getMs();
+ try {
+ String list[] = ms.getSecureContainerList();
+ if (list != null) {
+ for (String cid : list) {
+ boolean delete = false;
+ // STOPSHIP issues with rename should be fixed.
+ if (cid.contains(pkgName) ||
+ cid.contains("smdltmp")) {
+ Log.i(TAG, "Destroying container " + cid);
+ ms.destroySecureContainer(cid, true);
+ }
+ }
+ }
+ } catch (RemoteException e) {}
+ }
+
/*
* Utility function that reads a apk bundled as a raw resource
* copies it into own data directory and invokes
* PackageManager api to install it.
*/
private InstallParams installFromRawResource(String outFileName,
- int rawResId, int flags, boolean cleanUp, boolean fail, int result) {
+ int rawResId, int flags, boolean cleanUp, boolean fail, int result,
+ int expInstallLocation) {
File filesDir = mContext.getFilesDir();
File outFile = new File(filesDir, outFileName);
Uri packageURI = getInstallablePackage(rawResId, outFile);
PackageParser.Package pkg = parsePackage(packageURI);
assertNotNull(pkg);
InstallParams ip = null;
+ // Make sure the package doesn't exist
+ getPm().deletePackage(pkg.packageName, null, 0);
+ // Clean up the containers as well
+ clearSecureContainersForPkg(pkg.packageName);
try {
try {
if (fail) {
- // Make sure it doesn't exist
- getPm().deletePackage(pkg.packageName, null, 0);
assertTrue(invokeInstallPackageFail(packageURI, flags,
pkg.packageName, result));
assertNotInstalled(pkg.packageName);
@@ -323,7 +394,7 @@
assertTrue(invokeInstallPackage(packageURI, flags,
pkg.packageName, receiver));
// Verify installed information
- assertInstall(pkg.packageName, flags);
+ assertInstall(pkg, flags, expInstallLocation);
ip = new InstallParams(pkg, outFileName, packageURI);
}
} catch (Exception e) {
@@ -332,7 +403,7 @@
return ip;
} finally {
if (cleanUp) {
- cleanUpInstall(ip);
+ //cleanUpInstall(ip);
}
}
}
@@ -415,6 +486,7 @@
public void replaceFromRawResource(int flags) {
InstallParams ip = sampleInstallFromRawResource(flags, false);
boolean replace = ((flags & PackageManager.INSTALL_REPLACE_EXISTING) != 0);
+ Log.i(TAG, "replace=" + replace);
GenericReceiver receiver;
if (replace) {
receiver = new ReplaceReceiver(ip.pkg.packageName);
@@ -427,7 +499,7 @@
assertEquals(invokeInstallPackage(ip.packageURI, flags,
ip.pkg.packageName, receiver), replace);
if (replace) {
- assertInstall(ip.pkg.packageName, flags);
+ assertInstall(ip.pkg, flags, ip.pkg.installLocation);
}
} catch (Exception e) {
failStr("Failed with exception : " + e);
@@ -710,51 +782,73 @@
}
}
+ class StorageListener extends StorageEventListener {
+ String oldState;
+ String newState;
+ String path;
+ private boolean doneFlag = false;
+ @Override
+ public void onStorageStateChanged(String path, String oldState, String newState) {
+ if (localLOGV) Log.i(TAG, "Storage state changed from " + oldState + " to " + newState);
+ synchronized (this) {
+ this.oldState = oldState;
+ this.newState = newState;
+ this.path = path;
+ doneFlag = true;
+ notifyAll();
+ }
+ }
+
+ public boolean isDone() {
+ return doneFlag;
+ }
+ }
+
private boolean unmountMedia() {
if (!getMediaState()) {
return true;
}
+ String path = Environment.getExternalStorageDirectory().toString();
+ StorageListener observer = new StorageListener();
+ StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
+ sm.registerListener(observer);
try {
- String mPath = Environment.getExternalStorageDirectory().toString();
- int ret = getMs().unmountVolume(mPath);
- return ret == StorageResultCode.OperationSucceeded;
- } catch (RemoteException e) {
- return true;
+ // Wait on observer
+ synchronized(observer) {
+ getMs().unmountVolume(path, false);
+ long waitTime = 0;
+ while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("Timed out waiting for packageInstalled callback");
+ }
+ return true;
+ }
+ } catch (Exception e) {
+ return false;
+ } finally {
+ sm.unregisterListener(observer);
}
}
- /*
- * Install package on sdcard. Unmount and then mount the media.
- * (Use PackageManagerService private api for now)
- * Make sure the installed package is available.
- * STOPSHIP will uncomment when MountService api's to mount/unmount
- * are made asynchronous.
- */
- public void xxxtestMountSdNormalInternal() {
- assertTrue(mountFromRawResource());
- }
-
private boolean mountFromRawResource() {
// Install pkg on sdcard
- InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_EXTERNAL |
- PackageManager.INSTALL_REPLACE_EXISTING, false);
+ InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_EXTERNAL, false);
if (localLOGV) Log.i(TAG, "Installed pkg on sdcard");
boolean origState = getMediaState();
+ boolean registeredReceiver = false;
SdMountReceiver receiver = new SdMountReceiver(new String[]{ip.pkg.packageName});
try {
if (localLOGV) Log.i(TAG, "Unmounting media");
// Unmount media
assertTrue(unmountMedia());
if (localLOGV) Log.i(TAG, "Unmounted media");
- try {
- if (localLOGV) Log.i(TAG, "Sleeping for 10 second");
- Thread.sleep(10*1000);
- } catch (InterruptedException e) {
- failStr(e);
- }
// Register receiver here
PackageManager pm = getPm();
mContext.registerReceiver(receiver, receiver.filter);
+ registeredReceiver = true;
// Wait on receiver
synchronized (receiver) {
@@ -779,7 +873,7 @@
failStr(e);
return false;
} finally {
- mContext.unregisterReceiver(receiver);
+ if (registeredReceiver) mContext.unregisterReceiver(receiver);
// Restore original media state
if (origState) {
mountMedia();
@@ -791,6 +885,17 @@
}
}
+ /*
+ * Install package on sdcard. Unmount and then mount the media.
+ * (Use PackageManagerService private api for now)
+ * Make sure the installed package is available.
+ * STOPSHIP will uncomment when MountService api's to mount/unmount
+ * are made asynchronous.
+ */
+ public void xxxtestMountSdNormalInternal() {
+ assertTrue(mountFromRawResource());
+ }
+
void cleanUpInstall(InstallParams ip) {
if (ip == null) {
return;
@@ -806,29 +911,206 @@
public void testManifestInstallLocationInternal() {
installFromRawResource("install.apk", R.raw.install_loc_internal,
- 0, true, false, -1);
+ 0, true, false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
}
public void testManifestInstallLocationSdcard() {
installFromRawResource("install.apk", R.raw.install_loc_sdcard,
- PackageManager.INSTALL_EXTERNAL, true, false, -1);
+ 0, true, false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
}
public void testManifestInstallLocationAuto() {
installFromRawResource("install.apk", R.raw.install_loc_auto,
- 0, true, false, -1);
+ 0, true, false, -1, PackageInfo.INSTALL_LOCATION_AUTO);
}
public void testManifestInstallLocationUnspecified() {
installFromRawResource("install.apk", R.raw.install_loc_unspecified,
- 0, true, false, -1);
+ 0, true, false, -1, PackageInfo.INSTALL_LOCATION_AUTO);
}
public void testManifestInstallLocationFwdLockedSdcard() {
installFromRawResource("install.apk", R.raw.install_loc_sdcard,
- PackageManager.INSTALL_FORWARD_LOCK |
- PackageManager.INSTALL_EXTERNAL, true, true,
- PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION);
+ PackageManager.INSTALL_FORWARD_LOCK, true, true,
+ PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+ PackageInfo.INSTALL_LOCATION_AUTO);
+ }
+
+ public void xxxtestClearAllSecureContainers() {
+ IMountService ms = getMs();
+ try {
+ String list[] = ms.getSecureContainerList();
+ if (list != null) {
+ for (String cid : list) {
+ Log.i(TAG, "Destroying container " + cid);
+ ms.destroySecureContainer(cid, false);
+ }
+ }
+ } catch (RemoteException e) {}
+ }
+
+ class MoveReceiver extends GenericReceiver {
+ String pkgName;
+ final static int INVALID = -1;
+ final static int REMOVED = 1;
+ final static int ADDED = 2;
+ int removed = INVALID;
+
+ MoveReceiver(String pkgName) {
+ this.pkgName = pkgName;
+ filter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ super.setFilter(filter);
+ }
+
+ public boolean notifyNow(Intent intent) {
+ String action = intent.getAction();
+ Log.i(TAG, "MoveReceiver::" + action);
+ if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ String[] list = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ if (list != null) {
+ for (String pkg : list) {
+ if (pkg.equals(pkgName)) {
+ removed = REMOVED;
+ break;
+ }
+ }
+ }
+ removed = REMOVED;
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+ if (removed != REMOVED) {
+ return false;
+ }
+ String[] list = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ if (list != null) {
+ for (String pkg : list) {
+ if (pkg.equals(pkgName)) {
+ removed = ADDED;
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ private class PackageMoveObserver extends IPackageMoveObserver.Stub {
+ public int returnCode;
+ private boolean doneFlag = false;
+
+ public void packageMoved(String packageName, int returnCode) {
+ synchronized(this) {
+ this.returnCode = returnCode;
+ doneFlag = true;
+ notifyAll();
+ }
+ }
+
+ public boolean isDone() {
+ return doneFlag;
+ }
+ }
+
+ public boolean invokeMovePackage(String pkgName, int flags,
+ GenericReceiver receiver) throws Exception {
+ PackageMoveObserver observer = new PackageMoveObserver();
+ final boolean received = false;
+ mContext.registerReceiver(receiver, receiver.filter);
+ try {
+ // Wait on observer
+ synchronized(observer) {
+ synchronized (receiver) {
+ getPm().movePackage(pkgName, observer, flags);
+ long waitTime = 0;
+ while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("Timed out waiting for pkgmove callback");
+ }
+ if (observer.returnCode != PackageManager.MOVE_SUCCEEDED) {
+ return false;
+ }
+ // Verify we received the broadcast
+ waitTime = 0;
+ while((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ receiver.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!receiver.isDone()) {
+ throw new Exception("Timed out waiting for MOVE notifications");
+ }
+ return receiver.received;
+ }
+ }
+ } finally {
+ mContext.unregisterReceiver(receiver);
+ }
+ }
+
+ /*
+ * Utility function that reads a apk bundled as a raw resource
+ * copies it into own data directory and invokes
+ * PackageManager api to install first and then replace it
+ * again.
+ */
+ public void moveFromRawResource(int installFlags, int moveFlags,
+ int expRetCode) {
+ // Install first
+ InstallParams ip = sampleInstallFromRawResource(installFlags, false);
+ ApplicationInfo oldAppInfo = null;
+ try {
+ oldAppInfo = getPm().getApplicationInfo(ip.pkg.packageName, 0);
+ } catch (NameNotFoundException e) {
+ failStr("Pkg hasnt been installed correctly");
+ }
+
+ // Create receiver based on expRetCode
+ MoveReceiver receiver = new MoveReceiver(ip.pkg.packageName);
+ try {
+ boolean retCode = invokeMovePackage(ip.pkg.packageName, moveFlags,
+ receiver);
+ if (expRetCode == PackageManager.MOVE_SUCCEEDED) {
+ assertTrue(retCode);
+ ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.packageName, 0);
+ assertNotNull(info);
+ if ((moveFlags & PackageManager.MOVE_INTERNAL) != 0) {
+ assertTrue((info.flags & ApplicationInfo.FLAG_ON_SDCARD) == 0);
+ } else if ((moveFlags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0){
+ assertTrue((info.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0);
+ }
+ } else {
+ assertFalse(retCode);
+ ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.packageName, 0);
+ assertNotNull(info);
+ assertEquals(oldAppInfo.flags, info.flags);
+ }
+ } catch (Exception e) {
+ failStr("Failed with exception : " + e);
+ } finally {
+ cleanUpInstall(ip);
+ }
+ }
+
+ public void testMoveAppInternalToExternal() {
+ moveFromRawResource(0, PackageManager.MOVE_EXTERNAL_MEDIA,
+ PackageManager.MOVE_SUCCEEDED);
+ }
+
+ public void testMoveAppInternalToInternal() {
+ moveFromRawResource(0, PackageManager.MOVE_INTERNAL,
+ PackageManager.MOVE_FAILED_INVALID_LOCATION);
+ }
+
+ public void testMoveAppExternalToExternal() {
+ moveFromRawResource(PackageManager.INSTALL_EXTERNAL, PackageManager.MOVE_EXTERNAL_MEDIA,
+ PackageManager.MOVE_FAILED_INVALID_LOCATION);
+ }
+ public void testMoveAppExternalToInternal() {
+ moveFromRawResource(PackageManager.INSTALL_EXTERNAL, PackageManager.MOVE_INTERNAL,
+ PackageManager.MOVE_SUCCEEDED);
}
/*
* TODO's
diff --git a/tests/CoreTests/android/AndroidManifest.xml b/tests/CoreTests/android/AndroidManifest.xml
index 98cc9e5..f02673c 100644
--- a/tests/CoreTests/android/AndroidManifest.xml
+++ b/tests/CoreTests/android/AndroidManifest.xml
@@ -34,19 +34,6 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="StubTestBrowserActivity" android:label="Stubbed Test Browser">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.FOR_TESTS_ONLY"/>
- </intent-filter>
- </activity>
-
- <activity android:name="android.test.TestBrowserTests" android:label="Test Browser Tests">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.UNIT_TEST"/>
- </intent-filter>
- </activity>
</application>
<instrumentation
diff --git a/tests/CoreTests/android/core/MiscRegressionTest.java b/tests/CoreTests/android/core/MiscRegressionTest.java
index 8fe064c..8281db0 100644
--- a/tests/CoreTests/android/core/MiscRegressionTest.java
+++ b/tests/CoreTests/android/core/MiscRegressionTest.java
@@ -110,6 +110,16 @@
Logger.global.finest("This has logging Level.FINEST, should become VERBOSE");
}
+ // Regression test for Issue 5697:
+ // getContextClassLoader returns a non-application classloader
+ // http://code.google.com/p/android/issues/detail?id=5697
+ //
+ @MediumTest
+ public void testJavaContextClassLoader() throws Exception {
+ Assert.assertNotNull("Must hava a Java context ClassLoader",
+ Thread.currentThread().getContextClassLoader());
+ }
+
// Regression test for #1045939: Different output for Method.toString()
@SmallTest
public void testMethodToString() {
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java b/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java
index 50451e7..ce1bf8d 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java
@@ -47,6 +47,7 @@
private static final int EVENT_RELEASE_TOUCH_POINT = 16;
private static final int EVENT_CLEAR_TOUCH_POINTS = 17;
private static final int EVENT_CANCEL_TOUCH_POINT = 18;
+ private static final int EVENT_SET_TOUCH_MODIFIER = 19;
private static final int LAYOUT_CLEAR_LIST = 20;
private static final int LAYOUT_DISPLAY = 21;
@@ -145,6 +146,13 @@
mEventSender.updateTouchPoint(id, x, y);
break;
+ case EVENT_SET_TOUCH_MODIFIER:
+ Bundle modifierArgs = (Bundle) msg.obj;
+ String modifier = modifierArgs.getString("modifier");
+ boolean enabled = modifierArgs.getBoolean("enabled");
+ mEventSender.setTouchModifier(modifier, enabled);
+ break;
+
case EVENT_RELEASE_TOUCH_POINT:
mEventSender.releaseTouchPoint(msg.arg1);
break;
@@ -320,7 +328,10 @@
}
public void setTouchModifier(String modifier, boolean enabled) {
- // TODO(benm): Android doesn't support key modifiers on touch events yet.
+ Bundle map = new Bundle();
+ map.putString("modifier", modifier);
+ map.putBoolean("enabled", enabled);
+ obtainMessage(EVENT_SET_TOUCH_MODIFIER, map).sendToTarget();
}
public void touchMove() {
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
index 4d51356..6ceb0f9 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
@@ -16,59 +16,47 @@
package com.android.dumprendertree;
-import java.util.HashSet;
-import java.util.Hashtable;
+import java.util.Vector;
import android.util.*;
public class FileFilter {
+ private static final String LOGTAG = "FileFilter";
+
public static boolean ignoreTest(String file) {
- // treat files like directories for the time being.
- for (int i = 0; i < ignoreTestList.length; i ++) {
- if (file.endsWith(ignoreTestList[i])) {
- Log.e("FileFilter", "File path in IgnoreTest: " + file);
- return true;
- }
- }
- for (int i = 0; i < ignoreTestDirs.length; i++) {
- if (file.endsWith(ignoreTestDirs[i])) {
- Log.e("FileFilter", "File path in ignore list: " + file);
- return true;
- }
- }
-
- return false;
- }
-
- public static boolean ignoreResults(String file) {
- int index = file.indexOf("fast");
- if (index != -1) {
- String sub = file.substring(index);
- if (ignoreResultList.contains(sub))
+ // treat files like directories for the time being.
+ for (int i = 0; i < ignoreTestList.length; i ++) {
+ if (file.endsWith(ignoreTestList[i])) {
+ Log.v(LOGTAG, "File path in list of ignored tests: " + file);
return true;
+ }
+ }
+ for (int i = 0; i < ignoreTestDirs.length; i++) {
+ if (file.endsWith(ignoreTestDirs[i])) {
+ Log.v(LOGTAG, "File path in list of ignored directories: " + file);
+ return true;
+ }
+ }
+ // We should run tests for which the expected result is wrong, as there is
+ // value in checking that they don't cause crashes.
+ // TODO: Run these tests but ignore the result.
+ return ignoreResults(file);
+ }
+
+ public static boolean ignoreResults(String file) {
+ for (int i = 0; i < ignoreResultList.size(); i++) {
+ if (file.endsWith(ignoreResultList.get(i))) {
+ Log.v(LOGTAG, "File path in list of ignored results: " + file);
+ return true;
+ }
}
return false;
+ }
- }
+ final static Vector<String> ignoreResultList = new Vector<String>();
- public static String isKnownBug(String file) {
- int index = file.indexOf("fast");
- if (index != -1) {
- String sub = file.substring(index);
- // Log.e("FileFilter", "Looking for:"+sub);
- if (bugList.containsKey(sub))
- return bugList.get(sub);
- }
- return null;
- }
-
- final static HashSet<String> ignoreResultList = new HashSet<String>();
- final static Hashtable<String, String> bugList =
- new Hashtable<String, String>();
-
static {
- fillIgnoreResultSet();
- fillBugTable();
+ fillIgnoreResultList();
}
static final String[] ignoreTestDirs = {
@@ -84,38 +72,59 @@
static final String [] ignoreTestList = {
"editing/selection/move-left-right.html",
- "fast/events/touch/basic-multi-touch-events.html", // We do not support multi touch events.
"fast/js/regexp-charclass-crash.html", // RegExp is too large, causing OOM
"fast/regex/test1.html", // RegExp is exponential
"fast/regex/slow.html", // RegExp is exponential
- "storage/domstorage/localstorage/iframe-events.html", // Expects test to be in LayoutTests
"storage/domstorage/localstorage/private-browsing-affects-storage.html", // No notion of private browsing.
- "storage/domstorage/sessionstorage/iframe-events.html", // Expects test to be in LayoutTests
"storage/domstorage/sessionstorage/private-browsing-affects-storage.html", // No notion of private browsing.
"storage/private-browsing-readonly.html", // No notion of private browsing.
};
- static void fillIgnoreResultSet() {
+ static void fillIgnoreResultList() {
+ // This first block of tests are for HTML5 features, for which Android
+ // should pass all tests. They are skipped only temporarily.
+ // TODO: Fix these failing tests and remove them from this list.
+ ignoreResultList.add("fast/dom/Geolocation/callback-exception.html"); // exception output incorrect with V8
+ ignoreResultList.add("http/tests/appcache/auth.html"); // file not found
+ ignoreResultList.add("http/tests/appcache/deferred-events.html"); // file not found
+ ignoreResultList.add("http/tests/appcache/deferred-events-delete-while-raising.html"); // file not found
+ ignoreResultList.add("http/tests/appcache/destroyed-frame.html"); // file not found
+ ignoreResultList.add("http/tests/appcache/detached-iframe.html"); // file not found
+ ignoreResultList.add("http/tests/appcache/different-scheme.html"); // file not found
+ ignoreResultList.add("http/tests/appcache/disabled.html"); // not found
+ ignoreResultList.add("http/tests/appcache/empty-manifest.html"); // flaky
+ ignoreResultList.add("http/tests/appcache/foreign-iframe-main.html"); // flaky - skips states
+ ignoreResultList.add("http/tests/appcache/local-content.html"); // text diff
+ ignoreResultList.add("http/tests/appcache/max-size.html"); // no layoutTestController.setAppCacheMaximumSize
+ ignoreResultList.add("http/tests/appcache/manifest-with-empty-file.html"); // flaky
+ ignoreResultList.add("http/tests/appcache/whitelist-wildcard.html"); // file not found
+ ignoreResultList.add("storage/database-lock-after-reload.html"); // failure
+ ignoreResultList.add("storage/domstorage/localstorage/string-conversion.html"); // false failure due to whitespace diff in output with V8
+ ignoreResultList.add("storage/domstorage/sessionstorage/string-conversion.html"); // false failure due to whitespace diff in output with V8
+ ignoreResultList.add("storage/statement-error-callback.html"); // expected line number diff in V8 only
+ ignoreResultList.add("storage/transaction-error-callback.html"); // expected line number diff in V8 only
+ ignoreResultList.add("storage/transaction-callback-exception-crash.html"); // expected line number diff in V8 only
+
ignoreResultList.add("fast/css/case-transform.html"); // will not fix #619707
- ignoreResultList.add("fast/css/computed-style.html"); // different platform defaults for font and different screen size
ignoreResultList.add("fast/dom/Element/offsetLeft-offsetTop-body-quirk.html"); // different screen size result in extra spaces in Apple compared to us
ignoreResultList.add("fast/dom/Window/Plug-ins.html"); // need test plugin
ignoreResultList.add("fast/dom/Window/window-properties.html"); // xslt and xpath elements missing from property list
ignoreResultList.add("fast/dom/Window/window-screen-properties.html"); // pixel depth
ignoreResultList.add("fast/dom/Window/window-xy-properties.html"); // requires eventSender.mouseDown(),mouseUp()
- ignoreResultList.add("fast/dom/character-index-for-point.html"); // requires textInputController.characterIndexForPoint
+ ignoreResultList.add("fast/dom/attribute-namespaces-get-set.html"); // http://b/733229
ignoreResultList.add("fast/dom/gc-9.html"); // requires xpath support
ignoreResultList.add("fast/dom/global-constructors.html"); // requires xslt and xpath support
ignoreResultList.add("fast/dom/object-embed-plugin-scripting.html"); // dynamic plugins not supported
ignoreResultList.add("fast/dom/tabindex-clamp.html"); // there is extra spacing in the file due to multiple input boxes fitting on one line on Apple, ours are wrapped. Space at line ends are stripped.
ignoreResultList.add("fast/events/anchor-image-scrolled-x-y.html"); // requires eventSender.mouseDown(),mouseUp()
+ ignoreResultList.add("fast/events/arrow-navigation.html"); // http://b/735233
ignoreResultList.add("fast/events/capture-on-target.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/dblclick-addEventListener.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/drag-in-frames.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/drag-outside-window.html"); // requires eventSender.mouseDown(),mouseUp()
- ignoreResultList.add("fast/events/event-sender-mouse-click.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/event-view-toString.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/frame-click-focus.html"); // requires eventSender.mouseDown(),mouseUp()
+ ignoreResultList.add("fast/events/frame-tab-focus.html"); // http://b/734308
ignoreResultList.add("fast/events/iframe-object-onload.html"); // there is extra spacing in the file due to multiple frame boxes fitting on one line on Apple, ours are wrapped. Space at line ends are stripped.
ignoreResultList.add("fast/events/input-image-scrolled-x-y.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/mouseclick-target-and-positioning.html"); // requires eventSender.mouseDown(),mouseUp()
@@ -123,70 +132,49 @@
ignoreResultList.add("fast/events/mouseover-mouseout2.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/mouseup-outside-button.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/mouseup-outside-document.html"); // requires eventSender.mouseDown(),mouseUp()
- ignoreResultList.add("fast/events/objc-event-api.html"); // eventSender.mouseDown(), mouseUp() and objc API missing
- ignoreResultList.add("fast/events/objc-keyboard-event-creation.html"); // eventSender.mouseDown(), mouseUp() and objc API missing
ignoreResultList.add("fast/events/onclick-list-marker.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/ondragenter.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/onload-webkit-before-webcore.html"); // missing space in textrun, ok as text is wrapped. ignore. #714933
+ ignoreResultList.add("fast/events/option-tab.html"); // http://b/734308
ignoreResultList.add("fast/events/window-events-bubble.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/window-events-bubble2.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/window-events-capture.html"); // requires eventSender.mouseDown(),mouseUp()
- ignoreResultList.add("fast/forms/attributed-strings.html"); // missing support for textInputController.makeAttributedString()
ignoreResultList.add("fast/forms/drag-into-textarea.html"); // requires eventSender.mouseDown(),mouseUp()
+ ignoreResultList.add("fast/forms/focus-control-to-page.html"); // http://b/716638
+ ignoreResultList.add("fast/forms/focus2.html"); // http://b/735111
ignoreResultList.add("fast/forms/form-data-encoding-2.html"); // charset convert. #516936 ignore, won't fix
ignoreResultList.add("fast/forms/form-data-encoding.html"); // charset convert. #516936 ignore, won't fix
ignoreResultList.add("fast/forms/input-appearance-maxlength.html"); // execCommand "insertText" not supported
ignoreResultList.add("fast/forms/input-select-on-click.html"); // requires eventSender.mouseDown(),mouseUp()
- ignoreResultList.add("fast/forms/input-truncate-newline.html"); // Copy&Paste commands not supported
ignoreResultList.add("fast/forms/listbox-onchange.html"); // requires eventSender.mouseDown(),mouseUp()
+ ignoreResultList.add("fast/forms/listbox-selection.html"); // http://b/735116
ignoreResultList.add("fast/forms/onselect-textarea.html"); // requires eventSender.mouseMoveTo, mouseDown & mouseUp and abs. position of mouse to select a word. ignore, won't fix #716583
ignoreResultList.add("fast/forms/onselect-textfield.html"); // requires eventSender.mouseMoveTo, mouseDown & mouseUp and abs. position of mouse to select a word. ignore, won't fix #716583
ignoreResultList.add("fast/forms/plaintext-mode-1.html"); // not implemented queryCommandEnabled:BackColor, Undo & Redo
ignoreResultList.add("fast/forms/search-cancel-button-mouseup.html"); // requires eventSender.mouseDown(),mouseUp()
+ ignoreResultList.add("fast/forms/search-event-delay.html"); // http://b/735120
ignoreResultList.add("fast/forms/select-empty-list.html"); // requires eventSender.mouseDown(),mouseUp()
+ ignoreResultList.add("fast/forms/select-type-ahead-non-latin.html"); // http://b/735244
ignoreResultList.add("fast/forms/selected-index-assert.html"); // not capturing the console messages
ignoreResultList.add("fast/forms/selection-functions.html"); // there is extra spacing as the text areas and input boxes fit next to each other on Apple, but are wrapped on our screen.
ignoreResultList.add("fast/forms/textarea-appearance-wrap.html"); // Our text areas are a little thinner than Apples. Also RTL test failes
- ignoreResultList.add("fast/forms/textarea-hard-linewrap.html"); // Our text areas are a little thinner than Apples
ignoreResultList.add("fast/forms/textarea-initial-caret-position.html"); // Text selection done differently on our platform. When a inputbox gets focus, the entire block is selected.
ignoreResultList.add("fast/forms/textarea-no-scroll-on-blur.html"); // Text selection done differently on our platform. When a inputbox gets focus, the entire block is selected.
ignoreResultList.add("fast/forms/textarea-paste-newline.html"); // Copy&Paste commands not supported
ignoreResultList.add("fast/forms/textarea-scrolled-endline-caret.html"); // requires eventSender.mouseDown(),mouseUp()
+ ignoreResultList.add("fast/frames/iframe-window-focus.html"); // http://b/735140
ignoreResultList.add("fast/frames/frameElement-widthheight.html"); // screen width&height are different
ignoreResultList.add("fast/frames/frame-js-url-clientWidth.html"); // screen width&height are different
+ ignoreResultList.add("fast/html/tab-order.html"); // http://b/719289
ignoreResultList.add("fast/js/navigator-mimeTypes-length.html"); // dynamic plugins not supported
+ ignoreResultList.add("fast/js/string-capitalization.html"); // http://b/516936
ignoreResultList.add("fast/loader/local-JavaScript-from-local.html"); // Requires LayoutTests to exist at /tmp/LayoutTests
ignoreResultList.add("fast/loader/local-iFrame-source-from-local.html"); // Requires LayoutTests to exist at /tmp/LayoutTests
ignoreResultList.add("fast/loader/opaque-base-url.html"); // extra spacing because iFrames rendered next to each other on Apple
+ ignoreResultList.add("fast/overflow/scroll-vertical-not-horizontal.html"); // http://b/735196
ignoreResultList.add("fast/parser/script-tag-with-trailing-slash.html"); // not capturing the console messages
ignoreResultList.add("fast/replaced/image-map.html"); // requires eventSender.mouseDown(),mouseUp()
- ignoreResultList.add("fast/text/attributed-substring-from-range.html"); // requires JS test API, textInputController
- ignoreResultList.add("fast/text/attributed-substring-from-range-001.html"); // requires JS test API, textInputController
ignoreResultList.add("fast/text/plain-text-line-breaks.html"); // extra spacing because iFrames rendered next to each other on Apple
}
- static void fillBugTable() {
- bugList.put("fast/forms/focus-control-to-page.html", "716638");
- bugList.put("fast/html/tab-order.html", "719289");
- bugList.put("fast/dom/attribute-namespaces-get-set.html", "733229");
- bugList.put("fast/dom/set-innerHTML.html", "733823");
- bugList.put("fast/dom/xmlhttprequest-get.html", "733846");
- bugList.put("fast/encoding/css-charset-default.html", "733856");
- bugList.put("fast/encoding/default-xhtml-encoding.html", "733882");
- bugList.put("fast/encoding/meta-in-xhtml.html", "733882");
- bugList.put("fast/events/frame-tab-focus.html", "734308");
- bugList.put("fast/events/option-tab.html", "734308");
- bugList.put("fast/forms/focus2.html", "735111");
- bugList.put("fast/forms/listbox-selection.html", "735116");
- bugList.put("fast/forms/search-event-delay.html", "735120");
- bugList.put("fast/frames/iframe-window-focus.html", "735140");
- bugList.put("fast/innerHTML/004.html", "733882");
- bugList.put("fast/js/string-capitalization.html", "516936");
- bugList.put("fast/js/string-concatenate-outofmemory.html","735152");
- bugList.put("fast/parser/external-entities.html", "735176");
- bugList.put("fast/overflow/scroll-vertical-not-horizontal.html", "735196");
- bugList.put("fast/events/arrow-navigation.html", "735233");
- bugList.put("fast/forms/select-type-ahead-non-latin.html", "735244");
- }
-
}
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FsUtils.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FsUtils.java
index 1a265e8..ef0c6c6 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/FsUtils.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FsUtils.java
@@ -54,6 +54,9 @@
String[] files = d.list();
for (int i = 0; i < files.length; i++) {
String s = dir + "/" + files[i];
+ if (s.endsWith("TEMPLATE.html")) {
+ continue;
+ }
if (FileFilter.ignoreTest(s)) {
Log.v(LOGTAG, " Ignoring: " + s);
continue;
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
index 8983612..634d683 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
@@ -339,14 +339,7 @@
this.mTestList = new Vector<String>();
// Read settings
- try {
- this.mTestPathPrefix =
- (new File(LAYOUT_TESTS_ROOT + runner.mTestPath)).getCanonicalPath();
- } catch (IOException e) {
- Log.e(LOGTAG, "Cannot find test path prefix: " + e.getMessage());
- return;
- }
-
+ this.mTestPathPrefix = (new File(LAYOUT_TESTS_ROOT + runner.mTestPath)).getAbsolutePath();
this.mRebaselineResults = runner.mRebaseline;
int timeout = runner.mTimeoutInMillis;
@@ -395,11 +388,7 @@
if (runner.mTestPath != null) {
test_path += runner.mTestPath;
}
- try {
- test_path = new File(test_path).getCanonicalPath();
- } catch (IOException e) {
- Log.e("LayoutTestsAutoTest", "Cannot get cannonical path " + e.getMessage());
- }
+ test_path = new File(test_path).getAbsolutePath();
Log.v("LayoutTestsAutoTest", " Test path : " + test_path);
return test_path;
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
index 2667520..02a7046 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
@@ -716,6 +716,7 @@
mCanOpenWindows = false;
mEventSender.resetMouse();
mEventSender.clearTouchPoints();
+ mEventSender.clearTouchMetaState();
mPageFinished = false;
mOneHundredPercentComplete = false;
mDumpWebKitData = false;
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java b/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java
index 996eaba..0c2347d 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java
@@ -207,7 +207,7 @@
tp.setDownTime(SystemClock.uptimeMillis());
MotionEvent event = MotionEvent.obtain(tp.downTime(), tp.downTime(),
- MotionEvent.ACTION_DOWN, tp.getX(), tp.getY(), 0);
+ MotionEvent.ACTION_DOWN, tp.getX(), tp.getY(), mTouchMetaState);
mWebView.onTouchEvent(event);
}
@@ -223,7 +223,7 @@
}
MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(),
- MotionEvent.ACTION_MOVE, tp.getX(), tp.getY(), 0);
+ MotionEvent.ACTION_MOVE, tp.getX(), tp.getY(), mTouchMetaState);
mWebView.onTouchEvent(event);
tp.setMoved(false);
@@ -237,7 +237,7 @@
}
MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(),
- MotionEvent.ACTION_UP, tp.getX(), tp.getY(), 0);
+ MotionEvent.ACTION_UP, tp.getX(), tp.getY(), mTouchMetaState);
mWebView.onTouchEvent(event);
if (tp.isReleased()) {
@@ -253,7 +253,7 @@
if (tp.cancelled()) {
MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(),
- MotionEvent.ACTION_CANCEL, tp.getX(), tp.getY(), 0);
+ MotionEvent.ACTION_CANCEL, tp.getX(), tp.getY(), mTouchMetaState);
mWebView.onTouchEvent(event);
}
}
@@ -285,8 +285,20 @@
}
public void setTouchModifier(String modifier, boolean enabled) {
- // TODO(benm): This needs implementing when Android supports sending key modifiers
- // in touch events.
+ int mask = 0;
+ if ("alt".equals(modifier.toLowerCase())) {
+ mask = KeyEvent.META_ALT_ON;
+ } else if ("shift".equals(modifier.toLowerCase())) {
+ mask = KeyEvent.META_SHIFT_ON;
+ } else if ("ctrl".equals(modifier.toLowerCase())) {
+ mask = KeyEvent.META_SYM_ON;
+ }
+
+ if (enabled) {
+ mTouchMetaState |= mask;
+ } else {
+ mTouchMetaState &= ~mask;
+ }
}
public void releaseTouchPoint(int id) {
@@ -302,6 +314,10 @@
mTouchPoints.clear();
}
+ public void clearTouchMetaState() {
+ mTouchMetaState = 0;
+ }
+
private int contentsToWindowX(int x) {
return (int) (x * mWebView.getScale()) - mWebView.getScrollX();
}
@@ -352,4 +368,5 @@
};
private Vector<TouchPoint> mTouchPoints;
+ private int mTouchMetaState;
}
diff --git a/tests/LocationTracker/Android.mk b/tests/LocationTracker/Android.mk
new file mode 100644
index 0000000..b142d22
--- /dev/null
+++ b/tests/LocationTracker/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := LocationTracker
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/LocationTracker/AndroidManifest.xml b/tests/LocationTracker/AndroidManifest.xml
new file mode 100644
index 0000000..dc7ea99
--- /dev/null
+++ b/tests/LocationTracker/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.locationtracker">
+
+ <!-- Permissions for the Location Service -->
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Permission for wifi -->
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+
+ <!-- give the location tracker ability to induce device insomnia -->
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+ <!-- Permission for SD card -->
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application android:label="@string/app_label">
+ <activity android:name="TrackerActivity" android:label="Location Tracker">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <service android:name=".TrackerService" />
+ <activity android:label="@string/settings_menu" android:name="SettingsActivity" />
+ <provider android:name=".data.TrackerProvider"
+ android:authorities="com.android.locationtracker" />
+ </application>
+
+</manifest>
diff --git a/tests/LocationTracker/res/layout/entrylist_item.xml b/tests/LocationTracker/res/layout/entrylist_item.xml
new file mode 100644
index 0000000..d2a8033
--- /dev/null
+++ b/tests/LocationTracker/res/layout/entrylist_item.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2006-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.
+ */
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/entrylist_item"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+/>
diff --git a/tests/LocationTracker/res/menu/menu.xml b/tests/LocationTracker/res/menu/menu.xml
new file mode 100644
index 0000000..05d13fd
--- /dev/null
+++ b/tests/LocationTracker/res/menu/menu.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+* 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.
+*/
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/start_service_menu"
+ android:title="@string/start_service_menu" />
+ <item android:id="@+id/stop_service_menu"
+ android:title="@string/stop_service_menu" />
+ <item android:id="@+id/settings_menu"
+ android:title="@string/settings_menu" />
+ <item android:id="@+id/export_sub_menu"
+ android:title="@string/export_sub_menu">
+ <menu>
+ <item android:id="@+id/export_kml"
+ android:title="@string/export_kml" />
+ <item android:id="@+id/export_csv"
+ android:title="@string/export_csv" />
+ </menu>
+ </item>
+ <item android:title="@string/clear_data"
+ android:id="@+id/clear_data_menu"/>
+</menu>
diff --git a/tests/LocationTracker/res/values/strings.xml b/tests/LocationTracker/res/values/strings.xml
new file mode 100644
index 0000000..ea6bf2d
--- /dev/null
+++ b/tests/LocationTracker/res/values/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* 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.
+*/
+-->
+
+<resources>
+ <string name="start_service_menu">Start Service</string>
+ <string name="stop_service_menu">Stop Service</string>
+ <string name="settings_menu">Settings</string>
+ <string name="update_preference">Update frequency</string>
+ <string name="title_mintime_preference">Minimum update time</string>
+ <string name="summary_mintime_preference">The suggested minimum time interval for location updates, in seconds</string>
+ <string name="dialog_title_mintime_preference">Minimum update time</string>
+ <string name="title_mindistance_preference">Minimum distance</string>
+ <string name="summary_mindistance_preference">Minimum distance interval for location updates, in meters</string>
+ <string name="dialog_title_mindistance_preference">Minimum distance</string>
+ <string name="provider_preferences">Location providers</string>
+ <string name="title_network_preference">Network location</string>
+ <string name="summary_network_preference">Listen for updates to network location (Wi-Fi/cellid)</string>
+ <string name="title_gps_preference">GPS location</string>
+ <string name="summary_gps_preference">Listen for updates to GPS location</string>
+ <string name="title_signal_preference">Signal strength</string>
+ <string name="summary_signal_preference">Listen for updates to signal strength</string>
+ <string name="advanced_preferences">Advanced</string>
+ <string name="title_advanced_log_preference">Location debug logging</string>
+ <string name="summary_advanced_preference">Logs detailed location data, only relevant for location/test engineers</string>
+ <string name="app_label">Location Tracker</string>
+ <string name="export_sub_menu">Export\u2026</string>
+ <string name="export_kml">Export As KML</string>
+ <string name="export_csv">Export As CSV</string>
+ <string name="clear_data">Clear data</string>
+ <string name="delete_confirm">All current tracking data will be deleted.</string>
+ <string name="confirm_title">Clear data</string>
+</resources>
diff --git a/tests/LocationTracker/res/xml/preferences.xml b/tests/LocationTracker/res/xml/preferences.xml
new file mode 100755
index 0000000..61d4817
--- /dev/null
+++ b/tests/LocationTracker/res/xml/preferences.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- The Location preferences UI -->
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <PreferenceCategory android:title="@string/update_preference">
+ <EditTextPreference android:key="mintime_preference"
+ android:defaultValue="0"
+ android:title="@string/title_mintime_preference"
+ android:summary="@string/summary_mintime_preference"
+ android:dialogTitle="@string/dialog_title_mintime_preference" />
+
+ <EditTextPreference android:key="mindistance_preference"
+ android:defaultValue="0"
+ android:title="@string/title_mindistance_preference"
+ android:summary="@string/summary_mindistance_preference"
+ android:dialogTitle="@string/dialog_title_mindistance_preference" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory android:title="@string/provider_preferences">
+
+ <CheckBoxPreference android:key="network_preference"
+ android:defaultValue="true"
+ android:title="@string/title_network_preference"
+ android:summary="@string/summary_network_preference" />
+
+ <CheckBoxPreference android:key="gps_preference"
+ android:defaultValue="true"
+ android:title="@string/title_gps_preference"
+ android:summary="@string/summary_gps_preference" />
+
+ <CheckBoxPreference android:key="signal_preference"
+ android:defaultValue="false"
+ android:title="@string/title_signal_preference"
+ android:summary="@string/summary_signal_preference" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory android:title="@string/advanced_preferences">
+
+ <CheckBoxPreference android:key="advanced_log_preference"
+ android:defaultValue="false"
+ android:title="@string/title_advanced_log_preference"
+ android:summary="@string/summary_advanced_preference" />
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java b/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java
new file mode 100755
index 0000000..cb77118
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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.locationtracker;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+/**
+ * Settings preference screen for location tracker
+ */
+public class SettingsActivity extends PreferenceActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.preferences);
+ }
+
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/TrackerActivity.java b/tests/LocationTracker/src/com/android/locationtracker/TrackerActivity.java
new file mode 100644
index 0000000..98d0a50
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/TrackerActivity.java
@@ -0,0 +1,226 @@
+/*
+ * 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.locationtracker;
+
+import com.android.locationtracker.data.DateUtils;
+import com.android.locationtracker.data.TrackerDataHelper;
+import com.android.locationtracker.data.TrackerListHelper;
+
+import android.app.AlertDialog;
+import android.app.ListActivity;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.database.Cursor;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Activity for location tracker service
+ *
+ * Contains facilities for starting and
+ * stopping location tracker service, as well as displaying the current location
+ * data
+ *
+ * Future enhancements:
+ * - export data as dB
+ * - enable/disable "start service" and "stop service" menu items as
+ * appropriate
+ */
+public class TrackerActivity extends ListActivity {
+
+ static final String LOG_TAG = "LocationTracker";
+
+ private TrackerListHelper mDataHelper;
+
+ /**
+ * Retrieves and displays the currently logged location data from file
+ *
+ * @param icicle
+ */
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mDataHelper = new TrackerListHelper(this);
+ mDataHelper.bindListUI(R.layout.entrylist_item);
+ }
+
+ /**
+ * Builds the menu
+ *
+ * @param menu - menu to add items to
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater menuInflater = getMenuInflater();
+ menuInflater.inflate(R.menu.menu, menu);
+ return true;
+ }
+
+ /**
+ * Handles menu item selection
+ *
+ * @param item - the selected menu item
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.start_service_menu: {
+ startService(new Intent(TrackerActivity.this,
+ TrackerService.class));
+ break;
+ }
+ case R.id.stop_service_menu: {
+ stopService(new Intent(TrackerActivity.this,
+ TrackerService.class));
+ break;
+ }
+ case R.id.settings_menu: {
+ launchSettings();
+ break;
+ }
+ case R.id.export_kml: {
+ exportKML();
+ break;
+ }
+ case R.id.export_csv: {
+ exportCSV();
+ break;
+ }
+ case R.id.clear_data_menu: {
+ clearData();
+ break;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void clearData() {
+ Runnable clearAction = new Runnable() {
+ public void run() {
+ TrackerDataHelper helper =
+ new TrackerDataHelper(TrackerActivity.this);
+ helper.deleteAll();
+ }
+ };
+ showConfirm(R.string.delete_confirm, clearAction);
+ }
+
+ private void showConfirm(int textId, final Runnable onConfirmAction) {
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
+ dialogBuilder.setTitle(R.string.confirm_title);
+ dialogBuilder.setMessage(textId);
+ dialogBuilder.setPositiveButton(android.R.string.ok,
+ new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ onConfirmAction.run();
+ }
+ });
+ dialogBuilder.setNegativeButton(android.R.string.cancel,
+ new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // do nothing
+ }
+ });
+ dialogBuilder.show();
+ }
+
+ private void exportCSV() {
+ String exportFileName = getUniqueFileName("csv");
+ exportFile(null, exportFileName, new TrackerDataHelper(this,
+ TrackerDataHelper.CSV_FORMATTER));
+ }
+
+ private void exportKML() {
+ String exportFileName = getUniqueFileName(
+ LocationManager.NETWORK_PROVIDER + ".kml");
+ exportFile(LocationManager.NETWORK_PROVIDER, exportFileName,
+ new TrackerDataHelper(this, TrackerDataHelper.KML_FORMATTER));
+ exportFileName = getUniqueFileName(
+ LocationManager.GPS_PROVIDER + ".kml");
+ exportFile(LocationManager.GPS_PROVIDER, exportFileName,
+ new TrackerDataHelper(this, TrackerDataHelper.KML_FORMATTER));
+ }
+
+ private void exportFile(String tagFilter,
+ String exportFileName,
+ TrackerDataHelper trackerData) {
+ BufferedWriter exportWriter = null;
+ Cursor cursor = trackerData.query(tagFilter);
+ try {
+ exportWriter = new BufferedWriter(new FileWriter(exportFileName));
+ exportWriter.write(trackerData.getOutputHeader());
+
+ String line = null;
+
+ while ((line = trackerData.getNextOutput(cursor)) != null) {
+ exportWriter.write(line);
+ }
+ exportWriter.write(trackerData.getOutputFooter());
+ Toast.makeText(this, "Successfully exported data to " +
+ exportFileName, Toast.LENGTH_SHORT).show();
+
+ } catch (IOException e) {
+ Toast.makeText(this, "Error exporting file: " +
+ e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
+
+ Log.e(LOG_TAG, "Error exporting file", e);
+ } finally {
+ closeWriter(exportWriter);
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ private void closeWriter(Writer exportWriter) {
+ if (exportWriter != null) {
+ try {
+ exportWriter.close();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "error closing file", e);
+ }
+ }
+ }
+
+ private String getUniqueFileName(String ext) {
+ File dir = new File("/sdcard/locationtracker");
+ if (!dir.exists()) {
+ dir.mkdir();
+ }
+ return "/sdcard/locationtracker/tracking-" +
+ DateUtils.getCurrentTimestamp() + "." + ext;
+ }
+
+ private void launchSettings() {
+ Intent settingsIntent = new Intent();
+ settingsIntent.setClass(this, SettingsActivity.class);
+ startActivity(settingsIntent);
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/TrackerService.java b/tests/LocationTracker/src/com/android/locationtracker/TrackerService.java
new file mode 100644
index 0000000..5b75653
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/TrackerService.java
@@ -0,0 +1,445 @@
+/*
+ * 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.locationtracker;
+
+import com.android.locationtracker.data.TrackerDataHelper;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.telephony.CellLocation;
+import android.telephony.PhoneStateListener;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+import android.telephony.cdma.CdmaCellLocation;
+import android.telephony.gsm.GsmCellLocation;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Location Tracking service
+ *
+ * Records location updates for all registered location providers, and cell
+ * location updates
+ */
+public class TrackerService extends Service {
+
+ private List<LocationTrackingListener> mListeners;
+
+ private static final String LOG_TAG = TrackerActivity.LOG_TAG;
+
+ // controls which location providers to track
+ private Set<String> mTrackedProviders;
+
+ private TrackerDataHelper mTrackerData;
+
+ private TelephonyManager mTelephonyManager;
+ private Location mNetworkLocation;
+
+ // Handlers and Receivers for phone and network state
+ private NetworkStateBroadcastReceiver mNetwork;
+ private static final String CELL_PROVIDER_TAG = "cell";
+ // signal strength updates
+ private static final String SIGNAL_PROVIDER_TAG = "signal";
+ private static final String WIFI_PROVIDER_TAG = "wifi";
+ // tracking tag for data connectivity issues
+ private static final String DATA_CONN_PROVIDER_TAG = "data";
+
+ // preference constants
+ private static final String MIN_TIME_PREF = "mintime_preference";
+ private static final String MIN_DIS_PREF = "mindistance_preference";
+ private static final String GPS_PREF = "gps_preference";
+ private static final String NETWORK_PREF = "network_preference";
+ private static final String SIGNAL_PREF = "signal_preference";
+ private static final String DEBUG_PREF = "advanced_log_preference";
+
+ private PreferenceListener mPrefListener;
+
+ public TrackerService() {
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // ignore - nothing to do
+ return null;
+ }
+
+ /**
+ * registers location listeners
+ *
+ * @param intent
+ * @param startId
+ */
+ @Override
+ public void onStart(Intent intent, int startId) {
+ super.onStart(intent, startId);
+ mNetworkLocation = null;
+
+ initLocationListeners();
+ Toast.makeText(this, "Tracking service started", Toast.LENGTH_SHORT);
+ }
+
+ private synchronized void initLocationListeners() {
+ mTrackerData = new TrackerDataHelper(this);
+ LocationManager lm = getLocationManager();
+
+ mTrackedProviders = getTrackedProviders();
+
+ List<String> locationProviders = lm.getAllProviders();
+ mListeners = new ArrayList<LocationTrackingListener>(
+ locationProviders.size());
+
+ long minUpdateTime = getLocationUpdateTime();
+ float minDistance = getLocationMinDistance();
+
+ for (String providerName : locationProviders) {
+ if (mTrackedProviders.contains(providerName)) {
+ Log.d(LOG_TAG, "Adding location listener for provider " +
+ providerName);
+ if (doDebugLogging()) {
+ mTrackerData.writeEntry("init", String.format(
+ "start listening to %s : %d ms; %f meters",
+ providerName, minUpdateTime, minDistance));
+ }
+ LocationTrackingListener listener =
+ new LocationTrackingListener();
+ lm.requestLocationUpdates(providerName, minUpdateTime,
+ minDistance, listener);
+ mListeners.add(listener);
+ }
+ }
+ mTelephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
+
+ if (doDebugLogging()) {
+ // register for cell location updates
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_LOCATION);
+
+ // Register for Network (Wifi or Mobile) updates
+ mNetwork = new NetworkStateBroadcastReceiver();
+ IntentFilter mIntentFilter;
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ Log.d(LOG_TAG, "registering receiver");
+ registerReceiver(mNetwork, mIntentFilter);
+ }
+
+ if (trackSignalStrength()) {
+ mTelephonyManager.listen(mPhoneStateListener,
+ PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+ }
+
+ // register for preference changes, so we can restart listeners on
+ // pref changes
+ mPrefListener = new PreferenceListener();
+ getPreferences().registerOnSharedPreferenceChangeListener(mPrefListener);
+ }
+
+ private Set<String> getTrackedProviders() {
+ Set<String> providerSet = new HashSet<String>();
+
+ if (trackGPS()) {
+ providerSet.add(LocationManager.GPS_PROVIDER);
+ }
+ if (trackNetwork()) {
+ providerSet.add(LocationManager.NETWORK_PROVIDER);
+ }
+ return providerSet;
+ }
+
+ private SharedPreferences getPreferences() {
+ return PreferenceManager.getDefaultSharedPreferences(this);
+ }
+
+ private boolean trackNetwork() {
+ return getPreferences().getBoolean(NETWORK_PREF, true);
+ }
+
+ private boolean trackGPS() {
+ return getPreferences().getBoolean(GPS_PREF, true);
+ }
+
+ private boolean doDebugLogging() {
+ return getPreferences().getBoolean(DEBUG_PREF, true);
+ }
+
+ private boolean trackSignalStrength() {
+ return getPreferences().getBoolean(SIGNAL_PREF, true);
+ }
+
+ private float getLocationMinDistance() {
+ try {
+ String disString = getPreferences().getString(MIN_DIS_PREF, "0");
+ return Float.parseFloat(disString);
+ }
+ catch (NumberFormatException e) {
+ Log.e(LOG_TAG, "Invalid preference for location min distance", e);
+ }
+ return 0;
+ }
+
+ private long getLocationUpdateTime() {
+ try {
+ String timeString = getPreferences().getString(MIN_TIME_PREF, "0");
+ long secondsTime = Long.valueOf(timeString);
+ return secondsTime * 1000;
+ }
+ catch (NumberFormatException e) {
+ Log.e(LOG_TAG, "Invalid preference for location min time", e);
+ }
+ return 0;
+ }
+
+ /**
+ * Shuts down this service
+ */
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(LOG_TAG, "Removing location listeners");
+ stopListeners();
+ Toast.makeText(this, "Tracking service stopped", Toast.LENGTH_SHORT);
+ }
+
+ /**
+ * De-registers all location listeners, closes persistent storage
+ */
+ protected synchronized void stopListeners() {
+ LocationManager lm = getLocationManager();
+ if (mListeners != null) {
+ for (LocationTrackingListener listener : mListeners) {
+ lm.removeUpdates(listener);
+ }
+ mListeners.clear();
+ }
+ mListeners = null;
+
+ // stop cell state listener
+ if (mTelephonyManager != null) {
+ mTelephonyManager.listen(mPhoneStateListener, 0);
+ }
+
+ // stop network/wifi listener
+ if (mNetwork != null) {
+ unregisterReceiver(mNetwork);
+ }
+ mNetwork = null;
+
+ mTrackerData = null;
+ if (mPrefListener != null) {
+ getPreferences().unregisterOnSharedPreferenceChangeListener(mPrefListener);
+ mPrefListener = null;
+ }
+ }
+
+ private LocationManager getLocationManager() {
+ return (LocationManager) getSystemService(Context.LOCATION_SERVICE);
+ }
+
+ /**
+ * Determine the current distance from given location to the last
+ * approximated network location
+ *
+ * @param location - new location
+ *
+ * @return float distance in meters
+ */
+ private synchronized float getDistanceFromNetwork(Location location) {
+ float value = 0;
+ if (mNetworkLocation != null) {
+ value = location.distanceTo(mNetworkLocation);
+ }
+ if (LocationManager.NETWORK_PROVIDER.equals(location.getProvider())) {
+ mNetworkLocation = location;
+ }
+ return value;
+ }
+
+ private class LocationTrackingListener implements LocationListener {
+
+ /**
+ * Writes details of location update to tracking file, including
+ * recording the distance between this location update and the last
+ * network location update
+ *
+ * @param location - new location
+ */
+ public void onLocationChanged(Location location) {
+ if (location == null) {
+ return;
+ }
+ float distance = getDistanceFromNetwork(location);
+ mTrackerData.writeEntry(location, distance);
+ }
+
+ /**
+ * Writes update to tracking file
+ *
+ * @param provider - name of disabled provider
+ */
+ public void onProviderDisabled(String provider) {
+ if (doDebugLogging()) {
+ mTrackerData.writeEntry(provider, "provider disabled");
+ }
+ }
+
+ /**
+ * Writes update to tracking file
+ *
+ * @param provider - name of enabled provider
+ */
+ public void onProviderEnabled(String provider) {
+ if (doDebugLogging()) {
+ mTrackerData.writeEntry(provider, "provider enabled");
+ }
+ }
+
+ /**
+ * Writes update to tracking file
+ *
+ * @param provider - name of provider whose status changed
+ * @param status - new status
+ * @param extras - optional set of extra status messages
+ */
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ if (doDebugLogging()) {
+ mTrackerData.writeEntry(provider, "status change: " + status);
+ }
+ }
+ }
+
+ PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onCellLocationChanged(CellLocation location) {
+ try {
+ if (location instanceof GsmCellLocation) {
+ GsmCellLocation cellLocation = (GsmCellLocation)location;
+ String updateMsg = "cid=" + cellLocation.getCid() +
+ ", lac=" + cellLocation.getLac();
+ mTrackerData.writeEntry(CELL_PROVIDER_TAG, updateMsg);
+ } else if (location instanceof CdmaCellLocation) {
+ CdmaCellLocation cellLocation = (CdmaCellLocation)location;
+ String updateMsg = "BID=" + cellLocation.getBaseStationId() +
+ ", SID=" + cellLocation.getSystemId() +
+ ", NID=" + cellLocation.getNetworkId() +
+ ", lat=" + cellLocation.getBaseStationLatitude() +
+ ", long=" + cellLocation.getBaseStationLongitude() +
+ ", SID=" + cellLocation.getSystemId() +
+ ", NID=" + cellLocation.getNetworkId();
+ mTrackerData.writeEntry(CELL_PROVIDER_TAG, updateMsg);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Exception in CellStateHandler.handleMessage:", e);
+ }
+ }
+
+ public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+ if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+ String updateMsg = "cdma dBM=" + signalStrength.getCdmaDbm();
+ mTrackerData.writeEntry(SIGNAL_PROVIDER_TAG, updateMsg);
+ } else if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+ String updateMsg = "gsm signal=" + signalStrength.getGsmSignalStrength();
+ mTrackerData.writeEntry(SIGNAL_PROVIDER_TAG, updateMsg);
+ }
+ }
+ };
+
+ /**
+ * Listener + recorder for mobile or wifi updates
+ */
+ private class NetworkStateBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+ WifiManager wifiManager =
+ (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ List<ScanResult> wifiScanResults = wifiManager.getScanResults();
+ String updateMsg = "num scan results=" +
+ (wifiScanResults == null ? "0" : wifiScanResults.size());
+ mTrackerData.writeEntry(WIFI_PROVIDER_TAG, updateMsg);
+
+ } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ String updateMsg;
+ boolean noConnectivity =
+ intent.getBooleanExtra(
+ ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
+ if (noConnectivity) {
+ updateMsg = "no connectivity";
+ }
+ else {
+ updateMsg = "connection available";
+ }
+ mTrackerData.writeEntry(DATA_CONN_PROVIDER_TAG, updateMsg);
+
+ } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+ WifiManager.WIFI_STATE_UNKNOWN);
+
+ String stateString = "unknown";
+ switch (state) {
+ case WifiManager.WIFI_STATE_DISABLED:
+ stateString = "disabled";
+ break;
+ case WifiManager.WIFI_STATE_DISABLING:
+ stateString = "disabling";
+ break;
+ case WifiManager.WIFI_STATE_ENABLED:
+ stateString = "enabled";
+ break;
+ case WifiManager.WIFI_STATE_ENABLING:
+ stateString = "enabling";
+ break;
+ }
+ mTrackerData.writeEntry(WIFI_PROVIDER_TAG,
+ "state = " + stateString);
+ }
+ }
+ }
+
+ private class PreferenceListener implements OnSharedPreferenceChangeListener {
+
+ public void onSharedPreferenceChanged(
+ SharedPreferences sharedPreferences, String key) {
+ Log.d(LOG_TAG, "restarting listeners due to preference change");
+ synchronized (TrackerService.this) {
+ stopListeners();
+ initLocationListeners();
+ }
+ }
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/CSVFormatter.java b/tests/LocationTracker/src/com/android/locationtracker/data/CSVFormatter.java
new file mode 100644
index 0000000..672ce28
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/CSVFormatter.java
@@ -0,0 +1,87 @@
+/*
+ * 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.locationtracker.data;
+
+import com.android.locationtracker.data.TrackerEntry.EntryType;
+
+/**
+ * Formats tracker data as CSV output
+ */
+class CSVFormatter implements IFormatter {
+
+ private static final String DELIMITER = ", ";
+
+ public String getHeader() {
+ StringBuilder csvBuilder = new StringBuilder();
+ for (String col : TrackerEntry.ATTRIBUTES) {
+ // skip type and id column
+ if (!TrackerEntry.ENTRY_TYPE.equals(col) &&
+ !TrackerEntry.ID_COL.equals(col)) {
+ csvBuilder.append(col);
+ csvBuilder.append(DELIMITER);
+ }
+ }
+ csvBuilder.append("\n");
+ return csvBuilder.toString();
+ }
+
+ public String getOutput(TrackerEntry entry) {
+ StringBuilder rowOutput = new StringBuilder();
+ // these must match order of columns added in getHeader
+ rowOutput.append(entry.getTimestamp());
+ rowOutput.append(DELIMITER);
+ rowOutput.append(entry.getTag());
+ rowOutput.append(DELIMITER);
+ //rowOutput.append(entry.getType());
+ //rowOutput.append(DELIMITER);
+ if (entry.getType() == EntryType.LOCATION_TYPE) {
+ if (entry.getLocation().hasAccuracy()) {
+ rowOutput.append(entry.getLocation().getAccuracy());
+ }
+ rowOutput.append(DELIMITER);
+ rowOutput.append(entry.getLocation().getLatitude());
+ rowOutput.append(DELIMITER);
+ rowOutput.append(entry.getLocation().getLongitude());
+ rowOutput.append(DELIMITER);
+ if (entry.getLocation().hasAltitude()) {
+ rowOutput.append(entry.getLocation().getAltitude());
+ }
+ rowOutput.append(DELIMITER);
+ if (entry.getLocation().hasSpeed()) {
+ rowOutput.append(entry.getLocation().getSpeed());
+ }
+ rowOutput.append(DELIMITER);
+ if (entry.getLocation().hasBearing()) {
+ rowOutput.append(entry.getLocation().getBearing());
+ }
+ rowOutput.append(DELIMITER);
+ rowOutput.append(entry.getDistFromNetLocation());
+ rowOutput.append(DELIMITER);
+ rowOutput.append(DateUtils.getKMLTimestamp(entry.getLocation()
+ .getTime()));
+ rowOutput.append(DELIMITER);
+ }
+ rowOutput.append(entry.getLogMsg());
+ rowOutput.append("\n");
+ return rowOutput.toString();
+ }
+
+ public String getFooter() {
+ // not needed, return empty string
+ return "";
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/DateUtils.java b/tests/LocationTracker/src/com/android/locationtracker/data/DateUtils.java
new file mode 100644
index 0000000..13226bd6
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/DateUtils.java
@@ -0,0 +1,61 @@
+/*
+ * 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.locationtracker.data;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Provides formatting date as string utilities
+ */
+public class DateUtils {
+
+ private DateUtils() {
+
+ }
+
+ /**
+ * Returns timestamp given by param in KML format ie yyyy-mm-ddThh:mm:ssZ,
+ * where T is the separator between the date and the time and the time zone
+ * is Z (for UTC)
+ *
+ * @return KML timestamp as String
+ */
+ public static String getKMLTimestamp(long when) {
+ TimeZone tz = TimeZone.getTimeZone("GMT");
+ Calendar c = Calendar.getInstance(tz);
+ c.setTimeInMillis(when);
+ return String.format("%tY-%tm-%tdT%tH:%tM:%tSZ", c, c, c, c, c, c);
+ }
+
+ /**
+ * Helper version of getKMLTimestamp, that returns timestamp for current
+ * time
+ */
+ public static String getCurrentKMLTimestamp() {
+ return getKMLTimestamp(System.currentTimeMillis());
+ }
+
+ /**
+ * Returns timestamp in following format: yyyy-mm-dd-hh-mm-ss
+ */
+ public static String getCurrentTimestamp() {
+ Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(System.currentTimeMillis());
+ return String.format("%tY-%tm-%td-%tH-%tM-%tS", c, c, c, c, c, c);
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/IFormatter.java b/tests/LocationTracker/src/com/android/locationtracker/data/IFormatter.java
new file mode 100644
index 0000000..af0b5ed
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/IFormatter.java
@@ -0,0 +1,27 @@
+/*
+ * 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.locationtracker.data;
+
+/**
+ * interface for formatting tracker data output
+ */
+interface IFormatter {
+
+ String getHeader();
+ String getOutput(TrackerEntry entry);
+ String getFooter();
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/KMLFormatter.java b/tests/LocationTracker/src/com/android/locationtracker/data/KMLFormatter.java
new file mode 100644
index 0000000..a5e1816
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/KMLFormatter.java
@@ -0,0 +1,88 @@
+/*
+ * 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.locationtracker.data;
+
+import com.android.locationtracker.data.TrackerEntry.EntryType;
+
+import android.location.Location;
+
+/**
+ * Formats tracker data as KML output
+ */
+class KMLFormatter implements IFormatter {
+
+ public String getHeader() {
+ LineBuilder builder = new LineBuilder();
+ builder.addLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+ builder.addLine("<kml xmlns=\"http://earth.google.com/kml/2.2\">");
+ builder.addLine("<Document>");
+ return builder.toString();
+ }
+
+ public String getFooter() {
+ LineBuilder builder = new LineBuilder();
+ builder.addLine("</Document>");
+ builder.addLine("</kml>");
+ return builder.toString();
+ }
+
+ public String getOutput(TrackerEntry entry) {
+ LineBuilder builder = new LineBuilder();
+
+ if (entry.getType() == EntryType.LOCATION_TYPE) {
+
+ Location loc = entry.getLocation();
+ builder.addLine("<Placemark>");
+ builder.addLine("<description>");
+ builder.addLine("accuracy = " + loc.getAccuracy());
+ builder.addLine("distance from last network location = "
+ + entry.getDistFromNetLocation());
+ builder.addLine("</description>");
+ builder.addLine("<TimeStamp>");
+ builder.addLine("<when>" + entry.getTimestamp() + "</when>");
+ builder.addLine("</TimeStamp>");
+ builder.addLine("<Point>");
+ builder.addLine("<coordinates>");
+ builder.addLine(loc.getLongitude() + "," + loc.getLatitude() + ","
+ + loc.getAltitude());
+ builder.addLine("</coordinates>");
+ builder.addLine("</Point>");
+ builder.addLine("</Placemark>");
+ }
+ return builder.toString();
+ }
+
+ private static class LineBuilder {
+ private StringBuilder mBuilder;
+
+ public LineBuilder() {
+ mBuilder = new StringBuilder();
+ }
+
+ public void addLine(String line) {
+ mBuilder.append(line);
+ mBuilder.append("\n");
+ }
+
+ @Override
+ public String toString() {
+ return mBuilder.toString();
+ }
+
+ }
+
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerDataHelper.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerDataHelper.java
new file mode 100644
index 0000000..a3838df
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerDataHelper.java
@@ -0,0 +1,173 @@
+/*
+ * 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.locationtracker.data;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.location.Location;
+
+/**
+ * Helper class for writing and retrieving data using the TrackerProvider
+ * content provider
+ *
+ */
+public class TrackerDataHelper {
+
+ private Context mContext;
+ /** formats data output */
+ protected IFormatter mFormatter;
+
+ /** formats output as Comma separated value CSV file */
+ public static final IFormatter CSV_FORMATTER = new CSVFormatter();
+ /** formats output as KML file */
+ public static final IFormatter KML_FORMATTER = new KMLFormatter();
+ /** provides no formatting */
+ public static final IFormatter NO_FORMATTER = new IFormatter() {
+ public String getFooter() {
+ return "";
+ }
+
+ public String getHeader() {
+ return "";
+ }
+
+ public String getOutput(TrackerEntry entry) {
+ return "";
+ }
+ };
+
+ /**
+ * Creates instance
+ *
+ * @param context - content context
+ * @param formatter - formats the output from the get*Output* methods
+ */
+ public TrackerDataHelper(Context context, IFormatter formatter) {
+ mContext = context;
+ mFormatter = formatter;
+ }
+
+ /**
+ * Creates a instance with no output formatting capabilities. Useful for
+ * clients that require write-only access
+ */
+ public TrackerDataHelper(Context context) {
+ this(context, NO_FORMATTER);
+ }
+
+ /**
+ * insert given TrackerEntry into content provider
+ */
+ void writeEntry(TrackerEntry entry) {
+ mContext.getContentResolver().insert(TrackerProvider.CONTENT_URI,
+ entry.getAsContentValues());
+ }
+
+ /**
+ * insert given location into tracker data
+ */
+ public void writeEntry(Location loc, float distFromNetLoc) {
+ writeEntry(TrackerEntry.createEntry(loc, distFromNetLoc));
+ }
+
+ /**
+ * insert given log message into tracker data
+ */
+ public void writeEntry(String tag, String logMsg) {
+ writeEntry(TrackerEntry.createEntry(tag, logMsg));
+ }
+
+ /**
+ * Deletes all tracker entries
+ */
+ public void deleteAll() {
+ mContext.getContentResolver().delete(TrackerProvider.CONTENT_URI, null,
+ null);
+ }
+
+ /**
+ * Query tracker data, filtering by given tag
+ *
+ * @param tag
+ * @return Cursor to data
+ */
+ public Cursor query(String tag, int limit) {
+ String selection = (tag == null ? null : TrackerEntry.TAG + "=?");
+ String[] selectionArgs = (tag == null ? null : new String[] {tag});
+ Cursor cursor = mContext.getContentResolver().query(
+ TrackerProvider.CONTENT_URI, TrackerEntry.ATTRIBUTES,
+ selection, selectionArgs, null);
+ if (cursor == null) {
+ return cursor;
+ }
+ int pos = (cursor.getCount() < limit ? 0 : cursor.getCount() - limit);
+ cursor.moveToPosition(pos);
+ return cursor;
+ }
+
+ /**
+ * Retrieves a cursor that starts at the last limit rows
+ *
+ * @param limit
+ * @return a cursor, null if bad things happened
+ */
+ public Cursor query(int limit) {
+ return query(null, limit);
+ }
+
+ /**
+ * Query tracker data, filtering by given tag. mo limit to number of rows
+ * returned
+ *
+ * @param tag
+ * @return Cursor to data
+ */
+ public Cursor query(String tag) {
+ return query(tag, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Returns the output header particular to the associated formatter
+ */
+ public String getOutputHeader() {
+ return mFormatter.getHeader();
+ }
+
+ /**
+ * Returns the output footer particular to the associated formatter
+ */
+ public String getOutputFooter() {
+ return mFormatter.getFooter();
+ }
+
+ /**
+ * Helper method which converts row referenced by given cursor to a string
+ * output
+ *
+ * @param cursor
+ * @return CharSequence output, null if given cursor is invalid or no more
+ * data
+ */
+ public String getNextOutput(Cursor cursor) {
+ if (cursor == null || cursor.isAfterLast()) {
+ return null;
+ }
+ String output = mFormatter.getOutput(TrackerEntry.createEntry(cursor));
+ cursor.moveToNext();
+ return output;
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerEntry.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerEntry.java
new file mode 100644
index 0000000..b2741f6
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerEntry.java
@@ -0,0 +1,253 @@
+/*
+ * 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.locationtracker.data;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.location.Location;
+
+
+/**
+ * Class that holds a tracker entry. An entry can be either a valid location, or
+ * a simple log msg
+ *
+ * It provides a concrete data structure to represent data stored in the
+ * TrackerProvider
+ */
+class TrackerEntry {
+
+ static final String TIMESTAMP = "Timestamp";
+ static final String TAG = "Tag";
+ static final String ENTRY_TYPE = "Type";
+
+ private Location mLocation;
+ private float mDistFromNetLocation;
+ private String mLogMsg;
+
+ static final String ID_COL = "_id";
+ static final String ACCURACY = "Accuracy";
+ static final String LATITUDE = "Latitude";
+ static final String LONGITUDE = "Longitude";
+ static final String ALTITUDE = "Altitude";
+ static final String SPEED = "Speed";
+ static final String BEARING = "Bearing";
+ static final String DIST_NET_LOCATION = "DistFromNetLocation";
+ static final String LOC_TIME = "LocationTime";
+ static final String DEBUG_INFO = "DebugInfo";
+
+ static final String STRING_DATA = "STRING";
+ static final String INT_DATA = "INTEGER";
+ static final String REAL_DATA = "REAL";
+ static final String BLOB_DATA = "BLOB";
+
+ static final String[] ATTRIBUTES = {
+ ID_COL, TIMESTAMP, TAG, ENTRY_TYPE, ACCURACY, LATITUDE, LONGITUDE,
+ ALTITUDE, SPEED, BEARING, DIST_NET_LOCATION, LOC_TIME, DEBUG_INFO};
+ static final String[] ATTRIBUTES_DATA_TYPE = {
+ INT_DATA + " PRIMARY KEY", STRING_DATA, STRING_DATA, STRING_DATA,
+ REAL_DATA, REAL_DATA, REAL_DATA, REAL_DATA, REAL_DATA, REAL_DATA,
+ REAL_DATA, INT_DATA, STRING_DATA};
+
+ // location extra keys used to retrieve debug info
+ private static final String NETWORK_LOCATION_SOURCE_KEY =
+ "networkLocationSource";
+ private static final String NETWORK_LOCATION_TYPE_KEY =
+ "networkLocationType";
+ private static final String[] LOCATION_DEBUG_KEYS = {
+ NETWORK_LOCATION_SOURCE_KEY, NETWORK_LOCATION_TYPE_KEY};
+
+ enum EntryType {
+ LOCATION_TYPE, LOG_TYPE
+ }
+
+ private String mTimestamp;
+ private String mTag;
+ private EntryType mType;
+
+ private TrackerEntry(String tag, EntryType type) {
+ mType = type;
+ mTag = tag;
+ mLocation = null;
+ }
+
+ private TrackerEntry(Location loc) {
+ this(loc.getProvider(), EntryType.LOCATION_TYPE);
+ mLocation = new Location(loc);
+ }
+
+ /**
+ * Creates a TrackerEntry from a Location
+ */
+ static TrackerEntry createEntry(Location loc, float distFromNetLocation) {
+ TrackerEntry entry = new TrackerEntry(loc);
+
+ String timestampVal = DateUtils.getCurrentKMLTimestamp();
+ entry.setTimestamp(timestampVal);
+ entry.setDistFromNetLocation(distFromNetLocation);
+ return entry;
+ }
+
+ /**
+ * Creates a TrackerEntry from a log msg
+ */
+ static TrackerEntry createEntry(String tag, String msg) {
+ TrackerEntry entry = new TrackerEntry(tag, EntryType.LOG_TYPE);
+ String timestampVal = DateUtils.getCurrentKMLTimestamp();
+ entry.setTimestamp(timestampVal);
+ entry.setLogMsg(msg);
+ return entry;
+ }
+
+ private void setTimestamp(String timestamp) {
+ mTimestamp = timestamp;
+ }
+
+ EntryType getType() {
+ return mType;
+ }
+
+ private void setDistFromNetLocation(float distFromNetLocation) {
+ mDistFromNetLocation = distFromNetLocation;
+ }
+
+ private void setLogMsg(String msg) {
+ mLogMsg = msg;
+ }
+
+ private void setLocation(Location location) {
+ mLocation = location;
+ }
+
+ String getTimestamp() {
+ return mTimestamp;
+ }
+
+ String getTag() {
+ return mTag;
+ }
+
+ Location getLocation() {
+ return mLocation;
+ }
+
+ String getLogMsg() {
+ return mLogMsg;
+ }
+
+ float getDistFromNetLocation() {
+ return mDistFromNetLocation;
+ }
+
+ static void buildCreationString(StringBuilder builder) {
+ if (ATTRIBUTES.length != ATTRIBUTES_DATA_TYPE.length) {
+ throw new IllegalArgumentException(
+ "Attribute length does not match data type length");
+ }
+ for (int i = 0; i < ATTRIBUTES_DATA_TYPE.length; i++) {
+ if (i != 0) {
+ builder.append(", ");
+ }
+ builder.append(String.format("%s %s", ATTRIBUTES[i],
+ ATTRIBUTES_DATA_TYPE[i]));
+ }
+ }
+
+ ContentValues getAsContentValues() {
+ ContentValues cValues = new ContentValues(ATTRIBUTES.length);
+ cValues.put(TIMESTAMP, mTimestamp);
+ cValues.put(TAG, mTag);
+ cValues.put(ENTRY_TYPE, mType.toString());
+ if (mType == EntryType.LOCATION_TYPE) {
+ cValues.put(LATITUDE, mLocation.getLatitude());
+ cValues.put(LONGITUDE, mLocation.getLongitude());
+ if (mLocation.hasAccuracy()) {
+ cValues.put(ACCURACY, mLocation.getAccuracy());
+ }
+ if (mLocation.hasAltitude()) {
+ cValues.put(ALTITUDE, mLocation.getAltitude());
+ }
+ if (mLocation.hasSpeed()) {
+ cValues.put(SPEED, mLocation.getSpeed());
+ }
+ if (mLocation.hasBearing()) {
+ cValues.put(BEARING, mLocation.getBearing());
+ }
+ cValues.put(DIST_NET_LOCATION, mDistFromNetLocation);
+ cValues.put(LOC_TIME, mLocation.getTime());
+ StringBuilder debugBuilder = new StringBuilder("");
+ if (mLocation.getExtras() != null) {
+ for (String key : LOCATION_DEBUG_KEYS) {
+ Object val = mLocation.getExtras().get(key);
+ if (val != null) {
+ debugBuilder.append(String.format("%s=%s; ", key, val
+ .toString()));
+ }
+ }
+ }
+ cValues.put(DEBUG_INFO, debugBuilder.toString());
+ } else {
+ cValues.put(DEBUG_INFO, mLogMsg);
+ }
+ return cValues;
+ }
+
+ static TrackerEntry createEntry(Cursor cursor) {
+ String timestamp = cursor.getString(cursor.getColumnIndex(TIMESTAMP));
+ String tag = cursor.getString(cursor.getColumnIndex(TAG));
+ String sType = cursor.getString(cursor.getColumnIndex(ENTRY_TYPE));
+ TrackerEntry entry = new TrackerEntry(tag, EntryType.valueOf(sType));
+ entry.setTimestamp(timestamp);
+ if (entry.getType() == EntryType.LOCATION_TYPE) {
+ Location location = new Location(tag);
+ location.setLatitude(cursor.getFloat(cursor
+ .getColumnIndexOrThrow(LATITUDE)));
+ location.setLongitude(cursor.getFloat(cursor
+ .getColumnIndexOrThrow(LONGITUDE)));
+
+ Float accuracy = getNullableFloat(cursor, ACCURACY);
+ if (accuracy != null) {
+ location.setAccuracy(accuracy);
+ }
+ Float altitude = getNullableFloat(cursor, ALTITUDE);
+ if (altitude != null) {
+ location.setAltitude(altitude);
+ }
+ Float bearing = getNullableFloat(cursor, BEARING);
+ if (bearing != null) {
+ location.setBearing(bearing);
+ }
+ Float speed = getNullableFloat(cursor, SPEED);
+ if (speed != null) {
+ location.setSpeed(speed);
+ }
+ location.setTime(cursor.getLong(cursor.getColumnIndex(LOC_TIME)));
+ entry.setLocation(location);
+ }
+ entry.setLogMsg(cursor.getString(cursor.getColumnIndex(DEBUG_INFO)));
+
+ return entry;
+ }
+
+ private static Float getNullableFloat(Cursor cursor, String colName) {
+ Float retValue = null;
+ int colIndex = cursor.getColumnIndexOrThrow(colName);
+ if (!cursor.isNull(colIndex)) {
+ retValue = cursor.getFloat(colIndex);
+ }
+ return retValue;
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java
new file mode 100644
index 0000000..55d4d1e
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java
@@ -0,0 +1,75 @@
+/*
+ * 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.locationtracker.data;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+import com.android.locationtracker.R;
+
+/**
+ * Used to bind Tracker data to a list view UI
+ */
+public class TrackerListHelper extends TrackerDataHelper {
+
+ private ListActivity mActivity;
+
+ // sort entries by most recent first
+ private static final String SORT_ORDER = TrackerEntry.ID_COL + " DESC";
+
+ public TrackerListHelper(ListActivity activity) {
+ super(activity, TrackerDataHelper.CSV_FORMATTER);
+ mActivity = activity;
+ }
+
+ /**
+ * Helper method for binding the list activities UI to the tracker data
+ * Tracker data will be sorted in most-recent first order
+ * Will enable automatic UI changes as tracker data changes
+ *
+ * @param layout - layout to populate data
+ */
+ public void bindListUI(int layout) {
+ Cursor cursor = mActivity.managedQuery(TrackerProvider.CONTENT_URI,
+ TrackerEntry.ATTRIBUTES, null, null, SORT_ORDER);
+ // Used to map tracker entries from the database to views
+ TrackerAdapter adapter = new TrackerAdapter(mActivity, layout, cursor);
+ mActivity.setListAdapter(adapter);
+ cursor.setNotificationUri(mActivity.getContentResolver(),
+ TrackerProvider.CONTENT_URI);
+
+ }
+
+ private class TrackerAdapter extends ResourceCursorAdapter {
+
+ public TrackerAdapter(Context context, int layout, Cursor c) {
+ super(context, layout, c);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ final TextView v = (TextView) view
+ .findViewById(R.id.entrylist_item);
+ String rowText = mFormatter.getOutput(TrackerEntry
+ .createEntry(cursor));
+ v.setText(rowText);
+ }
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerProvider.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerProvider.java
new file mode 100644
index 0000000..88f24c3
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerProvider.java
@@ -0,0 +1,126 @@
+/*
+ * 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.locationtracker.data;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.util.Log;
+
+/**
+ * Content provider for location tracking.
+ *
+ * It is recommended to use the TrackerDataHelper class to access this data
+ * rather than this class directly
+ */
+public class TrackerProvider extends ContentProvider {
+
+ public static final Uri CONTENT_URI = Uri
+ .parse("content://com.android.locationtracker");
+
+ private static final String DB_NAME = "tracking.db";
+ private static final String TABLE_NAME = "tracking";
+ private static final int DB_VERSION = 1;
+
+ private static final String LOG_TAG = "TrackerProvider";
+
+ /**
+ * This class helps open, create, and upgrade the database file.
+ */
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+
+ DatabaseHelper(Context context) {
+ super(context, DB_NAME, null, DB_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ StringBuilder queryBuilder = new StringBuilder();
+ queryBuilder.append(String.format("CREATE TABLE %s (", TABLE_NAME));
+ TrackerEntry.buildCreationString(queryBuilder);
+
+ queryBuilder.append(");");
+ db.execSQL(queryBuilder.toString());
+ db.setVersion(DB_VERSION);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // TODO: reimplement this when dB version changes
+ Log.w(LOG_TAG, "Upgrading database from version " + oldVersion
+ + " to " + newVersion
+ + ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+ onCreate(db);
+ }
+ }
+
+ private DatabaseHelper mOpenHelper;
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = new DatabaseHelper(getContext());
+ return true;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int result = db.delete(TABLE_NAME, selection, selectionArgs);
+ getContext().getContentResolver().notifyChange(uri, null);
+ return result;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(TABLE_NAME, null, values);
+ if (rowId > 0) {
+ Uri addedUri = ContentUris.withAppendedId(CONTENT_URI, rowId);
+ getContext().getContentResolver().notifyChange(addedUri, null);
+ return addedUri;
+ }
+ return null;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ // TODO: extract limit from URI ?
+ Cursor cursor = db.query(TABLE_NAME, projection, selection,
+ selectionArgs, null, null, sortOrder);
+ getContext().getContentResolver().notifyChange(uri, null);
+ return cursor;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/tests/backup/Android.mk b/tests/backup/Android.mk
index 0813c35..498ec40 100644
--- a/tests/backup/Android.mk
+++ b/tests/backup/Android.mk
@@ -23,6 +23,7 @@
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := backup_helper_test
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_SHARED_LIBRARIES := libutils
include $(BUILD_EXECUTABLE)
@@ -33,6 +34,8 @@
LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := BackupTest
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index 663a33a..69b2207 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -938,17 +938,17 @@
uint8_t mask = 0;
uint8_t value = 0;
if (strcmp(name, kWildcardName) == 0) {
- mask = out->MASK_KEYSHIDDEN;
- value = out->KEYSHIDDEN_ANY;
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_ANY;
} else if (strcmp(name, "keysexposed") == 0) {
- mask = out->MASK_KEYSHIDDEN;
- value = out->KEYSHIDDEN_NO;
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_NO;
} else if (strcmp(name, "keyshidden") == 0) {
- mask = out->MASK_KEYSHIDDEN;
- value = out->KEYSHIDDEN_YES;
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_YES;
} else if (strcmp(name, "keyssoft") == 0) {
- mask = out->MASK_KEYSHIDDEN;
- value = out->KEYSHIDDEN_SOFT;
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_SOFT;
}
if (mask != 0) {
@@ -985,14 +985,14 @@
uint8_t mask = 0;
uint8_t value = 0;
if (strcmp(name, kWildcardName) == 0) {
- mask = out->MASK_NAVHIDDEN;
- value = out->NAVHIDDEN_ANY;
+ mask = ResTable_config::MASK_NAVHIDDEN;
+ value = ResTable_config::NAVHIDDEN_ANY;
} else if (strcmp(name, "navexposed") == 0) {
- mask = out->MASK_NAVHIDDEN;
- value = out->NAVHIDDEN_NO;
+ mask = ResTable_config::MASK_NAVHIDDEN;
+ value = ResTable_config::NAVHIDDEN_NO;
} else if (strcmp(name, "navhidden") == 0) {
- mask = out->MASK_NAVHIDDEN;
- value = out->NAVHIDDEN_YES;
+ mask = ResTable_config::MASK_NAVHIDDEN;
+ value = ResTable_config::NAVHIDDEN_YES;
}
if (mask != 0) {
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 88c5441..ae4bd14 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -221,12 +221,12 @@
&& code != ResXMLTree::BAD_DOCUMENT) {
if (code == ResXMLTree::START_TAG) {
if (strcmp16(block.getElementName(&len), uses_sdk16.string()) == 0) {
- ssize_t minSdkIndex = block.indexOfAttribute("android",
+ ssize_t minSdkIndex = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE,
"minSdkVersion");
if (minSdkIndex >= 0) {
- String8 minSdkString = String8(
- block.getAttributeStringValue(minSdkIndex, &len));
- bundle->setMinSdkVersion(minSdkString.string());
+ const uint16_t* minSdk16 = block.getAttributeStringValue(minSdkIndex, &len);
+ const char* minSdk8 = strdup(String8(minSdk16).string());
+ bundle->setMinSdkVersion(minSdk8);
}
}
}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index a389bfb..0b531c2 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -382,7 +382,7 @@
}
attr.createIfNeeded(outTable);
if (!attr.hasErrors) {
- char buf[10];
+ char buf[11];
sprintf(buf, "%d", l10n_required);
err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
String16(""), String16("^l10n"), String16(buf), NULL, NULL);
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
index 51afc0a..a09cec0 100644
--- a/tools/aapt/StringPool.cpp
+++ b/tools/aapt/StringPool.cpp
@@ -25,8 +25,12 @@
const size_t NS = pool->size();
for (size_t s=0; s<NS; s++) {
size_t len;
- printf("String #%ld: %s\n", s,
- String8(pool->stringAt(s, &len)).string());
+ const char *str = (const char*)pool->string8At(s, &len);
+ if (str == NULL) {
+ str = String8(pool->stringAt(s, &len)).string();
+ }
+
+ printf("String #%ld: %s\n", s, str);
}
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
index 8bf5e85..d5d315e 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
@@ -236,11 +236,6 @@
// OVERRIDEN METHODS
// --------------------
- @Override
- public void finalize() throws Throwable {
- // pass
- }
-
/* (non-Javadoc)
* @see android.graphics.Canvas#setBitmap(android.graphics.Bitmap)
*/
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix.java b/tools/layoutlib/bridge/src/android/graphics/Matrix.java
index 522415c..9e30671 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Matrix.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Matrix.java
@@ -52,11 +52,6 @@
mValues = data;
}
- @Override
- public void finalize() throws Throwable {
- // pass
- }
-
//---------- Custom Methods
/**
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint.java b/tools/layoutlib/bridge/src/android/graphics/Paint.java
index 2d03618..e4f9794 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint.java
@@ -219,11 +219,6 @@
}
@Override
- public void finalize() throws Throwable {
- // pass
- }
-
- @Override
public void reset() {
super.reset();
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 990498f..41d9f9d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -30,6 +30,7 @@
import com.android.tools.layoutlib.create.MethodAdapter;
import com.android.tools.layoutlib.create.OverrideMethod;
+import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -1133,7 +1134,7 @@
}
@SuppressWarnings("unused")
- public void resized(int arg0, int arg1, Rect arg2, Rect arg3, boolean arg4)
+ public void resized(int arg0, int arg1, Rect arg2, Rect arg3, boolean arg4, Configuration arg5)
throws RemoteException {
// pass for now.
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
index 73a3986..744bfbe6 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
@@ -927,6 +927,7 @@
return null;
}
+ @Override
public File getExternalCacheDir() {
// TODO Auto-generated method stub
return null;
@@ -964,6 +965,7 @@
return null;
}
+ @Override
public File getExternalFilesDir(String type) {
// TODO Auto-generated method stub
return null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java
index efd222e..6a98780 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java
@@ -392,7 +392,8 @@
if (s == null) {
return defValue;
- } else if (s.equals(BridgeConstants.MATCH_PARENT)) {
+ } else if (s.equals(BridgeConstants.MATCH_PARENT) ||
+ s.equals(BridgeConstants.FILL_PARENT)) {
return LayoutParams.MATCH_PARENT;
} else if (s.equals(BridgeConstants.WRAP_CONTENT)) {
return LayoutParams.WRAP_CONTENT;
@@ -460,7 +461,8 @@
if (s == null) {
return defValue;
- } else if (s.equals(BridgeConstants.MATCH_PARENT)) {
+ } else if (s.equals(BridgeConstants.MATCH_PARENT) ||
+ s.equals(BridgeConstants.FILL_PARENT)) {
return LayoutParams.MATCH_PARENT;
} else if (s.equals(BridgeConstants.WRAP_CONTENT)) {
return LayoutParams.WRAP_CONTENT;
diff --git a/tools/preload/20100223.compiled b/tools/preload/20100223.compiled
new file mode 100644
index 0000000..3056388
--- /dev/null
+++ b/tools/preload/20100223.compiled
Binary files differ
diff --git a/tools/preload/MemoryUsage.java b/tools/preload/MemoryUsage.java
index bc21b6f..d8f95f4 100644
--- a/tools/preload/MemoryUsage.java
+++ b/tools/preload/MemoryUsage.java
@@ -166,7 +166,7 @@
+ ":/system/framework/loadclass.jar";
private static final String[] GET_DIRTY_PAGES = {
- "adb", "-e", "shell", "dalvikvm", CLASS_PATH, "LoadClass" };
+ "adb", "shell", "dalvikvm", CLASS_PATH, "LoadClass" };
/**
* Measures memory usage for the given class.
@@ -248,7 +248,7 @@
String line = in.readLine();
if (line == null || !line.startsWith("DECAFBAD,")) {
System.err.println("Got bad response for " + className
- + ": " + line);
+ + ": " + line + "; command was " + Arrays.toString(commands));
errorCount += 1;
return NOT_AVAILABLE;
}
diff --git a/tools/preload/Policy.java b/tools/preload/Policy.java
index a8d761d..f557365 100644
--- a/tools/preload/Policy.java
+++ b/tools/preload/Policy.java
@@ -43,9 +43,14 @@
"system_server",
"com.google.process.content",
"android.process.media",
+ "com.android.bluetooth",
+ "com.android.calendar",
+ "com.android.inputmethod.latin",
"com.android.phone",
- "com.google.android.apps.maps.FriendService",
+ "com.google.android.apps.maps.FriendService", // pre froyo
+ "com.google.android.apps.maps:FriendService", // froyo
"com.google.android.apps.maps.LocationFriendService",
+ "com.google.android.deskclock",
"com.google.process.gapps",
"android.tts"
));
diff --git a/tools/preload/Record.java b/tools/preload/Record.java
index b2be4d4..9d45a26 100644
--- a/tools/preload/Record.java
+++ b/tools/preload/Record.java
@@ -19,6 +19,19 @@
*/
class Record {
+ /**
+ * The delimiter character we use, {@code :}, conflicts with some other
+ * names. In that case, manually replace the delimiter with something else.
+ */
+ private static final String[] REPLACE_CLASSES = {
+ "com.google.android.apps.maps:FriendService",
+ "com.google.android.apps.maps\\u003AFriendService",
+ "com.google.android.apps.maps:driveabout",
+ "com.google.android.apps.maps\\u003Adriveabout",
+ "com.google.android.apps.maps:LocationFriendService",
+ "com.google.android.apps.maps\\u003ALocationFriendService",
+ };
+
enum Type {
/** Start of initialization. */
START_LOAD,
@@ -74,6 +87,10 @@
}
sourceLineNumber = lineNum;
+
+ for (int i = 0; i < REPLACE_CLASSES.length; i+= 2) {
+ line = line.replace(REPLACE_CLASSES[i], REPLACE_CLASSES[i+1]);
+ }
line = line.substring(1);
String[] parts = line.split(":");
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index afaed24..810e4d2 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -943,7 +943,7 @@
} else {
newDetailedState = DetailedState.FAILED;
}
- handleDisconnectedState(newDetailedState);
+ handleDisconnectedState(newDetailedState, true);
/**
* If we were associated with a network (networkId != -1),
* assume we reached this state because of a failed attempt
@@ -965,7 +965,7 @@
} else if (newState == SupplicantState.DISCONNECTED) {
mHaveIpAddress = false;
if (isDriverStopped() || mDisconnectExpected) {
- handleDisconnectedState(DetailedState.DISCONNECTED);
+ handleDisconnectedState(DetailedState.DISCONNECTED, true);
} else {
scheduleDisconnect();
}
@@ -1072,16 +1072,10 @@
*/
if (wasDisconnectPending) {
DetailedState saveState = getNetworkInfo().getDetailedState();
- handleDisconnectedState(DetailedState.DISCONNECTED);
+ handleDisconnectedState(DetailedState.DISCONNECTED, false);
setDetailedStateInternal(saveState);
- } else {
- /**
- * stop DHCP to ensure there is a new IP address
- * even if the supplicant transitions without disconnect
- * COMPLETED -> ASSOCIATED -> COMPLETED
- */
- resetConnections(false);
}
+
configureInterface();
mLastBssid = result.BSSID;
mLastSsid = mWifiInfo.getSSID();
@@ -1116,7 +1110,7 @@
case EVENT_DEFERRED_DISCONNECT:
if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
- handleDisconnectedState(DetailedState.DISCONNECTED);
+ handleDisconnectedState(DetailedState.DISCONNECTED, true);
}
break;
@@ -1284,13 +1278,15 @@
* Reset our IP state and send out broadcasts following a disconnect.
* @param newState the {@code DetailedState} to set. Should be either
* {@code DISCONNECTED} or {@code FAILED}.
+ * @param disableInterface indicates whether the interface should
+ * be disabled
*/
- private void handleDisconnectedState(DetailedState newState) {
+ private void handleDisconnectedState(DetailedState newState, boolean disableInterface) {
if (mDisconnectPending) {
cancelDisconnect();
}
mDisconnectExpected = false;
- resetConnections(true);
+ resetConnections(disableInterface);
setDetailedState(newState);
sendNetworkStateChangeBroadcast(mLastBssid);
mWifiInfo.setBSSID(null);