Merge "Fix tests"
diff --git a/Android.mk b/Android.mk
index 53e892f..3f44d62 100644
--- a/Android.mk
+++ b/Android.mk
@@ -430,6 +430,14 @@
core/java/android/service/quicksettings/IQSService.aidl \
core/java/android/service/quicksettings/IQSTileService.aidl \
+# The following are native binders that need to go with the native component
+# at system/update_engine/binder_bindings/. Use relative path to refer to them.
+LOCAL_SRC_FILES += \
+ ../../system/update_engine/binder_bindings/android/os/IUpdateEngine.aidl \
+ ../../system/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl \
+
+LOCAL_AIDL_INCLUDES += system/update_engine/binder_bindings
+
# FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk
LOCAL_AIDL_INCLUDES += \
$(FRAMEWORKS_BASE_JAVA_SRC_DIRS) \
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index f3e1fc3..dedc8e5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3328,17 +3328,6 @@
}
r.activity.performResume();
- // If there is a pending relaunch that was requested when the activity was paused,
- // it will put the activity into paused state when it finally happens. Since the
- // activity resumed before being relaunched, we don't want that to happen, so we
- // need to clear the request to relaunch paused.
- for (int i = mRelaunchingActivities.size() - 1; i >= 0; i--) {
- final ActivityClientRecord relaunching = mRelaunchingActivities.get(i);
- if (relaunching.token == r.token && relaunching.startsNotResumed) {
- relaunching.startsNotResumed = false;
- }
- }
-
EventLog.writeEvent(LOG_AM_ON_RESUME_CALLED,
UserHandle.myUserId(), r.activity.getComponentName().getClassName());
@@ -3567,7 +3556,6 @@
private void handlePauseActivity(IBinder token, boolean finished,
boolean userLeaving, int configChanges, boolean dontReport, int seq) {
ActivityClientRecord r = mActivities.get(token);
- if (DEBUG_ORDER) Slog.d(TAG, "handlePauseActivity " + r + ", seq: " + seq);
if (!checkAndUpdateLifecycleSeq(seq, r, "pauseActivity")) {
return;
}
@@ -4207,7 +4195,6 @@
synchronized (mResourcesManager) {
for (int i=0; i<mRelaunchingActivities.size(); i++) {
ActivityClientRecord r = mRelaunchingActivities.get(i);
- if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: " + this + ", trying: " + r);
if (r.token == token) {
target = r;
if (pendingResults != null) {
@@ -4238,19 +4225,14 @@
}
if (target == null) {
- if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: target is null, fromServer:"
- + fromServer);
target = new ActivityClientRecord();
target.token = token;
target.pendingResults = pendingResults;
target.pendingIntents = pendingNewIntents;
target.mPreserveWindow = preserveWindow;
if (!fromServer) {
- final ActivityClientRecord existing = mActivities.get(token);
- if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: " + existing);
+ ActivityClientRecord existing = mActivities.get(token);
if (existing != null) {
- if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: paused= "
- + existing.paused);;
target.startsNotResumed = existing.paused;
target.overrideConfig = existing.overrideConfig;
}
@@ -4273,8 +4255,8 @@
target.pendingConfigChanges |= configChanges;
target.relaunchSeq = getLifecycleSeq();
}
- if (DEBUG_ORDER) Slog.d(TAG, "relaunchActivity " + ActivityThread.this + ", target "
- + target + " operation received seq: " + target.relaunchSeq);
+ if (DEBUG_ORDER) Slog.d(TAG, "relaunchActivity " + ActivityThread.this
+ + " operation received seq: " + target.relaunchSeq);
}
private void handleRelaunchActivity(ActivityClientRecord tmp) {
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 04caa8f..c96c9be 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -496,7 +496,10 @@
*/
@RequiresPermission(MANAGE_FINGERPRINT)
public void enroll(byte [] token, CancellationSignal cancel, int flags,
- EnrollmentCallback callback) {
+ EnrollmentCallback callback, int userId) {
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = getCurrentUserId();
+ }
if (callback == null) {
throw new IllegalArgumentException("Must supply an enrollment callback");
}
@@ -512,7 +515,7 @@
if (mService != null) try {
mEnrollmentCallback = callback;
- mService.enroll(mToken, token, getCurrentUserId(), mServiceReceiver, flags);
+ mService.enroll(mToken, token, userId, mServiceReceiver, flags);
} catch (RemoteException e) {
Log.w(TAG, "Remote exception in enroll: ", e);
if (callback != null) {
@@ -556,6 +559,21 @@
}
/**
+ * Sets the active user. This is meant to be used to select the current profile for enrollment
+ * to allow separate enrolled fingers for a work profile
+ * @param userId
+ * @hide
+ */
+ @RequiresPermission(MANAGE_FINGERPRINT)
+ public void setActiveUser(int userId) {
+ if (mService != null) try {
+ mService.setActiveUser(userId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Remote exception in setActiveUser: ", e);
+ }
+ }
+
+ /**
* Remove given fingerprint template from fingerprint hardware and/or protected storage.
* @param fp the fingerprint item to remove
* @param callback an optional callback to verify that fingerprint templates have been
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 690a751..43d5577 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -75,4 +75,7 @@
// Add a callback which gets notified when the fingerprint lockout period expired.
void addLockoutResetCallback(IFingerprintServiceLockoutResetCallback callback);
+
+ // Explicitly set the active user (for enrolling work profile)
+ void setActiveUser(int uid);
}
diff --git a/core/java/android/print/PrintJob.java b/core/java/android/print/PrintJob.java
index 777baab..66181e0 100644
--- a/core/java/android/print/PrintJob.java
+++ b/core/java/android/print/PrintJob.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import java.util.Objects;
+
/**
* This class represents a print job from the perspective of an
* application. It contains behavior methods for performing operations
@@ -30,11 +32,11 @@
*/
public final class PrintJob {
- private final PrintManager mPrintManager;
+ private final @NonNull PrintManager mPrintManager;
- private PrintJobInfo mCachedInfo;
+ private @NonNull PrintJobInfo mCachedInfo;
- PrintJob(PrintJobInfo info, PrintManager printManager) {
+ PrintJob(@NonNull PrintJobInfo info, @NonNull PrintManager printManager) {
mCachedInfo = info;
mPrintManager = printManager;
}
@@ -44,7 +46,7 @@
*
* @return The id.
*/
- public @NonNull PrintJobId getId() {
+ public @Nullable PrintJobId getId() {
return mCachedInfo.getId();
}
@@ -58,7 +60,7 @@
*
* @return The print job info.
*/
- public @Nullable PrintJobInfo getInfo() {
+ public @NonNull PrintJobInfo getInfo() {
if (isInImmutableState()) {
return mCachedInfo;
}
@@ -193,11 +195,17 @@
return false;
}
PrintJob other = (PrintJob) obj;
- return mCachedInfo.getId().equals(other.mCachedInfo.getId());
+ return Objects.equals(mCachedInfo.getId(), other.mCachedInfo.getId());
}
@Override
public int hashCode() {
- return mCachedInfo.getId().hashCode();
+ PrintJobId printJobId = mCachedInfo.getId();
+
+ if (printJobId == null) {
+ return 0;
+ } else {
+ return printJobId.hashCode();
+ }
}
}
diff --git a/core/java/android/print/PrintJobId.java b/core/java/android/print/PrintJobId.java
index a2ee02b..186ae9b 100644
--- a/core/java/android/print/PrintJobId.java
+++ b/core/java/android/print/PrintJobId.java
@@ -19,7 +19,8 @@
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
import java.util.UUID;
@@ -27,7 +28,7 @@
* This class represents the id of a print job.
*/
public final class PrintJobId implements Parcelable {
- private final String mValue;
+ private final @NonNull String mValue;
/**
* Creates a new instance.
@@ -45,7 +46,7 @@
*
* @hide
*/
- public PrintJobId(String value) {
+ public PrintJobId(@NonNull String value) {
mValue = value;
}
@@ -53,7 +54,7 @@
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + ((mValue != null) ? mValue.hashCode() : 0);
+ result = prime * result + mValue.hashCode();
return result;
}
@@ -69,7 +70,7 @@
return false;
}
PrintJobId other = (PrintJobId) obj;
- if (!TextUtils.equals(mValue, other.mValue)) {
+ if (!mValue.equals(other.mValue)) {
return false;
}
return true;
@@ -104,7 +105,7 @@
*
* @hide
*/
- public static PrintJobId unflattenFromString(String string) {
+ public static @NonNull PrintJobId unflattenFromString(@NonNull String string) {
return new PrintJobId(string);
}
@@ -112,7 +113,7 @@
new Parcelable.Creator<PrintJobId>() {
@Override
public PrintJobId createFromParcel(Parcel parcel) {
- return new PrintJobId(parcel.readString());
+ return new PrintJobId(Preconditions.checkNotNull(parcel.readString()));
}
@Override
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 21836b3..7e3a72f 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -244,7 +244,7 @@
*
* @return The id.
*/
- public @NonNull PrintJobId getId() {
+ public @Nullable PrintJobId getId() {
return mId;
}
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 34877e0..35b5016 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -86,20 +86,20 @@
}
static jint save(JNIEnv*, jobject, jlong canvasHandle, jint flagsHandle) {
- SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
+ SaveFlags::Flags flags = static_cast<SaveFlags::Flags>(flagsHandle);
return static_cast<jint>(get_canvas(canvasHandle)->save(flags));
}
static jint saveLayer(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t,
jfloat r, jfloat b, jlong paintHandle, jint flagsHandle) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
+ SaveFlags::Flags flags = static_cast<SaveFlags::Flags>(flagsHandle);
return static_cast<jint>(get_canvas(canvasHandle)->saveLayer(l, t, r, b, paint, flags));
}
static jint saveLayerAlpha(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t,
jfloat r, jfloat b, jint alpha, jint flagsHandle) {
- SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
+ SaveFlags::Flags flags = static_cast<SaveFlags::Flags>(flagsHandle);
return static_cast<jint>(get_canvas(canvasHandle)->saveLayerAlpha(l, t, r, b, alpha, flags));
}
@@ -351,7 +351,7 @@
if (CC_LIKELY(dstDensity == srcDensity || dstDensity == 0 || srcDensity == 0)) {
canvas->drawNinePatch(skiaBitmap, *chunk, left, top, right, bottom, paint);
} else {
- canvas->save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas->save(SaveFlags::MatrixClip);
SkScalar scale = dstDensity / (float)srcDensity;
canvas->translate(left, top);
@@ -390,7 +390,7 @@
canvas->drawBitmap(bitmap, left, top, paint);
}
} else {
- canvas->save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas->save(SaveFlags::MatrixClip);
SkScalar scale = canvasDensity / (float)bitmapDensity;
canvas->translate(left, top);
canvas->scale(scale, scale);
diff --git a/core/res/res/layout/app_error_dialog.xml b/core/res/res/layout/app_error_dialog.xml
new file mode 100644
index 0000000..aaa2dbc
--- /dev/null
+++ b/core/res/res/layout/app_error_dialog.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2015, 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:orientation="vertical"
+ android:paddingTop="@dimen/dialog_list_padding_vertical_material"
+ android:paddingBottom="@dimen/dialog_list_padding_vertical_material"
+>
+
+
+ <TextView
+ android:id="@+id/aerr_restart"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_restart"
+ style="@style/aerr_list_item"
+ />
+
+ <TextView
+ android:id="@+id/aerr_reset"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_reset"
+ style="@style/aerr_list_item"
+ />
+
+ <TextView
+ android:id="@+id/aerr_report"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_report"
+ style="@style/aerr_list_item"
+ />
+
+ <TextView
+ android:id="@+id/aerr_close"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_close"
+ style="@style/aerr_list_item"
+ />
+
+ <TextView
+ android:id="@+id/aerr_mute"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_mute"
+ style="@style/aerr_list_item"
+ />
+
+
+</LinearLayout>
diff --git a/core/res/res/layout/app_error_dialog_dont_show_again.xml b/core/res/res/layout/app_error_dialog_dont_show_again.xml
deleted file mode 100644
index ba79ecd..0000000
--- a/core/res/res/layout/app_error_dialog_dont_show_again.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2015, 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="wrap_content"
- android:layout_height="wrap_content"
- android:paddingStart="14dp"
- android:paddingEnd="10dp"
- android:gravity="center_vertical"
- >
- <CheckBox
- android:id="@+id/checkbox"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- />
- <TextView
- android:id="@+id/text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:layout_marginBottom="8dp"
- />
-
-</LinearLayout>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 28c76bb..2f2c5e4 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2588,16 +2588,27 @@
<!-- Text to display when there are no activities found to display in the
activity chooser. See the "Select an action" title. -->
<string name="noApplications">No apps can perform this action.</string>
- <!-- Title of the alert when an application has crashed. -->
- <string name="aerr_title"></string>
<!-- Text of the alert that is displayed when an application has crashed. -->
- <string name="aerr_application">Unfortunately, <xliff:g id="application">%1$s</xliff:g> has stopped.</string>
- <!-- Text of the alert that is displayed when an application has crashed. -->
- <string name="aerr_process">Unfortunately, the process <xliff:g id="process">%1$s</xliff:g> has
- stopped.</string>
- <!-- Text of the alert that is displayed when an application has crashed. -->
- <string name="aerr_process_silence">Silence crashes from <xliff:g id="process">%1$s</xliff:g>
- until reboot.</string>
+ <string name="aerr_application"><xliff:g id="application">%1$s</xliff:g> has stopped</string>
+ <!-- Text of the alert that is displayed when a process has crashed. -->
+ <string name="aerr_process"><xliff:g id="process">%1$s</xliff:g> has
+ stopped</string>
+ <!-- Text of the alert that is displayed when an application has crashed repeatedly. -->
+ <string name="aerr_application_repeated"><xliff:g id="application">%1$s</xliff:g> is repeatedly stopping</string>
+ <!-- Text of the alert that is displayed when a process has crashed repeatedly. -->
+ <string name="aerr_process_repeated"><xliff:g id="process">%1$s</xliff:g> is
+ repeatedly stopping</string>
+ <!-- Button that restarts a crashed application -->
+ <string name="aerr_restart">Restart app</string>
+ <!-- Button that clears cache and restarts a crashed application -->
+ <string name="aerr_reset">Reset and restart app</string>
+ <!-- Button that sends feedback about a crashed application -->
+ <string name="aerr_report">Send feedback</string>
+ <!-- Button that closes a crashed application -->
+ <string name="aerr_close">Close</string>
+ <!-- Button that mutes further crashes of the crashed application-->
+ <string name="aerr_mute">Mute</string>
+
<!-- Title of the alert when an application is not responding. -->
<string name="anr_title"></string>
<!-- Text of the alert that is displayed when an application is not responding. -->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index d5349b2..b660277 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1397,6 +1397,16 @@
<item name="pointerIconGrabbing">@drawable/pointer_grabbing_large_icon</item>
</style>
+ <!-- @hide -->
+ <style name="aerr_list_item" parent="Widget.Material.Light.TextView">
+ <item name="minHeight">?attr/listPreferredItemHeightSmall</item>
+ <item name="textAppearance">?attr/textAppearanceListItemSmall</item>
+ <item name="textColor">?attr/textColorAlertDialogListItem</item>
+ <item name="gravity">center_vertical</item>
+ <item name="paddingStart">?attr/listPreferredItemPaddingStart</item>
+ <item name="paddingEnd">?attr/listPreferredItemPaddingEnd</item>
+ </style>
+
<!-- Wifi dialog styles -->
<!-- @hide -->
<style name="wifi_item">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 57ff243..1f2c6e1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1763,7 +1763,7 @@
<java-symbol type="layout" name="launch_warning" />
<java-symbol type="layout" name="safe_mode" />
<java-symbol type="layout" name="simple_list_item_2_single_choice" />
- <java-symbol type="layout" name="app_error_dialog_dont_show_again" />
+ <java-symbol type="layout" name="app_error_dialog" />
<java-symbol type="plurals" name="wifi_available" />
<java-symbol type="plurals" name="wifi_available_detailed" />
<java-symbol type="string" name="accessibility_binding_label" />
@@ -1777,8 +1777,8 @@
<java-symbol type="string" name="remote_bugreport_progress_notification_message_can_cancel" />
<java-symbol type="string" name="aerr_application" />
<java-symbol type="string" name="aerr_process" />
- <java-symbol type="string" name="aerr_process_silence" />
- <java-symbol type="string" name="aerr_title" />
+ <java-symbol type="string" name="aerr_application_repeated" />
+ <java-symbol type="string" name="aerr_process_repeated" />
<java-symbol type="string" name="android_upgrading_fstrim" />
<java-symbol type="string" name="android_upgrading_apk" />
<java-symbol type="string" name="android_upgrading_complete" />
@@ -2429,6 +2429,12 @@
<java-symbol type="id" name="work_widget_app_icon" />
<java-symbol type="drawable" name="work_widget_mask_view_background" />
+ <java-symbol type="id" name="aerr_report" />
+ <java-symbol type="id" name="aerr_reset" />
+ <java-symbol type="id" name="aerr_restart" />
+ <java-symbol type="id" name="aerr_close" />
+ <java-symbol type="id" name="aerr_mute" />
+
<!-- Framework-private Material.DayNight styles. -->
<java-symbol type="style" name="Theme.Material.DayNight" />
<java-symbol type="style" name="Theme.Material.DayNight.DarkActionBar" />
diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h
index 0643a54..9dfe454 100644
--- a/libs/hwui/Canvas.h
+++ b/libs/hwui/Canvas.h
@@ -27,6 +27,22 @@
namespace android {
+namespace SaveFlags {
+
+// These must match the corresponding Canvas API constants.
+enum {
+ Matrix = 0x01,
+ Clip = 0x02,
+ HasAlphaLayer = 0x04,
+ ClipToLayer = 0x10,
+
+ // Helper constant
+ MatrixClip = Matrix | Clip,
+};
+typedef uint32_t Flags;
+
+} // namespace SaveFlags
+
class ANDROID_API Canvas {
public:
virtual ~Canvas() {};
@@ -70,16 +86,17 @@
// ----------------------------------------------------------------------------
// Canvas state operations
// ----------------------------------------------------------------------------
+
// Save (layer)
virtual int getSaveCount() const = 0;
- virtual int save(SkCanvas::SaveFlags flags) = 0;
+ virtual int save(SaveFlags::Flags flags) = 0;
virtual void restore() = 0;
virtual void restoreToCount(int saveCount) = 0;
virtual int saveLayer(float left, float top, float right, float bottom,
- const SkPaint* paint, SkCanvas::SaveFlags flags) = 0;
+ const SkPaint* paint, SaveFlags::Flags flags) = 0;
virtual int saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, SkCanvas::SaveFlags flags) = 0;
+ int alpha, SaveFlags::Flags flags) = 0;
// Matrix
virtual void getMatrix(SkMatrix* outMatrix) const = 0;
diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp
index cf2726b..43ff33f 100644
--- a/libs/hwui/CanvasState.cpp
+++ b/libs/hwui/CanvasState.cpp
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#include <SkCanvas.h>
-
+#include "Canvas.h"
#include "CanvasState.h"
#include "utils/MathUtils.h"
@@ -54,8 +53,7 @@
}
freeAllSnapshots();
- mSnapshot = allocSnapshot(&mFirstSnapshot,
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ mSnapshot = allocSnapshot(&mFirstSnapshot, SaveFlags::MatrixClip);
mSnapshot->setRelativeLightCenter(Vector3());
mSaveCount = 1;
}
@@ -72,8 +70,7 @@
}
freeAllSnapshots();
- mSnapshot = allocSnapshot(&mFirstSnapshot,
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ mSnapshot = allocSnapshot(&mFirstSnapshot, SaveFlags::MatrixClip);
mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom);
mSnapshot->fbo = mCanvas.getTargetFbo();
mSnapshot->setRelativeLightCenter(lightCenter);
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index a1825c5..1b0f424 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#include <SkCanvas.h>
-
#include <utils/Trace.h>
#include <ui/Rect.h>
#include <ui/Region.h>
@@ -419,7 +417,7 @@
* beginning of the frame. This would avoid targetting and removing an FBO in the middle of a frame.
*
* saveLayer operations should be pulled to the beginning of the frame if the canvas doesn't have a
- * complex clip, and if the flags (kClip_SaveFlag & kClipToLayer_SaveFlag) are set.
+ * complex clip, and if the flags (SaveFlags::Clip & SaveFlags::ClipToLayer) are set.
*/
void DeferredDisplayList::addSaveLayer(OpenGLRenderer& renderer,
SaveLayerOp* op, int newSaveCount) {
@@ -438,7 +436,7 @@
int saveFlags = op->getFlags();
DEFER_LOGD("%p adding saveOp %p, flags %x, new count %d", this, op, saveFlags, newSaveCount);
- if (recordingComplexClip() && (saveFlags & SkCanvas::kClip_SaveFlag)) {
+ if (recordingComplexClip() && (saveFlags & SaveFlags::Clip)) {
// store and replay the save operation, as it may be needed to correctly playback the clip
DEFER_LOGD(" adding save barrier with new save count %d", newSaveCount);
storeStateOpBarrier(renderer, op);
@@ -621,7 +619,7 @@
this, newSaveCount, mBatches.size());
// store displayState for the restore operation, as it may be associated with a saveLayer that
- // doesn't have kClip_SaveFlag set
+ // doesn't have SaveFlags::Clip set
DeferredDisplayState* state = createState();
renderer.storeDisplayState(*state, getStateOpDeferFlags());
mBatches.push_back(new RestoreToCountBatch(op, state, newSaveCount));
@@ -654,7 +652,7 @@
renderer.eventMark("Flush");
// save and restore so that reordering doesn't affect final state
- renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ renderer.save(SaveFlags::MatrixClip);
if (CC_LIKELY(avoidOverdraw())) {
for (unsigned int i = 1; i < mBatches.size(); i++) {
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index 759c12a..384e64d 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -102,7 +102,7 @@
return mSkiaCanvasProxy.get();
}
-int DisplayListCanvas::save(SkCanvas::SaveFlags flags) {
+int DisplayListCanvas::save(SaveFlags::Flags flags) {
addStateOp(new (alloc()) SaveOp((int) flags));
return mState.save((int) flags);
}
@@ -125,9 +125,9 @@
}
int DisplayListCanvas::saveLayer(float left, float top, float right, float bottom,
- const SkPaint* paint, SkCanvas::SaveFlags flags) {
+ const SkPaint* paint, SaveFlags::Flags flags) {
// force matrix/clip isolation for layer
- flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag;
+ flags |= SaveFlags::MatrixClip;
paint = refPaint(paint);
addStateOp(new (alloc()) SaveLayerOp(left, top, right, bottom, paint, (int) flags));
@@ -232,7 +232,7 @@
void DisplayListCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top,
const SkPaint* paint) {
- save(SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::Matrix);
translate(left, top);
drawBitmap(&bitmap, paint);
restore();
@@ -253,7 +253,7 @@
drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint);
} else {
- save(SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::Matrix);
concat(matrix);
drawBitmap(&bitmap, paint);
restore();
@@ -269,7 +269,7 @@
&& (srcBottom - srcTop == dstBottom - dstTop)
&& (srcRight - srcLeft == dstRight - dstLeft)) {
// transform simple rect to rect drawing case into position bitmap ops, since they merge
- save(SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::Matrix);
translate(dstLeft, dstTop);
drawBitmap(&bitmap, paint);
restore();
@@ -283,7 +283,7 @@
// Apply the scale transform on the canvas, so that the shader
// effectively calculates positions relative to src rect space
- save(SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::Matrix);
translate(dstLeft, dstTop);
scale(scaleX, scaleY);
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index 72fc100..f1cfa08 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -128,14 +128,14 @@
// ----------------------------------------------------------------------------
// Save (layer)
virtual int getSaveCount() const override { return mState.getSaveCount(); }
- virtual int save(SkCanvas::SaveFlags flags) override;
+ virtual int save(SaveFlags::Flags flags) override;
virtual void restore() override;
virtual void restoreToCount(int saveCount) override;
virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
- SkCanvas::SaveFlags flags) override;
+ SaveFlags::Flags flags) override;
virtual int saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, SkCanvas::SaveFlags flags) override {
+ int alpha, SaveFlags::Flags flags) override {
SkPaint paint;
paint.setAlpha(alpha);
return saveLayer(left, top, right, bottom, &paint, flags);
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index aa905c5..3e37a05 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -16,6 +16,7 @@
#include "FrameBuilder.h"
+#include "Canvas.h"
#include "LayerUpdateQueue.h"
#include "RenderNode.h"
#include "renderstate/OffscreenBufferPool.h"
@@ -23,7 +24,6 @@
#include "utils/PaintUtils.h"
#include "utils/TraceUtils.h"
-#include <SkCanvas.h>
#include <SkPathOps.h>
#include <utils/TypeHelpers.h>
@@ -77,7 +77,7 @@
if (node->nothingToDraw()) continue;
node->computeOrdering();
- int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+ int count = mCanvasState.save(SaveFlags::MatrixClip);
deferNodePropsAndOps(*node);
mCanvasState.restoreToCount(count);
}
@@ -327,7 +327,7 @@
void FrameBuilder::deferProjectedChildren(const RenderNode& renderNode) {
const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
- int count = mCanvasState.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ int count = mCanvasState.save(SaveFlags::MatrixClip);
// can't be null, since DL=null node rejection happens before deferNodePropsAndOps
const DisplayList& displayList = *(renderNode.getDisplayList());
@@ -348,7 +348,7 @@
for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
- int restoreTo = mCanvasState.save(SkCanvas::kMatrix_SaveFlag);
+ int restoreTo = mCanvasState.save(SaveFlags::Matrix);
mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
deferRenderNodeOpImpl(*childOp);
mCanvasState.restoreToCount(restoreTo);
@@ -392,7 +392,7 @@
void FrameBuilder::deferRenderNodeOpImpl(const RenderNodeOp& op) {
if (op.renderNode->nothingToDraw()) return;
- int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+ int count = mCanvasState.save(SaveFlags::MatrixClip);
// apply state from RecordedOp (clip first, since op's clip is transformed by current matrix)
mCanvasState.writableSnapshot()->mutateClipArea().applyClip(op.localClip,
@@ -602,7 +602,7 @@
const Rect& repaintRect,
const Vector3& lightCenter,
const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
- mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+ mCanvasState.save(SaveFlags::MatrixClip);
mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter);
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index cc58d6b..6c2e244 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -17,6 +17,7 @@
#include <GpuMemoryTracker.h>
#include "OpenGLRenderer.h"
+#include "Canvas.h"
#include "DeferredDisplayList.h"
#include "GammaFontRenderer.h"
#include "Glop.h"
@@ -39,7 +40,6 @@
#include <stdint.h>
#include <sys/types.h>
-#include <SkCanvas.h>
#include <SkColor.h>
#include <SkPaintDefaults.h>
#include <SkPathOps.h>
@@ -472,7 +472,7 @@
int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
const SkPaint* paint, int flags, const SkPath* convexMask) {
// force matrix/clip isolation for layer
- flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag;
+ flags |= SaveFlags::MatrixClip;
const int count = mState.saveSnapshot(flags);
@@ -531,7 +531,7 @@
const SkPaint* paint, int flags) {
const int count = mState.saveSnapshot(flags);
- if (!mState.currentlyIgnored() && (flags & SkCanvas::kClipToLayer_SaveFlag)) {
+ if (!mState.currentlyIgnored() && (flags & SaveFlags::ClipToLayer)) {
// initialize the snapshot as though it almost represents an FBO layer so deferred draw
// operations will be able to store and restore the current clip and transform info, and
// quick rejection will be correct (for display lists)
@@ -558,7 +558,7 @@
* and the frame buffer still receive every drawing command. For instance, if a
* layer is created and a shape intersecting the bounds of the layers and the
* framebuffer is draw, the shape will be drawn on both (unless the layer was
- * created with the SkCanvas::kClipToLayer_SaveFlag flag.)
+ * created with the SaveFlags::ClipToLayer flag.)
*
* A way to implement layers is to create an FBO for each layer, backed by an RGBA
* texture. Unfortunately, this is inefficient as it requires every primitive to
@@ -608,7 +608,7 @@
LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top);
LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize());
- const bool fboLayer = flags & SkCanvas::kClipToLayer_SaveFlag;
+ const bool fboLayer = flags & SaveFlags::ClipToLayer;
// Window coordinates of the layer
Rect clip;
@@ -890,7 +890,7 @@
if (CC_UNLIKELY(layer->region.isEmpty())) return; // nothing to draw
if (layer->getConvexMask()) {
- save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::MatrixClip);
// clip to the area of the layer the mask can be larger
clipRect(rect.left, rect.top, rect.right, rect.bottom, SkRegion::kIntersect_Op);
@@ -2233,7 +2233,7 @@
if (layer->isTextureLayer()) {
transform = &layer->getTransform();
if (!transform->isIdentity()) {
- save(SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::Matrix);
concatMatrix(*transform);
}
}
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 78855e5..328e291 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -92,7 +92,7 @@
// android/graphics/Canvas state operations
// ----------------------------------------------------------------------------
// Save (layer)
-int RecordingCanvas::save(SkCanvas::SaveFlags flags) {
+int RecordingCanvas::save(SaveFlags::Flags flags) {
return mState.save((int) flags);
}
@@ -105,10 +105,10 @@
}
int RecordingCanvas::saveLayer(float left, float top, float right, float bottom,
- const SkPaint* paint, SkCanvas::SaveFlags flags) {
+ const SkPaint* paint, SaveFlags::Flags flags) {
// force matrix/clip isolation for layer
- flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag;
- bool clippedLayer = flags & SkCanvas::kClipToLayer_SaveFlag;
+ flags |= SaveFlags::MatrixClip;
+ bool clippedLayer = flags & SaveFlags::ClipToLayer;
const Snapshot& previous = *mState.currentSnapshot();
@@ -128,7 +128,7 @@
// unlikely case where an unclipped savelayer is recorded with a clip it can use,
// as none of its unaffected/unclipped area is visible
clippedLayer = true;
- flags |= SkCanvas::kClipToLayer_SaveFlag;
+ flags |= SaveFlags::ClipToLayer;
}
visibleBounds.doIntersect(previous.getRenderTargetClip());
@@ -424,7 +424,7 @@
// Bitmap-based
void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) {
- save(SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::Matrix);
translate(left, top);
drawBitmap(&bitmap, paint);
restore();
@@ -445,7 +445,7 @@
drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint);
} else {
- save(SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::Matrix);
concat(matrix);
drawBitmap(&bitmap, paint);
restore();
@@ -461,7 +461,7 @@
&& (srcBottom - srcTop == dstBottom - dstTop)
&& (srcRight - srcLeft == dstRight - dstLeft)) {
// transform simple rect to rect drawing case into position bitmap ops, since they merge
- save(SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::Matrix);
translate(dstLeft, dstTop);
drawBitmap(&bitmap, paint);
restore();
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 8aa7506..786f96e 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -108,14 +108,14 @@
// ----------------------------------------------------------------------------
// Save (layer)
virtual int getSaveCount() const override { return mState.getSaveCount(); }
- virtual int save(SkCanvas::SaveFlags flags) override;
+ virtual int save(SaveFlags::Flags flags) override;
virtual void restore() override;
virtual void restoreToCount(int saveCount) override;
virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
- SkCanvas::SaveFlags flags) override;
+ SaveFlags::Flags flags) override;
virtual int saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, SkCanvas::SaveFlags flags) override {
+ int alpha, SaveFlags::Flags flags) override {
SkPaint paint;
paint.setAlpha(alpha);
return saveLayer(left, top, right, bottom, &paint, flags);
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index ae690fd..d4588ed 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -33,8 +33,6 @@
#include "protos/hwui.pb.h"
#include "protos/ProtoHelpers.h"
-#include <SkCanvas.h>
-
#include <algorithm>
#include <sstream>
#include <string>
@@ -105,8 +103,7 @@
(isRenderable() ? "" : ", empty"),
(properties().getProjectBackwards() ? ", projected" : ""),
(mLayer != nullptr ? ", on HW Layer" : ""));
- ALOGD("%*s%s %d", level * 2, "", "Save",
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ ALOGD("%*s%s %d", level * 2, "", "Save", SaveFlags::MatrixClip);
properties().debugOutputProperties(level);
@@ -574,7 +571,7 @@
layerBounds.left, layerBounds.top,
layerBounds.right, layerBounds.bottom,
(int) (properties().getAlpha() * 255),
- SkCanvas::kHasAlphaLayer_SaveFlag | SkCanvas::kClipToLayer_SaveFlag);
+ SaveFlags::HasAlphaLayer | SaveFlags::ClipToLayer);
handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds());
}
@@ -875,7 +872,7 @@
// Apply the base transform of the parent of the 3d children. This isolates
// 3d children of the current chunk from transformations made in previous chunks.
- int rootRestoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
+ int rootRestoreTo = renderer.save(SaveFlags::Matrix);
renderer.setGlobalMatrix(initialTransform);
/**
@@ -919,7 +916,7 @@
// only the actual child DL draw needs to be in save/restore,
// since it modifies the renderer's matrix
- int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
+ int restoreTo = renderer.save(SaveFlags::Matrix);
DrawRenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
@@ -941,7 +938,7 @@
int restoreTo = renderer.getSaveCount();
LinearAllocator& alloc = handler.allocator();
- handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
+ handler(new (alloc) SaveOp(SaveFlags::MatrixClip),
PROPERTY_SAVECOUNT, properties().getClipToBounds());
// Transform renderer to match background we're projecting onto
@@ -966,7 +963,7 @@
renderNodeOp_t* childOp = mProjectedNodes[i];
// matrix save, concat, and restore can be done safely without allocating operations
- int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
+ int restoreTo = renderer.save(SaveFlags::Matrix);
renderer.concatMatrix(childOp->transformFromCompositingAncestor);
childOp->skipInOrderDraw = false; // this is horrible, I'm so sorry everyone
handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
@@ -1027,11 +1024,11 @@
LinearAllocator& alloc = handler.allocator();
int restoreTo = renderer.getSaveCount();
- handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
+ handler(new (alloc) SaveOp(SaveFlags::MatrixClip),
PROPERTY_SAVECOUNT, properties().getClipToBounds());
DISPLAY_LIST_LOGD("%*sSave %d %d", (handler.level() + 1) * 2, "",
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag, restoreTo);
+ SaveFlags::MatrixClip, restoreTo);
if (useViewProperties) {
setViewProperties<T>(renderer, handler);
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index ce1bd6a..b848af4 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -18,12 +18,12 @@
#include <utils/Trace.h>
-#include <SkCanvas.h>
#include <SkColorFilter.h>
#include <SkMatrix.h>
#include <SkPath.h>
#include <SkPathOps.h>
+#include "Canvas.h"
#include "Matrix.h"
#include "OpenGLRenderer.h"
#include "utils/MathUtils.h"
@@ -144,7 +144,7 @@
(int)layerBounds.left, (int)layerBounds.top,
(int)layerBounds.right, (int)layerBounds.bottom,
(int)(mPrimitiveFields.mAlpha * 255),
- SkCanvas::kHasAlphaLayer_SaveFlag | SkCanvas::kClipToLayer_SaveFlag);
+ SaveFlags::HasAlphaLayer | SaveFlags::ClipToLayer);
}
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 96c1a7c..20e7c71 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -24,6 +24,7 @@
#include <SkGraphics.h>
#include <SkShader.h>
#include <SkTArray.h>
+#include <SkTLazy.h>
#include <SkTemplates.h>
#include <memory>
@@ -63,14 +64,14 @@
virtual bool isHighContrastText() override { return mHighContrastText; }
virtual int getSaveCount() const override;
- virtual int save(SkCanvas::SaveFlags flags) override;
+ virtual int save(SaveFlags::Flags flags) override;
virtual void restore() override;
virtual void restoreToCount(int saveCount) override;
virtual int saveLayer(float left, float top, float right, float bottom,
- const SkPaint* paint, SkCanvas::SaveFlags flags) override;
+ const SkPaint* paint, SaveFlags::Flags flags) override;
virtual int saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, SkCanvas::SaveFlags flags) override;
+ int alpha, SaveFlags::Flags flags) override;
virtual void getMatrix(SkMatrix* outMatrix) const override;
virtual void setMatrix(const SkMatrix& matrix) override;
@@ -138,13 +139,13 @@
private:
struct SaveRec {
- int saveCount;
- SkCanvas::SaveFlags saveFlags;
+ int saveCount;
+ SaveFlags::Flags saveFlags;
};
bool mHighContrastText = false;
- void recordPartialSave(SkCanvas::SaveFlags flags);
+ void recordPartialSave(SaveFlags::Flags flags);
void saveClipsForFrame(SkTArray<SkClipStack::Element>& clips, int frameSaveCount);
void applyClips(const SkTArray<SkClipStack::Element>& clips);
@@ -231,7 +232,7 @@
return mCanvas->getSaveCount();
}
-int SkiaCanvas::save(SkCanvas::SaveFlags flags) {
+int SkiaCanvas::save(SaveFlags::Flags flags) {
int count = mCanvas->save();
recordPartialSave(flags);
return count;
@@ -254,8 +255,8 @@
return;
}
- bool preserveMatrix = !(rec->saveFlags & SkCanvas::kMatrix_SaveFlag);
- bool preserveClip = !(rec->saveFlags & SkCanvas::kClip_SaveFlag);
+ bool preserveMatrix = !(rec->saveFlags & SaveFlags::Matrix);
+ bool preserveClip = !(rec->saveFlags & SaveFlags::Clip);
SkMatrix savedMatrix;
if (preserveMatrix) {
@@ -291,34 +292,53 @@
}
}
+static inline SkCanvas::SaveLayerFlags layerFlags(SaveFlags::Flags flags) {
+ SkCanvas::SaveLayerFlags layerFlags = 0;
+
+ if (!(flags & SaveFlags::HasAlphaLayer)) {
+ layerFlags |= SkCanvas::kIsOpaque_SaveLayerFlag;
+ }
+
+ if (!(flags & SaveFlags::ClipToLayer)) {
+ layerFlags |= SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag;
+ }
+
+ return layerFlags;
+}
+
int SkiaCanvas::saveLayer(float left, float top, float right, float bottom,
- const SkPaint* paint, SkCanvas::SaveFlags flags) {
- SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
- int count = mCanvas->saveLayer(&bounds, paint, flags | SkCanvas::kMatrixClip_SaveFlag);
+ const SkPaint* paint, SaveFlags::Flags flags) {
+ const SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
+ const SkCanvas::SaveLayerRec rec(&bounds, paint, layerFlags(flags));
+
+ int count = mCanvas->saveLayer(rec);
recordPartialSave(flags);
return count;
}
int SkiaCanvas::saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, SkCanvas::SaveFlags flags) {
- SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
- int count = mCanvas->saveLayerAlpha(&bounds, alpha, flags | SkCanvas::kMatrixClip_SaveFlag);
- recordPartialSave(flags);
- return count;
+ int alpha, SaveFlags::Flags flags) {
+ SkTLazy<SkPaint> alphaPaint;
+ if (static_cast<unsigned>(alpha) < 0xFF) {
+ alphaPaint.init()->setAlpha(alpha);
+ }
+
+ return this->saveLayer(left, top, right, bottom, alphaPaint.getMaybeNull(),
+ flags);
}
// ----------------------------------------------------------------------------
// functions to emulate legacy SaveFlags (i.e. independent matrix/clip flags)
// ----------------------------------------------------------------------------
-void SkiaCanvas::recordPartialSave(SkCanvas::SaveFlags flags) {
+void SkiaCanvas::recordPartialSave(SaveFlags::Flags flags) {
// A partial save is a save operation which doesn't capture the full canvas state.
- // (either kMatrix_SaveFlags or kClip_SaveFlag is missing).
+ // (either SaveFlags::Matrix or SaveFlags::Clip is missing).
// Mask-out non canvas state bits.
- flags = static_cast<SkCanvas::SaveFlags>(flags & SkCanvas::kMatrixClip_SaveFlag);
+ flags &= SaveFlags::MatrixClip;
- if (SkCanvas::kMatrixClip_SaveFlag == flags) {
+ if (flags == SaveFlags::MatrixClip) {
// not a partial save.
return;
}
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index 976f775..6530d4ed8 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -159,7 +159,21 @@
}
void SkiaCanvasProxy::willSave() {
- mCanvas->save(SkCanvas::kMatrixClip_SaveFlag);
+ mCanvas->save(android::SaveFlags::MatrixClip);
+}
+
+static inline SaveFlags::Flags saveFlags(SkCanvas::SaveLayerFlags layerFlags) {
+ SaveFlags::Flags saveFlags = 0;
+
+ if (!(layerFlags & SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag)) {
+ saveFlags |= SaveFlags::ClipToLayer;
+ }
+
+ if (!(layerFlags & SkCanvas::kIsOpaque_SaveLayerFlag)) {
+ saveFlags |= SaveFlags::HasAlphaLayer;
+ }
+
+ return saveFlags;
}
SkCanvas::SaveLayerStrategy SkiaCanvasProxy::getSaveLayerStrategy(const SaveLayerRec& saveLayerRec) {
@@ -170,7 +184,7 @@
rect = SkRect::MakeEmpty();
}
mCanvas->saveLayer(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, saveLayerRec.fPaint,
- (SkCanvas::SaveFlags) SaveLayerFlagsToSaveFlags(saveLayerRec.fSaveLayerFlags));
+ saveFlags(saveLayerRec.fSaveLayerFlags));
return SkCanvas::kNoLayer_SaveLayerStrategy;
}
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index c6d8977..27fea1f 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -16,7 +16,7 @@
#include "Snapshot.h"
-#include <SkCanvas.h>
+#include "Canvas.h"
namespace android {
namespace uirenderer {
@@ -57,14 +57,14 @@
, mClipArea(nullptr)
, mViewportData(s->mViewportData)
, mRelativeLightCenter(s->mRelativeLightCenter) {
- if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
+ if (saveFlags & SaveFlags::Matrix) {
mTransformRoot = *s->transform;
transform = &mTransformRoot;
} else {
transform = s->transform;
}
- if (saveFlags & SkCanvas::kClip_SaveFlag) {
+ if (saveFlags & SaveFlags::Clip) {
mClipAreaRoot = s->getClipArea();
mClipArea = &mClipAreaRoot;
} else {
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 3e20608..1d31c9e 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -420,7 +420,7 @@
return;
}
- int saveCount = outCanvas->save(SkCanvas::SaveFlags::kMatrixClip_SaveFlag);
+ int saveCount = outCanvas->save(SaveFlags::MatrixClip);
outCanvas->translate(mBounds.fLeft, mBounds.fTop);
// Handle RTL mirroring.
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 2769d35..7148c4b 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -19,6 +19,7 @@
#include "AnimationContext.h"
#include "Caches.h"
+#include "Canvas.h"
#include "DeferredLayerUpdater.h"
#include "EglManager.h"
#include "LayerUpdateQueue.h"
@@ -394,7 +395,7 @@
backdropBounds.doIntersect(targetBounds);
// Check if we have to draw something on the left side ...
if (targetBounds.left < contentBounds.left) {
- mCanvas->save(SkCanvas::kClip_SaveFlag);
+ mCanvas->save(SaveFlags::Clip);
if (mCanvas->clipRect(targetBounds.left, targetBounds.top,
contentBounds.left, targetBounds.bottom,
SkRegion::kIntersect_Op)) {
@@ -407,7 +408,7 @@
// ... or on the right side ...
if (targetBounds.right > contentBounds.right &&
!targetBounds.isEmpty()) {
- mCanvas->save(SkCanvas::kClip_SaveFlag);
+ mCanvas->save(SaveFlags::Clip);
if (mCanvas->clipRect(contentBounds.right, targetBounds.top,
targetBounds.right, targetBounds.bottom,
SkRegion::kIntersect_Op)) {
@@ -420,7 +421,7 @@
// ... or at the top ...
if (targetBounds.top < contentBounds.top &&
!targetBounds.isEmpty()) {
- mCanvas->save(SkCanvas::kClip_SaveFlag);
+ mCanvas->save(SaveFlags::Clip);
if (mCanvas->clipRect(targetBounds.left, targetBounds.top, targetBounds.right,
contentBounds.top,
SkRegion::kIntersect_Op)) {
@@ -433,7 +434,7 @@
// ... or at the bottom.
if (targetBounds.bottom > contentBounds.bottom &&
!targetBounds.isEmpty()) {
- mCanvas->save(SkCanvas::kClip_SaveFlag);
+ mCanvas->save(SaveFlags::Clip);
if (mCanvas->clipRect(targetBounds.left, contentBounds.bottom, targetBounds.right,
targetBounds.bottom, SkRegion::kIntersect_Op)) {
mCanvas->drawRenderNode(node.get(), outBounds);
@@ -442,7 +443,7 @@
}
} else if (layer == 1) { // Content
// It gets cropped against the bounds of the backdrop to stay inside.
- mCanvas->save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+ mCanvas->save(SaveFlags::MatrixClip);
// We shift and clip the content to match its final location in the window.
const float left = mContentDrawBounds.left;
diff --git a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
index db6402c..a5fd712 100644
--- a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
@@ -32,7 +32,7 @@
canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
card = TestUtils::createNode(0, 0, 200, 400,
[](RenderProperties& props, TestCanvas& canvas) {
- canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
{
canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
canvas.translate(100, 100);
@@ -43,7 +43,7 @@
}
canvas.restore();
- canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
{
SkPath clipCircle;
clipCircle.addCircle(100, 300, 100);
diff --git a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
index c899850..6904bec 100644
--- a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
@@ -34,18 +34,18 @@
card = TestUtils::createNode(0, 0, 400, 800,
[](RenderProperties& props, TestCanvas& canvas) {
// nested clipped saveLayers
- canvas.saveLayerAlpha(0, 0, 400, 400, 200, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.saveLayerAlpha(0, 0, 400, 400, 200, SaveFlags::ClipToLayer);
canvas.drawColor(Color::Green_700, SkXfermode::kSrcOver_Mode);
canvas.clipRect(50, 50, 350, 350, SkRegion::kIntersect_Op);
- canvas.saveLayerAlpha(100, 100, 300, 300, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer);
canvas.drawColor(Color::Blue_500, SkXfermode::kSrcOver_Mode);
canvas.restore();
canvas.restore();
// single unclipped saveLayer
- canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.translate(0, 400);
- canvas.saveLayerAlpha(100, 100, 300, 300, 128, SkCanvas::SaveFlags(0)); // unclipped
+ canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::Flags(0)); // unclipped
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(Color::Green_700);
diff --git a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
index 0cba344..6d27c9d 100644
--- a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
@@ -69,7 +69,7 @@
float cellSize = floorf(width / 7 - cellSpace);
// each combination of strokeWidth + style gets a column
- int outerCount = canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ int outerCount = canvas.save(SaveFlags::MatrixClip);
SkPaint paint;
paint.setAntiAlias(true);
SkPaint::Style styles[] = {
@@ -79,9 +79,9 @@
for (auto strokeWidth : { 0.0f, 0.5f, 8.0f }) {
paint.setStrokeWidth(strokeWidth);
// fill column with each op
- int middleCount = canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ int middleCount = canvas.save(SaveFlags::MatrixClip);
for (auto op : ops) {
- int innerCount = canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ int innerCount = canvas.save(SaveFlags::MatrixClip);
canvas.clipRect(0, 0, cellSize, cellSize, SkRegion::kIntersect_Op);
canvas.drawColor(Color::White, SkXfermode::Mode::kSrcOver_Mode);
op(canvas, cellSize, paint);
diff --git a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
index 2e59eb4..83af148 100644
--- a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
+++ b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
@@ -78,8 +78,8 @@
StartBenchmarkTiming();
for (int i = 0; i < iters; ++i) {
canvas.reset(100, 100);
- canvas.save(SkCanvas::kMatrixClip_SaveFlag);
- canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.save(SaveFlags::MatrixClip);
MicroBench::DoNotOptimize(&canvas);
canvas.restore();
canvas.restore();
@@ -121,12 +121,12 @@
for (int i = 0; i < iters; ++i) {
canvas.reset(100, 100);
{
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.drawRect(0, 0, 100, 100, rectPaint);
canvas.restore();
}
{
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.translate(10, 10);
canvas.drawBitmap(iconBitmap, 0, 0, nullptr);
canvas.restore();
@@ -151,8 +151,8 @@
StartBenchmarkTiming();
for (int i = 0; i < iters; ++i) {
- state.save(SkCanvas::kMatrixClip_SaveFlag);
- state.save(SkCanvas::kMatrixClip_SaveFlag);
+ state.save(SaveFlags::MatrixClip);
+ state.save(SaveFlags::MatrixClip);
MicroBench::DoNotOptimize(&state);
state.restore();
state.restore();
diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
index 67c95e2..f9c2b67 100644
--- a/libs/hwui/tests/microbench/FrameBuilderBench.cpp
+++ b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
@@ -47,7 +47,7 @@
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
// Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
for (int i = 0; i < 30; i++) {
canvas.translate(0, 10);
canvas.drawRect(0, 0, 10, 10, paint);
diff --git a/libs/hwui/tests/unit/CanvasStateTests.cpp b/libs/hwui/tests/unit/CanvasStateTests.cpp
index 4df2687..68d74ee 100644
--- a/libs/hwui/tests/unit/CanvasStateTests.cpp
+++ b/libs/hwui/tests/unit/CanvasStateTests.cpp
@@ -16,6 +16,7 @@
#include "CanvasState.h"
+#include "Canvas.h"
#include "Matrix.h"
#include "Rect.h"
#include "utils/LinearAllocator.h"
@@ -23,7 +24,6 @@
#include <gtest/gtest.h>
#include <SkPath.h>
#include <SkRegion.h>
-#include <SkCanvas.h>
namespace android {
namespace uirenderer {
@@ -83,7 +83,7 @@
state.initializeSaveStack(200, 200,
0, 0, 200, 200, Vector3());
- state.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+ state.save(SaveFlags::MatrixClip);
{
// rotated clip causes complex clip
state.rotate(10);
@@ -93,7 +93,7 @@
}
state.restore();
- state.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+ state.save(SaveFlags::MatrixClip);
{
// subtracted clip causes complex clip
EXPECT_TRUE(state.clipIsSimple());
@@ -102,7 +102,7 @@
}
state.restore();
- state.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+ state.save(SaveFlags::MatrixClip);
{
// complex path causes complex clip
SkPath path;
@@ -119,7 +119,7 @@
state.initializeSaveStack(200, 200,
0, 0, 200, 200, Vector3());
- state.save(SkCanvas::kClip_SaveFlag);
+ state.save(SaveFlags::Clip);
{
state.clipRect(0, 0, 10, 10, SkRegion::kIntersect_Op);
ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10));
@@ -129,7 +129,7 @@
Matrix4 simpleTranslate;
simpleTranslate.loadTranslate(10, 10, 0);
- state.save(SkCanvas::kMatrix_SaveFlag);
+ state.save(SaveFlags::Matrix);
{
state.translate(10, 10, 0);
EXPECT_TRUE(approxEqual(*state.currentTransform(), simpleTranslate));
@@ -143,7 +143,7 @@
state.initializeSaveStack(200, 200,
0, 0, 200, 200, Vector3());
- state.save(SkCanvas::kMatrix_SaveFlag); // NOTE: clip not saved
+ state.save(SaveFlags::Matrix); // NOTE: clip not saved
{
state.clipRect(0, 0, 10, 10, SkRegion::kIntersect_Op);
ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10));
@@ -153,7 +153,7 @@
Matrix4 simpleTranslate;
simpleTranslate.loadTranslate(10, 10, 0);
- state.save(SkCanvas::kClip_SaveFlag); // NOTE: matrix not saved
+ state.save(SaveFlags::Clip); // NOTE: matrix not saved
{
state.translate(10, 10, 0);
EXPECT_TRUE(approxEqual(*state.currentTransform(), simpleTranslate));
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index cdbab2e..618df14 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -167,7 +167,7 @@
TEST(FrameBuilder, simpleRejection) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op); // intersection should be empty
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();
@@ -198,7 +198,7 @@
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
// Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
for (int i = 0; i < LOOPS; i++) {
canvas.translate(0, 10);
canvas.drawRect(0, 0, 10, 10, SkPaint());
@@ -337,7 +337,7 @@
auto node = TestUtils::createNode(0, 0, 200, 200,
[&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.clipRect(50, 50, 150, 150, SkRegion::kIntersect_Op);
canvas.drawLayer(layerUpdater.get());
canvas.restore();
@@ -381,7 +381,7 @@
paint.setColor(SK_ColorDKGRAY);
canvas.drawRect(0, 0, 200, 200, paint);
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.translate(40, 40);
canvas.drawRenderNode(child.get());
canvas.restore();
@@ -449,7 +449,7 @@
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.saveLayerAlpha(10, 10, 190, 190, 128, SaveFlags::ClipToLayer);
canvas.drawRect(10, 10, 190, 190, SkPaint());
canvas.restore();
});
@@ -513,10 +513,10 @@
auto node = TestUtils::createNode(0, 0, 800, 800,
[](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(0, 0, 800, 800, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.saveLayerAlpha(0, 0, 800, 800, 128, SaveFlags::ClipToLayer);
{
canvas.drawRect(0, 0, 800, 800, SkPaint());
- canvas.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer);
{
canvas.drawRect(0, 0, 400, 400, SkPaint());
}
@@ -535,9 +535,9 @@
TEST(FrameBuilder, saveLayer_contentRejection) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op);
- canvas.saveLayerAlpha(200, 200, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.saveLayerAlpha(200, 200, 400, 400, 128, SaveFlags::ClipToLayer);
// draw within save layer may still be recorded, but shouldn't be drawn
canvas.drawRect(200, 200, 400, 400, SkPaint());
@@ -584,7 +584,7 @@
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SkCanvas::SaveFlags)(0));
+ canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0));
canvas.drawRect(0, 0, 200, 200, SkPaint());
canvas.restore();
});
@@ -633,12 +633,12 @@
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
- int restoreTo = canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ int restoreTo = canvas.save(SaveFlags::MatrixClip);
canvas.scale(2, 2);
- canvas.saveLayerAlpha(0, 0, 5, 5, 128, SkCanvas::kMatrixClip_SaveFlag);
- canvas.saveLayerAlpha(95, 0, 100, 5, 128, SkCanvas::kMatrixClip_SaveFlag);
- canvas.saveLayerAlpha(0, 95, 5, 100, 128, SkCanvas::kMatrixClip_SaveFlag);
- canvas.saveLayerAlpha(95, 95, 100, 100, 128, SkCanvas::kMatrixClip_SaveFlag);
+ canvas.saveLayerAlpha(0, 0, 5, 5, 128, SaveFlags::MatrixClip);
+ canvas.saveLayerAlpha(95, 0, 100, 5, 128, SaveFlags::MatrixClip);
+ canvas.saveLayerAlpha(0, 95, 5, 100, 128, SaveFlags::MatrixClip);
+ canvas.saveLayerAlpha(95, 95, 100, 100, 128, SaveFlags::MatrixClip);
canvas.drawRect(0, 0, 100, 100, SkPaint());
canvas.restoreToCount(restoreTo);
});
@@ -696,9 +696,9 @@
auto node = TestUtils::createNode(0, 0, 600, 600, // 500x500 triggers clipping
[](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(0, 0, 500, 500, 128, (SkCanvas::SaveFlags)0); // unclipped
- canvas.saveLayerAlpha(100, 100, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag); // clipped
- canvas.saveLayerAlpha(200, 200, 300, 300, 128, (SkCanvas::SaveFlags)0); // unclipped
+ canvas.saveLayerAlpha(0, 0, 500, 500, 128, (SaveFlags::Flags)0); // unclipped
+ canvas.saveLayerAlpha(100, 100, 400, 400, 128, SaveFlags::ClipToLayer); // clipped
+ canvas.saveLayerAlpha(200, 200, 300, 300, 128, (SaveFlags::Flags)0); // unclipped
canvas.drawRect(200, 200, 300, 300, SkPaint());
canvas.restore();
canvas.restore();
@@ -850,7 +850,7 @@
paint.setColor(SK_ColorDKGRAY);
canvas.drawRect(0, 0, 200, 200, paint);
- canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.saveLayerAlpha(50, 50, 150, 150, 128, SaveFlags::ClipToLayer);
canvas.drawRenderNode(childPtr);
canvas.restore();
});
@@ -988,7 +988,7 @@
});
auto parent = TestUtils::createNode(0, 0, 100, 100,
[&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
canvas.drawRenderNode(receiverBackground.get());
canvas.drawRenderNode(child.get());
@@ -1072,7 +1072,7 @@
[](RenderProperties& props, RecordingCanvas& canvas) {
// save/restore outside of reorderBarrier, so they don't get moved out of place
canvas.translate(20, 10);
- int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+ int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SaveFlags::ClipToLayer);
canvas.insertReorderBarrier(true);
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
canvas.insertReorderBarrier(false);
@@ -1112,7 +1112,7 @@
[](RenderProperties& props, RecordingCanvas& canvas) {
props.mutateLayerProperties().setType(LayerType::RenderLayer);
canvas.insertReorderBarrier(true);
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.translate(20, 10);
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
canvas.restore();
diff --git a/libs/hwui/tests/unit/LeakCheckTests.cpp b/libs/hwui/tests/unit/LeakCheckTests.cpp
index 41e44fc..4a635fb 100644
--- a/libs/hwui/tests/unit/LeakCheckTests.cpp
+++ b/libs/hwui/tests/unit/LeakCheckTests.cpp
@@ -32,7 +32,7 @@
RENDERTHREAD_TEST(LeakCheck, saveLayerUnclipped_simple) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SkCanvas::SaveFlags)(0));
+ canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0));
canvas.drawRect(0, 0, 200, 200, SkPaint());
canvas.restore();
});
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index ff098c8..01bfc5a 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -35,7 +35,7 @@
TEST(RecordingCanvas, emptyPlayback) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.restore();
});
playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
@@ -43,7 +43,7 @@
TEST(RecordingCanvas, clipRect) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
- canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op);
canvas.drawRect(0, 0, 50, 50, SkPaint());
canvas.drawRect(50, 50, 100, 100, SkPaint());
@@ -176,16 +176,16 @@
SkPaint paint;
paint.setColor(SK_ColorBLUE);
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
{
// a background!
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.drawRect(0, 0, 100, 200, paint);
canvas.restore();
}
{
// an image!
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.translate(25, 25);
canvas.scale(2, 2);
canvas.drawBitmap(bitmap, 0, 0, nullptr);
@@ -224,7 +224,7 @@
TEST(RecordingCanvas, saveLayer_simple) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.saveLayerAlpha(10, 20, 190, 180, 128, SaveFlags::ClipToLayer);
canvas.drawRect(10, 20, 190, 180, SkPaint());
canvas.restore();
});
@@ -258,7 +258,7 @@
TEST(RecordingCanvas, saveLayer_missingRestore) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer);
canvas.drawRect(0, 0, 200, 200, SkPaint());
// Note: restore omitted, shouldn't result in unmatched save
});
@@ -273,7 +273,7 @@
TEST(RecordingCanvas, saveLayer_simpleUnclipped) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SkCanvas::SaveFlags)0); // unclipped
+ canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
canvas.drawRect(10, 20, 190, 180, SkPaint());
canvas.restore();
});
@@ -305,9 +305,9 @@
TEST(RecordingCanvas, saveLayer_addClipFlag) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.clipRect(10, 20, 190, 180, SkRegion::kIntersect_Op);
- canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SkCanvas::SaveFlags)0); // unclipped
+ canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
canvas.drawRect(10, 20, 190, 180, SkPaint());
canvas.restore();
canvas.restore();
@@ -327,7 +327,7 @@
// shouldn't matter, since saveLayer will clip to its bounds
canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op);
- canvas.saveLayerAlpha(100, 100, 300, 300, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer);
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();
});
@@ -348,12 +348,12 @@
TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.translate(100, 100);
canvas.rotate(45);
canvas.translate(-50, -50);
- canvas.saveLayerAlpha(0, 0, 100, 100, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer);
canvas.drawRect(0, 0, 100, 100, SkPaint());
canvas.restore();
@@ -374,13 +374,13 @@
TEST(RecordingCanvas, saveLayer_rotateClipped) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.save(SaveFlags::MatrixClip);
canvas.translate(100, 100);
canvas.rotate(45);
canvas.translate(-200, -200);
// area of saveLayer will be clipped to parent viewport, so we ask for 400x400...
- canvas.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer);
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();
diff --git a/packages/DocumentsUI/res/layout/item_dir_grid.xml b/packages/DocumentsUI/res/layout/item_dir_grid.xml
index a08e029..b0331be 100644
--- a/packages/DocumentsUI/res/layout/item_dir_grid.xml
+++ b/packages/DocumentsUI/res/layout/item_dir_grid.xml
@@ -17,7 +17,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:layout_margin="@dimen/grid_item_margin"
android:background="@color/item_doc_background"
android:elevation="5dp"
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 811adda..743df99 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -1003,7 +1003,9 @@
if (currMediaSize == null) {
attributes.setMediaSize(defaults.getMediaSize());
} else {
- boolean foundCurrentMediaSize = false;
+ MediaSize newMediaSize = null;
+ boolean isPortrait = currMediaSize.isPortrait();
+
// Try to find the current media size in the capabilities as
// it may be in a different orientation.
MediaSize currMediaSizePortrait = currMediaSize.asPortrait();
@@ -1011,14 +1013,21 @@
for (int i = 0; i < mediaSizeCount; i++) {
MediaSize mediaSize = sortedMediaSizes.get(i);
if (currMediaSizePortrait.equals(mediaSize.asPortrait())) {
- attributes.setMediaSize(currMediaSize);
- foundCurrentMediaSize = true;
+ newMediaSize = mediaSize;
break;
}
}
// If we did not find the current media size fall back to default.
- if (!foundCurrentMediaSize) {
- attributes.setMediaSize(defaults.getMediaSize());
+ if (newMediaSize == null) {
+ newMediaSize = defaults.getMediaSize();
+ }
+
+ if (newMediaSize != null) {
+ if (isPortrait) {
+ attributes.setMediaSize(newMediaSize.asPortrait());
+ } else {
+ attributes.setMediaSize(newMediaSize.asLandscape());
+ }
}
}
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 2377684..ab590f8 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -70,6 +70,18 @@
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+ <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_alignParentEnd="true"
+ android:background="@drawable/ripple_drawable" >
+ <ImageView android:id="@+id/multi_user_avatar"
+ android:layout_width="@dimen/multi_user_avatar_expanded_size"
+ android:layout_height="@dimen/multi_user_avatar_expanded_size"
+ android:layout_gravity="center"
+ android:scaleType="centerInside"/>
+ </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 543a2f3..8ae2d7b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -73,7 +73,7 @@
mCurrentTiles = tileSpecs;
final TileGroup group = new TileGroup("com.android.settings", mContext);
String possible = mContext.getString(R.string.quick_settings_tiles_default)
- + ",user,hotspot,inversion,saver";
+ + ",hotspot,inversion,saver";
String[] possibleTiles = possible.split(",");
for (int i = 0; i < possibleTiles.length; i++) {
final String spec = possibleTiles[i];
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 71267cd..8717a15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -65,6 +65,10 @@
setUserSwitcherController(qsPanel.getHost().getUserSwitcherController());
}
+ public boolean hasMultipleUsers() {
+ return mUserListener.getCount() != 0;
+ }
+
public void setUserSwitcherController(UserSwitcherController userSwitcherController) {
mUserSwitcherController = userSwitcherController;
registerListener();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 5b44f0a..f18c341 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.View;
@@ -70,6 +71,8 @@
private QuickQSPanel mHeaderQsPanel;
private boolean mShowEmergencyCallsOnly;
private float mDateTimeTranslation;
+ private MultiUserSwitch mMultiUserSwitch;
+ private ImageView mMultiUserAvatar;
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -100,6 +103,9 @@
mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle);
mQsDetailHeaderProgress = (ImageView) findViewById(R.id.qs_detail_header_progress);
+ mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch);
+ mMultiUserAvatar = (ImageView) mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
+
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
((RippleDrawable) getBackground()).setForceSoftware(true);
@@ -173,6 +179,12 @@
? View.VISIBLE : View.INVISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
+ mMultiUserSwitch.setVisibility(mMultiUserSwitch.hasMultipleUsers() ? View.VISIBLE
+ : View.GONE);
+ }
+
+ private boolean hasMultiUsers() {
+ return false;
}
private void updateListeners() {
@@ -194,6 +206,7 @@
setupHost(qsPanel.getHost());
if (mQsPanel != null) {
mQsPanel.setCallback(mQsPanelCallback);
+ mMultiUserSwitch.setQsPanel(qsPanel);
}
}
@@ -254,7 +267,12 @@
@Override
public void setUserInfoController(UserInfoController userInfoController) {
- // Don't care.
+ userInfoController.addListener(new UserInfoController.OnUserInfoChangedListener() {
+ @Override
+ public void onUserInfoChanged(String name, Drawable picture) {
+ mMultiUserAvatar.setImageDrawable(picture);
+ }
+ });
}
@Override
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7ba6338..8c0ec78 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2775,7 +2775,7 @@
}
if (anrMessage != null) {
- mAm.appNotResponding(proc, null, null, false, anrMessage);
+ mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b8327c1..2c55ee2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -277,7 +277,6 @@
import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
-import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
@@ -392,7 +391,7 @@
// The flags that are set for all calls we make to the package manager.
static final int STOCK_PM_FLAGS = PackageManager.GET_SHARED_LIBRARY_FILES;
- private static final String SYSTEM_DEBUGGABLE = "ro.debuggable";
+ static final String SYSTEM_DEBUGGABLE = "ro.debuggable";
static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
@@ -598,6 +597,12 @@
final UserController mUserController;
+ final AppErrors mAppErrors;
+
+ public boolean canShowErrorDialogs() {
+ return mShowDialogs && !mSleeping && !mShuttingDown;
+ }
+
public class PendingAssistExtras extends Binder implements Runnable {
public final ActivityRecord activity;
public final Bundle extras;
@@ -668,38 +673,6 @@
ProcessRecord mHeavyWeightProcess = null;
/**
- * The last time that various processes have crashed.
- */
- final ProcessMap<Long> mProcessCrashTimes = new ProcessMap<Long>();
-
- /**
- * Information about a process that is currently marked as bad.
- */
- static final class BadProcessInfo {
- BadProcessInfo(long time, String shortMsg, String longMsg, String stack) {
- this.time = time;
- this.shortMsg = shortMsg;
- this.longMsg = longMsg;
- this.stack = stack;
- }
-
- final long time;
- final String shortMsg;
- final String longMsg;
- final String stack;
- }
-
- /**
- * Set of applications that we consider to be bad, and will reject
- * incoming broadcasts from (which the user has no control over).
- * Processes are added to this set when they have crashed twice within
- * a minimum amount of time; they are removed from it when they are
- * later restarted (hopefully due to some user action). The value is the
- * time it was added to the list.
- */
- final ProcessMap<BadProcessInfo> mBadProcesses = new ProcessMap<BadProcessInfo>();
-
- /**
* All of the processes we currently have running organized by pid.
* The keys are the pid running the application.
*
@@ -1351,8 +1324,6 @@
final ArrayList<UidRecord.ChangeItem> mPendingUidChanges = new ArrayList<>();
final ArrayList<UidRecord.ChangeItem> mAvailUidChanges = new ArrayList<>();
- ArraySet<String> mAppsNotReportingCrashes;
-
/**
* Runtime CPU use collection thread. This object's lock is used to
* perform synchronization with the thread (notifying it to run).
@@ -1511,80 +1482,11 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_ERROR_UI_MSG: {
- HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
- boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
- synchronized (ActivityManagerService.this) {
- ProcessRecord proc = (ProcessRecord)data.get("app");
- AppErrorResult res = (AppErrorResult) data.get("result");
- if (proc != null && proc.crashDialog != null) {
- Slog.e(TAG, "App already has crash dialog: " + proc);
- if (res != null) {
- res.set(0);
- }
- return;
- }
- boolean isBackground = (UserHandle.getAppId(proc.uid)
- >= Process.FIRST_APPLICATION_UID
- && proc.pid != MY_PID);
- for (int userId : mUserController.getCurrentProfileIdsLocked()) {
- isBackground &= (proc.userId != userId);
- }
- if (isBackground && !showBackground) {
- Slog.w(TAG, "Skipping crash dialog of " + proc + ": background");
- if (res != null) {
- res.set(0);
- }
- return;
- }
- final boolean crashSilenced = mAppsNotReportingCrashes != null &&
- mAppsNotReportingCrashes.contains(proc.info.packageName);
- if (mShowDialogs && !mSleeping && !mShuttingDown && !crashSilenced) {
- Dialog d = new AppErrorDialog(mContext,
- ActivityManagerService.this, res, proc);
- d.show();
- proc.crashDialog = d;
- } else {
- // The device is asleep, so just pretend that the user
- // saw a crash dialog and hit "force quit".
- if (res != null) {
- res.set(0);
- }
- }
- }
-
+ mAppErrors.handleShowAppErrorUi(msg);
ensureBootCompleted();
} break;
case SHOW_NOT_RESPONDING_UI_MSG: {
- synchronized (ActivityManagerService.this) {
- HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
- ProcessRecord proc = (ProcessRecord)data.get("app");
- if (proc != null && proc.anrDialog != null) {
- Slog.e(TAG, "App already has anr dialog: " + proc);
- return;
- }
-
- Intent intent = new Intent("android.intent.action.ANR");
- if (!mProcessesReady) {
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- }
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */);
-
- if (mShowDialogs) {
- Dialog d = new AppNotRespondingDialog(ActivityManagerService.this,
- mContext, proc, (ActivityRecord)data.get("activity"),
- msg.arg1 != 0);
- d.show();
- proc.anrDialog = d;
- } else {
- // Just kill the app if there is no dialog to be shown.
- killAppAtUsersRequest(proc, null);
- }
- }
-
+ mAppErrors.handleShowAnrUi(msg);
ensureBootCompleted();
} break;
case SHOW_STRICT_MODE_VIOLATION_UI_MSG: {
@@ -2509,6 +2411,7 @@
mServices = new ActiveServices(this);
mProviderMap = new ProviderMap(this);
+ mAppErrors = new AppErrors(mContext, this);
// TODO: Move creation of battery stats service outside of activity manager service.
File dataDir = Environment.getDataDirectory();
@@ -3027,7 +2930,7 @@
return index;
}
- private static void killProcessGroup(int uid, int pid) {
+ static void killProcessGroup(int uid, int pid) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "killProcessGroup");
Process.killProcessGroup(uid, pid);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -3349,7 +3252,7 @@
if ((intentFlags & Intent.FLAG_FROM_BACKGROUND) != 0) {
// If we are in the background, then check to see if this process
// is bad. If so, we will just silently fail.
- if (mBadProcesses.get(info.processName, info.uid) != null) {
+ if (mAppErrors.isBadProcessLocked(info)) {
if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid
+ "/" + info.processName);
return null;
@@ -3361,12 +3264,12 @@
// if it had been bad.
if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid
+ "/" + info.processName);
- mProcessCrashTimes.remove(info.processName, info.uid);
- if (mBadProcesses.get(info.processName, info.uid) != null) {
+ mAppErrors.resetProcessCrashTimeLocked(info);
+ if (mAppErrors.isBadProcessLocked(info)) {
EventLog.writeEvent(EventLogTags.AM_PROC_GOOD,
UserHandle.getUserId(info.uid), info.uid,
info.processName);
- mBadProcesses.remove(info.processName, info.uid);
+ mAppErrors.clearBadProcessLocked(info);
if (app != null) {
app.bad = false;
}
@@ -4774,46 +4677,7 @@
}
synchronized(this) {
- ProcessRecord proc = null;
-
- // Figure out which process to kill. We don't trust that initialPid
- // still has any relation to current pids, so must scan through the
- // list.
- synchronized (mPidsSelfLocked) {
- for (int i=0; i<mPidsSelfLocked.size(); i++) {
- ProcessRecord p = mPidsSelfLocked.valueAt(i);
- if (p.uid != uid) {
- continue;
- }
- if (p.pid == initialPid) {
- proc = p;
- break;
- }
- if (p.pkgList.containsKey(packageName)) {
- proc = p;
- }
- }
- }
-
- if (proc == null) {
- Slog.w(TAG, "crashApplication: nothing for uid=" + uid
- + " initialPid=" + initialPid
- + " packageName=" + packageName);
- return;
- }
-
- if (proc.thread != null) {
- if (proc.pid == Process.myPid()) {
- Log.w(TAG, "crashApplication: trying to crash self!");
- return;
- }
- long ident = Binder.clearCallingIdentity();
- try {
- proc.thread.scheduleCrash(message);
- } catch (RemoteException e) {
- }
- Binder.restoreCallingIdentity(ident);
- }
+ mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, message);
}
}
@@ -5294,169 +5158,6 @@
}
}
- final void appNotResponding(ProcessRecord app, ActivityRecord activity,
- ActivityRecord parent, boolean aboveSystem, final String annotation) {
- ArrayList<Integer> firstPids = new ArrayList<Integer>(5);
- SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20);
-
- if (mController != null) {
- try {
- // 0 == continue, -1 = kill process immediately
- int res = mController.appEarlyNotResponding(app.processName, app.pid, annotation);
- if (res < 0 && app.pid != MY_PID) {
- app.kill("anr", true);
- }
- } catch (RemoteException e) {
- mController = null;
- Watchdog.getInstance().setActivityController(null);
- }
- }
-
- long anrTime = SystemClock.uptimeMillis();
- if (MONITOR_CPU_USAGE) {
- updateCpuStatsNow();
- }
-
- synchronized (this) {
- // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
- if (mShuttingDown) {
- Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation);
- return;
- } else if (app.notResponding) {
- Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation);
- return;
- } else if (app.crashing) {
- Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);
- return;
- }
-
- // In case we come through here for the same app before completing
- // this one, mark as anring now so we will bail out.
- app.notResponding = true;
-
- // Log the ANR to the event log.
- EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
- app.processName, app.info.flags, annotation);
-
- // Dump thread traces as quickly as we can, starting with "interesting" processes.
- firstPids.add(app.pid);
-
- int parentPid = app.pid;
- if (parent != null && parent.app != null && parent.app.pid > 0) parentPid = parent.app.pid;
- if (parentPid != app.pid) firstPids.add(parentPid);
-
- if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID);
-
- for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
- ProcessRecord r = mLruProcesses.get(i);
- if (r != null && r.thread != null) {
- int pid = r.pid;
- if (pid > 0 && pid != app.pid && pid != parentPid && pid != MY_PID) {
- if (r.persistent) {
- firstPids.add(pid);
- } else {
- lastPids.put(pid, Boolean.TRUE);
- }
- }
- }
- }
- }
-
- // Log the ANR to the main log.
- StringBuilder info = new StringBuilder();
- info.setLength(0);
- info.append("ANR in ").append(app.processName);
- if (activity != null && activity.shortComponentName != null) {
- info.append(" (").append(activity.shortComponentName).append(")");
- }
- info.append("\n");
- info.append("PID: ").append(app.pid).append("\n");
- if (annotation != null) {
- info.append("Reason: ").append(annotation).append("\n");
- }
- if (parent != null && parent != activity) {
- info.append("Parent: ").append(parent.shortComponentName).append("\n");
- }
-
- final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
-
- File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, lastPids,
- NATIVE_STACKS_OF_INTEREST);
-
- String cpuInfo = null;
- if (MONITOR_CPU_USAGE) {
- updateCpuStatsNow();
- synchronized (mProcessCpuTracker) {
- cpuInfo = mProcessCpuTracker.printCurrentState(anrTime);
- }
- info.append(processCpuTracker.printCurrentLoad());
- info.append(cpuInfo);
- }
-
- info.append(processCpuTracker.printCurrentState(anrTime));
-
- Slog.e(TAG, info.toString());
- if (tracesFile == null) {
- // There is no trace file, so dump (only) the alleged culprit's threads to the log
- Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
- }
-
- addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
- cpuInfo, tracesFile, null);
-
- if (mController != null) {
- try {
- // 0 == show dialog, 1 = keep waiting, -1 = kill process immediately
- int res = mController.appNotResponding(app.processName, app.pid, info.toString());
- if (res != 0) {
- if (res < 0 && app.pid != MY_PID) {
- app.kill("anr", true);
- } else {
- synchronized (this) {
- mServices.scheduleServiceTimeoutLocked(app);
- }
- }
- return;
- }
- } catch (RemoteException e) {
- mController = null;
- Watchdog.getInstance().setActivityController(null);
- }
- }
-
- // Unless configured otherwise, swallow ANRs in background processes & kill the process.
- boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
-
- synchronized (this) {
- mBatteryStatsService.noteProcessAnr(app.processName, app.uid);
-
- if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
- app.kill("bg anr", true);
- return;
- }
-
- // Set the app's notResponding state, and look up the errorReportReceiver
- makeAppNotRespondingLocked(app,
- activity != null ? activity.shortComponentName : null,
- annotation != null ? "ANR " + annotation : "ANR",
- info.toString());
-
- // Bring up the infamous App Not Responding dialog
- Message msg = Message.obtain();
- HashMap<String, Object> map = new HashMap<String, Object>();
- msg.what = SHOW_NOT_RESPONDING_UI_MSG;
- msg.obj = map;
- msg.arg1 = aboveSystem ? 1 : 0;
- map.put("app", app);
- if (activity != null) {
- map.put("activity", activity);
- }
-
- mUiHandler.sendMessage(msg);
- }
- }
-
final void showLaunchWarningLocked(final ActivityRecord cur, final ActivityRecord next) {
if (!mLaunchWarningShown) {
mLaunchWarningShown = true;
@@ -6079,33 +5780,7 @@
Slog.i(TAG, "Force stopping u" + userId + ": " + reason);
}
- final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
- for (int ip = pmap.size() - 1; ip >= 0; ip--) {
- SparseArray<Long> ba = pmap.valueAt(ip);
- for (i = ba.size() - 1; i >= 0; i--) {
- boolean remove = false;
- final int entUid = ba.keyAt(i);
- if (packageName != null) {
- if (userId == UserHandle.USER_ALL) {
- if (UserHandle.getAppId(entUid) == appId) {
- remove = true;
- }
- } else {
- if (entUid == UserHandle.getUid(userId, appId)) {
- remove = true;
- }
- }
- } else if (UserHandle.getUserId(entUid) == userId) {
- remove = true;
- }
- if (remove) {
- ba.removeAt(i);
- }
- }
- if (ba.size() == 0) {
- pmap.removeAt(ip);
- }
- }
+ mAppErrors.resetProcessCrashTimeLocked(packageName == null, appId, userId);
}
boolean didSomething = killPackageProcessesLocked(packageName, appId, userId,
@@ -6270,7 +5945,7 @@
}
}
- private final boolean removeProcessLocked(ProcessRecord app,
+ boolean removeProcessLocked(ProcessRecord app,
boolean callerWillRestart, boolean allowRestart, String reason) {
final String name = app.processName;
final int uid = app.uid;
@@ -9003,6 +8678,11 @@
continue;
}
+ if (tr.realActivitySuspended) {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, activity suspended: " + tr);
+ continue;
+ }
+
// Return the entry if desired by the caller. We always return
// the first entry, because callers always expect this to be the
// foreground app. We may filter others if the caller has
@@ -10933,7 +10613,7 @@
final long token = Binder.clearCallingIdentity();
try {
- appNotResponding(host, null, null, false, "ContentProvider not responding");
+ mAppErrors.appNotResponding(host, null, null, false, "ContentProvider not responding");
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -11697,7 +11377,7 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- appNotResponding(proc, activity, parent, aboveSystem, annotation);
+ mAppErrors.appNotResponding(proc, activity, parent, aboveSystem, annotation);
}
});
}
@@ -12491,17 +12171,8 @@
com.android.internal.R.dimen.thumbnail_height);
mDefaultPinnedStackBounds = Rect.unflattenFromString(res.getString(
com.android.internal.R.string.config_defaultPictureInPictureBounds));
- final String appsNotReportingCrashes = res.getString(
- com.android.internal.R.string.config_appsNotReportingCrashes);
- if (appsNotReportingCrashes != null) {
- final String[] split = appsNotReportingCrashes.split(",");
- if (split.length > 0) {
- mAppsNotReportingCrashes = new ArraySet<>();
- for (int i = 0; i < split.length; i++) {
- mAppsNotReportingCrashes.add(split[i]);
- }
- }
- }
+ mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString(
+ com.android.internal.R.string.config_appsNotReportingCrashes));
}
}
@@ -12906,174 +12577,12 @@
}
}
- private boolean makeAppCrashingLocked(ProcessRecord app,
- String shortMsg, String longMsg, String stackTrace) {
- app.crashing = true;
- app.crashingReport = generateProcessError(app,
- ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);
- startAppProblemLocked(app);
- app.stopFreezingAllLocked();
- return handleAppCrashLocked(app, "force-crash" /*reason*/, shortMsg, longMsg, stackTrace);
- }
-
- private void makeAppNotRespondingLocked(ProcessRecord app,
- String activity, String shortMsg, String longMsg) {
- app.notResponding = true;
- app.notRespondingReport = generateProcessError(app,
- ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
- activity, shortMsg, longMsg, null);
- startAppProblemLocked(app);
- app.stopFreezingAllLocked();
- }
-
- /**
- * Generate a process error record, suitable for attachment to a ProcessRecord.
- *
- * @param app The ProcessRecord in which the error occurred.
- * @param condition Crashing, Application Not Responding, etc. Values are defined in
- * ActivityManager.AppErrorStateInfo
- * @param activity The activity associated with the crash, if known.
- * @param shortMsg Short message describing the crash.
- * @param longMsg Long message describing the crash.
- * @param stackTrace Full crash stack trace, may be null.
- *
- * @return Returns a fully-formed AppErrorStateInfo record.
- */
- private ActivityManager.ProcessErrorStateInfo generateProcessError(ProcessRecord app,
- int condition, String activity, String shortMsg, String longMsg, String stackTrace) {
- ActivityManager.ProcessErrorStateInfo report = new ActivityManager.ProcessErrorStateInfo();
-
- report.condition = condition;
- report.processName = app.processName;
- report.pid = app.pid;
- report.uid = app.info.uid;
- report.tag = activity;
- report.shortMsg = shortMsg;
- report.longMsg = longMsg;
- report.stackTrace = stackTrace;
-
- return report;
- }
-
void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) {
synchronized (this) {
- app.crashing = false;
- app.crashingReport = null;
- app.notResponding = false;
- app.notRespondingReport = null;
- if (app.anrDialog == fromDialog) {
- app.anrDialog = null;
- }
- if (app.waitDialog == fromDialog) {
- app.waitDialog = null;
- }
- if (app.pid > 0 && app.pid != MY_PID) {
- handleAppCrashLocked(app, "user-terminated" /*reason*/,
- null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/);
- app.kill("user request after error", true);
- }
+ mAppErrors.killAppAtUserRequestLocked(app, fromDialog);
}
}
- private boolean handleAppCrashLocked(ProcessRecord app, String reason,
- String shortMsg, String longMsg, String stackTrace) {
- long now = SystemClock.uptimeMillis();
-
- Long crashTime;
- if (!app.isolated) {
- crashTime = mProcessCrashTimes.get(app.info.processName, app.uid);
- } else {
- crashTime = null;
- }
- if (crashTime != null && now < crashTime+ProcessList.MIN_CRASH_INTERVAL) {
- // This process loses!
- Slog.w(TAG, "Process " + app.info.processName
- + " has crashed too many times: killing!");
- EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH,
- app.userId, app.info.processName, app.uid);
- mStackSupervisor.handleAppCrashLocked(app);
- if (!app.persistent) {
- // We don't want to start this process again until the user
- // explicitly does so... but for persistent process, we really
- // need to keep it running. If a persistent process is actually
- // repeatedly crashing, then badness for everyone.
- EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.userId, app.uid,
- app.info.processName);
- if (!app.isolated) {
- // XXX We don't have a way to mark isolated processes
- // as bad, since they don't have a peristent identity.
- mBadProcesses.put(app.info.processName, app.uid,
- new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
- mProcessCrashTimes.remove(app.info.processName, app.uid);
- }
- app.bad = true;
- app.removed = true;
- // Don't let services in this process be restarted and potentially
- // annoy the user repeatedly. Unless it is persistent, since those
- // processes run critical code.
- removeProcessLocked(app, false, false, "crash");
- mStackSupervisor.resumeFocusedStackTopActivityLocked();
- return false;
- }
- mStackSupervisor.resumeFocusedStackTopActivityLocked();
- } else {
- mStackSupervisor.finishTopRunningActivityLocked(app, reason);
- }
-
- // Bump up the crash count of any services currently running in the proc.
- for (int i=app.services.size()-1; i>=0; i--) {
- // Any services running in the application need to be placed
- // back in the pending list.
- ServiceRecord sr = app.services.valueAt(i);
- sr.crashCount++;
- }
-
- // If the crashing process is what we consider to be the "home process" and it has been
- // replaced by a third-party app, clear the package preferred activities from packages
- // with a home activity running in the process to prevent a repeatedly crashing app
- // from blocking the user to manually clear the list.
- final ArrayList<ActivityRecord> activities = app.activities;
- if (app == mHomeProcess && activities.size() > 0
- && (mHomeProcess.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
- if (r.isHomeActivity()) {
- Log.i(TAG, "Clearing package preferred activities from " + r.packageName);
- try {
- ActivityThread.getPackageManager()
- .clearPackagePreferredActivities(r.packageName);
- } catch (RemoteException c) {
- // pm is in same process, this will never happen.
- }
- }
- }
- }
-
- if (!app.isolated) {
- // XXX Can't keep track of crash times for isolated processes,
- // because they don't have a perisistent identity.
- mProcessCrashTimes.put(app.info.processName, app.uid, now);
- }
-
- if (app.crashHandler != null) mHandler.post(app.crashHandler);
- return true;
- }
-
- void startAppProblemLocked(ProcessRecord app) {
- // If this app is not running under the current user, then we
- // can't give it a report button because that would require
- // launching the report UI under a different user.
- app.errorReportReceiver = null;
-
- for (int userId : mUserController.getCurrentProfileIdsLocked()) {
- if (app.userId == userId) {
- app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
- mContext, app.info.packageName, app.info.flags);
- }
- }
- skipCurrentReceiverLocked(app);
- }
-
void skipCurrentReceiverLocked(ProcessRecord app) {
for (BroadcastQueue queue : mBroadcastQueues) {
queue.skipCurrentReceiverLocked(app);
@@ -13109,7 +12618,7 @@
addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
- crashApplication(r, crashInfo);
+ mAppErrors.crashApplication(r, crashInfo);
}
public void handleApplicationStrictModeViolation(
@@ -13320,7 +12829,7 @@
if (r != null && r.pid != Process.myPid() &&
Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.WTF_IS_FATAL, 0) != 0) {
- crashApplication(r, crashInfo);
+ mAppErrors.crashApplication(r, crashInfo);
return true;
} else {
return false;
@@ -13529,164 +13038,6 @@
}
}
- /**
- * Bring up the "unexpected error" dialog box for a crashing app.
- * Deal with edge cases (intercepts from instrumented applications,
- * ActivityController, error intent receivers, that sort of thing).
- * @param r the application crashing
- * @param crashInfo describing the failure
- */
- private void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
- long timeMillis = System.currentTimeMillis();
- String shortMsg = crashInfo.exceptionClassName;
- String longMsg = crashInfo.exceptionMessage;
- String stackTrace = crashInfo.stackTrace;
- if (shortMsg != null && longMsg != null) {
- longMsg = shortMsg + ": " + longMsg;
- } else if (shortMsg != null) {
- longMsg = shortMsg;
- }
-
- AppErrorResult result = new AppErrorResult();
- synchronized (this) {
- if (mController != null) {
- try {
- String name = r != null ? r.processName : null;
- int pid = r != null ? r.pid : Binder.getCallingPid();
- int uid = r != null ? r.info.uid : Binder.getCallingUid();
- if (!mController.appCrashed(name, pid,
- shortMsg, longMsg, timeMillis, crashInfo.stackTrace)) {
- if ("1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"))
- && "Native crash".equals(crashInfo.exceptionClassName)) {
- Slog.w(TAG, "Skip killing native crashed app " + name
- + "(" + pid + ") during testing");
- } else {
- Slog.w(TAG, "Force-killing crashed app " + name
- + " at watcher's request");
- if (r != null) {
- r.kill("crash", true);
- } else {
- // Huh.
- Process.killProcess(pid);
- killProcessGroup(uid, pid);
- }
- }
- return;
- }
- } catch (RemoteException e) {
- mController = null;
- Watchdog.getInstance().setActivityController(null);
- }
- }
-
- final long origId = Binder.clearCallingIdentity();
-
- // If this process is running instrumentation, finish it.
- if (r != null && r.instrumentationClass != null) {
- Slog.w(TAG, "Error in app " + r.processName
- + " running instrumentation " + r.instrumentationClass + ":");
- if (shortMsg != null) Slog.w(TAG, " " + shortMsg);
- if (longMsg != null) Slog.w(TAG, " " + longMsg);
- Bundle info = new Bundle();
- info.putString("shortMsg", shortMsg);
- info.putString("longMsg", longMsg);
- finishInstrumentationLocked(r, Activity.RESULT_CANCELED, info);
- Binder.restoreCallingIdentity(origId);
- return;
- }
-
- // Log crash in battery stats.
- if (r != null) {
- mBatteryStatsService.noteProcessCrash(r.processName, r.uid);
- }
-
- // If we can't identify the process or it's already exceeded its crash quota,
- // quit right away without showing a crash dialog.
- if (r == null || !makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace)) {
- Binder.restoreCallingIdentity(origId);
- return;
- }
-
- Message msg = Message.obtain();
- msg.what = SHOW_ERROR_UI_MSG;
- HashMap data = new HashMap();
- data.put("result", result);
- data.put("app", r);
- msg.obj = data;
- mUiHandler.sendMessage(msg);
-
- Binder.restoreCallingIdentity(origId);
- }
-
- int res = result.get();
-
- Intent appErrorIntent = null;
- synchronized (this) {
- if (r != null && !r.isolated) {
- // XXX Can't keep track of crash time for isolated processes,
- // since they don't have a persistent identity.
- mProcessCrashTimes.put(r.info.processName, r.uid,
- SystemClock.uptimeMillis());
- }
- if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {
- appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);
- }
- }
-
- if (appErrorIntent != null) {
- try {
- mContext.startActivityAsUser(appErrorIntent, new UserHandle(r.userId));
- } catch (ActivityNotFoundException e) {
- Slog.w(TAG, "bug report receiver dissappeared", e);
- }
- }
- }
-
- Intent createAppErrorIntentLocked(ProcessRecord r,
- long timeMillis, ApplicationErrorReport.CrashInfo crashInfo) {
- ApplicationErrorReport report = createAppErrorReportLocked(r, timeMillis, crashInfo);
- if (report == null) {
- return null;
- }
- Intent result = new Intent(Intent.ACTION_APP_ERROR);
- result.setComponent(r.errorReportReceiver);
- result.putExtra(Intent.EXTRA_BUG_REPORT, report);
- result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return result;
- }
-
- private ApplicationErrorReport createAppErrorReportLocked(ProcessRecord r,
- long timeMillis, ApplicationErrorReport.CrashInfo crashInfo) {
- if (r.errorReportReceiver == null) {
- return null;
- }
-
- if (!r.crashing && !r.notResponding && !r.forceCrashReport) {
- return null;
- }
-
- ApplicationErrorReport report = new ApplicationErrorReport();
- report.packageName = r.info.packageName;
- report.installerPackageName = r.errorReportReceiver.getPackageName();
- report.processName = r.processName;
- report.time = timeMillis;
- report.systemApp = (r.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-
- if (r.crashing || r.forceCrashReport) {
- report.type = ApplicationErrorReport.TYPE_CRASH;
- report.crashInfo = crashInfo;
- } else if (r.notResponding) {
- report.type = ApplicationErrorReport.TYPE_ANR;
- report.anrInfo = new ApplicationErrorReport.AnrInfo();
-
- report.anrInfo.activity = r.notRespondingReport.tag;
- report.anrInfo.cause = r.notRespondingReport.shortMsg;
- report.anrInfo.info = r.notRespondingReport.longMsg;
- }
-
- return report;
- }
-
public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() {
enforceNotIsolatedCaller("getProcessesInErrorState");
// assume our apps are happy - lazy create the list
@@ -14424,88 +13775,9 @@
needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, dumpPackage);
- if (mProcessCrashTimes.getMap().size() > 0) {
- boolean printed = false;
- long now = SystemClock.uptimeMillis();
- final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
- final int NP = pmap.size();
- for (int ip=0; ip<NP; ip++) {
- String pname = pmap.keyAt(ip);
- SparseArray<Long> uids = pmap.valueAt(ip);
- final int N = uids.size();
- for (int i=0; i<N; i++) {
- int puid = uids.keyAt(i);
- ProcessRecord r = mProcessNames.get(pname, puid);
- if (dumpPackage != null && (r == null
- || !r.pkgList.containsKey(dumpPackage))) {
- continue;
- }
- if (!printed) {
- if (needSep) pw.println();
- needSep = true;
- pw.println(" Time since processes crashed:");
- printed = true;
- printedAnything = true;
- }
- pw.print(" Process "); pw.print(pname);
- pw.print(" uid "); pw.print(puid);
- pw.print(": last crashed ");
- TimeUtils.formatDuration(now-uids.valueAt(i), pw);
- pw.println(" ago");
- }
- }
- }
-
- if (mBadProcesses.getMap().size() > 0) {
- boolean printed = false;
- final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = mBadProcesses.getMap();
- final int NP = pmap.size();
- for (int ip=0; ip<NP; ip++) {
- String pname = pmap.keyAt(ip);
- SparseArray<BadProcessInfo> uids = pmap.valueAt(ip);
- final int N = uids.size();
- for (int i=0; i<N; i++) {
- int puid = uids.keyAt(i);
- ProcessRecord r = mProcessNames.get(pname, puid);
- if (dumpPackage != null && (r == null
- || !r.pkgList.containsKey(dumpPackage))) {
- continue;
- }
- if (!printed) {
- if (needSep) pw.println();
- needSep = true;
- pw.println(" Bad processes:");
- printedAnything = true;
- }
- BadProcessInfo info = uids.valueAt(i);
- pw.print(" Bad process "); pw.print(pname);
- pw.print(" uid "); pw.print(puid);
- pw.print(": crashed at time "); pw.println(info.time);
- if (info.shortMsg != null) {
- pw.print(" Short msg: "); pw.println(info.shortMsg);
- }
- if (info.longMsg != null) {
- pw.print(" Long msg: "); pw.println(info.longMsg);
- }
- if (info.stack != null) {
- pw.println(" Stack:");
- int lastPos = 0;
- for (int pos=0; pos<info.stack.length(); pos++) {
- if (info.stack.charAt(pos) == '\n') {
- pw.print(" ");
- pw.write(info.stack, lastPos, pos-lastPos);
- pw.println();
- lastPos = pos+1;
- }
- }
- if (lastPos < info.stack.length()) {
- pw.print(" ");
- pw.write(info.stack, lastPos, info.stack.length()-lastPos);
- pw.println();
- }
- }
- }
- }
+ needSep = mAppErrors.dumpLocked(fd, pw, needSep, dumpPackage);
+ if (needSep) {
+ printedAnything = true;
}
if (dumpPackage == null) {
@@ -17587,6 +16859,8 @@
case Intent.ACTION_PACKAGE_CHANGED:
case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+ case Intent.ACTION_PACKAGES_SUSPENDED:
+ case Intent.ACTION_PACKAGES_UNSUSPENDED:
// Handle special intents: if this broadcast is from the package
// manager about a package being removed, we need to remove all of
// its activities from the history stack.
@@ -17667,6 +16941,20 @@
}
}
break;
+ case Intent.ACTION_PACKAGES_SUSPENDED:
+ case Intent.ACTION_PACKAGES_UNSUSPENDED:
+ final boolean suspended = Intent.ACTION_PACKAGES_SUSPENDED.equals(
+ intent.getAction());
+ final String[] packageNames = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ final int userHandle = intent.getIntExtra(
+ Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
+ synchronized(ActivityManagerService.this) {
+ mRecentTasks.onPackagesSuspendedChanged(
+ packageNames, suspended, userHandle);
+ }
+ break;
}
break;
case Intent.ACTION_PACKAGE_ADDED:
@@ -21152,13 +20440,6 @@
}
}
- void stopReportingCrashesLocked(ProcessRecord proc) {
- if (mAppsNotReportingCrashes == null) {
- mAppsNotReportingCrashes = new ArraySet<>();
- }
- mAppsNotReportingCrashes.add(proc.info.packageName);
- }
-
private final class LocalService extends ActivityManagerInternal {
@Override
public void onWakefulnessChanged(int wakefulness) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 4bac2d6..ef8d230 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -3041,42 +3041,44 @@
mService.updateOomAdjLocked();
}
- final void finishTopRunningActivityLocked(ProcessRecord app, String reason) {
+ final TaskRecord finishTopRunningActivityLocked(ProcessRecord app, String reason) {
ActivityRecord r = topRunningActivityLocked();
- if (r != null && r.app == app) {
- // If the top running activity is from this crashing
- // process, then terminate it to avoid getting in a loop.
- Slog.w(TAG, " Force finishing activity "
- + r.intent.getComponent().flattenToShortString());
- int taskNdx = mTaskHistory.indexOf(r.task);
- int activityNdx = r.task.mActivities.indexOf(r);
- finishActivityLocked(r, Activity.RESULT_CANCELED, null, reason, false);
- // Also terminate any activities below it that aren't yet
- // stopped, to avoid a situation where one will get
- // re-start our crashing activity once it gets resumed again.
- --activityNdx;
- if (activityNdx < 0) {
- do {
- --taskNdx;
- if (taskNdx < 0) {
- break;
- }
- activityNdx = mTaskHistory.get(taskNdx).mActivities.size() - 1;
- } while (activityNdx < 0);
- }
- if (activityNdx >= 0) {
- r = mTaskHistory.get(taskNdx).mActivities.get(activityNdx);
- if (r.state == ActivityState.RESUMED
- || r.state == ActivityState.PAUSING
- || r.state == ActivityState.PAUSED) {
- if (!r.isHomeActivity() || mService.mHomeProcess != r.app) {
- Slog.w(TAG, " Force finishing activity "
- + r.intent.getComponent().flattenToShortString());
- finishActivityLocked(r, Activity.RESULT_CANCELED, null, reason, false);
- }
+ TaskRecord finishedTask = null;
+ if (r == null || r.app != app) {
+ return null;
+ }
+ Slog.w(TAG, " Force finishing activity "
+ + r.intent.getComponent().flattenToShortString());
+ int taskNdx = mTaskHistory.indexOf(r.task);
+ int activityNdx = r.task.mActivities.indexOf(r);
+ finishActivityLocked(r, Activity.RESULT_CANCELED, null, reason, false);
+ finishedTask = r.task;
+ // Also terminate any activities below it that aren't yet
+ // stopped, to avoid a situation where one will get
+ // re-start our crashing activity once it gets resumed again.
+ --activityNdx;
+ if (activityNdx < 0) {
+ do {
+ --taskNdx;
+ if (taskNdx < 0) {
+ break;
+ }
+ activityNdx = mTaskHistory.get(taskNdx).mActivities.size() - 1;
+ } while (activityNdx < 0);
+ }
+ if (activityNdx >= 0) {
+ r = mTaskHistory.get(taskNdx).mActivities.get(activityNdx);
+ if (r.state == ActivityState.RESUMED
+ || r.state == ActivityState.PAUSING
+ || r.state == ActivityState.PAUSED) {
+ if (!r.isHomeActivity() || mService.mHomeProcess != r.app) {
+ Slog.w(TAG, " Force finishing activity "
+ + r.intent.getComponent().flattenToShortString());
+ finishActivityLocked(r, Activity.RESULT_CANCELED, null, reason, false);
}
}
}
+ return finishedTask;
}
final void finishVoiceTask(IVoiceInteractionSession session) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 1fc674b..8db2f8f 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1668,15 +1668,21 @@
return false;
}
- void finishTopRunningActivityLocked(ProcessRecord app, String reason) {
+ TaskRecord finishTopRunningActivityLocked(ProcessRecord app, String reason) {
+ TaskRecord finishedTask = null;
+ ActivityStack focusedStack = getFocusedStack();
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
final int numStacks = stacks.size();
for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
- stack.finishTopRunningActivityLocked(app, reason);
+ TaskRecord t = stack.finishTopRunningActivityLocked(app, reason);
+ if (stack == focusedStack || finishedTask == null) {
+ finishedTask = t;
+ }
}
}
+ return finishedTask;
}
void finishVoiceTask(IVoiceInteractionSession session) {
diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java
index c87eae0..b746a4b 100644
--- a/services/core/java/com/android/server/am/AppErrorDialog.java
+++ b/services/core/java/com/android/server/am/AppErrorDialog.java
@@ -16,73 +16,74 @@
package com.android.server.am;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
import android.content.Context;
-import android.content.DialogInterface;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.view.WindowManager;
-import android.widget.CheckBox;
import android.widget.FrameLayout;
import android.widget.TextView;
-final class AppErrorDialog extends BaseErrorDialog {
+import java.util.List;
+
+import static com.android.server.am.ActivityManagerService.IS_USER_BUILD;
+
+final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListener {
private final ActivityManagerService mService;
private final AppErrorResult mResult;
private final ProcessRecord mProc;
+ private final boolean mRepeating;
+
private CharSequence mName;
// Event 'what' codes
- static final int FORCE_QUIT = 0;
- static final int FORCE_QUIT_AND_REPORT = 1;
+ static final int FORCE_QUIT = 1;
+ static final int FORCE_QUIT_AND_REPORT = 2;
+ static final int RESTART = 3;
+ static final int RESET = 4;
+ static final int MUTE = 5;
// 5-minute timeout, then we automatically dismiss the crash dialog
static final long DISMISS_TIMEOUT = 1000 * 60 * 5;
-
- public AppErrorDialog(Context context, ActivityManagerService service,
- AppErrorResult result, ProcessRecord app) {
- super(context);
+ public AppErrorDialog(Context context, ActivityManagerService service, Data data) {
+ super(context);
Resources res = context.getResources();
mService = service;
- mProc = app;
- mResult = result;
- if ((app.pkgList.size() == 1) &&
- (mName = context.getPackageManager().getApplicationLabel(app.info)) != null) {
- setMessage(res.getString(
- com.android.internal.R.string.aerr_application,
- mName.toString(), app.info.processName));
+ mProc = data.proc;
+ mResult = data.result;
+ mRepeating = data.repeating;
+ if ((mProc.pkgList.size() == 1) &&
+ (mName = context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
+ setTitle(res.getString(
+ mRepeating ? com.android.internal.R.string.aerr_application_repeated
+ : com.android.internal.R.string.aerr_application,
+ mName.toString(), mProc.info.processName));
} else {
- mName = app.processName;
- setMessage(res.getString(
- com.android.internal.R.string.aerr_process,
+ mName = mProc.processName;
+ setTitle(res.getString(
+ mRepeating ? com.android.internal.R.string.aerr_process_repeated
+ : com.android.internal.R.string.aerr_process,
mName.toString()));
}
setCancelable(false);
- setButton(DialogInterface.BUTTON_POSITIVE,
- res.getText(com.android.internal.R.string.force_close),
- mHandler.obtainMessage(FORCE_QUIT));
-
- if (app.errorReportReceiver != null) {
- setButton(DialogInterface.BUTTON_NEGATIVE,
- res.getText(com.android.internal.R.string.report),
- mHandler.obtainMessage(FORCE_QUIT_AND_REPORT));
- }
-
- setTitle(res.getText(com.android.internal.R.string.aerr_title));
WindowManager.LayoutParams attrs = getWindow().getAttributes();
- attrs.setTitle("Application Error: " + app.info.processName);
+ attrs.setTitle("Application Error: " + mProc.info.processName);
attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR
| WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
getWindow().setAttributes(attrs);
- if (app.persistent) {
+ if (mProc.persistent) {
getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
}
@@ -95,38 +96,44 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (!ActivityManagerService.IS_USER_BUILD) {
- FrameLayout frame = (FrameLayout) findViewById(android.R.id.custom);
- Context context = getContext();
- LayoutInflater.from(context).inflate(
- com.android.internal.R.layout.app_error_dialog_dont_show_again, frame, true);
- ((TextView) frame.findViewById(com.android.internal.R.id.text)).setText(
- context.getResources().getString(
- com.android.internal.R.string.aerr_process_silence,
- mName.toString()));
- findViewById(com.android.internal.R.id.customPanel).setVisibility(View.VISIBLE);
- }
+ final FrameLayout frame = (FrameLayout) findViewById(android.R.id.custom);
+ final Context context = getContext();
+ LayoutInflater.from(context).inflate(
+ com.android.internal.R.layout.app_error_dialog, frame, true);
+
+ final TextView restart = (TextView) findViewById(com.android.internal.R.id.aerr_restart);
+ restart.setOnClickListener(this);
+ restart.setVisibility(!mRepeating ? View.VISIBLE : View.GONE);
+ final TextView reset = (TextView) findViewById(com.android.internal.R.id.aerr_reset);
+ reset.setOnClickListener(this);
+ reset.setVisibility(mRepeating ? View.VISIBLE : View.GONE);
+ final TextView report = (TextView) findViewById(com.android.internal.R.id.aerr_report);
+ report.setOnClickListener(this);
+ final boolean hasReceiver = mProc.errorReportReceiver != null;
+ report.setVisibility(hasReceiver ? View.VISIBLE : View.GONE);
+ final TextView close = (TextView) findViewById(com.android.internal.R.id.aerr_close);
+ close.setOnClickListener(this);
+ final TextView mute = (TextView) findViewById(com.android.internal.R.id.aerr_mute);
+ mute.setOnClickListener(this);
+ mute.setVisibility(!IS_USER_BUILD ? View.VISIBLE : View.GONE);
+
+ findViewById(com.android.internal.R.id.customPanel).setVisibility(View.VISIBLE);
}
private final Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
- View view = findViewById(com.android.internal.R.id.checkbox);
- final boolean stopReporting = view != null && ((CheckBox) view).isChecked();
+ final int result = msg.what;
+
synchronized (mService) {
if (mProc != null && mProc.crashDialog == AppErrorDialog.this) {
mProc.crashDialog = null;
}
- if (stopReporting) {
- mService.stopReportingCrashesLocked(mProc);
- }
}
- mResult.set(msg.what);
+ mResult.set(result);
// Make sure we don't have time timeout still hanging around.
removeMessages(FORCE_QUIT);
- // If this is a timeout we won't be automatically closed, so go
- // ahead and explicitly dismiss ourselves just in case.
dismiss();
}
};
@@ -139,4 +146,34 @@
}
super.dismiss();
}
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case com.android.internal.R.id.aerr_restart:
+ mHandler.obtainMessage(RESTART).sendToTarget();
+ break;
+ case com.android.internal.R.id.aerr_reset:
+ mHandler.obtainMessage(RESET).sendToTarget();
+ break;
+ case com.android.internal.R.id.aerr_report:
+ mHandler.obtainMessage(FORCE_QUIT_AND_REPORT).sendToTarget();
+ break;
+ case com.android.internal.R.id.aerr_close:
+ mHandler.obtainMessage(FORCE_QUIT).sendToTarget();
+ break;
+ case com.android.internal.R.id.aerr_mute:
+ mHandler.obtainMessage(MUTE).sendToTarget();
+ break;
+ default:
+ break;
+ }
+ }
+
+ static class Data {
+ AppErrorResult result;
+ TaskRecord task;
+ boolean repeating;
+ ProcessRecord proc;
+ }
}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
new file mode 100644
index 0000000..58d9f45
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -0,0 +1,964 @@
+/*
+ * Copyright (C) 2016 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.am;
+
+import com.android.internal.app.ProcessMap;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.server.Watchdog;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityThread;
+import android.app.AppOpsManager;
+import android.app.ApplicationErrorReport;
+import android.app.Dialog;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.concurrent.Semaphore;
+
+import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.MY_PID;
+import static com.android.server.am.ActivityManagerService.SYSTEM_DEBUGGABLE;
+
+/**
+ * Controls error conditions in applications.
+ */
+class AppErrors {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "AppErrors" : TAG_AM;
+
+ private final ActivityManagerService mService;
+ private final Context mContext;
+
+ private ArraySet<String> mAppsNotReportingCrashes;
+
+ /**
+ * The last time that various processes have crashed since they were last explicitly started.
+ */
+ private final ProcessMap<Long> mProcessCrashTimes = new ProcessMap<>();
+
+ /**
+ * The last time that various processes have crashed (not reset even when explicitly started).
+ */
+ private final ProcessMap<Long> mProcessCrashTimesPersistent = new ProcessMap<>();
+
+ /**
+ * Set of applications that we consider to be bad, and will reject
+ * incoming broadcasts from (which the user has no control over).
+ * Processes are added to this set when they have crashed twice within
+ * a minimum amount of time; they are removed from it when they are
+ * later restarted (hopefully due to some user action). The value is the
+ * time it was added to the list.
+ */
+ private final ProcessMap<BadProcessInfo> mBadProcesses = new ProcessMap<>();
+
+
+ AppErrors(Context context, ActivityManagerService service) {
+ mService = service;
+ mContext = context;
+ }
+
+ boolean dumpLocked(FileDescriptor fd, PrintWriter pw, boolean needSep,
+ String dumpPackage) {
+ if (!mProcessCrashTimes.getMap().isEmpty()) {
+ boolean printed = false;
+ final long now = SystemClock.uptimeMillis();
+ final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
+ final int processCount = pmap.size();
+ for (int ip = 0; ip < processCount; ip++) {
+ final String pname = pmap.keyAt(ip);
+ final SparseArray<Long> uids = pmap.valueAt(ip);
+ final int uidCount = uids.size();
+ for (int i = 0; i < uidCount; i++) {
+ final int puid = uids.keyAt(i);
+ final ProcessRecord r = mService.mProcessNames.get(pname, puid);
+ if (dumpPackage != null && (r == null
+ || !r.pkgList.containsKey(dumpPackage))) {
+ continue;
+ }
+ if (!printed) {
+ if (needSep) pw.println();
+ needSep = true;
+ pw.println(" Time since processes crashed:");
+ printed = true;
+ }
+ pw.print(" Process "); pw.print(pname);
+ pw.print(" uid "); pw.print(puid);
+ pw.print(": last crashed ");
+ TimeUtils.formatDuration(now-uids.valueAt(i), pw);
+ pw.println(" ago");
+ }
+ }
+ }
+
+ if (!mBadProcesses.getMap().isEmpty()) {
+ boolean printed = false;
+ final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = mBadProcesses.getMap();
+ final int processCount = pmap.size();
+ for (int ip = 0; ip < processCount; ip++) {
+ final String pname = pmap.keyAt(ip);
+ final SparseArray<BadProcessInfo> uids = pmap.valueAt(ip);
+ final int uidCount = uids.size();
+ for (int i = 0; i < uidCount; i++) {
+ final int puid = uids.keyAt(i);
+ final ProcessRecord r = mService.mProcessNames.get(pname, puid);
+ if (dumpPackage != null && (r == null
+ || !r.pkgList.containsKey(dumpPackage))) {
+ continue;
+ }
+ if (!printed) {
+ if (needSep) pw.println();
+ needSep = true;
+ pw.println(" Bad processes:");
+ printed = true;
+ }
+ final BadProcessInfo info = uids.valueAt(i);
+ pw.print(" Bad process "); pw.print(pname);
+ pw.print(" uid "); pw.print(puid);
+ pw.print(": crashed at time "); pw.println(info.time);
+ if (info.shortMsg != null) {
+ pw.print(" Short msg: "); pw.println(info.shortMsg);
+ }
+ if (info.longMsg != null) {
+ pw.print(" Long msg: "); pw.println(info.longMsg);
+ }
+ if (info.stack != null) {
+ pw.println(" Stack:");
+ int lastPos = 0;
+ for (int pos = 0; pos < info.stack.length(); pos++) {
+ if (info.stack.charAt(pos) == '\n') {
+ pw.print(" ");
+ pw.write(info.stack, lastPos, pos-lastPos);
+ pw.println();
+ lastPos = pos+1;
+ }
+ }
+ if (lastPos < info.stack.length()) {
+ pw.print(" ");
+ pw.write(info.stack, lastPos, info.stack.length()-lastPos);
+ pw.println();
+ }
+ }
+ }
+ }
+ }
+ return needSep;
+ }
+
+ boolean isBadProcessLocked(ApplicationInfo info) {
+ return mBadProcesses.get(info.processName, info.uid) != null;
+ }
+
+ void clearBadProcessLocked(ApplicationInfo info) {
+ mBadProcesses.remove(info.processName, info.uid);
+ }
+
+ void resetProcessCrashTimeLocked(ApplicationInfo info) {
+ mProcessCrashTimes.remove(info.processName, info.uid);
+ }
+
+ void resetProcessCrashTimeLocked(boolean resetEntireUser, int appId, int userId) {
+ final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
+ for (int ip = pmap.size() - 1; ip >= 0; ip--) {
+ SparseArray<Long> ba = pmap.valueAt(ip);
+ for (int i = ba.size() - 1; i >= 0; i--) {
+ boolean remove = false;
+ final int entUid = ba.keyAt(i);
+ if (!resetEntireUser) {
+ if (userId == UserHandle.USER_ALL) {
+ if (UserHandle.getAppId(entUid) == appId) {
+ remove = true;
+ }
+ } else {
+ if (entUid == UserHandle.getUid(userId, appId)) {
+ remove = true;
+ }
+ }
+ } else if (UserHandle.getUserId(entUid) == userId) {
+ remove = true;
+ }
+ if (remove) {
+ ba.removeAt(i);
+ }
+ }
+ if (ba.size() == 0) {
+ pmap.removeAt(ip);
+ }
+ }
+ }
+
+ void loadAppsNotReportingCrashesFromConfigLocked(String appsNotReportingCrashesConfig) {
+ if (appsNotReportingCrashesConfig != null) {
+ final String[] split = appsNotReportingCrashesConfig.split(",");
+ if (split.length > 0) {
+ mAppsNotReportingCrashes = new ArraySet<>();
+ Collections.addAll(mAppsNotReportingCrashes, split);
+ }
+ }
+ }
+
+ void killAppAtUserRequestLocked(ProcessRecord app, Dialog fromDialog) {
+ app.crashing = false;
+ app.crashingReport = null;
+ app.notResponding = false;
+ app.notRespondingReport = null;
+ if (app.anrDialog == fromDialog) {
+ app.anrDialog = null;
+ }
+ if (app.waitDialog == fromDialog) {
+ app.waitDialog = null;
+ }
+ if (app.pid > 0 && app.pid != MY_PID) {
+ handleAppCrashLocked(app, "user-terminated" /*reason*/,
+ null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/, null /*data*/);
+ app.kill("user request after error", true);
+ }
+ }
+
+ void scheduleAppCrashLocked(int uid, int initialPid, String packageName,
+ String message) {
+ ProcessRecord proc = null;
+
+ // Figure out which process to kill. We don't trust that initialPid
+ // still has any relation to current pids, so must scan through the
+ // list.
+
+ synchronized (mService.mPidsSelfLocked) {
+ for (int i=0; i<mService.mPidsSelfLocked.size(); i++) {
+ ProcessRecord p = mService.mPidsSelfLocked.valueAt(i);
+ if (p.uid != uid) {
+ continue;
+ }
+ if (p.pid == initialPid) {
+ proc = p;
+ break;
+ }
+ if (p.pkgList.containsKey(packageName)) {
+ proc = p;
+ }
+ }
+ }
+
+ if (proc == null) {
+ Slog.w(TAG, "crashApplication: nothing for uid=" + uid
+ + " initialPid=" + initialPid
+ + " packageName=" + packageName);
+ return;
+ }
+
+ if (proc.thread != null) {
+ if (proc.pid == Process.myPid()) {
+ Log.w(TAG, "crashApplication: trying to crash self!");
+ return;
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
+ proc.thread.scheduleCrash(message);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ /**
+ * Bring up the "unexpected error" dialog box for a crashing app.
+ * Deal with edge cases (intercepts from instrumented applications,
+ * ActivityController, error intent receivers, that sort of thing).
+ * @param r the application crashing
+ * @param crashInfo describing the failure
+ */
+ void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
+ long timeMillis = System.currentTimeMillis();
+ String shortMsg = crashInfo.exceptionClassName;
+ String longMsg = crashInfo.exceptionMessage;
+ String stackTrace = crashInfo.stackTrace;
+ if (shortMsg != null && longMsg != null) {
+ longMsg = shortMsg + ": " + longMsg;
+ } else if (shortMsg != null) {
+ longMsg = shortMsg;
+ }
+
+ AppErrorResult result = new AppErrorResult();
+ TaskRecord task;
+ synchronized (mService) {
+ if (mService.mController != null) {
+ try {
+ String name = r != null ? r.processName : null;
+ int pid = r != null ? r.pid : Binder.getCallingPid();
+ int uid = r != null ? r.info.uid : Binder.getCallingUid();
+ if (!mService.mController.appCrashed(name, pid,
+ shortMsg, longMsg, timeMillis, crashInfo.stackTrace)) {
+ if ("1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"))
+ && "Native crash".equals(crashInfo.exceptionClassName)) {
+ Slog.w(TAG, "Skip killing native crashed app " + name
+ + "(" + pid + ") during testing");
+ } else {
+ Slog.w(TAG, "Force-killing crashed app " + name
+ + " at watcher's request");
+ if (r != null) {
+ r.kill("crash", true);
+ } else {
+ // Huh.
+ Process.killProcess(pid);
+ ActivityManagerService.killProcessGroup(uid, pid);
+ }
+ }
+ return;
+ }
+ } catch (RemoteException e) {
+ mService.mController = null;
+ Watchdog.getInstance().setActivityController(null);
+ }
+ }
+
+ final long origId = Binder.clearCallingIdentity();
+
+ // If this process is running instrumentation, finish it.
+ if (r != null && r.instrumentationClass != null) {
+ Slog.w(TAG, "Error in app " + r.processName
+ + " running instrumentation " + r.instrumentationClass + ":");
+ if (shortMsg != null) Slog.w(TAG, " " + shortMsg);
+ if (longMsg != null) Slog.w(TAG, " " + longMsg);
+ Bundle info = new Bundle();
+ info.putString("shortMsg", shortMsg);
+ info.putString("longMsg", longMsg);
+ mService.finishInstrumentationLocked(r, Activity.RESULT_CANCELED, info);
+ Binder.restoreCallingIdentity(origId);
+ return;
+ }
+
+ // Log crash in battery stats.
+ if (r != null) {
+ mService.mBatteryStatsService.noteProcessCrash(r.processName, r.uid);
+ }
+
+ AppErrorDialog.Data data = new AppErrorDialog.Data();
+ data.result = result;
+ data.proc = r;
+
+ // If we can't identify the process or it's already exceeded its crash quota,
+ // quit right away without showing a crash dialog.
+ if (r == null || !makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, data)) {
+ Binder.restoreCallingIdentity(origId);
+ return;
+ }
+
+ Message msg = Message.obtain();
+ msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG;
+
+ task = data.task;
+ msg.obj = data;
+ mService.mUiHandler.sendMessage(msg);
+
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ int res = result.get();
+
+ Intent appErrorIntent = null;
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (res == AppErrorDialog.RESET) {
+ String[] packageList = r.getPackageList();
+ if (packageList != null) {
+ PackageManager pm = mContext.getPackageManager();
+ final Semaphore s = new Semaphore(0);
+ for (int i = 0; i < packageList.length; i++) {
+ if (i < packageList.length - 1) {
+ pm.deleteApplicationCacheFiles(packageList[i], null);
+ } else {
+ pm.deleteApplicationCacheFiles(packageList[i],
+ new IPackageDataObserver.Stub() {
+ @Override
+ public void onRemoveCompleted(String packageName,
+ boolean succeeded) {
+ s.release();
+ }
+ });
+
+ // Wait until cache has been cleared before we restart.
+ try {
+ s.acquire();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ // If there was nothing to reset, just restart;
+ res = AppErrorDialog.RESTART;
+ }
+ synchronized (mService) {
+ if (res == AppErrorDialog.MUTE) {
+ stopReportingCrashesLocked(r);
+ }
+ if (res == AppErrorDialog.RESTART) {
+ mService.removeProcessLocked(r, false, true, "crash");
+ if (task != null) {
+ try {
+ mService.startActivityFromRecents(task.taskId,
+ ActivityOptions.makeBasic().toBundle());
+ } catch (IllegalArgumentException e) {
+ // Hmm, that didn't work, app might have crashed before creating a
+ // recents entry. Let's see if we have a safe-to-restart intent.
+ if (task.intent.getCategories().contains(
+ Intent.CATEGORY_LAUNCHER)) {
+ mService.startActivityInPackage(task.mCallingUid,
+ task.mCallingPackage, task.intent,
+ null, null, null, 0, 0,
+ ActivityOptions.makeBasic().toBundle(),
+ task.userId, null, null);
+ }
+ }
+ }
+ }
+ if (res == AppErrorDialog.FORCE_QUIT) {
+ long orig = Binder.clearCallingIdentity();
+ try {
+ // Kill it with fire!
+ mService.mStackSupervisor.handleAppCrashLocked(r);
+ if (!r.persistent) {
+ mService.removeProcessLocked(r, false, false, "crash");
+ mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(orig);
+ }
+ }
+ if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {
+ appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);
+ }
+ if (r != null && !r.isolated && res != AppErrorDialog.RESTART) {
+ // XXX Can't keep track of crash time for isolated processes,
+ // since they don't have a persistent identity.
+ mProcessCrashTimes.put(r.info.processName, r.uid,
+ SystemClock.uptimeMillis());
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ if (appErrorIntent != null) {
+ try {
+ mContext.startActivityAsUser(appErrorIntent, new UserHandle(r.userId));
+ } catch (ActivityNotFoundException e) {
+ Slog.w(TAG, "bug report receiver dissappeared", e);
+ }
+ }
+ }
+
+ private boolean makeAppCrashingLocked(ProcessRecord app,
+ String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) {
+ app.crashing = true;
+ app.crashingReport = generateProcessError(app,
+ ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);
+ startAppProblemLocked(app);
+ app.stopFreezingAllLocked();
+ return handleAppCrashLocked(app, "force-crash" /*reason*/, shortMsg, longMsg, stackTrace,
+ data);
+ }
+
+ void startAppProblemLocked(ProcessRecord app) {
+ // If this app is not running under the current user, then we
+ // can't give it a report button because that would require
+ // launching the report UI under a different user.
+ app.errorReportReceiver = null;
+
+ for (int userId : mService.mUserController.getCurrentProfileIdsLocked()) {
+ if (app.userId == userId) {
+ app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
+ mContext, app.info.packageName, app.info.flags);
+ }
+ }
+ mService.skipCurrentReceiverLocked(app);
+ }
+
+ /**
+ * Generate a process error record, suitable for attachment to a ProcessRecord.
+ *
+ * @param app The ProcessRecord in which the error occurred.
+ * @param condition Crashing, Application Not Responding, etc. Values are defined in
+ * ActivityManager.AppErrorStateInfo
+ * @param activity The activity associated with the crash, if known.
+ * @param shortMsg Short message describing the crash.
+ * @param longMsg Long message describing the crash.
+ * @param stackTrace Full crash stack trace, may be null.
+ *
+ * @return Returns a fully-formed AppErrorStateInfo record.
+ */
+ private ActivityManager.ProcessErrorStateInfo generateProcessError(ProcessRecord app,
+ int condition, String activity, String shortMsg, String longMsg, String stackTrace) {
+ ActivityManager.ProcessErrorStateInfo report = new ActivityManager.ProcessErrorStateInfo();
+
+ report.condition = condition;
+ report.processName = app.processName;
+ report.pid = app.pid;
+ report.uid = app.info.uid;
+ report.tag = activity;
+ report.shortMsg = shortMsg;
+ report.longMsg = longMsg;
+ report.stackTrace = stackTrace;
+
+ return report;
+ }
+
+ Intent createAppErrorIntentLocked(ProcessRecord r,
+ long timeMillis, ApplicationErrorReport.CrashInfo crashInfo) {
+ ApplicationErrorReport report = createAppErrorReportLocked(r, timeMillis, crashInfo);
+ if (report == null) {
+ return null;
+ }
+ Intent result = new Intent(Intent.ACTION_APP_ERROR);
+ result.setComponent(r.errorReportReceiver);
+ result.putExtra(Intent.EXTRA_BUG_REPORT, report);
+ result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return result;
+ }
+
+ private ApplicationErrorReport createAppErrorReportLocked(ProcessRecord r,
+ long timeMillis, ApplicationErrorReport.CrashInfo crashInfo) {
+ if (r.errorReportReceiver == null) {
+ return null;
+ }
+
+ if (!r.crashing && !r.notResponding && !r.forceCrashReport) {
+ return null;
+ }
+
+ ApplicationErrorReport report = new ApplicationErrorReport();
+ report.packageName = r.info.packageName;
+ report.installerPackageName = r.errorReportReceiver.getPackageName();
+ report.processName = r.processName;
+ report.time = timeMillis;
+ report.systemApp = (r.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+
+ if (r.crashing || r.forceCrashReport) {
+ report.type = ApplicationErrorReport.TYPE_CRASH;
+ report.crashInfo = crashInfo;
+ } else if (r.notResponding) {
+ report.type = ApplicationErrorReport.TYPE_ANR;
+ report.anrInfo = new ApplicationErrorReport.AnrInfo();
+
+ report.anrInfo.activity = r.notRespondingReport.tag;
+ report.anrInfo.cause = r.notRespondingReport.shortMsg;
+ report.anrInfo.info = r.notRespondingReport.longMsg;
+ }
+
+ return report;
+ }
+
+ boolean handleAppCrashLocked(ProcessRecord app, String reason,
+ String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) {
+ long now = SystemClock.uptimeMillis();
+
+ Long crashTime;
+ Long crashTimePersistent;
+ if (!app.isolated) {
+ crashTime = mProcessCrashTimes.get(app.info.processName, app.uid);
+ crashTimePersistent = mProcessCrashTimesPersistent.get(app.info.processName, app.uid);
+ } else {
+ crashTime = crashTimePersistent = null;
+ }
+ if (crashTime != null && now < crashTime+ProcessList.MIN_CRASH_INTERVAL) {
+ // This process loses!
+ Slog.w(TAG, "Process " + app.info.processName
+ + " has crashed too many times: killing!");
+ EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH,
+ app.userId, app.info.processName, app.uid);
+ mService.mStackSupervisor.handleAppCrashLocked(app);
+ if (!app.persistent) {
+ // We don't want to start this process again until the user
+ // explicitly does so... but for persistent process, we really
+ // need to keep it running. If a persistent process is actually
+ // repeatedly crashing, then badness for everyone.
+ EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.userId, app.uid,
+ app.info.processName);
+ if (!app.isolated) {
+ // XXX We don't have a way to mark isolated processes
+ // as bad, since they don't have a peristent identity.
+ mBadProcesses.put(app.info.processName, app.uid,
+ new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
+ mProcessCrashTimes.remove(app.info.processName, app.uid);
+ }
+ app.bad = true;
+ app.removed = true;
+ // Don't let services in this process be restarted and potentially
+ // annoy the user repeatedly. Unless it is persistent, since those
+ // processes run critical code.
+ mService.removeProcessLocked(app, false, false, "crash");
+ mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
+ return false;
+ }
+ mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
+ } else {
+ TaskRecord affectedTask =
+ mService.mStackSupervisor.finishTopRunningActivityLocked(app, reason);
+ if (data != null) {
+ data.task = affectedTask;
+ }
+ if (data != null && crashTimePersistent != null
+ && now < crashTimePersistent + ProcessList.MIN_CRASH_INTERVAL) {
+ data.repeating = true;
+ }
+ }
+
+ // Bump up the crash count of any services currently running in the proc.
+ for (int i=app.services.size()-1; i>=0; i--) {
+ // Any services running in the application need to be placed
+ // back in the pending list.
+ ServiceRecord sr = app.services.valueAt(i);
+ sr.crashCount++;
+ }
+
+ // If the crashing process is what we consider to be the "home process" and it has been
+ // replaced by a third-party app, clear the package preferred activities from packages
+ // with a home activity running in the process to prevent a repeatedly crashing app
+ // from blocking the user to manually clear the list.
+ final ArrayList<ActivityRecord> activities = app.activities;
+ if (app == mService.mHomeProcess && activities.size() > 0
+ && (mService.mHomeProcess.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = activities.get(activityNdx);
+ if (r.isHomeActivity()) {
+ Log.i(TAG, "Clearing package preferred activities from " + r.packageName);
+ try {
+ ActivityThread.getPackageManager()
+ .clearPackagePreferredActivities(r.packageName);
+ } catch (RemoteException c) {
+ // pm is in same process, this will never happen.
+ }
+ }
+ }
+ }
+
+ if (!app.isolated) {
+ // XXX Can't keep track of crash times for isolated processes,
+ // because they don't have a perisistent identity.
+ mProcessCrashTimes.put(app.info.processName, app.uid, now);
+ mProcessCrashTimesPersistent.put(app.info.processName, app.uid, now);
+ }
+
+ if (app.crashHandler != null) mService.mHandler.post(app.crashHandler);
+ return true;
+ }
+
+ void handleShowAppErrorUi(Message msg) {
+ AppErrorDialog.Data data = (AppErrorDialog.Data) msg.obj;
+ boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
+ synchronized (mService) {
+ ProcessRecord proc = data.proc;
+ AppErrorResult res = data.result;
+ if (proc != null && proc.crashDialog != null) {
+ Slog.e(TAG, "App already has crash dialog: " + proc);
+ if (res != null) {
+ res.set(0);
+ }
+ return;
+ }
+ boolean isBackground = (UserHandle.getAppId(proc.uid)
+ >= Process.FIRST_APPLICATION_UID
+ && proc.pid != MY_PID);
+ for (int userId : mService.mUserController.getCurrentProfileIdsLocked()) {
+ isBackground &= (proc.userId != userId);
+ }
+ if (isBackground && !showBackground) {
+ Slog.w(TAG, "Skipping crash dialog of " + proc + ": background");
+ if (res != null) {
+ res.set(0);
+ }
+ return;
+ }
+ final boolean crashSilenced = mAppsNotReportingCrashes != null &&
+ mAppsNotReportingCrashes.contains(proc.info.packageName);
+ if (mService.canShowErrorDialogs() && !crashSilenced) {
+ Dialog d = new AppErrorDialog(mContext, mService, data);
+ d.show();
+ proc.crashDialog = d;
+ } else {
+ // The device is asleep, so just pretend that the user
+ // saw a crash dialog and hit "force quit".
+ if (res != null) {
+ res.set(0);
+ }
+ }
+ }
+ }
+
+ void stopReportingCrashesLocked(ProcessRecord proc) {
+ if (mAppsNotReportingCrashes == null) {
+ mAppsNotReportingCrashes = new ArraySet<>();
+ }
+ mAppsNotReportingCrashes.add(proc.info.packageName);
+ }
+
+ final void appNotResponding(ProcessRecord app, ActivityRecord activity,
+ ActivityRecord parent, boolean aboveSystem, final String annotation) {
+ ArrayList<Integer> firstPids = new ArrayList<Integer>(5);
+ SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20);
+
+ if (mService.mController != null) {
+ try {
+ // 0 == continue, -1 = kill process immediately
+ int res = mService.mController.appEarlyNotResponding(app.processName, app.pid, annotation);
+ if (res < 0 && app.pid != MY_PID) {
+ app.kill("anr", true);
+ }
+ } catch (RemoteException e) {
+ mService.mController = null;
+ Watchdog.getInstance().setActivityController(null);
+ }
+ }
+
+ long anrTime = SystemClock.uptimeMillis();
+ if (ActivityManagerService.MONITOR_CPU_USAGE) {
+ mService.updateCpuStatsNow();
+ }
+
+ synchronized (mService) {
+ // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
+ if (mService.mShuttingDown) {
+ Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation);
+ return;
+ } else if (app.notResponding) {
+ Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation);
+ return;
+ } else if (app.crashing) {
+ Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);
+ return;
+ }
+
+ // In case we come through here for the same app before completing
+ // this one, mark as anring now so we will bail out.
+ app.notResponding = true;
+
+ // Log the ANR to the event log.
+ EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
+ app.processName, app.info.flags, annotation);
+
+ // Dump thread traces as quickly as we can, starting with "interesting" processes.
+ firstPids.add(app.pid);
+
+ int parentPid = app.pid;
+ if (parent != null && parent.app != null && parent.app.pid > 0) {
+ parentPid = parent.app.pid;
+ }
+ if (parentPid != app.pid) firstPids.add(parentPid);
+
+ if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID);
+
+ for (int i = mService.mLruProcesses.size() - 1; i >= 0; i--) {
+ ProcessRecord r = mService.mLruProcesses.get(i);
+ if (r != null && r.thread != null) {
+ int pid = r.pid;
+ if (pid > 0 && pid != app.pid && pid != parentPid && pid != MY_PID) {
+ if (r.persistent) {
+ firstPids.add(pid);
+ } else {
+ lastPids.put(pid, Boolean.TRUE);
+ }
+ }
+ }
+ }
+ }
+
+ // Log the ANR to the main log.
+ StringBuilder info = new StringBuilder();
+ info.setLength(0);
+ info.append("ANR in ").append(app.processName);
+ if (activity != null && activity.shortComponentName != null) {
+ info.append(" (").append(activity.shortComponentName).append(")");
+ }
+ info.append("\n");
+ info.append("PID: ").append(app.pid).append("\n");
+ if (annotation != null) {
+ info.append("Reason: ").append(annotation).append("\n");
+ }
+ if (parent != null && parent != activity) {
+ info.append("Parent: ").append(parent.shortComponentName).append("\n");
+ }
+
+ final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
+
+ File tracesFile = mService.dumpStackTraces(true, firstPids, processCpuTracker, lastPids,
+ NATIVE_STACKS_OF_INTEREST);
+
+ String cpuInfo = null;
+ if (ActivityManagerService.MONITOR_CPU_USAGE) {
+ mService.updateCpuStatsNow();
+ synchronized (mService.mProcessCpuTracker) {
+ cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);
+ }
+ info.append(processCpuTracker.printCurrentLoad());
+ info.append(cpuInfo);
+ }
+
+ info.append(processCpuTracker.printCurrentState(anrTime));
+
+ Slog.e(TAG, info.toString());
+ if (tracesFile == null) {
+ // There is no trace file, so dump (only) the alleged culprit's threads to the log
+ Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
+ }
+
+ mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
+ cpuInfo, tracesFile, null);
+
+ if (mService.mController != null) {
+ try {
+ // 0 == show dialog, 1 = keep waiting, -1 = kill process immediately
+ int res = mService.mController.appNotResponding(
+ app.processName, app.pid, info.toString());
+ if (res != 0) {
+ if (res < 0 && app.pid != MY_PID) {
+ app.kill("anr", true);
+ } else {
+ synchronized (mService) {
+ mService.mServices.scheduleServiceTimeoutLocked(app);
+ }
+ }
+ return;
+ }
+ } catch (RemoteException e) {
+ mService.mController = null;
+ Watchdog.getInstance().setActivityController(null);
+ }
+ }
+
+ // Unless configured otherwise, swallow ANRs in background processes & kill the process.
+ boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
+
+ synchronized (mService) {
+ mService.mBatteryStatsService.noteProcessAnr(app.processName, app.uid);
+
+ if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
+ app.kill("bg anr", true);
+ return;
+ }
+
+ // Set the app's notResponding state, and look up the errorReportReceiver
+ makeAppNotRespondingLocked(app,
+ activity != null ? activity.shortComponentName : null,
+ annotation != null ? "ANR " + annotation : "ANR",
+ info.toString());
+
+ // Bring up the infamous App Not Responding dialog
+ Message msg = Message.obtain();
+ HashMap<String, Object> map = new HashMap<String, Object>();
+ msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
+ msg.obj = map;
+ msg.arg1 = aboveSystem ? 1 : 0;
+ map.put("app", app);
+ if (activity != null) {
+ map.put("activity", activity);
+ }
+
+ mService.mUiHandler.sendMessage(msg);
+ }
+ }
+
+ private void makeAppNotRespondingLocked(ProcessRecord app,
+ String activity, String shortMsg, String longMsg) {
+ app.notResponding = true;
+ app.notRespondingReport = generateProcessError(app,
+ ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
+ activity, shortMsg, longMsg, null);
+ startAppProblemLocked(app);
+ app.stopFreezingAllLocked();
+ }
+
+ void handleShowAnrUi(Message msg) {
+ synchronized (mService) {
+ HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
+ ProcessRecord proc = (ProcessRecord)data.get("app");
+ if (proc != null && proc.anrDialog != null) {
+ Slog.e(TAG, "App already has anr dialog: " + proc);
+ return;
+ }
+
+ Intent intent = new Intent("android.intent.action.ANR");
+ if (!mService.mProcessesReady) {
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND);
+ }
+ mService.broadcastIntentLocked(null, null, intent,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */);
+
+ if (mService.canShowErrorDialogs()) {
+ Dialog d = new AppNotRespondingDialog(mService,
+ mContext, proc, (ActivityRecord)data.get("activity"),
+ msg.arg1 != 0);
+ d.show();
+ proc.anrDialog = d;
+ } else {
+ // Just kill the app if there is no dialog to be shown.
+ mService.killAppAtUsersRequest(proc, null);
+ }
+ }
+ }
+
+ /**
+ * Information about a process that is currently marked as bad.
+ */
+ static final class BadProcessInfo {
+ BadProcessInfo(long time, String shortMsg, String longMsg, String stack) {
+ this.time = time;
+ this.shortMsg = shortMsg;
+ this.longMsg = longMsg;
+ this.stack = stack;
+ }
+
+ final long time;
+ final String shortMsg;
+ final String longMsg;
+ final String stack;
+ }
+
+}
diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
index f4c1664..4587b72 100644
--- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java
+++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
@@ -117,7 +117,7 @@
ProcessRecord app = mProc;
if (msg.what == WAIT_AND_REPORT) {
- appErrorIntent = mService.createAppErrorIntentLocked(app,
+ appErrorIntent = mService.mAppErrors.createAppErrorIntentLocked(app,
System.currentTimeMillis(), null);
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 622aa16..37b0af1 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -199,7 +199,7 @@
@Override
public void run() {
- mService.appNotResponding(mApp, null, null, false, mAnnotation);
+ mService.mAppErrors.appNotResponding(mApp, null, null, false, mAnnotation);
}
}
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index 3f0674d..9c139d5 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -24,6 +24,8 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+import com.google.android.collect.Sets;
+
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.content.ComponentName;
@@ -45,6 +47,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.Set;
/**
* Class for managing the recent tasks list.
@@ -188,6 +191,21 @@
}
}
+ void onPackagesSuspendedChanged(String[] packages, boolean suspended, int userId) {
+ final Set<String> packageNames = Sets.newHashSet(packages);
+ for (int i = size() - 1; i >= 0; --i) {
+ final TaskRecord tr = get(i);
+ if (tr.realActivity != null
+ && packageNames.contains(tr.realActivity.getPackageName())
+ && tr.userId == userId
+ && tr.realActivitySuspended != suspended) {
+ tr.realActivitySuspended = suspended;
+ notifyTaskPersisterLocked(tr, false);
+ }
+ }
+
+ }
+
/**
* Update the recent tasks lists: make sure tasks should still be here (their
* applications / activities still exist), update their availability, fix-up ordering
@@ -683,5 +701,4 @@
// Let the caller know where we left off.
return start + tmpSize;
}
-
}
diff --git a/services/core/java/com/android/server/am/StrictModeViolationDialog.java b/services/core/java/com/android/server/am/StrictModeViolationDialog.java
index fda1ec1..6da84bd 100644
--- a/services/core/java/com/android/server/am/StrictModeViolationDialog.java
+++ b/services/core/java/com/android/server/am/StrictModeViolationDialog.java
@@ -73,7 +73,6 @@
mHandler.obtainMessage(ACTION_OK_AND_REPORT));
}
- setTitle(res.getText(com.android.internal.R.string.aerr_title));
getWindow().addPrivateFlags(PRIVATE_FLAG_SYSTEM_ERROR);
getWindow().setTitle("Strict Mode Violation: " + app.info.processName);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 4ce8b2f..fd787df 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -95,6 +95,7 @@
private static final String TAG_INTENT = "intent";
private static final String TAG_AFFINITYINTENT = "affinity_intent";
static final String ATTR_REALACTIVITY = "real_activity";
+ static final String ATTR_REALACTIVITY_SUSPENDED = "real_activity_suspended";
private static final String ATTR_ORIGACTIVITY = "orig_activity";
private static final String TAG_ACTIVITY = "activity";
private static final String ATTR_AFFINITY = "affinity";
@@ -136,6 +137,8 @@
int effectiveUid; // The current effective uid of the identity of this task.
ComponentName origActivity; // The non-alias activity component of the intent.
ComponentName realActivity; // The actual activity component that started the task.
+ boolean realActivitySuspended; // True if the actual activity component that started the
+ // task is suspended.
long firstActiveTime; // First time this task was active.
long lastActiveTime; // Last time this task was active, including sleep.
boolean inRecents; // Actually in the recents list?
@@ -305,7 +308,7 @@
boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription,
TaskThumbnailInfo lastThumbnailInfo, int taskAffiliation, int prevTaskId,
int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage,
- boolean resizeable, boolean privileged) {
+ boolean resizeable, boolean privileged, boolean realActivitySuspended) {
mService = service;
mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
TaskPersister.IMAGE_EXTENSION;
@@ -319,6 +322,7 @@
voiceSession = null;
voiceInteractor = null;
realActivity = _realActivity;
+ realActivitySuspended = realActivitySuspended;
origActivity = _origActivity;
rootWasReset = _rootWasReset;
isAvailable = true;
@@ -1027,6 +1031,7 @@
if (realActivity != null) {
out.attribute(null, ATTR_REALACTIVITY, realActivity.flattenToShortString());
}
+ out.attribute(null, ATTR_REALACTIVITY_SUSPENDED, String.valueOf(realActivitySuspended));
if (origActivity != null) {
out.attribute(null, ATTR_ORIGACTIVITY, origActivity.flattenToShortString());
}
@@ -1105,6 +1110,7 @@
Intent affinityIntent = null;
ArrayList<ActivityRecord> activities = new ArrayList<>();
ComponentName realActivity = null;
+ boolean realActivitySuspended = false;
ComponentName origActivity = null;
String affinity = null;
String rootAffinity = null;
@@ -1143,6 +1149,8 @@
if (taskId == INVALID_TASK_ID) taskId = Integer.valueOf(attrValue);
} else if (ATTR_REALACTIVITY.equals(attrName)) {
realActivity = ComponentName.unflattenFromString(attrValue);
+ } else if (ATTR_REALACTIVITY_SUSPENDED.equals(attrName)) {
+ realActivitySuspended = Boolean.valueOf(attrValue);
} else if (ATTR_ORIGACTIVITY.equals(attrName)) {
origActivity = ComponentName.unflattenFromString(attrValue);
} else if (ATTR_AFFINITY.equals(attrName)) {
@@ -1253,7 +1261,8 @@
autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription,
activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
taskDescription, thumbnailInfo, taskAffiliation, prevTaskId, nextTaskId,
- taskAffiliationColor, callingUid, callingPackage, resizeable, privileged);
+ taskAffiliationColor, callingUid, callingPackage, resizeable, privileged,
+ realActivitySuspended);
task.updateOverrideConfiguration(bounds);
for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 7ac3c4b..e74d636 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.trust.TrustManager;
import android.app.ActivityManagerNative;
import android.app.AlarmManager;
import android.app.AppOpsManager;
@@ -103,6 +104,7 @@
private static final int MAX_FAILED_ATTEMPTS = 5;
private static final int FINGERPRINT_ACQUIRED_GOOD = 0;
private final String mKeyguardPackage;
+ private int mCurrentUserId = UserHandle.USER_CURRENT;
Handler mHandler = new Handler() {
@Override
@@ -125,6 +127,7 @@
private IFingerprintDaemon mDaemon;
private final PowerManager mPowerManager;
private final AlarmManager mAlarmManager;
+ private final UserManager mUserManager;
private final BroadcastReceiver mLockoutReceiver = new BroadcastReceiver() {
@Override
@@ -152,6 +155,7 @@
mAlarmManager = mContext.getSystemService(AlarmManager.class);
mContext.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
RESET_FINGERPRINT_LOCKOUT, null /* handler */);
+ mUserManager = UserManager.get(mContext);
}
@Override
@@ -170,7 +174,7 @@
mDaemon.init(mDaemonCallback);
mHalDeviceId = mDaemon.openHal();
if (mHalDeviceId != 0) {
- updateActiveGroup(ActivityManager.getCurrentUser());
+ updateActiveGroup(ActivityManager.getCurrentUser(), null);
} else {
Slog.w(TAG, "Failed to open Fingerprint HAL!");
mDaemon = null;
@@ -261,7 +265,7 @@
}
void handleUserSwitching(int userId) {
- updateActiveGroup(userId);
+ updateActiveGroup(userId, null);
}
private void removeClient(ClientMonitor client) {
@@ -414,7 +418,7 @@
removeClient(mEnrollClient);
}
- void startAuthentication(IBinder token, long opId, int groupId,
+ void startAuthentication(IBinder token, long opId, int realUserId, int groupId,
IFingerprintServiceReceiver receiver, int flags, boolean restricted,
String opPackageName) {
IFingerprintDaemon daemon = getFingerprintDaemon();
@@ -423,6 +427,7 @@
return;
}
stopPendingOperations(true);
+ updateActiveGroup(groupId, opPackageName);
mAuthClient = new ClientMonitor(token, receiver, groupId, restricted, opPackageName);
if (inLockoutMode()) {
Slog.v(TAG, "In lockout mode; disallowing authentication");
@@ -564,7 +569,7 @@
checkPermission(USE_FINGERPRINT);
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
- if (opPackageName.equals(mKeyguardPackage)) {
+ if (isKeyguard(opPackageName)) {
return true; // Keyguard is always allowed
}
if (!isCurrentUserOrProfile(UserHandle.getCallingUserId())) {
@@ -583,6 +588,14 @@
return true;
}
+ /**
+ * @param clientPackage
+ * @return true if this is keyguard package
+ */
+ private boolean isKeyguard(String clientPackage) {
+ return mKeyguardPackage.equals(clientPackage);
+ }
+
private void addLockoutResetMonitor(FingerprintServiceLockoutResetMonitor monitor) {
if (!mLockoutMonitors.contains(monitor)) {
mLockoutMonitors.add(monitor);
@@ -927,14 +940,15 @@
// Group ID is arbitrarily set to parent profile user ID. It just represents
// the default fingerprints for the user.
final int effectiveGroupId = getEffectiveUserId(groupId);
+ final int realUserId = Binder.getCallingUid();
final boolean restricted = isRestricted();
mHandler.post(new Runnable() {
@Override
public void run() {
MetricsLogger.histogram(mContext, "fingerprint_token", opId != 0L ? 1 : 0);
- startAuthentication(token, opId, effectiveGroupId, receiver, flags, restricted,
- opPackageName);
+ startAuthentication(token, opId, realUserId, effectiveGroupId, receiver,
+ flags, restricted, opPackageName);
}
});
}
@@ -953,6 +967,17 @@
}
@Override // Binder call
+ public void setActiveUser(final int userId) {
+ checkPermission(MANAGE_FINGERPRINT);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ updateActiveGroup(userId, null);
+ }
+ });
+ }
+
+ @Override // Binder call
public void remove(final IBinder token, final int fingerId, final int groupId,
final IFingerprintServiceReceiver receiver) {
checkPermission(MANAGE_FINGERPRINT); // TODO: Maybe have another permission
@@ -1102,33 +1127,56 @@
listenForUserSwitches();
}
- private void updateActiveGroup(int userId) {
+ private void updateActiveGroup(int userId, String clientPackage) {
IFingerprintDaemon daemon = getFingerprintDaemon();
if (daemon != null) {
try {
- userId = getEffectiveUserId(userId);
- final File systemDir = Environment.getUserSystemDirectory(userId);
- final File fpDir = new File(systemDir, FP_DATA_DIR);
- if (!fpDir.exists()) {
- if (!fpDir.mkdir()) {
- Slog.v(TAG, "Cannot make directory: " + fpDir.getAbsolutePath());
- return;
+ userId = getUserOrWorkProfileId(clientPackage, userId);
+ if (userId != mCurrentUserId) {
+ final File systemDir = Environment.getUserSystemDirectory(userId);
+ final File fpDir = new File(systemDir, FP_DATA_DIR);
+ if (!fpDir.exists()) {
+ if (!fpDir.mkdir()) {
+ Slog.v(TAG, "Cannot make directory: " + fpDir.getAbsolutePath());
+ return;
+ }
+ // Calling mkdir() from this process will create a directory with our
+ // permissions (inherited from the containing dir). This command fixes
+ // the label.
+ if (!SELinux.restorecon(fpDir)) {
+ Slog.w(TAG, "Restorecons failed. Directory will have wrong label.");
+ return;
+ }
}
- // Calling mkdir() from this process will create a directory with our
- // permissions (inherited from the containing dir). This command fixes
- // the label.
- if (!SELinux.restorecon(fpDir)) {
- Slog.w(TAG, "Restorecons failed. Directory will have wrong label.");
- return;
- }
+ daemon.setActiveGroup(userId, fpDir.getAbsolutePath().getBytes());
+ mCurrentUserId = userId;
}
- daemon.setActiveGroup(userId, fpDir.getAbsolutePath().getBytes());
} catch (RemoteException e) {
Slog.e(TAG, "Failed to setActiveGroup():", e);
}
}
}
+ /**
+ * @param clientPackage the package of the caller
+ * @return the profile id
+ */
+ private int getUserOrWorkProfileId(String clientPackage, int userId) {
+ if (!isKeyguard(clientPackage) && isWorkProfile(userId)) {
+ return userId;
+ }
+ return getEffectiveUserId(userId);
+ }
+
+ /**
+ * @param userId
+ * @return true if this is a work profile
+ */
+ private boolean isWorkProfile(int userId) {
+ UserInfo info = mUserManager.getUserInfo(userId);
+ return info != null && info.isManagedProfile();
+ }
+
private void listenForUserSwitches() {
try {
ActivityManagerNative.getDefault().registerUserSwitchObserver(