Merge "Move the account picker class name to a config resource." into jb-mr2-dev
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 9fa7dbb..1c02960 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -63,6 +63,7 @@
private int mRepeat = 0;
private int mUserId;
+ private String mReceiverPermission;
private String mProfileFile;
@@ -332,6 +333,8 @@
mStartFlags |= ActivityManager.START_FLAG_OPENGL_TRACES;
} else if (opt.equals("--user")) {
mUserId = parseUserArg(nextArgRequired());
+ } else if (opt.equals("--receiver-permission")) {
+ mReceiverPermission = nextArgRequired();
} else {
System.err.println("Error: Unknown option: " + opt);
return null;
@@ -608,7 +611,7 @@
Intent intent = makeIntent(UserHandle.USER_ALL);
IntentReceiver receiver = new IntentReceiver();
System.out.println("Broadcasting: " + intent);
- mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, null,
+ mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, mReceiverPermission,
android.app.AppOpsManager.OP_NONE, true, false, mUserId);
receiver.waitForFinish();
}
@@ -1408,6 +1411,7 @@
"am broadcast: send a broadcast Intent. Options are:\n" +
" --user <USER_ID> | all | current: Specify which user to send to; if not\n" +
" specified then send to all users.\n" +
+ " --receiver-permission <PERMISSION>: Require receiver to hold permission.\n" +
"\n" +
"am instrument: start an Instrumentation. Typically this target <COMPONENT>\n" +
" is the form <TEST_PACKAGE>/<RUNNER_CLASS>. Options are:\n" +
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 37804e9..20114cc 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -261,6 +261,7 @@
context.getContentResolver()) : null;
try {
intent.setAllowFds(false);
+ intent.migrateExtraStreamToClipData();
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
@@ -285,6 +286,7 @@
context.getContentResolver()) : null;
try {
intent.setAllowFds(false);
+ intent.migrateExtraStreamToClipData();
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 4e89dec..5d7d677 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -68,7 +68,7 @@
try {
// Ensure the cursor window is filled.
cursor.getCount();
- registerContentObserver(cursor, mObserver);
+ cursor.registerContentObserver(mObserver);
} catch (RuntimeException ex) {
cursor.close();
throw ex;
@@ -93,14 +93,6 @@
}
}
- /**
- * Registers an observer to get notifications from the content provider
- * when the cursor needs to be refreshed.
- */
- void registerContentObserver(Cursor cursor, ContentObserver observer) {
- cursor.registerContentObserver(mObserver);
- }
-
/* Runs on the UI thread */
@Override
public void deliverResult(Cursor cursor) {
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 2d67875..d59c7b8 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -284,6 +284,12 @@
flags, resultReceiver));
}
+ @Override
+ public void removeSoftInputMessages() {
+ mCaller.removeMessages(DO_SHOW_SOFT_INPUT);
+ mCaller.removeMessages(DO_HIDE_SOFT_INPUT);
+ }
+
public void changeInputMethodSubtype(InputMethodSubtype subtype) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
subtype));
diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java
index fd22b10..518dd4b 100644
--- a/core/java/android/net/DhcpStateMachine.java
+++ b/core/java/android/net/DhcpStateMachine.java
@@ -50,7 +50,7 @@
public class DhcpStateMachine extends StateMachine {
private static final String TAG = "DhcpStateMachine";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
/* A StateMachine that controls the DhcpStateMachine */
@@ -77,7 +77,7 @@
RENEW
};
- private String mInterfaceName;
+ private final String mInterfaceName;
private boolean mRegisteredForPreDhcpNotification = false;
private static final int BASE = Protocol.BASE_DHCP;
@@ -349,6 +349,7 @@
private boolean runDhcp(DhcpAction dhcpAction) {
boolean success = false;
DhcpResults dhcpResults = new DhcpResults();
+ dhcpResults.linkProperties.mLogMe = true;
if (dhcpAction == DhcpAction.START) {
/* Stop any existing DHCP daemon before starting new */
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 5d13a18..9292e5f 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -20,6 +20,7 @@
import android.os.Parcelable;
import android.os.Parcel;
import android.text.TextUtils;
+import android.util.Log;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -57,6 +58,7 @@
private String mDomains;
private Collection<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
private ProxyProperties mHttpProxy;
+ public boolean mLogMe;
public static class CompareResult<T> {
public Collection<T> removed = new ArrayList<T>();
@@ -75,6 +77,7 @@
public LinkProperties() {
clear();
+ mLogMe = false;
}
// copy constructor instead of clone
@@ -91,6 +94,14 @@
}
public void setInterfaceName(String iface) {
+ if (mLogMe) {
+ Log.d("LinkProperties", "setInterfaceName from " + mIfaceName +
+ " to " + iface);
+ for (StackTraceElement e : Thread.currentThread().getStackTrace()) {
+ Log.d("LinkProperties", " " + e.toString());
+ }
+ }
+
mIfaceName = iface;
ArrayList<RouteInfo> newRoutes = new ArrayList<RouteInfo>(mRoutes.size());
for (RouteInfo route : mRoutes) {
@@ -166,6 +177,13 @@
}
public void clear() {
+ if (mLogMe) {
+ Log.d("LinkProperties", "clear from " + mIfaceName);
+ for (StackTraceElement e : Thread.currentThread().getStackTrace()) {
+ Log.d("LinkProperties", " " + e.toString());
+ }
+ }
+
mIfaceName = null;
mLinkAddresses.clear();
mDnses.clear();
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 9955bc1..0492d29 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -39,7 +39,6 @@
private native void nativeUnlockCanvasAndPost(int nativeObject, Canvas canvas);
private static native void nativeRelease(int nativeObject);
- private static native void nativeDestroy(int nativeObject);
private static native boolean nativeIsValid(int nativeObject);
private static native boolean nativeIsConsumerRunningBehind(int nativeObject);
private static native int nativeCopyFrom(int nativeObject, int surfaceControlNativeObject);
@@ -106,7 +105,6 @@
* @hide
*/
public Surface() {
- mCloseGuard.open("release");
}
/**
@@ -135,6 +133,7 @@
mCloseGuard.open("release");
}
+ /* called from android_view_Surface_createFromIGraphicBufferProducer() */
private Surface(int nativeObject) {
mNativeObject = nativeObject;
mCloseGuard.open("release");
@@ -146,9 +145,7 @@
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
- if (mNativeObject != 0) {
- nativeRelease(mNativeObject);
- }
+ release();
} finally {
super.finalize();
}
@@ -175,12 +172,7 @@
* @hide
*/
public void destroy() {
- if (mNativeObject != 0) {
- nativeDestroy(mNativeObject);
- mNativeObject = 0;
- mGenerationId++;
- }
- mCloseGuard.close();
+ release();
}
/**
@@ -287,6 +279,10 @@
"SurfaceControl native object is null. Are you using a released SurfaceControl?");
}
mNativeObject = nativeCopyFrom(mNativeObject, other.mNativeObject);
+ if (mNativeObject == 0) {
+ // nativeCopyFrom released our reference
+ mCloseGuard.close();
+ }
mGenerationId++;
}
@@ -308,11 +304,15 @@
nativeRelease(mNativeObject);
}
// transfer the reference from other to us
+ if (other.mNativeObject != 0 && mNativeObject == 0) {
+ mCloseGuard.open("release");
+ }
mNativeObject = other.mNativeObject;
mGenerationId++;
other.mNativeObject = 0;
other.mGenerationId++;
+ other.mCloseGuard.close();
}
}
@@ -326,7 +326,11 @@
throw new IllegalArgumentException("source must not be null");
}
mName = source.readString();
- mNativeObject = nativeReadFromParcel(mNativeObject, source);
+ int nativeObject = nativeReadFromParcel(mNativeObject, source);
+ if (nativeObject !=0 && mNativeObject == 0) {
+ mCloseGuard.open("release");
+ }
+ mNativeObject = nativeObject;
mGenerationId++;
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 792188b..96ef0b4 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -272,7 +272,7 @@
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
/**
- * Window type: window for showing media (e.g. video). These windows
+ * Window type: window for showing media (such as video). These windows
* are displayed behind their attached window.
*/
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1;
@@ -584,14 +584,14 @@
/** Window flag: this window can never receive touch events. */
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
- /** Window flag: Even when this window is focusable (its
- * {@link #FLAG_NOT_FOCUSABLE is not set), allow any pointer events
+ /** Window flag: even when this window is focusable (its
+ * {@link #FLAG_NOT_FOCUSABLE} is not set), allow any pointer events
* outside of the window to be sent to the windows behind it. Otherwise
* it will consume all pointer events itself, regardless of whether they
* are inside of the window. */
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
- /** Window flag: When set, if the device is asleep when the touch
+ /** Window flag: when set, if the device is asleep when the touch
* screen is pressed, you will receive this first touch event. Usually
* the first touch event is consumed by the system since the user can
* not see what they are pressing on.
@@ -603,7 +603,7 @@
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
/** Window flag: place the window within the entire screen, ignoring
- * decorations around the border (a.k.a. the status bar). The
+ * decorations around the border (such as the status bar). The
* window must correctly position its contents to take the screen
* decoration into account. This flag is normally set for you
* by Window as described in {@link Window#setFlags}. */
@@ -613,7 +613,7 @@
public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;
/**
- * Window flag: Hide all screen decorations (e.g. status bar) while
+ * Window flag: hide all screen decorations (such as the status bar) while
* this window is displayed. This allows the window to use the entire
* display space for itself -- the status bar will be hidden when
* an app window with this flag set is on the top layer.
@@ -631,8 +631,8 @@
*/
public static final int FLAG_FULLSCREEN = 0x00000400;
- /** Window flag: Override {@link #FLAG_FULLSCREEN and force the
- * screen decorations (such as status bar) to be shown. */
+ /** Window flag: override {@link #FLAG_FULLSCREEN} and force the
+ * screen decorations (such as the status bar) to be shown. */
public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
/** Window flag: turn on dithering when compositing this window to
@@ -641,7 +641,7 @@
@Deprecated
public static final int FLAG_DITHER = 0x00001000;
- /** Window flag: Treat the content of the window as secure, preventing
+ /** Window flag: treat the content of the window as secure, preventing
* it from appearing in screenshots or from being viewed on non-secure
* displays.
*
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index c7fcab8..c2a7fc7 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -33,26 +33,28 @@
* Service).
* {@hide}
*/
-oneway interface IInputMethod {
- void attachToken(IBinder token);
+interface IInputMethod {
+ oneway void attachToken(IBinder token);
- void bindInput(in InputBinding binding);
+ oneway void bindInput(in InputBinding binding);
- void unbindInput();
+ oneway void unbindInput();
- void startInput(in IInputContext inputContext, in EditorInfo attribute);
+ oneway void startInput(in IInputContext inputContext, in EditorInfo attribute);
- void restartInput(in IInputContext inputContext, in EditorInfo attribute);
+ oneway void restartInput(in IInputContext inputContext, in EditorInfo attribute);
- void createSession(IInputMethodCallback callback);
+ oneway void createSession(IInputMethodCallback callback);
- void setSessionEnabled(IInputMethodSession session, boolean enabled);
+ oneway void setSessionEnabled(IInputMethodSession session, boolean enabled);
- void revokeSession(IInputMethodSession session);
+ oneway void revokeSession(IInputMethodSession session);
- void showSoftInput(int flags, in ResultReceiver resultReceiver);
+ oneway void showSoftInput(int flags, in ResultReceiver resultReceiver);
- void hideSoftInput(int flags, in ResultReceiver resultReceiver);
+ oneway void hideSoftInput(int flags, in ResultReceiver resultReceiver);
- void changeInputMethodSubtype(in InputMethodSubtype subtype);
+ void removeSoftInputMessages();
+
+ oneway void changeInputMethodSubtype(in InputMethodSubtype subtype);
}
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 9c78abe..4671282 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -151,11 +151,6 @@
sur->decStrong(&sRefBaseOwner);
}
-static void nativeDestroy(JNIEnv* env, jclass clazz, jint nativeObject) {
- sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject));
- sur->decStrong(&sRefBaseOwner);
-}
-
static jboolean nativeIsValid(JNIEnv* env, jclass clazz, jint nativeObject) {
sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject));
return isSurfaceValid(sur) ? JNI_TRUE : JNI_FALSE;
@@ -332,19 +327,32 @@
doThrowNPE(env);
return 0;
}
+
sp<Surface> self(reinterpret_cast<Surface *>(nativeObject));
- if (self != NULL) {
- self->decStrong(&sRefBaseOwner);
+ sp<IBinder> binder(parcel->readStrongBinder());
+
+ // update the Surface only if the underlying IGraphicBufferProducer
+ // has changed.
+ if (self != NULL
+ && (self->getIGraphicBufferProducer()->asBinder() == binder)) {
+ // same IGraphicBufferProducer, return ourselves
+ return int(self.get());
}
sp<Surface> sur;
- sp<IGraphicBufferProducer> gbp(
- interface_cast<IGraphicBufferProducer>(parcel->readStrongBinder()));
+ sp<IGraphicBufferProducer> gbp(interface_cast<IGraphicBufferProducer>(binder));
if (gbp != NULL) {
+ // we have a new IGraphicBufferProducer, create a new Surface for it
sur = new Surface(gbp);
+ // and keep a reference before passing to java
sur->incStrong(&sRefBaseOwner);
}
+ if (self != NULL) {
+ // and loose the java reference to ourselves
+ self->decStrong(&sRefBaseOwner);
+ }
+
return int(sur.get());
}
@@ -366,8 +374,6 @@
(void*)nativeCreateFromSurfaceTexture },
{"nativeRelease", "(I)V",
(void*)nativeRelease },
- {"nativeDestroy", "(I)V",
- (void*)nativeDestroy },
{"nativeIsValid", "(I)Z",
(void*)nativeIsValid },
{"nativeIsConsumerRunningBehind", "(I)Z",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8a53cc3..5a1c0f8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -169,6 +169,7 @@
<protected-broadcast android:name="android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE" />
<protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />
<protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" />
+ <protected-broadcast android:name="android.intent.action.BUGREPORT_FINISHED" />
<protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
<protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" />
diff --git a/docs/html/index.jd b/docs/html/index.jd
index f2df7be..ec0469c 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -19,12 +19,11 @@
<div class="content-right col-5">
<h1>Google I/O 2013</h1>
<p>Android will be at Google I/O on May 15-17, 2013, with sessions covering a variety of topics
- such as design, performance, and how to extend your app with the latest Android features.
- Registration opens on March 13, 2013 at 7:00 AM PDT (GMT-7).</p>
+ such as design, performance, and how to extend your app with the latest Android features.</p>
<p>For more information about event details and planned sessions,
stay tuned to <a
href="http://google.com/+GoogleDevelopers">+Google Developers</a>.</p>
- <p><a href="https://developers.google.com/events/io/register" class="button">Register here</a></p>
+ <p><a href="https://developers.google.com/events/io/" class="button">Learn more</a></p>
</div>
</li>
<li class="item carousel-home">
diff --git a/packages/Shell/Android.mk b/packages/Shell/Android.mk
index f993ab5..fc4c0f5 100644
--- a/packages/Shell/Android.mk
+++ b/packages/Shell/Android.mk
@@ -5,6 +5,8 @@
LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
LOCAL_PACKAGE_NAME := Shell
LOCAL_CERTIFICATE := platform
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b42db45..ffb4c20 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -68,7 +68,32 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.BLUETOOTH_STACK" />
-
- <application android:hasCode="false" android:label="@string/app_label">
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+
+ <application android:label="@string/app_label">
+ <provider
+ android:name="android.support.v4.content.FileProvider"
+ android:authorities="com.android.shell"
+ android:grantUriPermissions="true"
+ android:exported="false">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_provider_paths" />
+ </provider>
+
+ <activity
+ android:name=".BugreportWarningActivity"
+ android:theme="@*android:style/Theme.Holo.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true"
+ android:exported="false" />
+
+ <receiver
+ android:name=".BugreportReceiver"
+ android:permission="android.permission.DUMP">
+ <intent-filter>
+ <action android:name="android.intent.action.BUGREPORT_FINISHED" />
+ </intent-filter>
+ </receiver>
</application>
</manifest>
diff --git a/packages/Shell/res/layout/confirm_repeat.xml b/packages/Shell/res/layout/confirm_repeat.xml
new file mode 100644
index 0000000..dc250d6
--- /dev/null
+++ b/packages/Shell/res/layout/confirm_repeat.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="16dip"
+ android:paddingEnd="16dip"
+ android:paddingTop="8dip"
+ android:paddingBottom="16dip"
+ android:orientation="vertical">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/bugreport_confirm"
+ android:paddingBottom="16dip"
+ style="?android:attr/textAppearanceMedium" />
+ <CheckBox
+ android:id="@android:id/checkbox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/bugreport_confirm_repeat" />
+</LinearLayout>
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 50610d5..e5606c7 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -16,4 +16,14 @@
<resources>
<string name="app_label">Shell</string>
+
+ <!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] -->
+ <string name="bugreport_finished_title">Bug report captured</string>
+ <!-- Text of notification indicating that touching will share the captured bugreport. [CHAR LIMIT=100] -->
+ <string name="bugreport_finished_text">Touch to share your bug report</string>
+
+ <!-- Body of dialog informing user about contents of a bugreport. [CHAR LIMIT=NONE] -->
+ <string name="bugreport_confirm">Bug reports contain data from the system\'s various log files, including personal and private information. Only share bug reports with apps and people you trust.</string>
+ <!-- Checkbox that indicates this dialog should be shown again when the next bugreport is taken. [CHAR LIMIT=50] -->
+ <string name="bugreport_confirm_repeat">Show this message next time</string>
</resources>
diff --git a/packages/Shell/res/xml/file_provider_paths.xml b/packages/Shell/res/xml/file_provider_paths.xml
new file mode 100644
index 0000000..225c757
--- /dev/null
+++ b/packages/Shell/res/xml/file_provider_paths.xml
@@ -0,0 +1,3 @@
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+ <files-path name="bugreports" path="bugreports/" />
+</paths>
diff --git a/packages/Shell/src/com/android/shell/BugreportPrefs.java b/packages/Shell/src/com/android/shell/BugreportPrefs.java
new file mode 100644
index 0000000..3748e89
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportPrefs.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 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.shell;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * Preferences related to bug reports.
+ */
+public class BugreportPrefs {
+ private static final String PREFS_BUGREPORT = "bugreports";
+
+ private static final String KEY_WARNING_STATE = "warning-state";
+
+ public static final int STATE_UNKNOWN = 0;
+ public static final int STATE_SHOW = 1;
+ public static final int STATE_HIDE = 2;
+
+ public static int getWarningState(Context context, int def) {
+ final SharedPreferences prefs = context.getSharedPreferences(
+ PREFS_BUGREPORT, Context.MODE_PRIVATE);
+ return prefs.getInt(KEY_WARNING_STATE, def);
+ }
+
+ public static void setWarningState(Context context, int value) {
+ final SharedPreferences prefs = context.getSharedPreferences(
+ PREFS_BUGREPORT, Context.MODE_PRIVATE);
+ prefs.edit().putInt(KEY_WARNING_STATE, value).apply();
+ }
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
new file mode 100644
index 0000000..3b1ebf4
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2013 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.shell;
+
+import static com.android.shell.BugreportPrefs.STATE_SHOW;
+import static com.android.shell.BugreportPrefs.getWarningState;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.SystemProperties;
+import android.support.v4.content.FileProvider;
+import android.util.Log;
+import android.util.Patterns;
+
+import com.google.android.collect.Lists;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Receiver that handles finished bugreports, usually by attaching them to an
+ * {@link Intent#ACTION_SEND}.
+ */
+public class BugreportReceiver extends BroadcastReceiver {
+ private static final String TAG = "Shell";
+
+ private static final String AUTHORITY = "com.android.shell";
+
+ private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
+ private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
+
+ /**
+ * Number of bugreports to retain before deleting the oldest; 4 reports and
+ * 4 screenshots are roughly 17MB of disk space.
+ */
+ private static final int NUM_OLD_FILES = 8;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
+ final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+
+ // Files are kept on private storage, so turn into Uris that we can
+ // grant temporary permissions for.
+ final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile);
+ final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile);
+
+ Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
+ Intent notifIntent;
+
+ // Send through warning dialog by default
+ if (getWarningState(context, STATE_SHOW) == STATE_SHOW) {
+ notifIntent = buildWarningIntent(context, sendIntent);
+ } else {
+ notifIntent = sendIntent;
+ }
+ notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ final Notification.Builder builder = new Notification.Builder(context);
+ builder.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb);
+ builder.setContentTitle(context.getString(R.string.bugreport_finished_title));
+ builder.setContentText(context.getString(R.string.bugreport_finished_text));
+ builder.setContentIntent(PendingIntent.getActivity(
+ context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT));
+ builder.setAutoCancel(true);
+ NotificationManager.from(context).notify(TAG, 0, builder.build());
+
+ // Clean up older bugreports in background
+ final PendingResult result = goAsync();
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ deleteOlderFiles(bugreportFile.getParentFile(), NUM_OLD_FILES);
+ result.finish();
+ return null;
+ }
+ }.execute();
+ }
+
+ private static Intent buildWarningIntent(Context context, Intent sendIntent) {
+ final Intent intent = new Intent(context, BugreportWarningActivity.class);
+ intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
+ return intent;
+ }
+
+ /**
+ * Build {@link Intent} that can be used to share the given bugreport.
+ */
+ private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
+ final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setType("application/vnd.android.bugreport");
+
+ intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
+ intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description"));
+
+ final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri);
+ intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
+
+ final Account sendToAccount = findSendToAccount(context);
+ if (sendToAccount != null) {
+ intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
+ }
+
+ return intent;
+ }
+
+ /**
+ * Find the best matching {@link Account} based on build properties.
+ */
+ private static Account findSendToAccount(Context context) {
+ final AccountManager am = (AccountManager) context.getSystemService(
+ Context.ACCOUNT_SERVICE);
+
+ String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
+ if (!preferredDomain.startsWith("@")) {
+ preferredDomain = "@" + preferredDomain;
+ }
+
+ final Account[] accounts = am.getAccounts();
+ Account foundAccount = null;
+ for (Account account : accounts) {
+ if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
+ if (!preferredDomain.isEmpty()) {
+ // if we have a preferred domain and it matches, return; otherwise keep
+ // looking
+ if (account.name.endsWith(preferredDomain)) {
+ return account;
+ } else {
+ foundAccount = account;
+ }
+ // if we don't have a preferred domain, just return since it looks like
+ // an email address
+ } else {
+ return account;
+ }
+ }
+ }
+ return foundAccount;
+ }
+
+ /**
+ * Delete the oldest files in given directory until only the requested
+ * number remain.
+ */
+ private static void deleteOlderFiles(File dir, int retainNum) {
+ final File[] files = dir.listFiles();
+ if (files == null) return;
+
+ Arrays.sort(files, new ModifiedComparator());
+ for (int i = retainNum; i < files.length; i++) {
+ Log.d(TAG, "Deleting old file " + files[i]);
+ files[i].delete();
+ }
+ }
+
+ private static class ModifiedComparator implements Comparator<File> {
+ @Override
+ public int compare(File lhs, File rhs) {
+ return (int) (rhs.lastModified() - lhs.lastModified());
+ }
+ }
+
+ private static File getFileExtra(Intent intent, String key) {
+ final String path = intent.getStringExtra(key);
+ if (path != null) {
+ return new File(path);
+ } else {
+ return null;
+ }
+ }
+
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
new file mode 100644
index 0000000..a1d879a
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 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.shell;
+
+import static com.android.shell.BugreportPrefs.STATE_HIDE;
+import static com.android.shell.BugreportPrefs.STATE_SHOW;
+import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
+import static com.android.shell.BugreportPrefs.getWarningState;
+import static com.android.shell.BugreportPrefs.setWarningState;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.widget.CheckBox;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+/**
+ * Dialog that warns about contents of a bugreport.
+ */
+public class BugreportWarningActivity extends AlertActivity
+ implements DialogInterface.OnClickListener {
+
+ private Intent mSendIntent;
+ private CheckBox mConfirmRepeat;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mSendIntent = getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
+
+ // We need to touch the extras to unpack them so they get migrated to
+ // ClipData correctly.
+ mSendIntent.hasExtra(Intent.EXTRA_STREAM);
+
+ final AlertController.AlertParams ap = mAlertParams;
+ ap.mView = LayoutInflater.from(this).inflate(R.layout.confirm_repeat, null);
+ ap.mPositiveButtonText = getString(android.R.string.ok);
+ ap.mNegativeButtonText = getString(android.R.string.cancel);
+ ap.mPositiveButtonListener = this;
+ ap.mNegativeButtonListener = this;
+
+ mConfirmRepeat = (CheckBox) ap.mView.findViewById(android.R.id.checkbox);
+ mConfirmRepeat.setChecked(getWarningState(this, STATE_UNKNOWN) == STATE_SHOW);
+
+ setupAlert();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == AlertDialog.BUTTON_POSITIVE) {
+ // Remember confirm state, and launch target
+ setWarningState(this, mConfirmRepeat.isChecked() ? STATE_SHOW : STATE_HIDE);
+ startActivity(mSendIntent);
+ }
+
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index f3eecf2..627235f 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -627,10 +627,26 @@
}
mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
+ if (mEglContext == EGL_NO_CONTEXT) {
+ throw new RuntimeException("createContext failed " +
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
+ }
+
+ int attribs[] = {
+ EGL_WIDTH, 1,
+ EGL_HEIGHT, 1,
+ EGL_NONE
+ };
+ EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
+ mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext);
int[] maxSize = new int[1];
Rect frame = surfaceHolder.getSurfaceFrame();
glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0);
+
+ mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ mEgl.eglDestroySurface(mEglDisplay, tmpSurface);
+
if(frame.width() > maxSize[0] || frame.height() > maxSize[0]) {
mEgl.eglDestroyContext(mEglDisplay, mEglContext);
mEgl.eglTerminate(mEglDisplay);
@@ -639,9 +655,8 @@
maxSize[0] + "x" + maxSize[0]);
return false;
}
-
+
mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null);
-
if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
int error = mEgl.eglGetError();
if (error == EGL_BAD_NATIVE_WINDOW || error == EGL_BAD_ALLOC) {
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java b/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java
index cd7324c..c68bab5 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java
@@ -20,6 +20,7 @@
import android.content.Intent;
import android.os.PowerManager;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.telephony.TelephonyManager;
import android.util.AttributeSet;
import android.view.View;
@@ -104,7 +105,8 @@
Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- getContext().startActivity(intent);
+ getContext().startActivityAsUser(intent,
+ new UserHandle(mLockPatternUtils.getCurrentUser()));
}
}
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index d0048bf..14841af 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -1199,7 +1199,7 @@
mCurId = info.getId();
mCurToken = new Binder();
try {
- if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
+ if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
mIWindowManager.addWindowToken(mCurToken,
WindowManager.LayoutParams.TYPE_INPUT_METHOD);
} catch (RemoteException e) {
@@ -1237,14 +1237,21 @@
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mMethodMap) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
+ IInputMethod prevMethod = mCurMethod;
mCurMethod = IInputMethod.Stub.asInterface(service);
if (mCurToken == null) {
Slog.w(TAG, "Service connected without a token!");
unbindCurrentMethodLocked(false, false);
return;
}
- // Remove commands relating to the previous service. Otherwise WindowManagerService
- // will reject the command because the token attached to these messages is invalid.
+ // Remove messages relating to the previous service. Otherwise WindowManagerService
+ // will throw a BadTokenException because the old token is being removed.
+ if (prevMethod != null) {
+ try {
+ prevMethod.removeSoftInputMessages();
+ } catch (RemoteException e) {
+ }
+ }
mCaller.removeMessages(MSG_SHOW_SOFT_INPUT);
mCaller.removeMessages(MSG_HIDE_SOFT_INPUT);
if (true || DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
@@ -2309,8 +2316,7 @@
try {
if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
+ msg.arg1 + ", " + args.arg2 + ")");
- ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
- (ResultReceiver)args.arg2);
+ ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
} catch (RemoteException e) {
}
args.recycle();
@@ -2320,8 +2326,7 @@
try {
if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
+ args.arg2 + ")");
- ((IInputMethod)args.arg1).hideSoftInput(0,
- (ResultReceiver)args.arg2);
+ ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
} catch (RemoteException e) {
}
args.recycle();
diff --git a/services/java/com/android/server/power/ElectronBeam.java b/services/java/com/android/server/power/ElectronBeam.java
index 457e92d..4a74149 100644
--- a/services/java/com/android/server/power/ElectronBeam.java
+++ b/services/java/com/android/server/power/ElectronBeam.java
@@ -82,7 +82,7 @@
private int mDisplayHeight; // real height, not rotated
private SurfaceSession mSurfaceSession;
private SurfaceControl mSurfaceControl;
- private final Surface mSurface = new Surface();
+ private Surface mSurface;
private NaturalSurfaceLayout mSurfaceLayout;
private EGLDisplay mEglDisplay;
private EGLConfig mEglConfig;
@@ -519,6 +519,7 @@
mSurfaceControl.setLayerStack(mDisplayLayerStack);
mSurfaceControl.setSize(mDisplayWidth, mDisplayHeight);
+ mSurface = new Surface();
mSurface.copyFrom(mSurfaceControl);
mSurfaceLayout = new NaturalSurfaceLayout(mDisplayManager, mSurfaceControl);
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index fbdd333..dffb617 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 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.
@@ -32,6 +32,7 @@
import android.test.InstrumentationTestRunner;
import android.util.Log;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -48,16 +49,22 @@
public class AppLaunch extends InstrumentationTestCase {
private static final int JOIN_TIMEOUT = 10000;
- private static final String TAG = "AppLaunch";
+ private static final String TAG = AppLaunch.class.getSimpleName();
private static final String KEY_APPS = "apps";
+ private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations";
+ private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 7500; //7.5s to allow app to idle
+ private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; //750ms idle for non initial launches
+ private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 2000; //2s between launching apps
private Map<String, Intent> mNameToIntent;
private Map<String, String> mNameToProcess;
private Map<String, String> mNameToResultKey;
-
+ private Map<String, Long> mNameToLaunchTime;
private IActivityManager mAm;
+ private int mLaunchIterations = 10;
+ private Bundle mResult = new Bundle();
- public void testMeasureStartUpTime() throws RemoteException {
+ public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException {
InstrumentationTestRunner instrumentation =
(InstrumentationTestRunner)getInstrumentation();
Bundle args = instrumentation.getArguments();
@@ -66,25 +73,59 @@
createMappings();
parseArgs(args);
- Bundle results = new Bundle();
+ // do initial app launch, without force stopping
for (String app : mNameToResultKey.keySet()) {
- try {
- startApp(app, results);
- sleep(750);
- closeApp(app);
- sleep(2000);
- } catch (NameNotFoundException e) {
- Log.i(TAG, "Application " + app + " not found");
+ long launchTime = startApp(app, false);
+ if (launchTime <=0 ) {
+ mNameToLaunchTime.put(app, -1L);
+ // simply pass the app if launch isn't successful
+ // error should have already been logged by startApp
+ continue;
}
-
+ sleep(INITIAL_LAUNCH_IDLE_TIMEOUT);
+ closeApp(app, false);
+ sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
}
- instrumentation.sendStatus(0, results);
+ // do the real app launch now
+ for (int i = 0; i < mLaunchIterations; i++) {
+ for (String app : mNameToResultKey.keySet()) {
+ long totalLaunchTime = mNameToLaunchTime.get(app);
+ long launchTime = 0;
+ if (totalLaunchTime < 0) {
+ // skip if the app has previous failures
+ continue;
+ }
+ launchTime = startApp(app, true);
+ if (launchTime <= 0) {
+ // if it fails once, skip the rest of the launches
+ mNameToLaunchTime.put(app, -1L);
+ continue;
+ }
+ totalLaunchTime += launchTime;
+ mNameToLaunchTime.put(app, totalLaunchTime);
+ sleep(POST_LAUNCH_IDLE_TIMEOUT);
+ closeApp(app, true);
+ sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
+ }
+ }
+ for (String app : mNameToResultKey.keySet()) {
+ long totalLaunchTime = mNameToLaunchTime.get(app);
+ if (totalLaunchTime != -1) {
+ mResult.putDouble(mNameToResultKey.get(app),
+ ((double) totalLaunchTime) / mLaunchIterations);
+ }
+ }
+ instrumentation.sendStatus(0, mResult);
}
private void parseArgs(Bundle args) {
mNameToResultKey = new LinkedHashMap<String, String>();
+ mNameToLaunchTime = new HashMap<String, Long>();
+ String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS);
+ if (launchIterations != null) {
+ mLaunchIterations = Integer.parseInt(launchIterations);
+ }
String appList = args.getString(KEY_APPS);
-
if (appList == null)
return;
@@ -97,6 +138,7 @@
}
mNameToResultKey.put(parts[0], parts[1]);
+ mNameToLaunchTime.put(parts[0], 0L);
}
}
@@ -118,23 +160,26 @@
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
startIntent.setClassName(ri.activityInfo.packageName,
ri.activityInfo.name);
- mNameToIntent.put(ri.loadLabel(pm).toString(), startIntent);
- mNameToProcess.put(ri.loadLabel(pm).toString(),
- ri.activityInfo.processName);
+ String appName = ri.loadLabel(pm).toString();
+ if (appName != null) {
+ mNameToIntent.put(appName, startIntent);
+ mNameToProcess.put(appName, ri.activityInfo.processName);
+ }
}
}
}
- private void startApp(String appName, Bundle results)
+ private long startApp(String appName, boolean forceStopBeforeLaunch)
throws NameNotFoundException, RemoteException {
Log.i(TAG, "Starting " + appName);
Intent startIntent = mNameToIntent.get(appName);
if (startIntent == null) {
Log.w(TAG, "App does not exist: " + appName);
- return;
+ mResult.putString(mNameToResultKey.get(appName), "App does not exist");
+ return -1;
}
- AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent);
+ AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch);
Thread t = new Thread(runnable);
t.start();
try {
@@ -143,27 +188,38 @@
// ignore
}
WaitResult result = runnable.getResult();
- if(t.isAlive() || (result != null && result.result != ActivityManager.START_SUCCESS)) {
+ // report error if any of the following is true:
+ // * launch thread is alive
+ // * result is not null, but:
+ // * result is not START_SUCESS
+ // * or in case of no force stop, result is not TASK_TO_FRONT either
+ if (t.isAlive() || (result != null
+ && ((result.result != ActivityManager.START_SUCCESS)
+ && (!forceStopBeforeLaunch
+ && result.result != ActivityManager.START_TASK_TO_FRONT)))) {
Log.w(TAG, "Assuming app " + appName + " crashed.");
- reportError(appName, mNameToProcess.get(appName), results);
- return;
+ reportError(appName, mNameToProcess.get(appName));
+ return -1;
}
- results.putString(mNameToResultKey.get(appName), String.valueOf(result.thisTime));
+ return result.thisTime;
}
- private void closeApp(String appName) {
+ private void closeApp(String appName, boolean forceStopApp) {
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
getInstrumentation().getContext().startActivity(homeIntent);
- Intent startIntent = mNameToIntent.get(appName);
- if (startIntent != null) {
- String packageName = startIntent.getComponent().getPackageName();
- try {
- mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
- } catch (RemoteException e) {
- Log.w(TAG, "Error closing app", e);
+ sleep(POST_LAUNCH_IDLE_TIMEOUT);
+ if (forceStopApp) {
+ Intent startIntent = mNameToIntent.get(appName);
+ if (startIntent != null) {
+ String packageName = startIntent.getComponent().getPackageName();
+ try {
+ mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error closing app", e);
+ }
}
}
}
@@ -176,7 +232,7 @@
}
}
- private void reportError(String appName, String processName, Bundle results) {
+ private void reportError(String appName, String processName) {
ActivityManager am = (ActivityManager) getInstrumentation()
.getContext().getSystemService(Context.ACTIVITY_SERVICE);
List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState();
@@ -186,12 +242,12 @@
continue;
Log.w(TAG, appName + " crashed: " + crash.shortMsg);
- results.putString(mNameToResultKey.get(appName), crash.shortMsg);
+ mResult.putString(mNameToResultKey.get(appName), crash.shortMsg);
return;
}
}
- results.putString(mNameToResultKey.get(appName),
+ mResult.putString(mNameToResultKey.get(appName),
"Crashed for unknown reason");
Log.w(TAG, appName
+ " not found in process list, most likely it is crashed");
@@ -200,8 +256,11 @@
private class AppLaunchRunnable implements Runnable {
private Intent mLaunchIntent;
private IActivityManager.WaitResult mResult;
- public AppLaunchRunnable(Intent intent) {
+ private boolean mForceStopBeforeLaunch;
+
+ public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch) {
mLaunchIntent = intent;
+ mForceStopBeforeLaunch = forceStopBeforeLaunch;
}
public IActivityManager.WaitResult getResult() {
@@ -211,7 +270,9 @@
public void run() {
try {
String packageName = mLaunchIntent.getComponent().getPackageName();
- mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
+ if (mForceStopBeforeLaunch) {
+ mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
+ }
String mimeType = mLaunchIntent.getType();
if (mimeType == null && mLaunchIntent.getData() != null
&& "content".equals(mLaunchIntent.getData().getScheme())) {