summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt3
-rw-r--r--api/system-current.txt3
-rw-r--r--api/test-current.txt3
-rw-r--r--core/java/android/app/ResourcesManager.java2
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java5
-rw-r--r--core/java/android/os/StrictMode.java1241
-rw-r--r--core/java/android/os/VibrationEffect.java52
-rwxr-xr-xcore/java/android/provider/Settings.java19
-rw-r--r--core/java/android/service/autofill/FillEventHistory.java27
-rw-r--r--core/java/android/util/ExceptionUtils.java8
-rw-r--r--core/java/android/util/StatsLog.java76
-rw-r--r--core/java/android/view/View.java3
-rw-r--r--core/java/com/android/internal/policy/DecorView.java2
-rw-r--r--core/java/com/android/internal/widget/FloatingToolbar.java6
-rw-r--r--core/jni/Android.bp1
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_util_StatsLog.cpp201
-rw-r--r--core/tests/coretests/src/android/provider/SettingsBackupTest.java1
-rw-r--r--graphics/java/android/graphics/Color.java2
-rw-r--r--libs/hwui/BakedOpRenderer.cpp15
-rw-r--r--libs/hwui/VectorDrawable.cpp43
-rw-r--r--libs/hwui/VectorDrawable.h5
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp5
-rw-r--r--libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp5
-rw-r--r--media/java/android/media/IMediaRouterService.aidl1
-rw-r--r--media/java/android/media/MediaRouter.java59
-rw-r--r--packages/SystemUI/res/drawable/car_qs_background_primary.xml20
-rw-r--r--packages/SystemUI/res/layout/battery_percentage_view.xml1
-rw-r--r--packages/SystemUI/res/layout/car_qs_panel.xml9
-rw-r--r--packages/SystemUI/res/values/colors_car.xml1
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java122
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozePauser.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java2
-rw-r--r--packages/SystemUI/tests/Android.mk3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java86
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java6
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/GestureUtils.java20
-rw-r--r--services/accessibility/java/com/android/server/accessibility/MagnificationController.java40
-rw-r--r--services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java826
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java21
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java8
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java2
-rw-r--r--services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java2
-rw-r--r--services/backup/java/com/android/server/backup/TransportManager.java42
-rw-r--r--services/core/java/com/android/server/VibratorService.java3
-rw-r--r--services/core/java/com/android/server/am/ActivityRecord.java14
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java50
-rw-r--r--services/core/java/com/android/server/am/PinnedActivityStack.java7
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java23
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java27
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java64
-rw-r--r--services/core/java/com/android/server/om/IdmapManager.java16
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java6
-rw-r--r--services/core/java/com/android/server/wm/AppWindowAnimator.java2
-rw-r--r--services/core/java/com/android/server/wm/AppWindowContainerController.java1
-rw-r--r--services/core/java/com/android/server/wm/AppWindowToken.java9
-rw-r--r--services/core/java/com/android/server/wm/BoundsAnimationController.java41
-rw-r--r--services/core/java/com/android/server/wm/BoundsAnimationTarget.java2
-rw-r--r--services/core/java/com/android/server/wm/PinnedStackWindowController.java6
-rw-r--r--services/core/java/com/android/server/wm/PinnedStackWindowListener.java3
-rw-r--r--services/core/java/com/android/server/wm/TaskStack.java12
-rw-r--r--services/core/jni/com_android_server_VibratorService.cpp6
-rw-r--r--services/robotests/Android.mk76
-rw-r--r--services/robotests/src/com/android/server/backup/TransportManagerTest.java137
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java535
-rw-r--r--services/tests/servicestests/src/com/android/server/backup/BackupPasswordManagerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/testutils/OffsettableClock.java79
-rw-r--r--services/tests/servicestests/src/com/android/server/testutils/TestHandler.java156
-rw-r--r--services/tests/servicestests/src/com/android/server/testutils/TestUtils.java (renamed from services/tests/servicestests/src/com/android/server/testutis/TestUtils.java)18
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java32
-rw-r--r--tools/aapt2/link/ManifestFixer.cpp1
79 files changed, 3007 insertions, 1380 deletions
diff --git a/api/current.txt b/api/current.txt
index 1b7e7d4b6e94..23a03940c3ab 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -37082,13 +37082,14 @@ package android.service.autofill {
public final class FillEventHistory implements android.os.Parcelable {
method public int describeContents();
- method public android.os.Bundle getClientState();
+ method public deprecated android.os.Bundle getClientState();
method public java.util.List<android.service.autofill.FillEventHistory.Event> getEvents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.autofill.FillEventHistory> CREATOR;
}
public static final class FillEventHistory.Event {
+ method public android.os.Bundle getClientState();
method public java.lang.String getDatasetId();
method public int getType();
field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index 5e1cf5a158b1..a843e6d30ee8 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -40173,13 +40173,14 @@ package android.service.autofill {
public final class FillEventHistory implements android.os.Parcelable {
method public int describeContents();
- method public android.os.Bundle getClientState();
+ method public deprecated android.os.Bundle getClientState();
method public java.util.List<android.service.autofill.FillEventHistory.Event> getEvents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.autofill.FillEventHistory> CREATOR;
}
public static final class FillEventHistory.Event {
+ method public android.os.Bundle getClientState();
method public java.lang.String getDatasetId();
method public int getType();
field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
diff --git a/api/test-current.txt b/api/test-current.txt
index d20d4b3b3c11..f5b31d10c67b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -37280,13 +37280,14 @@ package android.service.autofill {
public final class FillEventHistory implements android.os.Parcelable {
method public int describeContents();
- method public android.os.Bundle getClientState();
+ method public deprecated android.os.Bundle getClientState();
method public java.util.List<android.service.autofill.FillEventHistory.Event> getEvents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.autofill.FillEventHistory> CREATOR;
}
public static final class FillEventHistory.Event {
+ method public android.os.Bundle getClientState();
method public java.lang.String getDatasetId();
method public int getType();
field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 595ecd201e57..fb11272d7e62 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -964,7 +964,7 @@ public class ResourcesManager {
// TODO(adamlesinski): Make this accept more than just overlay directories.
final void applyNewResourceDirsLocked(@NonNull final String baseCodePath,
- @NonNull final String[] newResourceDirs) {
+ @Nullable final String[] newResourceDirs) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
"ResourcesManager#applyNewResourceDirsLocked");
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 1242cb0fbdfa..8a1eae2da976 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -40,7 +40,6 @@ import android.util.SparseArray;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Adapter;
import android.widget.AdapterView;
@@ -469,7 +468,9 @@ public class AppWidgetHostView extends FrameLayout {
// We've already done this -- nothing to do.
return ;
}
- Log.w(TAG, "updateAppWidget couldn't find any view, using error view", exception);
+ if (exception != null) {
+ Log.w(TAG, "Error inflating RemoteViews : " + exception.toString());
+ }
content = getErrorView();
mViewMode = VIEW_MODE_ERROR;
}
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 3e071437b097..4703af06c06e 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -53,31 +53,25 @@ import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
- * <p>StrictMode is a developer tool which detects things you might be
- * doing by accident and brings them to your attention so you can fix
- * them.
+ * StrictMode is a developer tool which detects things you might be doing by accident and brings
+ * them to your attention so you can fix them.
*
- * <p>StrictMode is most commonly used to catch accidental disk or
- * network access on the application's main thread, where UI
- * operations are received and animations take place. Keeping disk
- * and network operations off the main thread makes for much smoother,
- * more responsive applications. By keeping your application's main thread
- * responsive, you also prevent
- * <a href="{@docRoot}guide/practices/design/responsiveness.html">ANR dialogs</a>
- * from being shown to users.
+ * <p>StrictMode is most commonly used to catch accidental disk or network access on the
+ * application's main thread, where UI operations are received and animations take place. Keeping
+ * disk and network operations off the main thread makes for much smoother, more responsive
+ * applications. By keeping your application's main thread responsive, you also prevent <a
+ * href="{@docRoot}guide/practices/design/responsiveness.html">ANR dialogs</a> from being shown to
+ * users.
*
- * <p class="note">Note that even though an Android device's disk is
- * often on flash memory, many devices run a filesystem on top of that
- * memory with very limited concurrency. It's often the case that
- * almost all disk accesses are fast, but may in individual cases be
- * dramatically slower when certain I/O is happening in the background
- * from other processes. If possible, it's best to assume that such
- * things are not fast.</p>
+ * <p class="note">Note that even though an Android device's disk is often on flash memory, many
+ * devices run a filesystem on top of that memory with very limited concurrency. It's often the case
+ * that almost all disk accesses are fast, but may in individual cases be dramatically slower when
+ * certain I/O is happening in the background from other processes. If possible, it's best to assume
+ * that such things are not fast.
*
- * <p>Example code to enable from early in your
- * {@link android.app.Application}, {@link android.app.Activity}, or
- * other application component's
- * {@link android.app.Application#onCreate} method:
+ * <p>Example code to enable from early in your {@link android.app.Application}, {@link
+ * android.app.Activity}, or other application component's {@link android.app.Application#onCreate}
+ * method:
*
* <pre>
* public void onCreate() {
@@ -99,36 +93,32 @@ import java.util.concurrent.atomic.AtomicInteger;
* }
* </pre>
*
- * <p>You can decide what should happen when a violation is detected.
- * For example, using {@link ThreadPolicy.Builder#penaltyLog} you can
- * watch the output of <code>adb logcat</code> while you use your
- * application to see the violations as they happen.
+ * <p>You can decide what should happen when a violation is detected. For example, using {@link
+ * ThreadPolicy.Builder#penaltyLog} you can watch the output of <code>adb logcat</code> while you
+ * use your application to see the violations as they happen.
*
- * <p>If you find violations that you feel are problematic, there are
- * a variety of tools to help solve them: threads, {@link android.os.Handler},
- * {@link android.os.AsyncTask}, {@link android.app.IntentService}, etc.
- * But don't feel compelled to fix everything that StrictMode finds. In particular,
- * many cases of disk access are often necessary during the normal activity lifecycle. Use
- * StrictMode to find things you did by accident. Network requests on the UI thread
+ * <p>If you find violations that you feel are problematic, there are a variety of tools to help
+ * solve them: threads, {@link android.os.Handler}, {@link android.os.AsyncTask}, {@link
+ * android.app.IntentService}, etc. But don't feel compelled to fix everything that StrictMode
+ * finds. In particular, many cases of disk access are often necessary during the normal activity
+ * lifecycle. Use StrictMode to find things you did by accident. Network requests on the UI thread
* are almost always a problem, though.
*
- * <p class="note">StrictMode is not a security mechanism and is not
- * guaranteed to find all disk or network accesses. While it does
- * propagate its state across process boundaries when doing
- * {@link android.os.Binder} calls, it's still ultimately a best
- * effort mechanism. Notably, disk or network access from JNI calls
- * won't necessarily trigger it. Future versions of Android may catch
- * more (or fewer) operations, so you should never leave StrictMode
- * enabled in applications distributed on Google Play.
+ * <p class="note">StrictMode is not a security mechanism and is not guaranteed to find all disk or
+ * network accesses. While it does propagate its state across process boundaries when doing {@link
+ * android.os.Binder} calls, it's still ultimately a best effort mechanism. Notably, disk or network
+ * access from JNI calls won't necessarily trigger it. Future versions of Android may catch more (or
+ * fewer) operations, so you should never leave StrictMode enabled in applications distributed on
+ * Google Play.
*/
public final class StrictMode {
private static final String TAG = "StrictMode";
private static final boolean LOG_V = Log.isLoggable(TAG, Log.VERBOSE);
/**
- * Boolean system property to disable strict mode checks outright.
- * Set this to 'true' to force disable; 'false' has no effect on other
- * enable/disable policy.
+ * Boolean system property to disable strict mode checks outright. Set this to 'true' to force
+ * disable; 'false' has no effect on other enable/disable policy.
+ *
* @hide
*/
public static final String DISABLE_PROPERTY = "persist.sys.strictmode.disable";
@@ -141,9 +131,9 @@ public final class StrictMode {
public static final String VISUAL_PROPERTY = "persist.sys.strictmode.visual";
/**
- * Temporary property used to include {@link #DETECT_VM_CLEARTEXT_NETWORK}
- * in {@link VmPolicy.Builder#detectAll()}. Apps can still always opt-into
- * detection using {@link VmPolicy.Builder#detectCleartextNetwork()}.
+ * Temporary property used to include {@link #DETECT_VM_CLEARTEXT_NETWORK} in {@link
+ * VmPolicy.Builder#detectAll()}. Apps can still always opt-into detection using {@link
+ * VmPolicy.Builder#detectCleartextNetwork()}.
*/
private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear";
@@ -162,105 +152,96 @@ public final class StrictMode {
// Byte 1: Thread-policy
- /**
- * @hide
- */
- public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy
+ /** @hide */
+ public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy
- /**
- * @hide
- */
- public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy
+ /** @hide */
+ public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy
- /**
- * @hide
- */
- public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy
+ /** @hide */
+ public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy
/**
* For StrictMode.noteSlowCall()
*
* @hide
*/
- public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy
+ public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy
/**
* For StrictMode.noteResourceMismatch()
*
* @hide
*/
- public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy
+ public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy
- /**
- * @hide
- */
- public static final int DETECT_UNBUFFERED_IO = 0x20; // for ThreadPolicy
+ /** @hide */
+ public static final int DETECT_UNBUFFERED_IO = 0x20; // for ThreadPolicy
private static final int ALL_THREAD_DETECT_BITS =
- DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK | DETECT_CUSTOM |
- DETECT_RESOURCE_MISMATCH | DETECT_UNBUFFERED_IO;
+ DETECT_DISK_WRITE
+ | DETECT_DISK_READ
+ | DETECT_NETWORK
+ | DETECT_CUSTOM
+ | DETECT_RESOURCE_MISMATCH
+ | DETECT_UNBUFFERED_IO;
// Byte 2: Process-policy
/**
* Note, a "VM_" bit, not thread.
+ *
* @hide
*/
- public static final int DETECT_VM_CURSOR_LEAKS = 0x01 << 8; // for VmPolicy
+ public static final int DETECT_VM_CURSOR_LEAKS = 0x01 << 8; // for VmPolicy
/**
* Note, a "VM_" bit, not thread.
+ *
* @hide
*/
- public static final int DETECT_VM_CLOSABLE_LEAKS = 0x02 << 8; // for VmPolicy
+ public static final int DETECT_VM_CLOSABLE_LEAKS = 0x02 << 8; // for VmPolicy
/**
* Note, a "VM_" bit, not thread.
+ *
* @hide
*/
- public static final int DETECT_VM_ACTIVITY_LEAKS = 0x04 << 8; // for VmPolicy
+ public static final int DETECT_VM_ACTIVITY_LEAKS = 0x04 << 8; // for VmPolicy
- /**
- * @hide
- */
- private static final int DETECT_VM_INSTANCE_LEAKS = 0x08 << 8; // for VmPolicy
+ /** @hide */
+ private static final int DETECT_VM_INSTANCE_LEAKS = 0x08 << 8; // for VmPolicy
- /**
- * @hide
- */
- public static final int DETECT_VM_REGISTRATION_LEAKS = 0x10 << 8; // for VmPolicy
+ /** @hide */
+ public static final int DETECT_VM_REGISTRATION_LEAKS = 0x10 << 8; // for VmPolicy
- /**
- * @hide
- */
- private static final int DETECT_VM_FILE_URI_EXPOSURE = 0x20 << 8; // for VmPolicy
+ /** @hide */
+ private static final int DETECT_VM_FILE_URI_EXPOSURE = 0x20 << 8; // for VmPolicy
- /**
- * @hide
- */
- private static final int DETECT_VM_CLEARTEXT_NETWORK = 0x40 << 8; // for VmPolicy
+ /** @hide */
+ private static final int DETECT_VM_CLEARTEXT_NETWORK = 0x40 << 8; // for VmPolicy
- /**
- * @hide
- */
- private static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy
+ /** @hide */
+ private static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy
- /**
- * @hide
- */
- private static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy
+ /** @hide */
+ private static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy
private static final int ALL_VM_DETECT_BITS =
- DETECT_VM_CURSOR_LEAKS | DETECT_VM_CLOSABLE_LEAKS |
- DETECT_VM_ACTIVITY_LEAKS | DETECT_VM_INSTANCE_LEAKS |
- DETECT_VM_REGISTRATION_LEAKS | DETECT_VM_FILE_URI_EXPOSURE |
- DETECT_VM_CLEARTEXT_NETWORK | DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION |
- DETECT_VM_UNTAGGED_SOCKET;
+ DETECT_VM_CURSOR_LEAKS
+ | DETECT_VM_CLOSABLE_LEAKS
+ | DETECT_VM_ACTIVITY_LEAKS
+ | DETECT_VM_INSTANCE_LEAKS
+ | DETECT_VM_REGISTRATION_LEAKS
+ | DETECT_VM_FILE_URI_EXPOSURE
+ | DETECT_VM_CLEARTEXT_NETWORK
+ | DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION
+ | DETECT_VM_UNTAGGED_SOCKET;
// Byte 3: Penalty
/** {@hide} */
- public static final int PENALTY_LOG = 0x01 << 16; // normal android.util.Log
+ public static final int PENALTY_LOG = 0x01 << 16; // normal android.util.Log
/** {@hide} */
public static final int PENALTY_DIALOG = 0x02 << 16;
/** {@hide} */
@@ -271,13 +252,11 @@ public final class StrictMode {
public static final int PENALTY_DROPBOX = 0x20 << 16;
/**
- * Non-public penalty mode which overrides all the other penalty
- * bits and signals that we're in a Binder call and we should
- * ignore the other penalty bits and instead serialize back all
- * our offending stack traces to the caller to ultimately handle
- * in the originating process.
+ * Non-public penalty mode which overrides all the other penalty bits and signals that we're in
+ * a Binder call and we should ignore the other penalty bits and instead serialize back all our
+ * offending stack traces to the caller to ultimately handle in the originating process.
*
- * This must be kept in sync with the constant in libs/binder/Parcel.cpp
+ * <p>This must be kept in sync with the constant in libs/binder/Parcel.cpp
*
* @hide
*/
@@ -308,18 +287,23 @@ public final class StrictMode {
// CAUTION: we started stealing the top bits of Byte 4 for VM above
- /**
- * Mask of all the penalty bits valid for thread policies.
- */
+ /** Mask of all the penalty bits valid for thread policies. */
private static final int THREAD_PENALTY_MASK =
- PENALTY_LOG | PENALTY_DIALOG | PENALTY_DEATH | PENALTY_DROPBOX | PENALTY_GATHER |
- PENALTY_DEATH_ON_NETWORK | PENALTY_FLASH;
-
- /**
- * Mask of all the penalty bits valid for VM policies.
- */
- private static final int VM_PENALTY_MASK = PENALTY_LOG | PENALTY_DEATH | PENALTY_DROPBOX
- | PENALTY_DEATH_ON_CLEARTEXT_NETWORK | PENALTY_DEATH_ON_FILE_URI_EXPOSURE;
+ PENALTY_LOG
+ | PENALTY_DIALOG
+ | PENALTY_DEATH
+ | PENALTY_DROPBOX
+ | PENALTY_GATHER
+ | PENALTY_DEATH_ON_NETWORK
+ | PENALTY_FLASH;
+
+ /** Mask of all the penalty bits valid for VM policies. */
+ private static final int VM_PENALTY_MASK =
+ PENALTY_LOG
+ | PENALTY_DEATH
+ | PENALTY_DROPBOX
+ | PENALTY_DEATH_ON_CLEARTEXT_NETWORK
+ | PENALTY_DEATH_ON_FILE_URI_EXPOSURE;
/** {@hide} */
public static final int NETWORK_POLICY_ACCEPT = 0;
@@ -330,14 +314,16 @@ public final class StrictMode {
// TODO: wrap in some ImmutableHashMap thing.
// Note: must be before static initialization of sVmPolicy.
- private static final HashMap<Class, Integer> EMPTY_CLASS_LIMIT_MAP = new HashMap<Class, Integer>();
+ private static final HashMap<Class, Integer> EMPTY_CLASS_LIMIT_MAP =
+ new HashMap<Class, Integer>();
/**
* The current VmPolicy in effect.
*
- * TODO: these are redundant (mask is in VmPolicy). Should remove sVmPolicyMask.
+ * <p>TODO: these are redundant (mask is in VmPolicy). Should remove sVmPolicyMask.
*/
private static volatile int sVmPolicyMask = 0;
+
private static volatile VmPolicy sVmPolicy = VmPolicy.LAX;
/** {@hide} */
@@ -355,8 +341,8 @@ public final class StrictMode {
}
/**
- * The number of threads trying to do an async dropbox write.
- * Just to limit ourselves out of paranoia.
+ * The number of threads trying to do an async dropbox write. Just to limit ourselves out of
+ * paranoia.
*/
private static final AtomicInteger sDropboxCallsInFlight = new AtomicInteger(0);
@@ -365,18 +351,15 @@ public final class StrictMode {
/**
* {@link StrictMode} policy applied to a certain thread.
*
- * <p>The policy is enabled by {@link #setThreadPolicy}. The current policy
- * can be retrieved with {@link #getThreadPolicy}.
+ * <p>The policy is enabled by {@link #setThreadPolicy}. The current policy can be retrieved
+ * with {@link #getThreadPolicy}.
*
- * <p>Note that multiple penalties may be provided and they're run
- * in order from least to most severe (logging before process
- * death, for example). There's currently no mechanism to choose
+ * <p>Note that multiple penalties may be provided and they're run in order from least to most
+ * severe (logging before process death, for example). There's currently no mechanism to choose
* different penalties for different detected actions.
*/
public static final class ThreadPolicy {
- /**
- * The default, lax policy which doesn't catch anything.
- */
+ /** The default, lax policy which doesn't catch anything. */
public static final ThreadPolicy LAX = new ThreadPolicy(0);
final int mask;
@@ -391,16 +374,15 @@ public final class StrictMode {
}
/**
- * Creates {@link ThreadPolicy} instances. Methods whose names start
- * with {@code detect} specify what problems we should look
- * for. Methods whose names start with {@code penalty} specify what
- * we should do when we detect a problem.
+ * Creates {@link ThreadPolicy} instances. Methods whose names start with {@code detect}
+ * specify what problems we should look for. Methods whose names start with {@code penalty}
+ * specify what we should do when we detect a problem.
*
- * <p>You can call as many {@code detect} and {@code penalty}
- * methods as you like. Currently order is insignificant: all
- * penalties apply to all detected problems.
+ * <p>You can call as many {@code detect} and {@code penalty} methods as you like. Currently
+ * order is insignificant: all penalties apply to all detected problems.
*
* <p>For example, detect everything and log anything that's found:
+ *
* <pre>
* StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
* .detectAll()
@@ -413,18 +395,15 @@ public final class StrictMode {
private int mMask = 0;
/**
- * Create a Builder that detects nothing and has no
- * violations. (but note that {@link #build} will default
- * to enabling {@link #penaltyLog} if no other penalties
- * are specified)
+ * Create a Builder that detects nothing and has no violations. (but note that {@link
+ * #build} will default to enabling {@link #penaltyLog} if no other penalties are
+ * specified)
*/
public Builder() {
mMask = 0;
}
- /**
- * Initialize a Builder from an existing ThreadPolicy.
- */
+ /** Initialize a Builder from an existing ThreadPolicy. */
public Builder(ThreadPolicy policy) {
mMask = policy.mask;
}
@@ -432,8 +411,8 @@ public final class StrictMode {
/**
* Detect everything that's potentially suspect.
*
- * <p>As of the Gingerbread release this includes network and
- * disk operations but will likely expand in future releases.
+ * <p>As of the Gingerbread release this includes network and disk operations but will
+ * likely expand in future releases.
*/
public Builder detectAll() {
detectDiskReads();
@@ -453,135 +432,106 @@ public final class StrictMode {
return this;
}
- /**
- * Disable the detection of everything.
- */
+ /** Disable the detection of everything. */
public Builder permitAll() {
return disable(ALL_THREAD_DETECT_BITS);
}
- /**
- * Enable detection of network operations.
- */
+ /** Enable detection of network operations. */
public Builder detectNetwork() {
return enable(DETECT_NETWORK);
}
- /**
- * Disable detection of network operations.
- */
+ /** Disable detection of network operations. */
public Builder permitNetwork() {
return disable(DETECT_NETWORK);
}
- /**
- * Enable detection of disk reads.
- */
+ /** Enable detection of disk reads. */
public Builder detectDiskReads() {
return enable(DETECT_DISK_READ);
}
- /**
- * Disable detection of disk reads.
- */
+ /** Disable detection of disk reads. */
public Builder permitDiskReads() {
return disable(DETECT_DISK_READ);
}
- /**
- * Enable detection of slow calls.
- */
+ /** Enable detection of slow calls. */
public Builder detectCustomSlowCalls() {
return enable(DETECT_CUSTOM);
}
- /**
- * Disable detection of slow calls.
- */
+ /** Disable detection of slow calls. */
public Builder permitCustomSlowCalls() {
return disable(DETECT_CUSTOM);
}
- /**
- * Disable detection of mismatches between defined resource types
- * and getter calls.
- */
+ /** Disable detection of mismatches between defined resource types and getter calls. */
public Builder permitResourceMismatches() {
return disable(DETECT_RESOURCE_MISMATCH);
}
- /**
- * Detect unbuffered input/output operations.
- */
+ /** Detect unbuffered input/output operations. */
public Builder detectUnbufferedIo() {
return enable(DETECT_UNBUFFERED_IO);
}
- /**
- * Disable detection of unbuffered input/output operations.
- */
+ /** Disable detection of unbuffered input/output operations. */
public Builder permitUnbufferedIo() {
return disable(DETECT_UNBUFFERED_IO);
}
/**
- * Enables detection of mismatches between defined resource types
- * and getter calls.
- * <p>
- * This helps detect accidental type mismatches and potentially
- * expensive type conversions when obtaining typed resources.
- * <p>
- * For example, a strict mode violation would be thrown when
- * calling {@link android.content.res.TypedArray#getInt(int, int)}
- * on an index that contains a String-type resource. If the string
- * value can be parsed as an integer, this method call will return
- * a value without crashing; however, the developer should format
- * the resource as an integer to avoid unnecessary type conversion.
+ * Enables detection of mismatches between defined resource types and getter calls.
+ *
+ * <p>This helps detect accidental type mismatches and potentially expensive type
+ * conversions when obtaining typed resources.
+ *
+ * <p>For example, a strict mode violation would be thrown when calling {@link
+ * android.content.res.TypedArray#getInt(int, int)} on an index that contains a
+ * String-type resource. If the string value can be parsed as an integer, this method
+ * call will return a value without crashing; however, the developer should format the
+ * resource as an integer to avoid unnecessary type conversion.
*/
public Builder detectResourceMismatches() {
return enable(DETECT_RESOURCE_MISMATCH);
}
- /**
- * Enable detection of disk writes.
- */
+ /** Enable detection of disk writes. */
public Builder detectDiskWrites() {
return enable(DETECT_DISK_WRITE);
}
- /**
- * Disable detection of disk writes.
- */
+ /** Disable detection of disk writes. */
public Builder permitDiskWrites() {
return disable(DETECT_DISK_WRITE);
}
/**
- * Show an annoying dialog to the developer on detected
- * violations, rate-limited to be only a little annoying.
+ * Show an annoying dialog to the developer on detected violations, rate-limited to be
+ * only a little annoying.
*/
public Builder penaltyDialog() {
return enable(PENALTY_DIALOG);
}
/**
- * Crash the whole process on violation. This penalty runs at
- * the end of all enabled penalties so you'll still get
- * see logging or other violations before the process dies.
+ * Crash the whole process on violation. This penalty runs at the end of all enabled
+ * penalties so you'll still get see logging or other violations before the process
+ * dies.
*
- * <p>Unlike {@link #penaltyDeathOnNetwork}, this applies
- * to disk reads, disk writes, and network usage if their
- * corresponding detect flags are set.
+ * <p>Unlike {@link #penaltyDeathOnNetwork}, this applies to disk reads, disk writes,
+ * and network usage if their corresponding detect flags are set.
*/
public Builder penaltyDeath() {
return enable(PENALTY_DEATH);
}
/**
- * Crash the whole process on any network usage. Unlike
- * {@link #penaltyDeath}, this penalty runs
- * <em>before</em> anything else. You must still have
- * called {@link #detectNetwork} to enable this.
+ * Crash the whole process on any network usage. Unlike {@link #penaltyDeath}, this
+ * penalty runs <em>before</em> anything else. You must still have called {@link
+ * #detectNetwork} to enable this.
*
* <p>In the Honeycomb or later SDKs, this is on by default.
*/
@@ -589,25 +539,20 @@ public final class StrictMode {
return enable(PENALTY_DEATH_ON_NETWORK);
}
- /**
- * Flash the screen during a violation.
- */
+ /** Flash the screen during a violation. */
public Builder penaltyFlashScreen() {
return enable(PENALTY_FLASH);
}
- /**
- * Log detected violations to the system log.
- */
+ /** Log detected violations to the system log. */
public Builder penaltyLog() {
return enable(PENALTY_LOG);
}
/**
- * Enable detected violations log a stacktrace and timing data
- * to the {@link android.os.DropBoxManager DropBox} on policy
- * violation. Intended mostly for platform integrators doing
- * beta user field data collection.
+ * Enable detected violations log a stacktrace and timing data to the {@link
+ * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform
+ * integrators doing beta user field data collection.
*/
public Builder penaltyDropBox() {
return enable(PENALTY_DROPBOX);
@@ -626,16 +571,19 @@ public final class StrictMode {
/**
* Construct the ThreadPolicy instance.
*
- * <p>Note: if no penalties are enabled before calling
- * <code>build</code>, {@link #penaltyLog} is implicitly
- * set.
+ * <p>Note: if no penalties are enabled before calling <code>build</code>, {@link
+ * #penaltyLog} is implicitly set.
*/
public ThreadPolicy build() {
// If there are detection bits set but no violation bits
// set, enable simple logging.
- if (mMask != 0 &&
- (mMask & (PENALTY_DEATH | PENALTY_LOG |
- PENALTY_DROPBOX | PENALTY_DIALOG)) == 0) {
+ if (mMask != 0
+ && (mMask
+ & (PENALTY_DEATH
+ | PENALTY_LOG
+ | PENALTY_DROPBOX
+ | PENALTY_DIALOG))
+ == 0) {
penaltyLog();
}
return new ThreadPolicy(mMask);
@@ -649,9 +597,7 @@ public final class StrictMode {
* <p>The policy is enabled by {@link #setVmPolicy}.
*/
public static final class VmPolicy {
- /**
- * The default, lax policy which doesn't catch anything.
- */
+ /** The default, lax policy which doesn't catch anything. */
public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP);
final int mask;
@@ -673,16 +619,15 @@ public final class StrictMode {
}
/**
- * Creates {@link VmPolicy} instances. Methods whose names start
- * with {@code detect} specify what problems we should look
- * for. Methods whose names start with {@code penalty} specify what
- * we should do when we detect a problem.
+ * Creates {@link VmPolicy} instances. Methods whose names start with {@code detect} specify
+ * what problems we should look for. Methods whose names start with {@code penalty} specify
+ * what we should do when we detect a problem.
*
- * <p>You can call as many {@code detect} and {@code penalty}
- * methods as you like. Currently order is insignificant: all
- * penalties apply to all detected problems.
+ * <p>You can call as many {@code detect} and {@code penalty} methods as you like. Currently
+ * order is insignificant: all penalties apply to all detected problems.
*
* <p>For example, detect everything and log anything that's found:
+ *
* <pre>
* StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
* .detectAll()
@@ -694,16 +639,14 @@ public final class StrictMode {
public static final class Builder {
private int mMask;
- private HashMap<Class, Integer> mClassInstanceLimit; // null until needed
- private boolean mClassInstanceLimitNeedCow = false; // need copy-on-write
+ private HashMap<Class, Integer> mClassInstanceLimit; // null until needed
+ private boolean mClassInstanceLimitNeedCow = false; // need copy-on-write
public Builder() {
mMask = 0;
}
- /**
- * Build upon an existing VmPolicy.
- */
+ /** Build upon an existing VmPolicy. */
public Builder(VmPolicy base) {
mMask = base.mask;
mClassInstanceLimitNeedCow = true;
@@ -711,16 +654,16 @@ public final class StrictMode {
}
/**
- * Set an upper bound on how many instances of a class can be in memory
- * at once. Helps to prevent object leaks.
+ * Set an upper bound on how many instances of a class can be in memory at once. Helps
+ * to prevent object leaks.
*/
public Builder setClassInstanceLimit(Class klass, int instanceLimit) {
if (klass == null) {
throw new NullPointerException("klass == null");
}
if (mClassInstanceLimitNeedCow) {
- if (mClassInstanceLimit.containsKey(klass) &&
- mClassInstanceLimit.get(klass) == instanceLimit) {
+ if (mClassInstanceLimit.containsKey(klass)
+ && mClassInstanceLimit.get(klass) == instanceLimit) {
// no-op; don't break COW
return this;
}
@@ -734,9 +677,7 @@ public final class StrictMode {
return this;
}
- /**
- * Detect leaks of {@link android.app.Activity} subclasses.
- */
+ /** Detect leaks of {@link android.app.Activity} subclasses. */
public Builder detectActivityLeaks() {
return enable(DETECT_VM_ACTIVITY_LEAKS);
}
@@ -744,9 +685,8 @@ public final class StrictMode {
/**
* Detect everything that's potentially suspect.
*
- * <p>In the Honeycomb release this includes leaks of
- * SQLite cursors, Activities, and other closable objects
- * but will likely expand in future releases.
+ * <p>In the Honeycomb release this includes leaks of SQLite cursors, Activities, and
+ * other closable objects but will likely expand in future releases.
*/
public Builder detectAll() {
detectLeakedSqlLiteObjects();
@@ -777,53 +717,46 @@ public final class StrictMode {
}
/**
- * Detect when an
- * {@link android.database.sqlite.SQLiteCursor} or other
- * SQLite object is finalized without having been closed.
+ * Detect when an {@link android.database.sqlite.SQLiteCursor} or other SQLite object is
+ * finalized without having been closed.
*
- * <p>You always want to explicitly close your SQLite
- * cursors to avoid unnecessary database contention and
- * temporary memory leaks.
+ * <p>You always want to explicitly close your SQLite cursors to avoid unnecessary
+ * database contention and temporary memory leaks.
*/
public Builder detectLeakedSqlLiteObjects() {
return enable(DETECT_VM_CURSOR_LEAKS);
}
/**
- * Detect when an {@link java.io.Closeable} or other
- * object with a explict termination method is finalized
- * without having been closed.
+ * Detect when an {@link java.io.Closeable} or other object with a explict termination
+ * method is finalized without having been closed.
*
- * <p>You always want to explicitly close such objects to
- * avoid unnecessary resources leaks.
+ * <p>You always want to explicitly close such objects to avoid unnecessary resources
+ * leaks.
*/
public Builder detectLeakedClosableObjects() {
return enable(DETECT_VM_CLOSABLE_LEAKS);
}
/**
- * Detect when a {@link BroadcastReceiver} or
- * {@link ServiceConnection} is leaked during {@link Context}
- * teardown.
+ * Detect when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked during
+ * {@link Context} teardown.
*/
public Builder detectLeakedRegistrationObjects() {
return enable(DETECT_VM_REGISTRATION_LEAKS);
}
/**
- * Detect when the calling application exposes a {@code file://}
- * {@link android.net.Uri} to another app.
- * <p>
- * This exposure is discouraged since the receiving app may not have
- * access to the shared path. For example, the receiving app may not
- * have requested the
- * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} runtime
- * permission, or the platform may be sharing the
- * {@link android.net.Uri} across user profile boundaries.
- * <p>
- * Instead, apps should use {@code content://} Uris so the platform
- * can extend temporary permission for the receiving app to access
- * the resource.
+ * Detect when the calling application exposes a {@code file://} {@link android.net.Uri}
+ * to another app.
+ *
+ * <p>This exposure is discouraged since the receiving app may not have access to the
+ * shared path. For example, the receiving app may not have requested the {@link
+ * android.Manifest.permission#READ_EXTERNAL_STORAGE} runtime permission, or the
+ * platform may be sharing the {@link android.net.Uri} across user profile boundaries.
+ *
+ * <p>Instead, apps should use {@code content://} Uris so the platform can extend
+ * temporary permission for the receiving app to access the resource.
*
* @see android.support.v4.content.FileProvider
* @see Intent#FLAG_GRANT_READ_URI_PERMISSION
@@ -833,34 +766,32 @@ public final class StrictMode {
}
/**
- * Detect any network traffic from the calling app which is not
- * wrapped in SSL/TLS. This can help you detect places that your app
- * is inadvertently sending cleartext data across the network.
- * <p>
- * Using {@link #penaltyDeath()} or
- * {@link #penaltyDeathOnCleartextNetwork()} will block further
- * traffic on that socket to prevent accidental data leakage, in
- * addition to crashing your process.
- * <p>
- * Using {@link #penaltyDropBox()} will log the raw contents of the
- * packet that triggered the violation.
- * <p>
- * This inspects both IPv4/IPv6 and TCP/UDP network traffic, but it
- * may be subject to false positives, such as when STARTTLS
- * protocols or HTTP proxies are used.
+ * Detect any network traffic from the calling app which is not wrapped in SSL/TLS. This
+ * can help you detect places that your app is inadvertently sending cleartext data
+ * across the network.
+ *
+ * <p>Using {@link #penaltyDeath()} or {@link #penaltyDeathOnCleartextNetwork()} will
+ * block further traffic on that socket to prevent accidental data leakage, in addition
+ * to crashing your process.
+ *
+ * <p>Using {@link #penaltyDropBox()} will log the raw contents of the packet that
+ * triggered the violation.
+ *
+ * <p>This inspects both IPv4/IPv6 and TCP/UDP network traffic, but it may be subject to
+ * false positives, such as when STARTTLS protocols or HTTP proxies are used.
*/
public Builder detectCleartextNetwork() {
return enable(DETECT_VM_CLEARTEXT_NETWORK);
}
/**
- * Detect when the calling application sends a {@code content://}
- * {@link android.net.Uri} to another app without setting
- * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} or
- * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
- * <p>
- * Forgetting to include one or more of these flags when sending an
- * intent is typically an app bug.
+ * Detect when the calling application sends a {@code content://} {@link
+ * android.net.Uri} to another app without setting {@link
+ * Intent#FLAG_GRANT_READ_URI_PERMISSION} or {@link
+ * Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+ *
+ * <p>Forgetting to include one or more of these flags when sending an intent is
+ * typically an app bug.
*
* @see Intent#FLAG_GRANT_READ_URI_PERMISSION
* @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION
@@ -870,12 +801,11 @@ public final class StrictMode {
}
/**
- * Detect any sockets in the calling app which have not been tagged
- * using {@link TrafficStats}. Tagging sockets can help you
- * investigate network usage inside your app, such as a narrowing
- * down heavy usage to a specific library or component.
- * <p>
- * This currently does not detect sockets created in native code.
+ * Detect any sockets in the calling app which have not been tagged using {@link
+ * TrafficStats}. Tagging sockets can help you investigate network usage inside your
+ * app, such as a narrowing down heavy usage to a specific library or component.
+ *
+ * <p>This currently does not detect sockets created in native code.
*
* @see TrafficStats#setThreadStatsTag(int)
* @see TrafficStats#tagSocket(java.net.Socket)
@@ -886,17 +816,16 @@ public final class StrictMode {
}
/**
- * Crashes the whole process on violation. This penalty runs at the
- * end of all enabled penalties so you'll still get your logging or
- * other violations before the process dies.
+ * Crashes the whole process on violation. This penalty runs at the end of all enabled
+ * penalties so you'll still get your logging or other violations before the process
+ * dies.
*/
public Builder penaltyDeath() {
return enable(PENALTY_DEATH);
}
/**
- * Crashes the whole process when cleartext network traffic is
- * detected.
+ * Crashes the whole process when cleartext network traffic is detected.
*
* @see #detectCleartextNetwork()
*/
@@ -905,8 +834,8 @@ public final class StrictMode {
}
/**
- * Crashes the whole process when a {@code file://}
- * {@link android.net.Uri} is exposed beyond this app.
+ * Crashes the whole process when a {@code file://} {@link android.net.Uri} is exposed
+ * beyond this app.
*
* @see #detectFileUriExposure()
*/
@@ -914,18 +843,15 @@ public final class StrictMode {
return enable(PENALTY_DEATH_ON_FILE_URI_EXPOSURE);
}
- /**
- * Log detected violations to the system log.
- */
+ /** Log detected violations to the system log. */
public Builder penaltyLog() {
return enable(PENALTY_LOG);
}
/**
- * Enable detected violations log a stacktrace and timing data
- * to the {@link android.os.DropBoxManager DropBox} on policy
- * violation. Intended mostly for platform integrators doing
- * beta user field data collection.
+ * Enable detected violations log a stacktrace and timing data to the {@link
+ * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform
+ * integrators doing beta user field data collection.
*/
public Builder penaltyDropBox() {
return enable(PENALTY_DROPBOX);
@@ -944,48 +870,51 @@ public final class StrictMode {
/**
* Construct the VmPolicy instance.
*
- * <p>Note: if no penalties are enabled before calling
- * <code>build</code>, {@link #penaltyLog} is implicitly
- * set.
+ * <p>Note: if no penalties are enabled before calling <code>build</code>, {@link
+ * #penaltyLog} is implicitly set.
*/
public VmPolicy build() {
// If there are detection bits set but no violation bits
// set, enable simple logging.
- if (mMask != 0 &&
- (mMask & (PENALTY_DEATH | PENALTY_LOG |
- PENALTY_DROPBOX | PENALTY_DIALOG)) == 0) {
+ if (mMask != 0
+ && (mMask
+ & (PENALTY_DEATH
+ | PENALTY_LOG
+ | PENALTY_DROPBOX
+ | PENALTY_DIALOG))
+ == 0) {
penaltyLog();
}
- return new VmPolicy(mMask,
+ return new VmPolicy(
+ mMask,
mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP);
}
}
}
/**
- * Log of strict mode violation stack traces that have occurred
- * during a Binder call, to be serialized back later to the caller
- * via Parcel.writeNoException() (amusingly) where the caller can
- * choose how to react.
+ * Log of strict mode violation stack traces that have occurred during a Binder call, to be
+ * serialized back later to the caller via Parcel.writeNoException() (amusingly) where the
+ * caller can choose how to react.
*/
private static final ThreadLocal<ArrayList<ViolationInfo>> gatheredViolations =
new ThreadLocal<ArrayList<ViolationInfo>>() {
- @Override protected ArrayList<ViolationInfo> initialValue() {
- // Starts null to avoid unnecessary allocations when
- // checking whether there are any violations or not in
- // hasGatheredViolations() below.
- return null;
- }
- };
+ @Override
+ protected ArrayList<ViolationInfo> initialValue() {
+ // Starts null to avoid unnecessary allocations when
+ // checking whether there are any violations or not in
+ // hasGatheredViolations() below.
+ return null;
+ }
+ };
/**
- * Sets the policy for what actions on the current thread should
- * be detected, as well as the penalty if such actions occur.
+ * Sets the policy for what actions on the current thread should be detected, as well as the
+ * penalty if such actions occur.
*
- * <p>Internally this sets a thread-local variable which is
- * propagated across cross-process IPC calls, meaning you can
- * catch violations when a system service or another process
- * accesses the disk or network on your behalf.
+ * <p>Internally this sets a thread-local variable which is propagated across cross-process IPC
+ * calls, meaning you can catch violations when a system service or another process accesses the
+ * disk or network on your behalf.
*
* @param policy the policy to put into place
*/
@@ -1016,7 +945,7 @@ public final class StrictMode {
if (policy instanceof AndroidBlockGuardPolicy) {
androidPolicy = (AndroidBlockGuardPolicy) policy;
} else {
- androidPolicy = threadAndroidPolicy.get();
+ androidPolicy = THREAD_ANDROID_POLICY.get();
BlockGuard.setThreadPolicy(androidPolicy);
}
androidPolicy.setPolicyMask(policyMask);
@@ -1030,63 +959,49 @@ public final class StrictMode {
CloseGuard.setEnabled(enabled);
}
- /**
- * @hide
- */
+ /** @hide */
public static class StrictModeViolation extends BlockGuard.BlockGuardPolicyException {
public StrictModeViolation(int policyState, int policyViolated, String message) {
super(policyState, policyViolated, message);
}
}
- /**
- * @hide
- */
+ /** @hide */
public static class StrictModeNetworkViolation extends StrictModeViolation {
public StrictModeNetworkViolation(int policyMask) {
super(policyMask, DETECT_NETWORK, null);
}
}
- /**
- * @hide
- */
+ /** @hide */
private static class StrictModeDiskReadViolation extends StrictModeViolation {
public StrictModeDiskReadViolation(int policyMask) {
super(policyMask, DETECT_DISK_READ, null);
}
}
- /**
- * @hide
- */
- private static class StrictModeDiskWriteViolation extends StrictModeViolation {
+ /** @hide */
+ private static class StrictModeDiskWriteViolation extends StrictModeViolation {
public StrictModeDiskWriteViolation(int policyMask) {
super(policyMask, DETECT_DISK_WRITE, null);
}
}
- /**
- * @hide
- */
+ /** @hide */
private static class StrictModeCustomViolation extends StrictModeViolation {
public StrictModeCustomViolation(int policyMask, String name) {
super(policyMask, DETECT_CUSTOM, name);
}
}
- /**
- * @hide
- */
+ /** @hide */
private static class StrictModeResourceMismatchViolation extends StrictModeViolation {
public StrictModeResourceMismatchViolation(int policyMask, Object tag) {
super(policyMask, DETECT_RESOURCE_MISMATCH, tag != null ? tag.toString() : null);
}
}
- /**
- * @hide
- */
+ /** @hide */
private static class StrictModeUnbufferedIOViolation extends StrictModeViolation {
public StrictModeUnbufferedIOViolation(int policyMask) {
super(policyMask, DETECT_UNBUFFERED_IO, null);
@@ -1097,16 +1012,13 @@ public final class StrictMode {
* Returns the bitmask of the current thread's policy.
*
* @return the bitmask of all the DETECT_* and PENALTY_* bits currently enabled
- *
* @hide
*/
public static int getThreadPolicyMask() {
return BlockGuard.getThreadPolicy().getPolicyMask();
}
- /**
- * Returns the current thread's policy.
- */
+ /** Returns the current thread's policy. */
public static ThreadPolicy getThreadPolicy() {
// TODO: this was a last minute Gingerbread API change (to
// introduce VmPolicy cleanly) but this isn't particularly
@@ -1116,14 +1028,13 @@ public final class StrictMode {
}
/**
- * A convenience wrapper that takes the current
- * {@link ThreadPolicy} from {@link #getThreadPolicy}, modifies it
- * to permit both disk reads &amp; writes, and sets the new policy
- * with {@link #setThreadPolicy}, returning the old policy so you
- * can restore it at the end of a block.
+ * A convenience wrapper that takes the current {@link ThreadPolicy} from {@link
+ * #getThreadPolicy}, modifies it to permit both disk reads &amp; writes, and sets the new
+ * policy with {@link #setThreadPolicy}, returning the old policy so you can restore it at the
+ * end of a block.
*
- * @return the old policy, to be passed to {@link #setThreadPolicy} to
- * restore the policy at the end of a block
+ * @return the old policy, to be passed to {@link #setThreadPolicy} to restore the policy at the
+ * end of a block
*/
public static ThreadPolicy allowThreadDiskWrites() {
int oldPolicyMask = getThreadPolicyMask();
@@ -1135,14 +1046,11 @@ public final class StrictMode {
}
/**
- * A convenience wrapper that takes the current
- * {@link ThreadPolicy} from {@link #getThreadPolicy}, modifies it
- * to permit disk reads, and sets the new policy
- * with {@link #setThreadPolicy}, returning the old policy so you
- * can restore it at the end of a block.
+ * A convenience wrapper that takes the current {@link ThreadPolicy} from {@link
+ * #getThreadPolicy}, modifies it to permit disk reads, and sets the new policy with {@link
+ * #setThreadPolicy}, returning the old policy so you can restore it at the end of a block.
*
- * @return the old policy, to be passed to setThreadPolicy to
- * restore the policy.
+ * @return the old policy, to be passed to setThreadPolicy to restore the policy.
*/
public static ThreadPolicy allowThreadDiskReads() {
int oldPolicyMask = getThreadPolicyMask();
@@ -1182,8 +1090,8 @@ public final class StrictMode {
* @hide
*/
public static boolean conditionallyEnableDebugLogging() {
- boolean doFlashes = SystemProperties.getBoolean(VISUAL_PROPERTY, false)
- && !amTheSystemServerProcess();
+ boolean doFlashes =
+ SystemProperties.getBoolean(VISUAL_PROPERTY, false) && !amTheSystemServerProcess();
final boolean suppress = SystemProperties.getBoolean(DISABLE_PROPERTY, false);
// For debug builds, log event loop stalls to dropbox for analysis.
@@ -1201,9 +1109,10 @@ public final class StrictMode {
}
// Thread policy controls BlockGuard.
- int threadPolicyMask = StrictMode.DETECT_DISK_WRITE |
- StrictMode.DETECT_DISK_READ |
- StrictMode.DETECT_NETWORK;
+ int threadPolicyMask =
+ StrictMode.DETECT_DISK_WRITE
+ | StrictMode.DETECT_DISK_READ
+ | StrictMode.DETECT_NETWORK;
if (!Build.IS_USER) {
threadPolicyMask |= StrictMode.PENALTY_DROPBOX;
@@ -1243,8 +1152,7 @@ public final class StrictMode {
}
/**
- * Used by the framework to make network usage on the main
- * thread a fatal error.
+ * Used by the framework to make network usage on the main thread a fatal error.
*
* @hide
*/
@@ -1264,8 +1172,8 @@ public final class StrictMode {
}
/**
- * Used by lame internal apps that haven't done the hard work to get
- * themselves off file:// Uris yet.
+ * Used by lame internal apps that haven't done the hard work to get themselves off file:// Uris
+ * yet.
*
* @hide
*/
@@ -1274,17 +1182,14 @@ public final class StrictMode {
}
/**
- * Parses the BlockGuard policy mask out from the Exception's
- * getMessage() String value. Kinda gross, but least
- * invasive. :/
+ * Parses the BlockGuard policy mask out from the Exception's getMessage() String value. Kinda
+ * gross, but least invasive. :/
*
- * Input is of the following forms:
- * "policy=137 violation=64"
- * "policy=137 violation=64 msg=Arbitrary text"
+ * <p>Input is of the following forms: "policy=137 violation=64" "policy=137 violation=64
+ * msg=Arbitrary text"
*
- * Returns 0 on failure, which is a valid policy, but not a
- * valid policy during a violation (else there must've been
- * some policy in effect to violate).
+ * <p>Returns 0 on failure, which is a valid policy, but not a valid policy during a violation
+ * (else there must've been some policy in effect to violate).
*/
private static int parsePolicyFromMessage(String message) {
if (message == null || !message.startsWith("policy=")) {
@@ -1302,9 +1207,7 @@ public final class StrictMode {
}
}
- /**
- * Like parsePolicyFromMessage(), but returns the violation.
- */
+ /** Like parsePolicyFromMessage(), but returns the violation. */
private static int parseViolationFromMessage(String message) {
if (message == null) {
return 0;
@@ -1328,25 +1231,28 @@ public final class StrictMode {
private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed =
new ThreadLocal<ArrayList<ViolationInfo>>() {
- @Override protected ArrayList<ViolationInfo> initialValue() {
- return new ArrayList<ViolationInfo>();
- }
- };
+ @Override
+ protected ArrayList<ViolationInfo> initialValue() {
+ return new ArrayList<ViolationInfo>();
+ }
+ };
// Note: only access this once verifying the thread has a Looper.
- private static final ThreadLocal<Handler> threadHandler = new ThreadLocal<Handler>() {
- @Override protected Handler initialValue() {
- return new Handler();
- }
- };
+ private static final ThreadLocal<Handler> THREAD_HANDLER =
+ new ThreadLocal<Handler>() {
+ @Override
+ protected Handler initialValue() {
+ return new Handler();
+ }
+ };
- private static final ThreadLocal<AndroidBlockGuardPolicy>
- threadAndroidPolicy = new ThreadLocal<AndroidBlockGuardPolicy>() {
- @Override
- protected AndroidBlockGuardPolicy initialValue() {
- return new AndroidBlockGuardPolicy(0);
- }
- };
+ private static final ThreadLocal<AndroidBlockGuardPolicy> THREAD_ANDROID_POLICY =
+ new ThreadLocal<AndroidBlockGuardPolicy>() {
+ @Override
+ protected AndroidBlockGuardPolicy initialValue() {
+ return new AndroidBlockGuardPolicy(0);
+ }
+ };
private static boolean tooManyViolationsThisLoop() {
return violationsBeingTimed.get().size() >= MAX_OFFENSES_PER_LOOP;
@@ -1395,7 +1301,8 @@ public final class StrictMode {
if (tooManyViolationsThisLoop()) {
return;
}
- BlockGuard.BlockGuardPolicyException e = new StrictModeCustomViolation(mPolicyMask, name);
+ BlockGuard.BlockGuardPolicyException e =
+ new StrictModeCustomViolation(mPolicyMask, name);
e.fillInStackTrace();
startHandlingViolationException(e);
}
@@ -1496,9 +1403,8 @@ public final class StrictMode {
//
// TODO: if in gather mode, ignore Looper.myLooper() and always
// go into this immediate mode?
- if (looper == null ||
- (info.policy & THREAD_PENALTY_MASK) == PENALTY_DEATH) {
- info.durationMillis = -1; // unknown (redundant, already set)
+ if (looper == null || (info.policy & THREAD_PENALTY_MASK) == PENALTY_DEATH) {
+ info.durationMillis = -1; // unknown (redundant, already set)
handleViolation(info);
return;
}
@@ -1516,8 +1422,8 @@ public final class StrictMode {
return;
}
- final IWindowManager windowManager = (info.policy & PENALTY_FLASH) != 0 ?
- sWindowManager.get() : null;
+ final IWindowManager windowManager =
+ (info.policy & PENALTY_FLASH) != 0 ? sWindowManager.get() : null;
if (windowManager != null) {
try {
windowManager.showStrictModeViolation(true);
@@ -1534,31 +1440,34 @@ public final class StrictMode {
// throttled back to 60fps via SurfaceFlinger/View
// invalidates, _not_ by posting frame updates every 16
// milliseconds.
- threadHandler.get().postAtFrontOfQueue(new Runnable() {
- public void run() {
- long loopFinishTime = SystemClock.uptimeMillis();
-
- // Note: we do this early, before handling the
- // violation below, as handling the violation
- // may include PENALTY_DEATH and we don't want
- // to keep the red border on.
- if (windowManager != null) {
- try {
- windowManager.showStrictModeViolation(false);
- } catch (RemoteException unused) {
- }
- }
-
- for (int n = 0; n < records.size(); ++n) {
- ViolationInfo v = records.get(n);
- v.violationNumThisLoop = n + 1;
- v.durationMillis =
- (int) (loopFinishTime - v.violationUptimeMillis);
- handleViolation(v);
- }
- records.clear();
- }
- });
+ THREAD_HANDLER
+ .get()
+ .postAtFrontOfQueue(
+ new Runnable() {
+ public void run() {
+ long loopFinishTime = SystemClock.uptimeMillis();
+
+ // Note: we do this early, before handling the
+ // violation below, as handling the violation
+ // may include PENALTY_DEATH and we don't want
+ // to keep the red border on.
+ if (windowManager != null) {
+ try {
+ windowManager.showStrictModeViolation(false);
+ } catch (RemoteException unused) {
+ }
+ }
+
+ for (int n = 0; n < records.size(); ++n) {
+ ViolationInfo v = records.get(n);
+ v.violationNumThisLoop = n + 1;
+ v.durationMillis =
+ (int) (loopFinishTime - v.violationUptimeMillis);
+ handleViolation(v);
+ }
+ records.clear();
+ }
+ });
}
// Note: It's possible (even quite likely) that the
@@ -1603,17 +1512,21 @@ public final class StrictMode {
}
long now = SystemClock.uptimeMillis();
mLastViolationTime.put(crashFingerprint, now);
- long timeSinceLastViolationMillis = lastViolationTime == 0 ?
- Long.MAX_VALUE : (now - lastViolationTime);
+ long timeSinceLastViolationMillis =
+ lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime);
if ((info.policy & PENALTY_LOG) != 0 && sListener != null) {
sListener.onViolation(info.crashInfo.stackTrace);
}
- if ((info.policy & PENALTY_LOG) != 0 &&
- timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+ if ((info.policy & PENALTY_LOG) != 0
+ && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
if (info.durationMillis != -1) {
- Log.d(TAG, "StrictMode policy violation; ~duration=" +
- info.durationMillis + " ms: " + info.crashInfo.stackTrace);
+ Log.d(
+ TAG,
+ "StrictMode policy violation; ~duration="
+ + info.durationMillis
+ + " ms: "
+ + info.crashInfo.stackTrace);
} else {
Log.d(TAG, "StrictMode policy violation: " + info.crashInfo.stackTrace);
}
@@ -1625,8 +1538,8 @@ public final class StrictMode {
// by the ActivityManagerService remaining set.
int violationMaskSubset = 0;
- if ((info.policy & PENALTY_DIALOG) != 0 &&
- timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) {
+ if ((info.policy & PENALTY_DIALOG) != 0
+ && timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) {
violationMaskSubset |= PENALTY_DIALOG;
}
@@ -1659,10 +1572,9 @@ public final class StrictMode {
// We restore the current policy below, in the finally block.
setThreadPolicyMask(0);
- ActivityManager.getService().handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(),
- violationMaskSubset,
- info);
+ ActivityManager.getService()
+ .handleApplicationStrictModeViolation(
+ RuntimeInit.getApplicationObject(), violationMaskSubset, info);
} catch (RemoteException e) {
if (e instanceof DeadObjectException) {
// System process is dead; ignore
@@ -1687,12 +1599,10 @@ public final class StrictMode {
}
/**
- * In the common case, as set by conditionallyEnableDebugLogging,
- * we're just dropboxing any violations but not showing a dialog,
- * not loggging, and not killing the process. In these cases we
- * don't need to do a synchronous call to the ActivityManager.
- * This is used by both per-thread and vm-wide violations when
- * applicable.
+ * In the common case, as set by conditionallyEnableDebugLogging, we're just dropboxing any
+ * violations but not showing a dialog, not loggging, and not killing the process. In these
+ * cases we don't need to do a synchronous call to the ActivityManager. This is used by both
+ * per-thread and vm-wide violations when applicable.
*/
private static void dropboxViolationAsync(
final int violationMaskSubset, final ViolationInfo info) {
@@ -1715,9 +1625,7 @@ public final class StrictMode {
Log.d(TAG, "No activity manager; failed to Dropbox violation.");
} else {
am.handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(),
- violationMaskSubset,
- info);
+ RuntimeInit.getApplicationObject(), violationMaskSubset, info);
}
} catch (RemoteException e) {
if (e instanceof DeadObjectException) {
@@ -1738,25 +1646,20 @@ public final class StrictMode {
}
}
- /**
- * Called from Parcel.writeNoException()
- */
+ /** Called from Parcel.writeNoException() */
/* package */ static boolean hasGatheredViolations() {
return gatheredViolations.get() != null;
}
/**
- * Called from Parcel.writeException(), so we drop this memory and
- * don't incorrectly attribute it to the wrong caller on the next
- * Binder call on this thread.
+ * Called from Parcel.writeException(), so we drop this memory and don't incorrectly attribute
+ * it to the wrong caller on the next Binder call on this thread.
*/
/* package */ static void clearGatheredViolations() {
gatheredViolations.set(null);
}
- /**
- * @hide
- */
+ /** @hide */
public static void conditionallyCheckInstanceCounts() {
VmPolicy policy = getVmPolicy();
int policySize = policy.classInstanceLimit.size();
@@ -1784,7 +1687,7 @@ public final class StrictMode {
}
private static long sLastInstanceCountCheckMillis = 0;
- private static boolean sIsIdlerRegistered = false; // guarded by StrictMode.class
+ private static boolean sIsIdlerRegistered = false; // guarded by StrictMode.class
private static final MessageQueue.IdleHandler sProcessIdleHandler =
new MessageQueue.IdleHandler() {
public boolean queueIdle() {
@@ -1798,9 +1701,8 @@ public final class StrictMode {
};
/**
- * Sets the policy for what actions in the VM process (on any
- * thread) should be detected, as well as the penalty if such
- * actions occur.
+ * Sets the policy for what actions in the VM process (on any thread) should be detected, as
+ * well as the penalty if such actions occur.
*
* @param policy the policy to put into place
*/
@@ -1813,8 +1715,8 @@ public final class StrictMode {
Looper looper = Looper.getMainLooper();
if (looper != null) {
MessageQueue mq = looper.mQueue;
- if (policy.classInstanceLimit.size() == 0 ||
- (sVmPolicyMask & VM_PENALTY_MASK) == 0) {
+ if (policy.classInstanceLimit.size() == 0
+ || (sVmPolicyMask & VM_PENALTY_MASK) == 0) {
mq.removeIdleHandler(sProcessIdleHandler);
sIsIdlerRegistered = false;
} else if (!sIsIdlerRegistered) {
@@ -1833,8 +1735,9 @@ public final class StrictMode {
}
}
- final INetworkManagementService netd = INetworkManagementService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+ final INetworkManagementService netd =
+ INetworkManagementService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
if (netd != null) {
try {
netd.setUidCleartextNetworkPolicy(android.os.Process.myUid(), networkPolicy);
@@ -1846,9 +1749,7 @@ public final class StrictMode {
}
}
- /**
- * Gets the current VM policy.
- */
+ /** Gets the current VM policy. */
public static VmPolicy getVmPolicy() {
synchronized (StrictMode.class) {
return sVmPolicy;
@@ -1858,102 +1759,72 @@ public final class StrictMode {
/**
* Enable the recommended StrictMode defaults, with violations just being logged.
*
- * <p>This catches disk and network access on the main thread, as
- * well as leaked SQLite cursors and unclosed resources. This is
- * simply a wrapper around {@link #setVmPolicy} and {@link
+ * <p>This catches disk and network access on the main thread, as well as leaked SQLite cursors
+ * and unclosed resources. This is simply a wrapper around {@link #setVmPolicy} and {@link
* #setThreadPolicy}.
*/
public static void enableDefaults() {
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
- .detectAll()
- .penaltyLog()
- .build());
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
- .detectAll()
- .penaltyLog()
- .build());
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+ StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
}
- /**
- * @hide
- */
+ /** @hide */
public static boolean vmSqliteObjectLeaksEnabled() {
return (sVmPolicyMask & DETECT_VM_CURSOR_LEAKS) != 0;
}
- /**
- * @hide
- */
+ /** @hide */
public static boolean vmClosableObjectLeaksEnabled() {
return (sVmPolicyMask & DETECT_VM_CLOSABLE_LEAKS) != 0;
}
- /**
- * @hide
- */
+ /** @hide */
public static boolean vmRegistrationLeaksEnabled() {
return (sVmPolicyMask & DETECT_VM_REGISTRATION_LEAKS) != 0;
}
- /**
- * @hide
- */
+ /** @hide */
public static boolean vmFileUriExposureEnabled() {
return (sVmPolicyMask & DETECT_VM_FILE_URI_EXPOSURE) != 0;
}
- /**
- * @hide
- */
+ /** @hide */
public static boolean vmCleartextNetworkEnabled() {
return (sVmPolicyMask & DETECT_VM_CLEARTEXT_NETWORK) != 0;
}
- /**
- * @hide
- */
+ /** @hide */
public static boolean vmContentUriWithoutPermissionEnabled() {
return (sVmPolicyMask & DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION) != 0;
}
- /**
- * @hide
- */
+ /** @hide */
public static boolean vmUntaggedSocketEnabled() {
return (sVmPolicyMask & DETECT_VM_UNTAGGED_SOCKET) != 0;
}
- /**
- * @hide
- */
+ /** @hide */
public static void onSqliteObjectLeaked(String message, Throwable originStack) {
onVmPolicyViolation(message, originStack);
}
- /**
- * @hide
- */
+ /** @hide */
public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) {
onVmPolicyViolation(null, originStack);
}
- /**
- * @hide
- */
+ /** @hide */
public static void onIntentReceiverLeaked(Throwable originStack) {
onVmPolicyViolation(null, originStack);
}
- /**
- * @hide
- */
+ /** @hide */
public static void onServiceConnectionLeaked(Throwable originStack) {
onVmPolicyViolation(null, originStack);
}
- /**
- * @hide
- */
+ /** @hide */
public static void onFileUriExposed(Uri uri, String location) {
final String message = uri + " exposed beyond app through " + location;
if ((sVmPolicyMask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) {
@@ -1963,19 +1834,18 @@ public final class StrictMode {
}
}
- /**
- * @hide
- */
+ /** @hide */
public static void onContentUriWithoutPermission(Uri uri, String location) {
- final String message = uri + " exposed beyond app through " + location
- + " without permission grant flags; did you forget"
- + " FLAG_GRANT_READ_URI_PERMISSION?";
+ final String message =
+ uri
+ + " exposed beyond app through "
+ + location
+ + " without permission grant flags; did you forget"
+ + " FLAG_GRANT_READ_URI_PERMISSION?";
onVmPolicyViolation(null, new Throwable(message));
}
- /**
- * @hide
- */
+ /** @hide */
public static void onCleartextNetworkDetected(byte[] firstPacket) {
byte[] rawAddr = null;
if (firstPacket != null) {
@@ -1994,40 +1864,40 @@ public final class StrictMode {
String msg = "Detected cleartext network traffic from UID " + uid;
if (rawAddr != null) {
try {
- msg = "Detected cleartext network traffic from UID " + uid + " to "
- + InetAddress.getByAddress(rawAddr);
+ msg =
+ "Detected cleartext network traffic from UID "
+ + uid
+ + " to "
+ + InetAddress.getByAddress(rawAddr);
} catch (UnknownHostException ignored) {
}
}
final boolean forceDeath = (sVmPolicyMask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0;
- onVmPolicyViolation(HexDump.dumpHexString(firstPacket).trim(), new Throwable(msg),
- forceDeath);
+ onVmPolicyViolation(
+ HexDump.dumpHexString(firstPacket).trim(), new Throwable(msg), forceDeath);
}
- /**
- * @hide
- */
+ /** @hide */
public static void onUntaggedSocket() {
- onVmPolicyViolation(null, new Throwable("Untagged socket detected; use"
- + " TrafficStats.setThreadSocketTag() to track all network usage"));
+ onVmPolicyViolation(
+ null,
+ new Throwable(
+ "Untagged socket detected; use"
+ + " TrafficStats.setThreadSocketTag() to track all network usage"));
}
// Map from VM violation fingerprint to uptime millis.
private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>();
- /**
- * @hide
- */
+ /** @hide */
public static void onVmPolicyViolation(String message, Throwable originStack) {
onVmPolicyViolation(message, originStack, false);
}
- /**
- * @hide
- */
- public static void onVmPolicyViolation(String message, Throwable originStack,
- boolean forceDeath) {
+ /** @hide */
+ public static void onVmPolicyViolation(
+ String message, Throwable originStack, boolean forceDeath) {
final boolean penaltyDropbox = (sVmPolicyMask & PENALTY_DROPBOX) != 0;
final boolean penaltyDeath = ((sVmPolicyMask & PENALTY_DEATH) != 0) || forceDeath;
final boolean penaltyLog = (sVmPolicyMask & PENALTY_LOG) != 0;
@@ -2082,10 +1952,9 @@ public final class StrictMode {
// We restore the current policy below, in the finally block.
setThreadPolicyMask(0);
- ActivityManager.getService().handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(),
- violationMaskSubset,
- info);
+ ActivityManager.getService()
+ .handleApplicationStrictModeViolation(
+ RuntimeInit.getApplicationObject(), violationMaskSubset, info);
} catch (RemoteException e) {
if (e instanceof DeadObjectException) {
// System process is dead; ignore
@@ -2105,9 +1974,7 @@ public final class StrictMode {
}
}
- /**
- * Called from Parcel.writeNoException()
- */
+ /** Called from Parcel.writeNoException() */
/* package */ static void writeGatheredViolationsToParcel(Parcel p) {
ArrayList<ViolationInfo> violations = gatheredViolations.get();
if (violations == null) {
@@ -2128,8 +1995,8 @@ public final class StrictMode {
private static class LogStackTrace extends Exception {}
/**
- * Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS,
- * we here read back all the encoded violations.
+ * Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS, we here
+ * read back all the encoded violations.
*/
/* package */ static void readAndHandleBinderCallViolations(Parcel p) {
// Our own stack trace to append
@@ -2155,22 +2022,20 @@ public final class StrictMode {
}
/**
- * Called from android_util_Binder.cpp's
- * android_os_Parcel_enforceInterface when an incoming Binder call
- * requires changing the StrictMode policy mask. The role of this
- * function is to ask Binder for its current (native) thread-local
- * policy value and synchronize it to libcore's (Java)
- * thread-local policy value.
+ * Called from android_util_Binder.cpp's android_os_Parcel_enforceInterface when an incoming
+ * Binder call requires changing the StrictMode policy mask. The role of this function is to ask
+ * Binder for its current (native) thread-local policy value and synchronize it to libcore's
+ * (Java) thread-local policy value.
*/
private static void onBinderStrictModePolicyChange(int newPolicy) {
setBlockGuardPolicy(newPolicy);
}
/**
- * A tracked, critical time span. (e.g. during an animation.)
+ * A tracked, critical time span. (e.g. during an animation.)
*
- * The object itself is a linked list node, to avoid any allocations
- * during rapid span entries and exits.
+ * <p>The object itself is a linked list node, to avoid any allocations during rapid span
+ * entries and exits.
*
* @hide
*/
@@ -2178,7 +2043,7 @@ public final class StrictMode {
private String mName;
private long mCreateMillis;
private Span mNext;
- private Span mPrev; // not used when in freeList, only active
+ private Span mPrev; // not used when in freeList, only active
private final ThreadSpanState mContainerState;
Span(ThreadSpanState threadState) {
@@ -2191,12 +2056,10 @@ public final class StrictMode {
}
/**
- * To be called when the critical span is complete (i.e. the
- * animation is done animating). This can be called on any
- * thread (even a different one from where the animation was
- * taking place), but that's only a defensive implementation
- * measure. It really makes no sense for you to call this on
- * thread other than that where you created it.
+ * To be called when the critical span is complete (i.e. the animation is done animating).
+ * This can be called on any thread (even a different one from where the animation was
+ * taking place), but that's only a defensive implementation measure. It really makes no
+ * sense for you to call this on thread other than that where you created it.
*
* @hide
*/
@@ -2240,53 +2103,52 @@ public final class StrictMode {
}
// The no-op span that's used in user builds.
- private static final Span NO_OP_SPAN = new Span() {
- public void finish() {
- // Do nothing.
- }
- };
+ private static final Span NO_OP_SPAN =
+ new Span() {
+ public void finish() {
+ // Do nothing.
+ }
+ };
/**
* Linked lists of active spans and a freelist.
*
- * Locking notes: there's one of these structures per thread and
- * all members of this structure (as well as the Span nodes under
- * it) are guarded by the ThreadSpanState object instance. While
- * in theory there'd be no locking required because it's all local
- * per-thread, the finish() method above is defensive against
- * people calling it on a different thread from where they created
- * the Span, hence the locking.
+ * <p>Locking notes: there's one of these structures per thread and all members of this
+ * structure (as well as the Span nodes under it) are guarded by the ThreadSpanState object
+ * instance. While in theory there'd be no locking required because it's all local per-thread,
+ * the finish() method above is defensive against people calling it on a different thread from
+ * where they created the Span, hence the locking.
*/
private static class ThreadSpanState {
- public Span mActiveHead; // doubly-linked list.
+ public Span mActiveHead; // doubly-linked list.
public int mActiveSize;
- public Span mFreeListHead; // singly-linked list. only changes at head.
+ public Span mFreeListHead; // singly-linked list. only changes at head.
public int mFreeListSize;
}
private static final ThreadLocal<ThreadSpanState> sThisThreadSpanState =
new ThreadLocal<ThreadSpanState>() {
- @Override protected ThreadSpanState initialValue() {
- return new ThreadSpanState();
- }
- };
+ @Override
+ protected ThreadSpanState initialValue() {
+ return new ThreadSpanState();
+ }
+ };
- private static Singleton<IWindowManager> sWindowManager = new Singleton<IWindowManager>() {
- protected IWindowManager create() {
- return IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
- }
- };
+ private static Singleton<IWindowManager> sWindowManager =
+ new Singleton<IWindowManager>() {
+ protected IWindowManager create() {
+ return IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+ }
+ };
/**
* Enter a named critical span (e.g. an animation)
*
- * <p>The name is an arbitary label (or tag) that will be applied
- * to any strictmode violation that happens while this span is
- * active. You must call finish() on the span when done.
+ * <p>The name is an arbitary label (or tag) that will be applied to any strictmode violation
+ * that happens while this span is active. You must call finish() on the span when done.
*
- * <p>This will never return null, but on devices without debugging
- * enabled, this may return a dummy object on which the finish()
- * method is a no-op.
+ * <p>This will never return null, but on devices without debugging enabled, this may return a
+ * dummy object on which the finish() method is a no-op.
*
* <p>TODO: add CloseGuard to this, verifying callers call finish.
*
@@ -2325,13 +2187,11 @@ public final class StrictMode {
}
/**
- * For code to note that it's slow. This is a no-op unless the
- * current thread's {@link android.os.StrictMode.ThreadPolicy} has
- * {@link android.os.StrictMode.ThreadPolicy.Builder#detectCustomSlowCalls}
- * enabled.
+ * For code to note that it's slow. This is a no-op unless the current thread's {@link
+ * android.os.StrictMode.ThreadPolicy} has {@link
+ * android.os.StrictMode.ThreadPolicy.Builder#detectCustomSlowCalls} enabled.
*
- * @param name a short string for the exception stack trace that's
- * built if when this fires.
+ * @param name a short string for the exception stack trace that's built if when this fires.
*/
public static void noteSlowCall(String name) {
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
@@ -2343,14 +2203,11 @@ public final class StrictMode {
}
/**
- * For code to note that a resource was obtained using a type other than
- * its defined type. This is a no-op unless the current thread's
- * {@link android.os.StrictMode.ThreadPolicy} has
- * {@link android.os.StrictMode.ThreadPolicy.Builder#detectResourceMismatches()}
- * enabled.
+ * For code to note that a resource was obtained using a type other than its defined type. This
+ * is a no-op unless the current thread's {@link android.os.StrictMode.ThreadPolicy} has {@link
+ * android.os.StrictMode.ThreadPolicy.Builder#detectResourceMismatches()} enabled.
*
- * @param tag an object for the exception stack trace that's
- * built if when this fires.
+ * @param tag an object for the exception stack trace that's built if when this fires.
* @hide
*/
public static void noteResourceMismatch(Object tag) {
@@ -2362,9 +2219,7 @@ public final class StrictMode {
((AndroidBlockGuardPolicy) policy).onResourceMismatch(tag);
}
- /**
- * @hide
- */
+ /** @hide */
public static void noteUnbufferedIO() {
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
if (!(policy instanceof AndroidBlockGuardPolicy)) {
@@ -2374,9 +2229,7 @@ public final class StrictMode {
((AndroidBlockGuardPolicy) policy).onUnbufferedIO();
}
- /**
- * @hide
- */
+ /** @hide */
public static void noteDiskRead() {
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
if (!(policy instanceof AndroidBlockGuardPolicy)) {
@@ -2386,9 +2239,7 @@ public final class StrictMode {
((AndroidBlockGuardPolicy) policy).onReadFromDisk();
}
- /**
- * @hide
- */
+ /** @hide */
public static void noteDiskWrite() {
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
if (!(policy instanceof AndroidBlockGuardPolicy)) {
@@ -2403,17 +2254,16 @@ public final class StrictMode {
new HashMap<Class, Integer>();
/**
- * Returns an object that is used to track instances of activites.
- * The activity should store a reference to the tracker object in one of its fields.
+ * Returns an object that is used to track instances of activites. The activity should store a
+ * reference to the tracker object in one of its fields.
+ *
* @hide
*/
public static Object trackActivity(Object instance) {
return new InstanceTracker(instance);
}
- /**
- * @hide
- */
+ /** @hide */
public static void incrementExpectedActivityCount(Class klass) {
if (klass == null) {
return;
@@ -2430,9 +2280,7 @@ public final class StrictMode {
}
}
- /**
- * @hide
- */
+ /** @hide */
public static void decrementExpectedActivityCount(Class klass) {
if (klass == null) {
return;
@@ -2483,70 +2331,49 @@ public final class StrictMode {
}
/**
- * Parcelable that gets sent in Binder call headers back to callers
- * to report violations that happened during a cross-process call.
+ * Parcelable that gets sent in Binder call headers back to callers to report violations that
+ * happened during a cross-process call.
*
* @hide
*/
public static class ViolationInfo implements Parcelable {
public final String message;
- /**
- * Stack and other stuff info.
- */
+ /** Stack and other stuff info. */
public final ApplicationErrorReport.CrashInfo crashInfo;
- /**
- * The strict mode policy mask at the time of violation.
- */
+ /** The strict mode policy mask at the time of violation. */
public final int policy;
- /**
- * The wall time duration of the violation, when known. -1 when
- * not known.
- */
+ /** The wall time duration of the violation, when known. -1 when not known. */
public int durationMillis = -1;
- /**
- * The number of animations currently running.
- */
+ /** The number of animations currently running. */
public int numAnimationsRunning = 0;
- /**
- * List of tags from active Span instances during this
- * violation, or null for none.
- */
+ /** List of tags from active Span instances during this violation, or null for none. */
public String[] tags;
/**
- * Which violation number this was (1-based) since the last Looper loop,
- * from the perspective of the root caller (if it crossed any processes
- * via Binder calls). The value is 0 if the root caller wasn't on a Looper
- * thread.
+ * Which violation number this was (1-based) since the last Looper loop, from the
+ * perspective of the root caller (if it crossed any processes via Binder calls). The value
+ * is 0 if the root caller wasn't on a Looper thread.
*/
public int violationNumThisLoop;
- /**
- * The time (in terms of SystemClock.uptimeMillis()) that the
- * violation occurred.
- */
+ /** The time (in terms of SystemClock.uptimeMillis()) that the violation occurred. */
public long violationUptimeMillis;
/**
- * The action of the Intent being broadcast to somebody's onReceive
- * on this thread right now, or null.
+ * The action of the Intent being broadcast to somebody's onReceive on this thread right
+ * now, or null.
*/
public String broadcastIntentAction;
- /**
- * If this is a instance count violation, the number of instances in memory,
- * else -1.
- */
+ /** If this is a instance count violation, the number of instances in memory, else -1. */
public long numInstances = -1;
- /**
- * Create an uninitialized instance of ViolationInfo
- */
+ /** Create an uninitialized instance of ViolationInfo */
public ViolationInfo() {
message = null;
crashInfo = null;
@@ -2557,9 +2384,7 @@ public final class StrictMode {
this(null, tr, policy);
}
- /**
- * Create an instance of ViolationInfo initialized from an exception.
- */
+ /** Create an instance of ViolationInfo initialized from an exception. */
public ViolationInfo(String message, Throwable tr, int policy) {
this.message = message;
crashInfo = new ApplicationErrorReport.CrashInfo(tr);
@@ -2612,9 +2437,7 @@ public final class StrictMode {
return result;
}
- /**
- * Create an instance of ViolationInfo initialized from a Parcel.
- */
+ /** Create an instance of ViolationInfo initialized from a Parcel. */
public ViolationInfo(Parcel in) {
this(in, false);
}
@@ -2622,8 +2445,8 @@ public final class StrictMode {
/**
* Create an instance of ViolationInfo initialized from a Parcel.
*
- * @param unsetGatheringBit if true, the caller is the root caller
- * and the gathering penalty should be removed.
+ * @param unsetGatheringBit if true, the caller is the root caller and the gathering penalty
+ * should be removed.
*/
public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
message = in.readString();
@@ -2647,9 +2470,7 @@ public final class StrictMode {
tags = in.readStringArray();
}
- /**
- * Save a ViolationInfo instance to a parcel.
- */
+ /** Save a ViolationInfo instance to a parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(message);
@@ -2668,23 +2489,29 @@ public final class StrictMode {
dest.writeLong(numInstances);
dest.writeString(broadcastIntentAction);
dest.writeStringArray(tags);
- int total = dest.dataPosition()-start;
- if (Binder.CHECK_PARCEL_SIZE && total > 10*1024) {
- Slog.d(TAG, "VIO: policy=" + policy + " dur=" + durationMillis
- + " numLoop=" + violationNumThisLoop
- + " anim=" + numAnimationsRunning
- + " uptime=" + violationUptimeMillis
- + " numInst=" + numInstances);
+ int total = dest.dataPosition() - start;
+ if (Binder.CHECK_PARCEL_SIZE && total > 10 * 1024) {
+ Slog.d(
+ TAG,
+ "VIO: policy="
+ + policy
+ + " dur="
+ + durationMillis
+ + " numLoop="
+ + violationNumThisLoop
+ + " anim="
+ + numAnimationsRunning
+ + " uptime="
+ + violationUptimeMillis
+ + " numInst="
+ + numInstances);
Slog.d(TAG, "VIO: action=" + broadcastIntentAction);
Slog.d(TAG, "VIO: tags=" + Arrays.toString(tags));
- Slog.d(TAG, "VIO: TOTAL BYTES WRITTEN: " + (dest.dataPosition()-start));
+ Slog.d(TAG, "VIO: TOTAL BYTES WRITTEN: " + (dest.dataPosition() - start));
}
}
-
- /**
- * Dump a ViolationInfo instance to a Printer.
- */
+ /** Dump a ViolationInfo instance to a Printer. */
public void dump(Printer pw, String prefix) {
if (crashInfo != null) {
crashInfo.dump(pw, prefix);
@@ -2743,8 +2570,8 @@ public final class StrictMode {
final int mLimit;
private static final StackTraceElement[] FAKE_STACK = {
- new StackTraceElement("android.os.StrictMode", "setClassInstanceLimit",
- "StrictMode.java", 1)
+ new StackTraceElement(
+ "android.os.StrictMode", "setClassInstanceLimit", "StrictMode.java", 1)
};
public InstanceCountViolation(Class klass, long instances, int limit) {
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index fe9e8c67e566..da0ed54e003e 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -149,14 +149,43 @@ public abstract class VibrationEffect implements Parcelable {
* provide a better experience than you could otherwise build using the generic building
* blocks.
*
+ * This will fallback to a generic pattern if one exists and there does not exist a
+ * hardware-specific implementation of the effect.
+ *
* @param effectId The ID of the effect to perform:
- * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}.
+ * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
*
* @return The desired effect.
* @hide
*/
public static VibrationEffect get(int effectId) {
- VibrationEffect effect = new Prebaked(effectId);
+ return get(effectId, true);
+ }
+
+ /**
+ * Get a predefined vibration effect.
+ *
+ * Predefined effects are a set of common vibration effects that should be identical, regardless
+ * of the app they come from, in order to provide a cohesive experience for users across
+ * the entire device. They also may be custom tailored to the device hardware in order to
+ * provide a better experience than you could otherwise build using the generic building
+ * blocks.
+ *
+ * Some effects you may only want to play if there's a hardware specific implementation because
+ * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
+ * parameter allows you to decide whether you want to fallback to the generic implementation or
+ * only play if there's a tuned, hardware specific one available.
+ *
+ * @param effectId The ID of the effect to perform:
+ * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
+ * @param fallback Whether to fallback to a generic pattern if a hardware specific
+ * implementation doesn't exist.
+ *
+ * @return The desired effect.
+ * @hide
+ */
+ public static VibrationEffect get(int effectId, boolean fallback) {
+ VibrationEffect effect = new Prebaked(effectId, fallback);
effect.validate();
return effect;
}
@@ -374,19 +403,29 @@ public abstract class VibrationEffect implements Parcelable {
/** @hide */
public static class Prebaked extends VibrationEffect implements Parcelable {
private int mEffectId;
+ private boolean mFallback;
public Prebaked(Parcel in) {
- this(in.readInt());
+ this(in.readInt(), in.readByte() != 0);
}
- public Prebaked(int effectId) {
+ public Prebaked(int effectId, boolean fallback) {
mEffectId = effectId;
+ mFallback = fallback;
}
public int getId() {
return mEffectId;
}
+ /**
+ * Whether the effect should fall back to a generic pattern if there's no hardware specific
+ * implementation of it.
+ */
+ public boolean shouldFallback() {
+ return mFallback;
+ }
+
@Override
public void validate() {
switch (mEffectId) {
@@ -406,7 +445,7 @@ public abstract class VibrationEffect implements Parcelable {
return false;
}
VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
- return mEffectId == other.mEffectId;
+ return mEffectId == other.mEffectId && mFallback == other.mFallback;
}
@Override
@@ -416,7 +455,7 @@ public abstract class VibrationEffect implements Parcelable {
@Override
public String toString() {
- return "Prebaked{mEffectId=" + mEffectId + "}";
+ return "Prebaked{mEffectId=" + mEffectId + ", mFallback=" + mFallback + "}";
}
@@ -424,6 +463,7 @@ public abstract class VibrationEffect implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
out.writeInt(PARCEL_TOKEN_EFFECT);
out.writeInt(mEffectId);
+ out.writeByte((byte) (mFallback ? 1 : 0));
}
public static final Parcelable.Creator<Prebaked> CREATOR =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index fa523f36a113..27e399c1fcd6 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9334,6 +9334,25 @@ public final class Settings {
public static final String ANOMALY_DETECTION_CONSTANTS = "anomaly_detection_constants";
/**
+ * Always on display(AOD) specific settings
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * "prox_screen_off_delay=10000,screen_brightness_array=0:1:2:3:4"
+ *
+ * The following keys are supported:
+ *
+ * <pre>
+ * screen_brightness_array (string)
+ * dimming_scrim_array (string)
+ * prox_screen_off_delay (long)
+ * prox_cooldown_trigger (long)
+ * prox_cooldown_period (long)
+ * </pre>
+ * @hide
+ */
+ public static final String ALWAYS_ON_DISPLAY_CONSTANTS = "always_on_display_constants";
+
+ /**
* App standby (app idle) specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
*
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index f7dc1c58ade1..60c1c9a7e87a 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -81,10 +81,13 @@ public final class FillEventHistory implements Parcelable {
/**
* Returns the client state set in the previous {@link FillResponse}.
*
- * <p><b>NOTE: </b>the state is associated with the app that was autofilled in the previous
+ * <p><b>Note: </b>the state is associated with the app that was autofilled in the previous
* {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
* , which is not necessary the same app being autofilled now.
+ *
+ * @deprecated use {@link #getEvents()} then {@link Event#getClientState()} instead.
*/
+ @Deprecated
@Nullable public Bundle getClientState() {
return mClientState;
}
@@ -126,7 +129,6 @@ public final class FillEventHistory implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeBundle(mClientState);
-
if (mEvents == null) {
dest.writeInt(0);
} else {
@@ -137,6 +139,7 @@ public final class FillEventHistory implements Parcelable {
Event event = mEvents.get(i);
dest.writeInt(event.getType());
dest.writeString(event.getDatasetId());
+ dest.writeBundle(event.getClientState());
}
}
}
@@ -177,6 +180,7 @@ public final class FillEventHistory implements Parcelable {
@EventIds private final int mEventType;
@Nullable private final String mDatasetId;
+ @Nullable private final Bundle mClientState;
/**
* Returns the type of the event.
@@ -197,18 +201,32 @@ public final class FillEventHistory implements Parcelable {
}
/**
+ * Returns the client state from the {@link FillResponse} used to generate this event.
+ *
+ * <p><b>Note: </b>the state is associated with the app that was autofilled in the previous
+ * {@link
+ * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)},
+ * which is not necessary the same app being autofilled now.
+ */
+ @Nullable public Bundle getClientState() {
+ return mClientState;
+ }
+
+ /**
* Creates a new event.
*
* @param eventType The type of the event
* @param datasetId The dataset the event was on, or {@code null} if the event was on the
* whole response.
+ * @param clientState The client state associated with the event.
*
* @hide
*/
- public Event(int eventType, String datasetId) {
+ public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState) {
mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_SAVE_SHOWN,
"eventType");
mDatasetId = datasetId;
+ mClientState = clientState;
}
}
@@ -220,7 +238,8 @@ public final class FillEventHistory implements Parcelable {
int numEvents = parcel.readInt();
for (int i = 0; i < numEvents; i++) {
- selection.addEvent(new Event(parcel.readInt(), parcel.readString()));
+ selection.addEvent(new Event(parcel.readInt(), parcel.readString(),
+ parcel.readBundle()));
}
return selection;
diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java
index 44019c32560d..da7387fcae70 100644
--- a/core/java/android/util/ExceptionUtils.java
+++ b/core/java/android/util/ExceptionUtils.java
@@ -78,4 +78,12 @@ public class ExceptionUtils {
propagateIfInstanceOf(t, RuntimeException.class);
throw new RuntimeException(t);
}
+
+ /**
+ * Gets the root {@link Throwable#getCause() cause} of {@code t}
+ */
+ public static @NonNull Throwable getRootCause(@NonNull Throwable t) {
+ while (t.getCause() != null) t = t.getCause();
+ return t;
+ }
}
diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java
new file mode 100644
index 000000000000..0be1a8cfabae
--- /dev/null
+++ b/core/java/android/util/StatsLog.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+/**
+ * Logging access for platform metrics.
+ *
+ * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})!
+ * These diagnostic stats are for system integrators, not application authors.
+ *
+ * <p>Stats use integer tag codes.
+ * They carry a payload of one or more int, long, or String values.
+ * @hide
+ */
+public class StatsLog {
+ /** @hide */ public StatsLog() {}
+
+ private static final String TAG = "StatsLog";
+
+ // We assume that the native methods deal with any concurrency issues.
+
+ /**
+ * Records an stats log message.
+ * @param tag The stats type tag code
+ * @param value A value to log
+ * @return The number of bytes written
+ */
+ public static native int writeInt(int tag, int value);
+
+ /**
+ * Records an stats log message.
+ * @param tag The stats type tag code
+ * @param value A value to log
+ * @return The number of bytes written
+ */
+ public static native int writeLong(int tag, long value);
+
+ /**
+ * Records an stats log message.
+ * @param tag The stats type tag code
+ * @param value A value to log
+ * @return The number of bytes written
+ */
+ public static native int writeFloat(int tag, float value);
+
+ /**
+ * Records an stats log message.
+ * @param tag The stats type tag code
+ * @param str A value to log
+ * @return The number of bytes written
+ */
+ public static native int writeString(int tag, String str);
+
+ /**
+ * Records an stats log message.
+ * @param tag The stats type tag code
+ * @param list A list of values to log. All values should
+ * be of type int, long, float or String.
+ * @return The number of bytes written
+ */
+ public static native int writeArray(int tag, Object... list);
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ffb3203688eb..e5bd5ac076f3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7621,6 +7621,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <li>Call
* {@link android.view.autofill.AutofillManager#notifyValueChanged(View, int, AutofillValue)}
* when the value of a virtual child changed.
+ * <li>Call {@link
+ * android.view.autofill.AutofillManager#notifyViewVisibilityChanged(View, int, boolean)}
+ * when the visibility of a virtual child changed.
* <li>Call {@link AutofillManager#commit()} when the autofill context of the view structure
* changed and the current context should be committed (for example, when the user tapped
* a {@code SUBMIT} button in an HTML page).
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index bd94fc7b2112..8ea0242b3549 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1807,7 +1807,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
mFloatingActionMode.finish();
}
cleanupFloatingActionModeViews();
- mFloatingToolbar = new FloatingToolbar(mContext, mWindow);
+ mFloatingToolbar = new FloatingToolbar(mWindow);
final FloatingActionMode mode =
new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar);
mFloatingActionModeOriginatingView = originatingView;
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 1d56e1ad3e03..f63b5a213528 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -120,8 +120,10 @@ public final class FloatingToolbar {
/**
* Initializes a floating toolbar.
*/
- public FloatingToolbar(Context context, Window window) {
- mContext = applyDefaultTheme(Preconditions.checkNotNull(context));
+ public FloatingToolbar(Window window) {
+ // TODO(b/65172902): Pass context in constructor when DecorView (and other callers)
+ // supports multi-display.
+ mContext = applyDefaultTheme(window.getContext());
mWindow = Preconditions.checkNotNull(window);
mPopup = new FloatingToolbarPopup(mContext, window.getDecorView());
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index c62934100540..d63e22c189f8 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -103,6 +103,7 @@ cc_library_shared {
"android_nio_utils.cpp",
"android_util_AssetManager.cpp",
"android_util_Binder.cpp",
+ "android_util_StatsLog.cpp",
"android_util_EventLog.cpp",
"android_util_MemoryIntArray.cpp",
"android_util_Log.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 5afd06750601..02c9848ea149 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -111,6 +111,7 @@ namespace android {
extern int register_android_app_admin_SecurityLog(JNIEnv* env);
extern int register_android_content_AssetManager(JNIEnv* env);
extern int register_android_util_EventLog(JNIEnv* env);
+extern int register_android_util_StatsLog(JNIEnv* env);
extern int register_android_util_Log(JNIEnv* env);
extern int register_android_util_MemoryIntArray(JNIEnv* env);
extern int register_android_util_PathParser(JNIEnv* env);
@@ -1311,6 +1312,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),
REG_JNI(register_android_os_SystemClock),
REG_JNI(register_android_util_EventLog),
+ REG_JNI(register_android_util_StatsLog),
REG_JNI(register_android_util_Log),
REG_JNI(register_android_util_MemoryIntArray),
REG_JNI(register_android_util_PathParser),
diff --git a/core/jni/android_util_StatsLog.cpp b/core/jni/android_util_StatsLog.cpp
new file mode 100644
index 000000000000..c992365094f7
--- /dev/null
+++ b/core/jni/android_util_StatsLog.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2007-2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <log/log_event_list.h>
+
+#include <log/log.h>
+
+#include <nativehelper/JNIHelp.h>
+#include "core_jni_helpers.h"
+#include "jni.h"
+
+#define UNUSED __attribute__((__unused__))
+
+namespace android {
+
+static jclass gCollectionClass;
+static jmethodID gCollectionAddID;
+
+static jclass gIntegerClass;
+static jfieldID gIntegerValueID;
+
+static jclass gLongClass;
+static jfieldID gLongValueID;
+
+static jclass gFloatClass;
+static jfieldID gFloatValueID;
+
+static jclass gStringClass;
+
+/*
+ * In class android.util.StatsLog:
+ * static native int writeInt(int tag, int value)
+ */
+static jint android_util_StatsLog_write_Integer(JNIEnv* env UNUSED,
+ jobject clazz UNUSED,
+ jint tag, jint value)
+{
+ android_log_event_list ctx(tag);
+ ctx << (int32_t)value;
+ return ctx.write(LOG_ID_STATS);
+}
+
+/*
+ * In class android.util.StatsLog:
+ * static native int writeLong(long tag, long value)
+ */
+static jint android_util_StatsLog_write_Long(JNIEnv* env UNUSED,
+ jobject clazz UNUSED,
+ jint tag, jlong value)
+{
+ android_log_event_list ctx(tag);
+ ctx << (int64_t)value;
+ return ctx.write(LOG_ID_STATS);
+}
+
+/*
+ * In class android.util.StatsLog:
+ * static native int writeFloat(long tag, float value)
+ */
+static jint android_util_StatsLog_write_Float(JNIEnv* env UNUSED,
+ jobject clazz UNUSED,
+ jint tag, jfloat value)
+{
+ android_log_event_list ctx(tag);
+ ctx << (float)value;
+ return ctx.write(LOG_ID_STATS);
+}
+
+/*
+ * In class android.util.StatsLog:
+ * static native int writeString(int tag, String value)
+ */
+static jint android_util_StatsLog_write_String(JNIEnv* env,
+ jobject clazz UNUSED,
+ jint tag, jstring value) {
+ android_log_event_list ctx(tag);
+ // Don't throw NPE -- I feel like it's sort of mean for a logging function
+ // to be all crashy if you pass in NULL -- but make the NULL value explicit.
+ if (value != NULL) {
+ const char *str = env->GetStringUTFChars(value, NULL);
+ ctx << str;
+ env->ReleaseStringUTFChars(value, str);
+ } else {
+ ctx << "NULL";
+ }
+ return ctx.write(LOG_ID_STATS);
+}
+
+/*
+ * In class android.util.StatsLog:
+ * static native int writeArray(long tag, Object... value)
+ */
+static jint android_util_StatsLog_write_Array(JNIEnv* env, jobject clazz,
+ jint tag, jobjectArray value) {
+ android_log_event_list ctx(tag);
+
+ if (value == NULL) {
+ ctx << "[NULL]";
+ return ctx.write(LOG_ID_STATS);
+ }
+
+ jsize copied = 0, num = env->GetArrayLength(value);
+ for (; copied < num && copied < 255; ++copied) {
+ if (ctx.status()) break;
+ jobject item = env->GetObjectArrayElement(value, copied);
+ if (item == NULL) {
+ ctx << "NULL";
+ } else if (env->IsInstanceOf(item, gStringClass)) {
+ const char *str = env->GetStringUTFChars((jstring) item, NULL);
+ ctx << str;
+ env->ReleaseStringUTFChars((jstring) item, str);
+ } else if (env->IsInstanceOf(item, gIntegerClass)) {
+ ctx << (int32_t)env->GetIntField(item, gIntegerValueID);
+ } else if (env->IsInstanceOf(item, gLongClass)) {
+ ctx << (int64_t)env->GetLongField(item, gLongValueID);
+ } else if (env->IsInstanceOf(item, gFloatClass)) {
+ ctx << (float)env->GetFloatField(item, gFloatValueID);
+ } else {
+ jniThrowException(env,
+ "java/lang/IllegalArgumentException",
+ "Invalid payload item type");
+ return -1;
+ }
+ env->DeleteLocalRef(item);
+ }
+ return ctx.write(LOG_ID_STATS);
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gRegisterMethods[] = {
+ /* name, signature, funcPtr */
+ { "writeInt", "(II)I", (void*) android_util_StatsLog_write_Integer },
+ { "writeLong", "(IJ)I", (void*) android_util_StatsLog_write_Long },
+ { "writeFloat", "(IF)I", (void*) android_util_StatsLog_write_Float },
+ { "writeString",
+ "(ILjava/lang/String;)I",
+ (void*) android_util_StatsLog_write_String
+ },
+ { "writeArray",
+ "(I[Ljava/lang/Object;)I",
+ (void*) android_util_StatsLog_write_Array
+ },
+};
+
+static struct { const char *name; jclass *clazz; } gClasses[] = {
+ { "java/lang/Integer", &gIntegerClass },
+ { "java/lang/Long", &gLongClass },
+ { "java/lang/Float", &gFloatClass },
+ { "java/lang/String", &gStringClass },
+ { "java/util/Collection", &gCollectionClass },
+};
+
+static struct { jclass *c; const char *name, *ft; jfieldID *id; } gFields[] = {
+ { &gIntegerClass, "value", "I", &gIntegerValueID },
+ { &gLongClass, "value", "J", &gLongValueID },
+ { &gFloatClass, "value", "F", &gFloatValueID },
+};
+
+static struct { jclass *c; const char *name, *mt; jmethodID *id; } gMethods[] = {
+ { &gCollectionClass, "add", "(Ljava/lang/Object;)Z", &gCollectionAddID },
+};
+
+int register_android_util_StatsLog(JNIEnv* env) {
+ for (int i = 0; i < NELEM(gClasses); ++i) {
+ jclass clazz = FindClassOrDie(env, gClasses[i].name);
+ *gClasses[i].clazz = MakeGlobalRefOrDie(env, clazz);
+ }
+
+ for (int i = 0; i < NELEM(gFields); ++i) {
+ *gFields[i].id = GetFieldIDOrDie(env,
+ *gFields[i].c, gFields[i].name, gFields[i].ft);
+ }
+
+ for (int i = 0; i < NELEM(gMethods); ++i) {
+ *gMethods[i].id = GetMethodIDOrDie(env,
+ *gMethods[i].c, gMethods[i].name, gMethods[i].mt);
+ }
+
+ return RegisterMethodsOrDie(
+ env,
+ "android/util/StatsLog",
+ gRegisterMethods, NELEM(gRegisterMethods));
+}
+
+}; // namespace android
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index b41e892adae9..8a57ea9a9c13 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -99,6 +99,7 @@ public class SettingsBackupTest {
Settings.Global.ALARM_MANAGER_CONSTANTS,
Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED,
Settings.Global.ALWAYS_FINISH_ACTIVITIES,
+ Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS,
Settings.Global.ANIMATOR_DURATION_SCALE,
Settings.Global.ANOMALY_DETECTION_CONSTANTS,
Settings.Global.APN_DB_UPDATE_CONTENT_URL,
diff --git a/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java
index bdd828fd5127..c4bf9d3123bf 100644
--- a/graphics/java/android/graphics/Color.java
+++ b/graphics/java/android/graphics/Color.java
@@ -73,7 +73,7 @@ import java.util.function.DoubleUnaryOperator;
* <h4>Encoding</h4>
* <p>The four components of a color int are encoded in the following way:</p>
* <pre class="prettyprint">
- * int color = (A & 0xff) << 24 | (R & 0xff) << 16 | (G & 0xff) << 16 | (B & 0xff);
+ * int color = (A & 0xff) << 24 | (R & 0xff) << 16 | (G & 0xff) << 8 | (B & 0xff);
* </pre>
*
* <p>Because of this encoding, color ints can easily be described as an integer
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 4e59baa48983..3c3b3177159b 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -216,10 +216,7 @@ void BakedOpRenderer::drawRects(const float* rects, int count, const SkPaint* pa
.setTransform(Matrix4::identity(), TransformFlags::None)
.setModelViewIdentityEmptyBounds()
.build();
- // Disable blending if this is the first draw to the main framebuffer, in case app has defined
- // transparency where it doesn't make sense - as first draw in opaque window.
- bool overrideDisableBlending = !mHasDrawn && mOpaque && !mRenderTarget.frameBufferId;
- mRenderState.render(glop, mRenderTarget.orthoMatrix, overrideDisableBlending);
+ mRenderState.render(glop, mRenderTarget.orthoMatrix, false);
mHasDrawn = true;
}
@@ -350,8 +347,14 @@ void BakedOpRenderer::renderGlopImpl(const Rect* dirtyBounds, const ClipBase* cl
const Glop& glop) {
prepareRender(dirtyBounds, clip);
// Disable blending if this is the first draw to the main framebuffer, in case app has defined
- // transparency where it doesn't make sense - as first draw in opaque window.
- bool overrideDisableBlending = !mHasDrawn && mOpaque && !mRenderTarget.frameBufferId;
+ // transparency where it doesn't make sense - as first draw in opaque window. Note that we only
+ // apply this improvement when the blend mode is SRC_OVER - other modes (e.g. CLEAR) can be
+ // valid draws that affect other content (e.g. draw CLEAR, then draw DST_OVER)
+ bool overrideDisableBlending = !mHasDrawn
+ && mOpaque
+ && !mRenderTarget.frameBufferId
+ && glop.blend.src == GL_ONE
+ && glop.blend.dst == GL_ONE_MINUS_SRC_ALPHA;
mRenderState.render(glop, mRenderTarget.orthoMatrix, overrideDisableBlending);
if (!mRenderTarget.frameBufferId) mHasDrawn = true;
}
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index f4ce864e83e1..e0373cae9923 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -511,25 +511,19 @@ void Tree::updateCache(sp<skiapipeline::VectorDrawableAtlas>& atlas, GrContext*
}
}
if (!canReuseSurface || mCache.dirty) {
- draw(surface.get(), dst);
+ if (surface) {
+ Bitmap& bitmap = getBitmapUpdateIfDirty();
+ SkBitmap skiaBitmap;
+ bitmap.getSkBitmap(&skiaBitmap);
+ if (!surface->getCanvas()->writePixels(skiaBitmap, dst.fLeft, dst.fTop)) {
+ ALOGD("VectorDrawable caching failed to efficiently upload");
+ surface->getCanvas()->drawBitmap(skiaBitmap, dst.fLeft, dst.fTop);
+ }
+ }
mCache.dirty = false;
}
}
-void Tree::draw(SkSurface* surface, const SkRect& dst) {
- if (surface) {
- SkCanvas* canvas = surface->getCanvas();
- float scaleX = dst.width() / mProperties.getViewportWidth();
- float scaleY = dst.height() / mProperties.getViewportHeight();
- SkAutoCanvasRestore acr(canvas, true);
- canvas->translate(dst.fLeft, dst.fTop);
- canvas->clipRect(SkRect::MakeWH(dst.width(), dst.height()));
- canvas->clear(SK_ColorTRANSPARENT);
- canvas->scale(scaleX, scaleY);
- mRootNode->draw(canvas, false);
- }
-}
-
void Tree::Cache::setAtlas(sp<skiapipeline::VectorDrawableAtlas> newAtlas,
skiapipeline::AtlasKey newAtlasKey) {
LOG_ALWAYS_FATAL_IF(newAtlasKey == INVALID_ATLAS_KEY);
@@ -570,22 +564,15 @@ void Tree::draw(SkCanvas* canvas) {
// Handle the case when VectorDrawableAtlas has been destroyed, because of memory pressure.
// We render the VD into a temporary standalone buffer and mark the frame as dirty. Next
// frame will be cached into the atlas.
+ Bitmap& bitmap = getBitmapUpdateIfDirty();
+ SkBitmap skiaBitmap;
+ bitmap.getSkBitmap(&skiaBitmap);
+
int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
- SkRect src = SkRect::MakeWH(scaledWidth, scaledHeight);
-#ifndef ANDROID_ENABLE_LINEAR_BLENDING
- sk_sp<SkColorSpace> colorSpace = nullptr;
-#else
- sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB();
-#endif
- SkImageInfo info = SkImageInfo::MakeN32(scaledWidth, scaledHeight, kPremul_SkAlphaType,
- colorSpace);
- sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(canvas->getGrContext(),
- SkBudgeted::kYes, info);
- draw(surface.get(), src);
+ canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight),
+ mutateProperties()->getBounds(), getPaint(), SkCanvas::kFast_SrcRectConstraint);
mCache.clear();
- canvas->drawImageRect(surface->makeImageSnapshot().get(), mutateProperties()->getBounds(),
- getPaint(), SkCanvas::kFast_SrcRectConstraint);
markDirty();
}
}
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index efbb695a14dd..10d3e05c067f 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -738,11 +738,6 @@ private:
bool canReuseBitmap(Bitmap*, int width, int height);
void updateBitmapCache(Bitmap& outCache, bool useStagingData);
- /**
- * Draws the root node into "surface" at a given "dst" position.
- */
- void draw(SkSurface* surface, const SkRect& dst);
-
// Cap the bitmap size, such that it won't hurt the performance too much
// and it won't crash due to a very large scale.
// The drawable will look blurry above this size.
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index a8463ecc44d8..742f14d04db4 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -118,6 +118,8 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers,
return;
}
+ ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(), bounds.height());
+
layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
layerCanvas->clear(SK_ColorTRANSPARENT);
@@ -143,7 +145,6 @@ bool SkiaPipeline::createOrUpdateLayer(RenderNode* node,
}
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
- // TODO: Handle wide color gamut requests
node->setLayerSurface(
SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes,
info, 0, &props));
@@ -194,10 +195,10 @@ void SkiaPipeline::renderVectorDrawableCache() {
sp<VectorDrawableAtlas> atlas = mRenderThread.cacheManager().acquireVectorDrawableAtlas();
auto grContext = mRenderThread.getGrContext();
atlas->prepareForDraw(grContext);
+ ATRACE_NAME("Update VectorDrawables");
for (auto vd : mVectorDrawables) {
vd->updateCache(atlas, grContext);
}
- grContext->flush();
mVectorDrawables.clear();
}
}
diff --git a/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp b/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp
index 23969908ff4d..9c9e17d600bf 100644
--- a/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp
+++ b/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp
@@ -270,7 +270,10 @@ sk_sp<SkSurface> VectorDrawableAtlas::createSurface(int width, int height, GrCon
sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB();
#endif
SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace);
- return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info);
+ // This must have a top-left origin so that calls to surface->canvas->writePixels
+ // performs a basic texture upload instead of a more complex drawing operation
+ return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info, 0,
+ kTopLeft_GrSurfaceOrigin, nullptr);
}
void VectorDrawableAtlas::setStorageMode(StorageMode mode) {
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 3308fc929b03..dc7fa8c00f82 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -28,6 +28,7 @@ interface IMediaRouterService {
MediaRouterClientState getState(IMediaRouterClient client);
boolean isPlaybackActive(IMediaRouterClient client);
+ boolean isGlobalBluetoothA2doOn();
void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan);
void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit);
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 29b88a28294c..2894e8956c1c 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -88,7 +88,6 @@ public class MediaRouter {
RouteInfo mBluetoothA2dpRoute;
RouteInfo mSelectedRoute;
- RouteInfo mSystemAudioRoute;
final boolean mCanConfigureWifiDisplays;
boolean mActivelyScanningWifiDisplays;
@@ -150,7 +149,6 @@ public class MediaRouter {
}
addRouteStatic(mDefaultAudioVideo);
- mSystemAudioRoute = mDefaultAudioVideo;
// This will select the active wifi display route if there is one.
updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus());
@@ -185,7 +183,7 @@ public class MediaRouter {
}
void updateAudioRoutes(AudioRoutesInfo newRoutes) {
- boolean updated = false;
+ boolean audioRoutesChanged = false;
if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) {
mCurAudioRoutesInfo.mainType = newRoutes.mainType;
int name;
@@ -201,11 +199,10 @@ public class MediaRouter {
}
mDefaultAudioVideo.mNameResId = name;
dispatchRouteChanged(mDefaultAudioVideo);
- updated = true;
+ audioRoutesChanged = true;
}
final int mainType = mCurAudioRoutesInfo.mainType;
-
if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
if (mCurAudioRoutesInfo.bluetoothName != null) {
@@ -219,8 +216,6 @@ public class MediaRouter {
info.mDeviceType = RouteInfo.DEVICE_TYPE_BLUETOOTH;
mBluetoothA2dpRoute = info;
addRouteStatic(mBluetoothA2dpRoute);
- mSystemAudioRoute = mBluetoothA2dpRoute;
- selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mSystemAudioRoute, false);
} else {
mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.bluetoothName;
dispatchRouteChanged(mBluetoothA2dpRoute);
@@ -229,30 +224,32 @@ public class MediaRouter {
// BT disconnected
removeRouteStatic(mBluetoothA2dpRoute);
mBluetoothA2dpRoute = null;
- mSystemAudioRoute = mDefaultAudioVideo;
- selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mSystemAudioRoute, false);
- }
- updated = true;
- }
-
- if (mBluetoothA2dpRoute != null) {
- final boolean a2dpEnabled = isBluetoothA2dpOn();
- if (mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) {
- // A2DP off
- mSystemAudioRoute = mDefaultAudioVideo;
- updated = true;
- } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) &&
- a2dpEnabled) {
- // A2DP on or BT connected
- mSystemAudioRoute = mBluetoothA2dpRoute;
- updated = true;
}
+ audioRoutesChanged = true;
}
- if (updated) {
+
+ if (audioRoutesChanged) {
+ selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, getDefaultSystemAudioRoute(), false);
Log.v(TAG, "Audio routes updated: " + newRoutes + ", a2dp=" + isBluetoothA2dpOn());
}
}
+ RouteInfo getDefaultSystemAudioRoute() {
+ boolean globalBluetoothA2doOn = false;
+ try {
+ globalBluetoothA2doOn = mMediaRouterService.isGlobalBluetoothA2doOn();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to call isSystemBluetoothA2doOn.", ex);
+ }
+ return (globalBluetoothA2doOn && mBluetoothA2dpRoute != null)
+ ? mBluetoothA2dpRoute : mDefaultAudioVideo;
+ }
+
+ RouteInfo getCurrentSystemAudioRoute() {
+ return (isBluetoothA2dpOn() && mBluetoothA2dpRoute != null)
+ ? mBluetoothA2dpRoute : mDefaultAudioVideo;
+ }
+
boolean isBluetoothA2dpOn() {
try {
return mAudioService.isBluetoothA2dpOn();
@@ -603,15 +600,13 @@ public class MediaRouter {
@Override
public void onRestoreRoute() {
+ // Skip restoring route if the selected route is not a system audio route, or
+ // MediaRouter is initializing.
if ((mSelectedRoute != mDefaultAudioVideo && mSelectedRoute != mBluetoothA2dpRoute)
- || mSelectedRoute == mSystemAudioRoute) {
+ || mSelectedRoute == null) {
return;
}
- try {
- sStatic.mAudioService.setBluetoothA2dpOn(mSelectedRoute == mBluetoothA2dpRoute);
- } catch (RemoteException e) {
- Log.e(TAG, "Error changing Bluetooth A2DP state", e);
- }
+ mSelectedRoute.select();
}
}
}
@@ -946,7 +941,7 @@ public class MediaRouter {
boolean wasDefaultOrBluetoothRoute = (oldRoute == sStatic.mDefaultAudioVideo
|| oldRoute == sStatic.mBluetoothA2dpRoute);
if (oldRoute == route
- && (!wasDefaultOrBluetoothRoute || oldRoute == sStatic.mSystemAudioRoute)) {
+ && (!wasDefaultOrBluetoothRoute || route == sStatic.getCurrentSystemAudioRoute())) {
return;
}
if (!route.matchesTypes(types)) {
diff --git a/packages/SystemUI/res/drawable/car_qs_background_primary.xml b/packages/SystemUI/res/drawable/car_qs_background_primary.xml
deleted file mode 100644
index 0f77987bb7ce..000000000000
--- a/packages/SystemUI/res/drawable/car_qs_background_primary.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.
--->
-<inset xmlns:android="http://schemas.android.com/apk/res/android">
- <shape>
- <solid android:color="?android:attr/colorPrimaryDark"/>
- </shape>
-</inset>
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
index deb494fdefbc..59c0957d98cb 100644
--- a/packages/SystemUI/res/layout/battery_percentage_view.xml
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -26,4 +26,5 @@
android:textColor="?android:attr/textColorPrimary"
android:gravity="center_vertical|start"
android:paddingEnd="@dimen/battery_level_padding_start"
+ android:importantForAccessibility="no"
/>
diff --git a/packages/SystemUI/res/layout/car_qs_panel.xml b/packages/SystemUI/res/layout/car_qs_panel.xml
index 0b46b0bdaa1c..4cb0fd5fecfb 100644
--- a/packages/SystemUI/res/layout/car_qs_panel.xml
+++ b/packages/SystemUI/res/layout/car_qs_panel.xml
@@ -18,12 +18,13 @@
android:id="@+id/quick_settings_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/car_qs_background_primary"
+ android:background="@color/car_qs_background_primary"
android:orientation="vertical"
- android:elevation="4dp">
+ android:elevation="4dp"
+ android:theme="@android:style/Theme">
- <include layout="@layout/car_status_bar_header" />
- <include layout="@layout/car_qs_footer" />
+ <include layout="@layout/car_status_bar_header"/>
+ <include layout="@layout/car_qs_footer"/>
<com.android.systemui.statusbar.car.UserGridView
android:id="@+id/user_grid"
diff --git a/packages/SystemUI/res/values/colors_car.xml b/packages/SystemUI/res/values/colors_car.xml
index 9593fe51917c..1b8c2fa68244 100644
--- a/packages/SystemUI/res/values/colors_car.xml
+++ b/packages/SystemUI/res/values/colors_car.xml
@@ -17,6 +17,7 @@
*/
-->
<resources>
+ <color name="car_qs_background_primary">#263238</color> <!-- Blue Gray 900 -->
<color name="car_user_switcher_progress_bgcolor">#00000000</color> <!-- Transparent -->
<color name="car_user_switcher_progress_fgcolor">#80CBC4</color> <!-- Teal 200 -->
<color name="car_user_switcher_no_user_image_bgcolor">#FAFAFA</color> <!-- Grey 50 -->
diff --git a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
new file mode 100644
index 000000000000..d1d180819eef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.doze;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.Settings;
+import android.text.format.DateUtils;
+import android.util.KeyValueListParser;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+import java.util.Arrays;
+
+/**
+ * Class to store the policy for AOD, which comes from
+ * {@link android.provider.Settings.Global}
+ */
+public class AlwaysOnDisplayPolicy {
+ public static final String TAG = "AlwaysOnDisplayPolicy";
+
+ static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array";
+ static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array";
+ static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay";
+ static final String KEY_PROX_COOLDOWN_TRIGGER_MS = "prox_cooldown_trigger";
+ static final String KEY_PROX_COOLDOWN_PERIOD_MS = "prox_cooldown_period";
+
+ /**
+ * Integer array to map ambient brightness type to real screen brightness.
+ *
+ * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+ * @see #KEY_SCREEN_BRIGHTNESS_ARRAY
+ */
+ public final int[] screenBrightnessArray;
+
+ /**
+ * Integer array to map ambient brightness type to dimming scrim.
+ *
+ * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+ * @see #KEY_DIMMING_SCRIM_ARRAY
+ */
+ public final int[] dimmingScrimArray;
+
+ /**
+ * Delay time(ms) from covering the prox to turning off the screen.
+ *
+ * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+ * @see #KEY_PROX_SCREEN_OFF_DELAY_MS
+ */
+ public final long proxScreenOffDelayMs;
+
+ /**
+ * The threshold time(ms) to trigger the cooldown timer, which will
+ * turn off prox sensor for a period.
+ *
+ * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+ * @see #KEY_PROX_COOLDOWN_TRIGGER_MS
+ */
+ public final long proxCooldownTriggerMs;
+
+ /**
+ * The period(ms) to turning off the prox sensor if
+ * {@link #KEY_PROX_COOLDOWN_TRIGGER_MS} is triggered.
+ *
+ * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+ * @see #KEY_PROX_COOLDOWN_PERIOD_MS
+ */
+ public final long proxCooldownPeriodMs;
+
+ private final KeyValueListParser mParser;
+
+ public AlwaysOnDisplayPolicy(Context context) {
+ final Resources resources = context.getResources();
+ mParser = new KeyValueListParser(',');
+
+ final String value = Settings.Global.getString(context.getContentResolver(),
+ Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+
+ try {
+ mParser.setString(value);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Bad AOD constants");
+ }
+
+ proxScreenOffDelayMs = mParser.getLong(KEY_PROX_SCREEN_OFF_DELAY_MS,
+ 10 * DateUtils.MINUTE_IN_MILLIS);
+ proxCooldownTriggerMs = mParser.getLong(KEY_PROX_COOLDOWN_TRIGGER_MS,
+ 2 * DateUtils.MINUTE_IN_MILLIS);
+ proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
+ 5 * DateUtils.MINUTE_IN_MILLIS);
+ screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
+ resources.getIntArray(R.array.config_doze_brightness_sensor_to_brightness));
+ dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY,
+ resources.getIntArray(R.array.config_doze_brightness_sensor_to_scrim_opacity));
+ }
+
+ private int[] parseIntArray(final String key, final int[] defaultArray) {
+ final String value = mParser.getString(key, null);
+ if (value != null) {
+ return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+ Integer::parseInt).toArray();
+ } else {
+ return defaultArray;
+ }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index d374d68a456b..6f8bcff16a83 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -59,7 +59,7 @@ public class DozeFactory {
DozeMachine machine = new DozeMachine(wrappedService, config, wakeLock);
machine.setParts(new DozeMachine.Part[]{
- new DozePauser(handler, machine, alarmManager),
+ new DozePauser(handler, machine, alarmManager, new AlwaysOnDisplayPolicy(context)),
new DozeFalsingManagerAdapter(FalsingManager.getInstance(context)),
createDozeTriggers(context, sensorManager, host, alarmManager, config, params,
handler, wakeLock, machine),
@@ -76,7 +76,8 @@ public class DozeFactory {
Handler handler) {
Sensor sensor = DozeSensors.findSensorWithType(sensorManager,
context.getString(R.string.doze_brightness_sensor_type));
- return new DozeScreenBrightness(context, service, sensorManager, sensor, host, handler);
+ return new DozeScreenBrightness(context, service, sensorManager, sensor, host, handler,
+ new AlwaysOnDisplayPolicy(context));
}
private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java b/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java
index a33b454c6430..76a190213ba3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java
@@ -26,20 +26,22 @@ import com.android.systemui.util.AlarmTimeout;
*/
public class DozePauser implements DozeMachine.Part {
public static final String TAG = DozePauser.class.getSimpleName();
- private static final long TIMEOUT = 10 * 1000;
private final AlarmTimeout mPauseTimeout;
private final DozeMachine mMachine;
+ private final long mTimeoutMs;
- public DozePauser(Handler handler, DozeMachine machine, AlarmManager alarmManager) {
+ public DozePauser(Handler handler, DozeMachine machine, AlarmManager alarmManager,
+ AlwaysOnDisplayPolicy policy) {
mMachine = machine;
mPauseTimeout = new AlarmTimeout(alarmManager, this::onTimeout, TAG, handler);
+ mTimeoutMs = policy.proxScreenOffDelayMs;
}
@Override
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
switch (newState) {
case DOZE_AOD_PAUSING:
- mPauseTimeout.schedule(TIMEOUT, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+ mPauseTimeout.schedule(mTimeoutMs, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
break;
default:
mPauseTimeout.cancel();
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 30420529df56..11b4b0ef8294 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -42,7 +42,7 @@ public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListen
public DozeScreenBrightness(Context context, DozeMachine.Service service,
SensorManager sensorManager, Sensor lightSensor, DozeHost host,
- Handler handler) {
+ Handler handler, AlwaysOnDisplayPolicy policy) {
mContext = context;
mDozeService = service;
mSensorManager = sensorManager;
@@ -50,10 +50,8 @@ public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListen
mDozeHost = host;
mHandler = handler;
- mSensorToBrightness = context.getResources().getIntArray(
- R.array.config_doze_brightness_sensor_to_brightness);
- mSensorToScrimOpacity = context.getResources().getIntArray(
- R.array.config_doze_brightness_sensor_to_scrim_opacity);
+ mSensorToBrightness = policy.screenBrightnessArray;
+ mSensorToScrimOpacity = policy.dimmingScrimArray;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 566353c74b57..91cde378c41b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -72,7 +72,7 @@ public class DozeSensors {
public DozeSensors(Context context, AlarmManager alarmManager, SensorManager sensorManager,
DozeParameters dozeParameters,
AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback,
- Consumer<Boolean> proxCallback) {
+ Consumer<Boolean> proxCallback, AlwaysOnDisplayPolicy policy) {
mContext = context;
mAlarmManager = alarmManager;
mSensorManager = sensorManager;
@@ -112,7 +112,7 @@ public class DozeSensors {
true /* touchscreen */),
};
- mProxSensor = new ProxSensor();
+ mProxSensor = new ProxSensor(policy);
mCallback = callback;
}
@@ -206,17 +206,16 @@ public class DozeSensors {
private class ProxSensor implements SensorEventListener {
- static final long COOLDOWN_TRIGGER = 2 * 1000;
- static final long COOLDOWN_PERIOD = 5 * 1000;
-
boolean mRequested;
boolean mRegistered;
Boolean mCurrentlyFar;
long mLastNear;
final AlarmTimeout mCooldownTimer;
+ final AlwaysOnDisplayPolicy mPolicy;
- public ProxSensor() {
+ public ProxSensor(AlwaysOnDisplayPolicy policy) {
+ mPolicy = policy;
mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered,
"prox_cooldown", mHandler);
}
@@ -264,11 +263,12 @@ public class DozeSensors {
// Sensor has been unregistered by the proxCallback. Do nothing.
} else if (!mCurrentlyFar) {
mLastNear = now;
- } else if (mCurrentlyFar && now - mLastNear < COOLDOWN_TRIGGER) {
+ } else if (mCurrentlyFar && now - mLastNear < mPolicy.proxCooldownTriggerMs) {
// If the last near was very recent, we might be using more power for prox
// wakeups than we're saving from turning of the screen. Instead, turn it off
// for a while.
- mCooldownTimer.schedule(COOLDOWN_PERIOD, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+ mCooldownTimer.schedule(mPolicy.proxCooldownPeriodMs,
+ AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
updateRegistered();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 45831601a0f2..f7a258a2c959 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -84,7 +84,8 @@ public class DozeTriggers implements DozeMachine.Part {
mWakeLock = wakeLock;
mAllowPulseTriggers = allowPulseTriggers;
mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
- config, wakeLock, this::onSensor, this::onProximityFar);
+ config, wakeLock, this::onSensor, this::onProximityFar,
+ new AlwaysOnDisplayPolicy(context));
mUiModeManager = mContext.getSystemService(UiModeManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index c2a7ed3fdad4..aecf95fc677f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -132,6 +132,11 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// Preloads the next task
RecentsConfiguration config = Recents.getConfiguration();
if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
+ Rect windowRect = getWindowRect(null /* windowRectOverride */);
+ if (windowRect.isEmpty()) {
+ return;
+ }
+
// Load the next task only if we aren't svelte
SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
@@ -146,8 +151,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// This callback is made when a new activity is launched and the old one is
// paused so ignore the current activity and try and preload the thumbnail for
// the previous one.
- updateDummyStackViewLayout(mBackgroundLayoutAlgorithm, stack,
- getWindowRect(null /* windowRectOverride */));
+ updateDummyStackViewLayout(mBackgroundLayoutAlgorithm, stack, windowRect);
// Launched from app is always the worst case (in terms of how many
// thumbnails/tasks visible)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 46f9c04aa42e..afe5c917a856 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -404,8 +404,8 @@ public abstract class PanelView extends FrameLayout {
false /* collapseWhenFinished */);
notifyBarPanelExpansionChanged();
if (mVibrateOnOpening && !isHapticFeedbackDisabled(mContext)) {
- AsyncTask.execute(
- () -> mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_TICK)));
+ AsyncTask.execute(() ->
+ mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_TICK, false)));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 03f42a6f760d..d7f11f710501 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -422,7 +422,7 @@ public class StatusBarWindowView extends FrameLayout {
mFloatingActionMode.finish();
}
cleanupFloatingActionModeViews();
- mFloatingToolbar = new FloatingToolbar(mContext, mFakeWindow);
+ mFloatingToolbar = new FloatingToolbar(mFakeWindow);
final FloatingActionMode mode =
new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar);
mFloatingActionModeOriginatingView = originatingView;
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 5e71dd4684c5..27c16d53ce78 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -54,7 +54,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
SystemUI-proto \
SystemUI-tags \
legacy-android-test \
- testables
+ testables \
+ truth-prebuilt \
LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.car
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java
new file mode 100644
index 000000000000..abc2d0e5c845
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.doze;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.provider.Settings;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.format.DateUtils;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AlwaysOnDisplayPolicyTest extends SysuiTestCase {
+ private static final String ALWAYS_ON_DISPLAY_CONSTANTS_VALUE = "prox_screen_off_delay=1000"
+ + ",prox_cooldown_trigger=2000"
+ + ",prox_cooldown_period=3000"
+ + ",screen_brightness_array=1:2:3:4:5"
+ + ",dimming_scrim_array=5:4:3:2:1";
+
+ private String mPreviousConfig;
+
+ @Before
+ public void setUp() {
+ mPreviousConfig = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+ }
+
+ @After
+ public void tearDown() {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS, mPreviousConfig);
+ }
+
+ @Test
+ public void testPolicy_valueNull_containsDefaultValue() throws Exception {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS, null);
+
+ AlwaysOnDisplayPolicy policy = new AlwaysOnDisplayPolicy(mContext);
+
+ assertThat(policy.proxScreenOffDelayMs).isEqualTo(10 * DateUtils.MINUTE_IN_MILLIS);
+ assertThat(policy.proxCooldownTriggerMs).isEqualTo(2 * DateUtils.MINUTE_IN_MILLIS);
+ assertThat(policy.proxCooldownPeriodMs).isEqualTo(5 * DateUtils.MINUTE_IN_MILLIS);
+ assertThat(policy.screenBrightnessArray).isEqualTo(mContext.getResources().getIntArray(
+ R.array.config_doze_brightness_sensor_to_brightness));
+ assertThat(policy.dimmingScrimArray).isEqualTo(mContext.getResources().getIntArray(
+ R.array.config_doze_brightness_sensor_to_scrim_opacity));
+ }
+
+ @Test
+ public void testPolicy_valueNotNull_containsValue() throws Exception {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS, ALWAYS_ON_DISPLAY_CONSTANTS_VALUE);
+
+ AlwaysOnDisplayPolicy policy = new AlwaysOnDisplayPolicy(mContext);
+
+ assertThat(policy.proxScreenOffDelayMs).isEqualTo(1000);
+ assertThat(policy.proxCooldownTriggerMs).isEqualTo(2000);
+ assertThat(policy.proxCooldownPeriodMs).isEqualTo(3000);
+ assertThat(policy.screenBrightnessArray).isEqualTo(new int[]{1, 2, 3, 4, 5});
+ assertThat(policy.dimmingScrimArray).isEqualTo(new int[]{5, 4, 3, 2, 1});
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index c2758068a4ed..46e1d5562714 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -60,7 +60,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
mSensorManager = new FakeSensorManager(mContext);
mSensor = mSensorManager.getFakeLightSensor();
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- mSensor.getSensor(), mHostFake, null /* handler */);
+ mSensor.getSensor(), mHostFake, null /* handler */,
+ new AlwaysOnDisplayPolicy(mContext));
}
@Test
@@ -135,7 +136,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
@Test
public void testNullSensor() throws Exception {
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- null /* sensor */, mHostFake, null /* handler */);
+ null /* sensor */, mHostFake, null /* handler */,
+ new AlwaysOnDisplayPolicy(mContext));
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 7324b82351f6..c60647fada09 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -417,7 +417,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
final boolean triggerable = (mEnabledFeatures
& FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
mMagnificationGestureHandler = new MagnificationGestureHandler(
- mContext, mAms, detectControlGestures, triggerable);
+ mContext, mAms.getMagnificationController(),
+ detectControlGestures, triggerable);
addFirstEventHandler(mMagnificationGestureHandler);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java
index bc761914caf2..abfdb683c04c 100644
--- a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java
@@ -12,32 +12,27 @@ final class GestureUtils {
/* cannot be instantiated */
}
- public static boolean isTap(MotionEvent down, MotionEvent up, int tapTimeSlop,
- int tapDistanceSlop, int actionIndex) {
- return eventsWithinTimeAndDistanceSlop(down, up, tapTimeSlop, tapDistanceSlop, actionIndex);
- }
-
public static boolean isMultiTap(MotionEvent firstUp, MotionEvent secondUp,
- int multiTapTimeSlop, int multiTapDistanceSlop, int actionIndex) {
+ int multiTapTimeSlop, int multiTapDistanceSlop) {
+ if (firstUp == null || secondUp == null) return false;
return eventsWithinTimeAndDistanceSlop(firstUp, secondUp, multiTapTimeSlop,
- multiTapDistanceSlop, actionIndex);
+ multiTapDistanceSlop);
}
private static boolean eventsWithinTimeAndDistanceSlop(MotionEvent first, MotionEvent second,
- int timeout, int distance, int actionIndex) {
+ int timeout, int distance) {
if (isTimedOut(first, second, timeout)) {
return false;
}
- final double deltaMove = computeDistance(first, second, actionIndex);
+ final double deltaMove = distance(first, second);
if (deltaMove >= distance) {
return false;
}
return true;
}
- public static double computeDistance(MotionEvent first, MotionEvent second, int pointerIndex) {
- return MathUtils.dist(first.getX(pointerIndex), first.getY(pointerIndex),
- second.getX(pointerIndex), second.getY(pointerIndex));
+ public static double distance(MotionEvent first, MotionEvent second) {
+ return MathUtils.dist(first.getX(), first.getY(), second.getX(), second.getY());
}
public static boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) {
@@ -54,7 +49,6 @@ final class GestureUtils {
/**
* Determines whether a two pointer gesture is a dragging one.
*
- * @param event The event with the pointer data.
* @return True if the gesture is a dragging one.
*/
public static boolean isDraggingGesture(float firstPtrDownX, float firstPtrDownY,
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index caa74b9512d1..98b8e6b723ac 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -16,11 +16,6 @@
package com.android.server.accessibility;
-import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.SomeArgs;
-import com.android.server.LocalServices;
-
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
@@ -42,6 +37,12 @@ import android.view.View;
import android.view.WindowManagerInternal;
import android.view.animation.DecelerateInterpolator;
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
+import com.android.server.LocalServices;
+
import java.util.Locale;
/**
@@ -138,7 +139,7 @@ class MagnificationController implements Handler.Callback {
private final WindowManagerInternal mWindowManager;
// Flag indicating that we are registered with window manager.
- private boolean mRegistered;
+ @VisibleForTesting boolean mRegistered;
private boolean mUnregisterPending;
@@ -148,9 +149,14 @@ class MagnificationController implements Handler.Callback {
mHandler = new Handler(context.getMainLooper(), this);
}
- public MagnificationController(Context context, AccessibilityManagerService ams, Object lock,
- Handler handler, WindowManagerInternal windowManagerInternal,
- ValueAnimator valueAnimator, SettingsBridge settingsBridge) {
+ public MagnificationController(
+ Context context,
+ AccessibilityManagerService ams,
+ Object lock,
+ Handler handler,
+ WindowManagerInternal windowManagerInternal,
+ ValueAnimator valueAnimator,
+ SettingsBridge settingsBridge) {
mHandler = handler;
mWindowManager = windowManagerInternal;
mMainThreadId = context.getMainLooper().getThread().getId();
@@ -672,8 +678,7 @@ class MagnificationController implements Handler.Callback {
* Resets magnification if magnification and auto-update are both enabled.
*
* @param animate whether the animate the transition
- * @return {@code true} if magnification was reset to the disabled state,
- * {@code false} if magnification is still active
+ * @return whether was {@link #isMagnifying magnifying}
*/
boolean resetIfNeeded(boolean animate) {
synchronized (mLock) {
@@ -790,6 +795,19 @@ class MagnificationController implements Handler.Callback {
return true;
}
+ @Override
+ public String toString() {
+ return "MagnificationController{" +
+ "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec +
+ ", mMagnificationRegion=" + mMagnificationRegion +
+ ", mMagnificationBounds=" + mMagnificationBounds +
+ ", mUserId=" + mUserId +
+ ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify +
+ ", mRegistered=" + mRegistered +
+ ", mUnregisterPending=" + mUnregisterPending +
+ '}';
+ }
+
/**
* Class responsible for animating spec on the main thread and sending spec
* updates to the window manager.
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index b1ac5891dafa..d6452f87d155 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -16,6 +16,21 @@
package com.android.server.accessibility;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.server.accessibility.GestureUtils.distance;
+
+import static java.lang.Math.abs;
+import static java.util.Arrays.asList;
+import static java.util.Arrays.copyOfRange;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -27,7 +42,6 @@ import android.util.Slog;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
-import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
@@ -37,6 +51,8 @@ import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* This class handles magnification in response to touch events.
*
@@ -85,91 +101,109 @@ import android.view.accessibility.AccessibilityEvent;
*
* 7. The magnification scale will be persisted in settings and in the cloud.
*/
+@SuppressWarnings("WeakerAccess")
class MagnificationGestureHandler implements EventStreamTransformation {
private static final String LOG_TAG = "MagnificationEventHandler";
- private static final boolean DEBUG_STATE_TRANSITIONS = false;
- private static final boolean DEBUG_DETECTING = false;
- private static final boolean DEBUG_PANNING = false;
+ private static final boolean DEBUG_ALL = false;
+ private static final boolean DEBUG_STATE_TRANSITIONS = false || DEBUG_ALL;
+ private static final boolean DEBUG_DETECTING = false || DEBUG_ALL;
+ private static final boolean DEBUG_PANNING = false || DEBUG_ALL;
- private static final int STATE_DELEGATING = 1;
- private static final int STATE_DETECTING = 2;
- private static final int STATE_VIEWPORT_DRAGGING = 3;
- private static final int STATE_MAGNIFIED_INTERACTION = 4;
+ /** @see #handleMotionEventStateDelegating */
+ @VisibleForTesting static final int STATE_DELEGATING = 1;
+ /** @see DetectingStateHandler */
+ @VisibleForTesting static final int STATE_DETECTING = 2;
+ /** @see ViewportDraggingStateHandler */
+ @VisibleForTesting static final int STATE_VIEWPORT_DRAGGING = 3;
+ /** @see PanningScalingStateHandler */
+ @VisibleForTesting static final int STATE_PANNING_SCALING = 4;
private static final float MIN_SCALE = 2.0f;
private static final float MAX_SCALE = 5.0f;
- private final MagnificationController mMagnificationController;
- private final DetectingStateHandler mDetectingStateHandler;
- private final MagnifiedContentInteractionStateHandler mMagnifiedContentInteractionStateHandler;
- private final StateViewportDraggingHandler mStateViewportDraggingHandler;
+ @VisibleForTesting final MagnificationController mMagnificationController;
+
+ @VisibleForTesting final DetectingStateHandler mDetectingStateHandler;
+ @VisibleForTesting final PanningScalingStateHandler mPanningScalingStateHandler;
+ @VisibleForTesting final ViewportDraggingStateHandler mViewportDraggingStateHandler;
private final ScreenStateReceiver mScreenStateReceiver;
- private final boolean mDetectTripleTap;
- private final boolean mTriggerable;
+ /**
+ * {@code true} if this detector should detect and respond to triple-tap
+ * gestures for engaging and disengaging magnification,
+ * {@code false} if it should ignore such gestures
+ */
+ final boolean mDetectTripleTap;
+
+ /**
+ * Whether {@link #mShortcutTriggered shortcut} is enabled
+ */
+ final boolean mDetectShortcutTrigger;
- private EventStreamTransformation mNext;
+ EventStreamTransformation mNext;
- private int mCurrentState;
- private int mPreviousState;
+ @VisibleForTesting int mCurrentState;
+ @VisibleForTesting int mPreviousState;
- private boolean mTranslationEnabledBeforePan;
+ @VisibleForTesting boolean mShortcutTriggered;
- private boolean mShortcutTriggered;
+ /**
+ * Time of last {@link MotionEvent#ACTION_DOWN} while in {@link #STATE_DELEGATING}
+ */
+ long mDelegatingStateDownTime;
private PointerCoords[] mTempPointerCoords;
private PointerProperties[] mTempPointerProperties;
- private long mDelegatingStateDownTime;
-
/**
* @param context Context for resolving various magnification-related resources
- * @param ams AccessibilityManagerService used to obtain a {@link MagnificationController}
+ * @param magnificationController the {@link MagnificationController}
+ *
* @param detectTripleTap {@code true} if this detector should detect and respond to triple-tap
- * gestures for engaging and disengaging magnification,
- * {@code false} if it should ignore such gestures
- * @param triggerable {@code true} if this detector should be "triggerable" by some external
- * shortcut invoking {@link #notifyShortcutTriggered}, {@code
- * false} if it should ignore such triggers.
+ * gestures for engaging and disengaging magnification,
+ * {@code false} if it should ignore such gestures
+ * @param detectShortcutTrigger {@code true} if this detector should be "triggerable" by some
+ * external shortcut invoking {@link #notifyShortcutTriggered},
+ * {@code false} if it should ignore such triggers.
*/
- public MagnificationGestureHandler(Context context, AccessibilityManagerService ams,
- boolean detectTripleTap, boolean triggerable) {
- mMagnificationController = ams.getMagnificationController();
+ public MagnificationGestureHandler(Context context,
+ MagnificationController magnificationController,
+ boolean detectTripleTap,
+ boolean detectShortcutTrigger) {
+ mMagnificationController = magnificationController;
+
mDetectingStateHandler = new DetectingStateHandler(context);
- mStateViewportDraggingHandler = new StateViewportDraggingHandler();
- mMagnifiedContentInteractionStateHandler =
- new MagnifiedContentInteractionStateHandler(context);
+ mViewportDraggingStateHandler = new ViewportDraggingStateHandler();
+ mPanningScalingStateHandler =
+ new PanningScalingStateHandler(context);
+
mDetectTripleTap = detectTripleTap;
- mTriggerable = triggerable;
+ mDetectShortcutTrigger = detectShortcutTrigger;
- if (triggerable) {
+ if (mDetectShortcutTrigger) {
mScreenStateReceiver = new ScreenStateReceiver(context, this);
mScreenStateReceiver.register();
} else {
mScreenStateReceiver = null;
}
- transitionToState(STATE_DETECTING);
+ transitionTo(STATE_DETECTING);
}
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
- if (mNext != null) {
- mNext.onMotionEvent(event, rawEvent, policyFlags);
- }
- return;
- }
- if (!mDetectTripleTap && !mTriggerable) {
- if (mNext != null) {
- dispatchTransformedEvent(event, rawEvent, policyFlags);
- }
+ if ((!mDetectTripleTap && !mDetectShortcutTrigger)
+ || !event.isFromSource(SOURCE_TOUCHSCREEN)) {
+ dispatchTransformedEvent(event, rawEvent, policyFlags);
return;
}
- mMagnifiedContentInteractionStateHandler.onMotionEvent(event, rawEvent, policyFlags);
- switch (mCurrentState) {
+ // Local copy to avoid dispatching the same event to more than one state handler
+ // in case mPanningScalingStateHandler changes mCurrentState
+ int currentState = mCurrentState;
+ mPanningScalingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
+ switch (currentState) {
case STATE_DELEGATING: {
handleMotionEventStateDelegating(event, rawEvent, policyFlags);
}
@@ -179,17 +213,17 @@ class MagnificationGestureHandler implements EventStreamTransformation {
}
break;
case STATE_VIEWPORT_DRAGGING: {
- mStateViewportDraggingHandler.onMotionEvent(event, rawEvent, policyFlags);
+ mViewportDraggingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
}
break;
- case STATE_MAGNIFIED_INTERACTION: {
- // mMagnifiedContentInteractionStateHandler handles events only
+ case STATE_PANNING_SCALING: {
+ // mPanningScalingStateHandler handles events only
// if this is the current state since it uses ScaleGestureDetector
// and a GestureDetector which need well formed event stream.
}
break;
default: {
- throw new IllegalStateException("Unknown state: " + mCurrentState);
+ throw new IllegalStateException("Unknown state: " + currentState);
}
}
}
@@ -215,8 +249,8 @@ class MagnificationGestureHandler implements EventStreamTransformation {
@Override
public void clearEvents(int inputSource) {
- if (inputSource == InputDevice.SOURCE_TOUCHSCREEN) {
- clear();
+ if (inputSource == SOURCE_TOUCHSCREEN) {
+ clearAndTransitionToStateDetecting();
}
if (mNext != null) {
@@ -229,20 +263,25 @@ class MagnificationGestureHandler implements EventStreamTransformation {
if (mScreenStateReceiver != null) {
mScreenStateReceiver.unregister();
}
- clear();
+ clearAndTransitionToStateDetecting();
}
void notifyShortcutTriggered() {
- if (mTriggerable) {
- if (mMagnificationController.resetIfNeeded(true)) {
- clear();
+ if (mDetectShortcutTrigger) {
+ boolean wasMagnifying = mMagnificationController.resetIfNeeded(/* animate */ true);
+ if (wasMagnifying) {
+ clearAndTransitionToStateDetecting();
} else {
- setMagnificationShortcutTriggered(!mShortcutTriggered);
+ toggleShortcutTriggered();
}
}
}
- private void setMagnificationShortcutTriggered(boolean state) {
+ private void toggleShortcutTriggered() {
+ setShortcutTriggered(!mShortcutTriggered);
+ }
+
+ private void setShortcutTriggered(boolean state) {
if (mShortcutTriggered == state) {
return;
}
@@ -251,27 +290,25 @@ class MagnificationGestureHandler implements EventStreamTransformation {
mMagnificationController.setForceShowMagnifiableBounds(state);
}
- private void clear() {
+ void clearAndTransitionToStateDetecting() {
+ setShortcutTriggered(false);
mCurrentState = STATE_DETECTING;
- setMagnificationShortcutTriggered(false);
mDetectingStateHandler.clear();
- mStateViewportDraggingHandler.clear();
- mMagnifiedContentInteractionStateHandler.clear();
+ mViewportDraggingStateHandler.clear();
+ mPanningScalingStateHandler.clear();
}
private void handleMotionEventStateDelegating(MotionEvent event,
MotionEvent rawEvent, int policyFlags) {
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN: {
- mDelegatingStateDownTime = event.getDownTime();
- }
- break;
- case MotionEvent.ACTION_UP: {
- if (mDetectingStateHandler.mDelayedEventQueue == null) {
- transitionToState(STATE_DETECTING);
- }
- }
- break;
+ if (event.getActionMasked() == ACTION_UP) {
+ transitionTo(STATE_DETECTING);
+ }
+ delegateEvent(event, rawEvent, policyFlags);
+ }
+
+ void delegateEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mDelegatingStateDownTime = event.getDownTime();
}
if (mNext != null) {
// We cache some events to see if the user wants to trigger magnification.
@@ -287,13 +324,15 @@ class MagnificationGestureHandler implements EventStreamTransformation {
private void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent,
int policyFlags) {
- // If the event is within the magnified portion of the screen we have
+ if (mNext == null) return; // Nowhere to dispatch to
+
+ // If the touchscreen event is within the magnified portion of the screen we have
// to change its location to be where the user thinks he is poking the
// UI which may have been magnified and panned.
- final float eventX = event.getX();
- final float eventY = event.getY();
if (mMagnificationController.isMagnifying()
- && mMagnificationController.magnificationRegionContains(eventX, eventY)) {
+ && event.isFromSource(SOURCE_TOUCHSCREEN)
+ && mMagnificationController.magnificationRegionContains(
+ event.getX(), event.getY())) {
final float scale = mMagnificationController.getScale();
final float scaledOffsetX = mMagnificationController.getOffsetX();
final float scaledOffsetY = mMagnificationController.getOffsetY();
@@ -347,34 +386,27 @@ class MagnificationGestureHandler implements EventStreamTransformation {
return mTempPointerProperties;
}
- private void transitionToState(int state) {
+ private void transitionTo(int state) {
if (DEBUG_STATE_TRANSITIONS) {
- switch (state) {
- case STATE_DELEGATING: {
- Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING");
- }
- break;
- case STATE_DETECTING: {
- Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING");
- }
- break;
- case STATE_VIEWPORT_DRAGGING: {
- Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING");
- }
- break;
- case STATE_MAGNIFIED_INTERACTION: {
- Slog.i(LOG_TAG, "mCurrentState: STATE_MAGNIFIED_INTERACTION");
- }
- break;
- default: {
- throw new IllegalArgumentException("Unknown state: " + state);
- }
- }
+ Slog.i(LOG_TAG, (stateToString(mCurrentState) + " -> " + stateToString(state)
+ + " at " + asList(copyOfRange(new RuntimeException().getStackTrace(), 1, 5)))
+ .replace(getClass().getName(), ""));
}
mPreviousState = mCurrentState;
mCurrentState = state;
}
+ private static String stateToString(int state) {
+ switch (state) {
+ case STATE_DELEGATING: return "STATE_DELEGATING";
+ case STATE_DETECTING: return "STATE_DETECTING";
+ case STATE_VIEWPORT_DRAGGING: return "STATE_VIEWPORT_DRAGGING";
+ case STATE_PANNING_SCALING: return "STATE_PANNING_SCALING";
+ case 0: return "0";
+ default: throw new IllegalArgumentException("Unknown state: " + state);
+ }
+ }
+
private interface MotionEventHandler {
void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
@@ -384,21 +416,20 @@ class MagnificationGestureHandler implements EventStreamTransformation {
/**
* This class determines if the user is performing a scale or pan gesture.
+ *
+ * @see #STATE_PANNING_SCALING
*/
- private final class MagnifiedContentInteractionStateHandler extends SimpleOnGestureListener
+ final class PanningScalingStateHandler extends SimpleOnGestureListener
implements OnScaleGestureListener, MotionEventHandler {
private final ScaleGestureDetector mScaleGestureDetector;
-
private final GestureDetector mGestureDetector;
+ final float mScalingThreshold;
- private final float mScalingThreshold;
+ float mInitialScaleFactor = -1;
+ boolean mScaling;
- private float mInitialScaleFactor = -1;
-
- private boolean mScaling;
-
- public MagnifiedContentInteractionStateHandler(Context context) {
+ public PanningScalingStateHandler(Context context) {
final TypedValue scaleValue = new TypedValue();
context.getResources().getValue(
com.android.internal.R.dimen.config_screen_magnification_scaling_threshold,
@@ -411,26 +442,39 @@ class MagnificationGestureHandler implements EventStreamTransformation {
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ // Dispatches #onScaleBegin, #onScale, #onScaleEnd
mScaleGestureDetector.onTouchEvent(event);
+ // Dispatches #onScroll
mGestureDetector.onTouchEvent(event);
- if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
+
+ if (mCurrentState != STATE_PANNING_SCALING) {
return;
}
- if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- clear();
- mMagnificationController.persistScale();
- if (mPreviousState == STATE_VIEWPORT_DRAGGING) {
- transitionToState(STATE_VIEWPORT_DRAGGING);
- } else {
- transitionToState(STATE_DETECTING);
- }
+
+ int action = event.getActionMasked();
+ if (action == ACTION_POINTER_UP
+ && event.getPointerCount() == 2 // includes the pointer currently being released
+ && mPreviousState == STATE_VIEWPORT_DRAGGING) {
+
+ persistScaleAndTransitionTo(STATE_VIEWPORT_DRAGGING);
+
+ } else if (action == ACTION_UP) {
+
+ persistScaleAndTransitionTo(STATE_DETECTING);
+
}
}
+ public void persistScaleAndTransitionTo(int state) {
+ mMagnificationController.persistScale();
+ clear();
+ transitionTo(state);
+ }
+
@Override
- public boolean onScroll(MotionEvent first, MotionEvent second, float distanceX,
- float distanceY) {
- if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
+ public boolean onScroll(MotionEvent first, MotionEvent second,
+ float distanceX, float distanceY) {
+ if (mCurrentState != STATE_PANNING_SCALING) {
return true;
}
if (DEBUG_PANNING) {
@@ -447,14 +491,15 @@ class MagnificationGestureHandler implements EventStreamTransformation {
if (!mScaling) {
if (mInitialScaleFactor < 0) {
mInitialScaleFactor = detector.getScaleFactor();
+ return false;
+ }
+ final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor;
+ if (abs(deltaScale) > mScalingThreshold) {
+ mScaling = true;
+ return true;
} else {
- final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor;
- if (Math.abs(deltaScale) > mScalingThreshold) {
- mScaling = true;
- return true;
- }
+ return false;
}
- return false;
}
final float initialScale = mMagnificationController.getScale();
@@ -485,7 +530,7 @@ class MagnificationGestureHandler implements EventStreamTransformation {
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
- return (mCurrentState == STATE_MAGNIFIED_INTERACTION);
+ return (mCurrentState == STATE_PANNING_SCALING);
}
@Override
@@ -498,60 +543,65 @@ class MagnificationGestureHandler implements EventStreamTransformation {
mInitialScaleFactor = -1;
mScaling = false;
}
+
+ @Override
+ public String toString() {
+ return "MagnifiedContentInteractionStateHandler{" +
+ "mInitialScaleFactor=" + mInitialScaleFactor +
+ ", mScaling=" + mScaling +
+ '}';
+ }
}
/**
* This class handles motion events when the event dispatcher has
* determined that the user is performing a single-finger drag of the
* magnification viewport.
+ *
+ * @see #STATE_VIEWPORT_DRAGGING
*/
- private final class StateViewportDraggingHandler implements MotionEventHandler {
+ final class ViewportDraggingStateHandler implements MotionEventHandler {
+ /** Whether to disable zoom after dragging ends */
+ boolean mZoomedInBeforeDrag;
private boolean mLastMoveOutsideMagnifiedRegion;
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
final int action = event.getActionMasked();
switch (action) {
- case MotionEvent.ACTION_DOWN: {
- throw new IllegalArgumentException("Unexpected event type: ACTION_DOWN");
- }
- case MotionEvent.ACTION_POINTER_DOWN: {
+ case ACTION_POINTER_DOWN: {
clear();
- transitionToState(STATE_MAGNIFIED_INTERACTION);
+ transitionTo(STATE_PANNING_SCALING);
}
break;
- case MotionEvent.ACTION_MOVE: {
+ case ACTION_MOVE: {
if (event.getPointerCount() != 1) {
throw new IllegalStateException("Should have one pointer down.");
}
final float eventX = event.getX();
final float eventY = event.getY();
if (mMagnificationController.magnificationRegionContains(eventX, eventY)) {
- if (mLastMoveOutsideMagnifiedRegion) {
- mLastMoveOutsideMagnifiedRegion = false;
- mMagnificationController.setCenter(eventX, eventY, true,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
- } else {
- mMagnificationController.setCenter(eventX, eventY, false,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
- }
+ mMagnificationController.setCenter(eventX, eventY,
+ /* animate */ mLastMoveOutsideMagnifiedRegion,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ mLastMoveOutsideMagnifiedRegion = false;
} else {
mLastMoveOutsideMagnifiedRegion = true;
}
}
break;
- case MotionEvent.ACTION_UP: {
- if (!mTranslationEnabledBeforePan) {
- mMagnificationController.reset(true);
- }
+ case ACTION_UP: {
+ if (!mZoomedInBeforeDrag) zoomOff();
clear();
- transitionToState(STATE_DETECTING);
+ transitionTo(STATE_DETECTING);
}
break;
- case MotionEvent.ACTION_POINTER_UP: {
+
+ case ACTION_DOWN:
+ case ACTION_POINTER_UP: {
throw new IllegalArgumentException(
- "Unexpected event type: ACTION_POINTER_UP");
+ "Unexpected event type: " + MotionEvent.actionToString(action));
}
}
}
@@ -560,211 +610,224 @@ class MagnificationGestureHandler implements EventStreamTransformation {
public void clear() {
mLastMoveOutsideMagnifiedRegion = false;
}
+
+ @Override
+ public String toString() {
+ return "ViewportDraggingStateHandler{" +
+ "mZoomedInBeforeDrag=" + mZoomedInBeforeDrag +
+ ", mLastMoveOutsideMagnifiedRegion=" + mLastMoveOutsideMagnifiedRegion +
+ '}';
+ }
}
/**
* This class handles motion events when the event dispatch has not yet
* determined what the user is doing. It watches for various tap events.
+ *
+ * @see #STATE_DETECTING
*/
- private final class DetectingStateHandler implements MotionEventHandler {
-
- private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1;
+ final class DetectingStateHandler implements MotionEventHandler, Handler.Callback {
+ private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1;
private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
- private static final int ACTION_TAP_COUNT = 3;
-
- private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout();
-
- private final int mMultiTapTimeSlop;
-
- private final int mTapDistanceSlop;
-
- private final int mMultiTapDistanceSlop;
+ final int mLongTapMinDelay = ViewConfiguration.getJumpTapTimeout();
+ final int mSwipeMinDistance;
+ final int mMultiTapMaxDelay;
+ final int mMultiTapMaxDistance;
private MotionEventInfo mDelayedEventQueue;
+ MotionEvent mLastDown;
+ private MotionEvent mPreLastDown;
+ private MotionEvent mLastUp;
+ private MotionEvent mPreLastUp;
- private MotionEvent mLastDownEvent;
-
- private MotionEvent mLastTapUpEvent;
-
- private int mTapCount;
+ Handler mHandler = new Handler(this);
public DetectingStateHandler(Context context) {
- mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout()
+ mMultiTapMaxDelay = ViewConfiguration.getDoubleTapTimeout()
+ context.getResources().getInteger(
com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
- mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
- mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
- }
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- final int type = message.what;
- switch (type) {
- case MESSAGE_ON_ACTION_TAP_AND_HOLD: {
- MotionEvent event = (MotionEvent) message.obj;
- final int policyFlags = message.arg1;
- onActionTapAndHold(event, policyFlags);
- }
- break;
- case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
- transitionToState(STATE_DELEGATING);
- sendDelayedMotionEvents();
- clear();
- }
- break;
- default: {
- throw new IllegalArgumentException("Unknown message type: " + type);
- }
+ mSwipeMinDistance = ViewConfiguration.get(context).getScaledTouchSlop();
+ mMultiTapMaxDistance = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+ }
+
+ @Override
+ public boolean handleMessage(Message message) {
+ final int type = message.what;
+ switch (type) {
+ case MESSAGE_ON_TRIPLE_TAP_AND_HOLD: {
+ onTripleTapAndHold(/* down */ (MotionEvent) message.obj);
+ }
+ break;
+ case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
+ transitionToDelegatingState(/* andClear */ true);
+ }
+ break;
+ default: {
+ throw new IllegalArgumentException("Unknown message type: " + type);
}
}
- };
+ return true;
+ }
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
cacheDelayedMotionEvent(event, rawEvent, policyFlags);
- final int action = event.getActionMasked();
- switch (action) {
+ switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
+
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+
if (!mMagnificationController.magnificationRegionContains(
event.getX(), event.getY())) {
- transitionToDelegatingState(!mShortcutTriggered);
- return;
- }
- if (mShortcutTriggered) {
- Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD,
- policyFlags, 0, event);
- mHandler.sendMessageDelayed(message,
- ViewConfiguration.getLongPressTimeout());
- return;
- }
- if (mDetectTripleTap) {
- if ((mTapCount == ACTION_TAP_COUNT - 1) && (mLastDownEvent != null)
- && GestureUtils.isMultiTap(mLastDownEvent, event, mMultiTapTimeSlop,
- mMultiTapDistanceSlop, 0)) {
- Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD,
- policyFlags, 0, event);
- mHandler.sendMessageDelayed(message,
- ViewConfiguration.getLongPressTimeout());
- } else if (mTapCount < ACTION_TAP_COUNT) {
- Message message = mHandler.obtainMessage(
- MESSAGE_TRANSITION_TO_DELEGATING_STATE);
- mHandler.sendMessageDelayed(message, mMultiTapTimeSlop);
- }
- clearLastDownEvent();
- mLastDownEvent = MotionEvent.obtain(event);
- } else if (mMagnificationController.isMagnifying()) {
- // If magnified, consume an ACTION_DOWN until mMultiTapTimeSlop or
- // mTapDistanceSlop is reached to ensure MAGNIFIED_INTERACTION is reachable.
- Message message = mHandler.obtainMessage(
- MESSAGE_TRANSITION_TO_DELEGATING_STATE);
- mHandler.sendMessageDelayed(message, mMultiTapTimeSlop);
- return;
+
+ transitionToDelegatingState(/* andClear */ !mShortcutTriggered);
+
+ } else if (isMultiTapTriggered(2 /* taps */)) {
+
+ // 3tap and hold
+ delayedTransitionToDraggingState(event);
+
+ } else if (mDetectTripleTap
+ // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
+ // to ensure reachability of
+ // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
+ || mMagnificationController.isMagnifying()) {
+
+ delayedTransitionToDelegatingState();
+
} else {
- transitionToDelegatingState(true);
- return;
+
+ // Delegate pending events without delay
+ transitionToDelegatingState(/* andClear */ true);
}
}
break;
- case MotionEvent.ACTION_POINTER_DOWN: {
+ case ACTION_POINTER_DOWN: {
if (mMagnificationController.isMagnifying()) {
- mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
- transitionToState(STATE_MAGNIFIED_INTERACTION);
+ transitionTo(STATE_PANNING_SCALING);
clear();
} else {
- transitionToDelegatingState(true);
+ transitionToDelegatingState(/* andClear */ true);
}
}
break;
- case MotionEvent.ACTION_MOVE: {
- if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) {
- final double distance = GestureUtils.computeDistance(mLastDownEvent,
- event, 0);
- if (Math.abs(distance) > mTapDistanceSlop) {
- transitionToDelegatingState(true);
- }
+ case ACTION_MOVE: {
+ if (isFingerDown()
+ && distance(mLastDown, /* move */ event) > mSwipeMinDistance
+ // For convenience, viewport dragging on 3tap&hold takes precedence
+ // over insta-delegating on 3tap&swipe
+ // (which is a rare combo to be used aside from magnification)
+ && !isMultiTapTriggered(2 /* taps */)) {
+
+ // Swipe detected - delegate skipping timeout
+ transitionToDelegatingState(/* andClear */ true);
}
}
break;
- case MotionEvent.ACTION_UP: {
- mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
+ case ACTION_UP: {
+
+ mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
+
if (!mMagnificationController.magnificationRegionContains(
event.getX(), event.getY())) {
- transitionToDelegatingState(!mShortcutTriggered);
- return;
- }
- if (mShortcutTriggered) {
- clear();
- onActionTap(event, policyFlags);
- return;
- }
- if (mLastDownEvent == null) {
- return;
- }
- if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop,
- mTapDistanceSlop, 0)) {
- transitionToDelegatingState(true);
- return;
- }
- if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(
- mLastTapUpEvent, event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
- transitionToDelegatingState(true);
- return;
- }
- mTapCount++;
- if (DEBUG_DETECTING) {
- Slog.i(LOG_TAG, "Tap count:" + mTapCount);
- }
- if (mTapCount == ACTION_TAP_COUNT) {
- clear();
- onActionTap(event, policyFlags);
- return;
+
+ transitionToDelegatingState(/* andClear */ !mShortcutTriggered);
+
+ } else if (isMultiTapTriggered(3 /* taps */)) {
+
+ onTripleTap(/* up */ event);
+
+ } else if (
+ // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP
+ isFingerDown()
+ //TODO long tap should never happen here
+ && (timeBetween(mLastDown, /* mLastUp */ event) >= mLongTapMinDelay)
+ || distance(mLastDown, /* mLastUp */ event)
+ >= mSwipeMinDistance) {
+
+ transitionToDelegatingState(/* andClear */ true);
+
}
- clearLastTapUpEvent();
- mLastTapUpEvent = MotionEvent.obtain(event);
- }
- break;
- case MotionEvent.ACTION_POINTER_UP: {
- /* do nothing */
}
break;
}
}
- @Override
- public void clear() {
- setMagnificationShortcutTriggered(false);
- mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
- mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
- clearTapDetectionState();
- clearDelayedMotionEvents();
+ public boolean isMultiTapTriggered(int numTaps) {
+
+ // Shortcut acts as the 2 initial taps
+ if (mShortcutTriggered) return tapCount() + 2 >= numTaps;
+
+ return mDetectTripleTap
+ && tapCount() >= numTaps
+ && isMultiTap(mPreLastDown, mLastDown)
+ && isMultiTap(mPreLastUp, mLastUp);
}
- private void clearTapDetectionState() {
- mTapCount = 0;
- clearLastTapUpEvent();
- clearLastDownEvent();
+ private boolean isMultiTap(MotionEvent first, MotionEvent second) {
+ return GestureUtils.isMultiTap(first, second, mMultiTapMaxDelay, mMultiTapMaxDistance);
}
- private void clearLastTapUpEvent() {
- if (mLastTapUpEvent != null) {
- mLastTapUpEvent.recycle();
- mLastTapUpEvent = null;
- }
+ public boolean isFingerDown() {
+ return mLastDown != null;
}
- private void clearLastDownEvent() {
- if (mLastDownEvent != null) {
- mLastDownEvent.recycle();
- mLastDownEvent = null;
- }
+ private long timeBetween(@Nullable MotionEvent a, @Nullable MotionEvent b) {
+ if (a == null && b == null) return 0;
+ return abs(timeOf(a) - timeOf(b));
+ }
+
+ /**
+ * Nullsafe {@link MotionEvent#getEventTime} that interprets null event as something that
+ * has happened long enough ago to be gone from the event queue.
+ * Thus the time for a null event is a small number, that is below any other non-null
+ * event's time.
+ *
+ * @return {@link MotionEvent#getEventTime}, or {@link Long#MIN_VALUE} if the event is null
+ */
+ private long timeOf(@Nullable MotionEvent event) {
+ return event != null ? event.getEventTime() : Long.MIN_VALUE;
+ }
+
+ public int tapCount() {
+ return MotionEventInfo.countOf(mDelayedEventQueue, ACTION_UP);
}
+ /** -> {@link #STATE_DELEGATING} */
+ public void delayedTransitionToDelegatingState() {
+ mHandler.sendEmptyMessageDelayed(
+ MESSAGE_TRANSITION_TO_DELEGATING_STATE,
+ mMultiTapMaxDelay);
+ }
+
+ /** -> {@link #STATE_VIEWPORT_DRAGGING} */
+ public void delayedTransitionToDraggingState(MotionEvent event) {
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MESSAGE_ON_TRIPLE_TAP_AND_HOLD, event),
+ ViewConfiguration.getLongPressTimeout());
+ }
+
+ @Override
+ public void clear() {
+ setShortcutTriggered(false);
+ mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
+ mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+ clearDelayedMotionEvents();
+ }
+
+
private void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
int policyFlags) {
+ if (event.getActionMasked() == ACTION_DOWN) {
+ mPreLastDown = mLastDown;
+ mLastDown = event;
+ } else if (event.getActionMasked() == ACTION_UP) {
+ mPreLastUp = mLastUp;
+ mLastUp = event;
+ }
+
MotionEventInfo info = MotionEventInfo.obtain(event, rawEvent,
policyFlags);
if (mDelayedEventQueue == null) {
@@ -782,8 +845,13 @@ class MagnificationGestureHandler implements EventStreamTransformation {
while (mDelayedEventQueue != null) {
MotionEventInfo info = mDelayedEventQueue;
mDelayedEventQueue = info.mNext;
- MagnificationGestureHandler.this.onMotionEvent(info.mEvent, info.mRawEvent,
- info.mPolicyFlags);
+
+ // Because MagnifiedInteractionStateHandler requires well-formed event stream
+ mPanningScalingStateHandler.onMotionEvent(
+ info.event, info.rawEvent, info.policyFlags);
+
+ delegateEvent(info.event, info.rawEvent, info.policyFlags);
+
info.recycle();
}
}
@@ -794,91 +862,136 @@ class MagnificationGestureHandler implements EventStreamTransformation {
mDelayedEventQueue = info.mNext;
info.recycle();
}
+ mPreLastDown = null;
+ mPreLastUp = null;
+ mLastDown = null;
+ mLastUp = null;
}
- private void transitionToDelegatingState(boolean andClear) {
- transitionToState(STATE_DELEGATING);
+ void transitionToDelegatingState(boolean andClear) {
+ transitionTo(STATE_DELEGATING);
sendDelayedMotionEvents();
- if (andClear) {
- clear();
- }
+ if (andClear) clear();
}
- private void onActionTap(MotionEvent up, int policyFlags) {
+ private void onTripleTap(MotionEvent up) {
+
if (DEBUG_DETECTING) {
- Slog.i(LOG_TAG, "onActionTap()");
+ Slog.i(LOG_TAG, "onTripleTap(); delayed: "
+ + MotionEventInfo.toString(mDelayedEventQueue));
}
+ clear();
- if (!mMagnificationController.isMagnifying()) {
- final float targetScale = mMagnificationController.getPersistedScale();
- final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE);
- mMagnificationController.setScaleAndCenter(scale, up.getX(), up.getY(), true,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ // Toggle zoom
+ if (mMagnificationController.isMagnifying()) {
+ zoomOff();
} else {
- mMagnificationController.reset(true);
+ zoomOn(up.getX(), up.getY());
}
}
- private void onActionTapAndHold(MotionEvent down, int policyFlags) {
- if (DEBUG_DETECTING) {
- Slog.i(LOG_TAG, "onActionTapAndHold()");
- }
+ void onTripleTapAndHold(MotionEvent down) {
+ if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
clear();
- mTranslationEnabledBeforePan = mMagnificationController.isMagnifying();
- final float targetScale = mMagnificationController.getPersistedScale();
- final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE);
- mMagnificationController.setScaleAndCenter(scale, down.getX(), down.getY(), true,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ mViewportDraggingStateHandler.mZoomedInBeforeDrag =
+ mMagnificationController.isMagnifying();
+
+ zoomOn(down.getX(), down.getY());
- transitionToState(STATE_VIEWPORT_DRAGGING);
+ transitionTo(STATE_VIEWPORT_DRAGGING);
+ }
+
+ @Override
+ public String toString() {
+ return "DetectingStateHandler{" +
+ "tapCount()=" + tapCount() +
+ ", mDelayedEventQueue=" + MotionEventInfo.toString(mDelayedEventQueue) +
+ '}';
}
}
+ private void zoomOn(float centerX, float centerY) {
+ final float scale = MathUtils.constrain(
+ mMagnificationController.getPersistedScale(),
+ MIN_SCALE, MAX_SCALE);
+ mMagnificationController.setScaleAndCenter(
+ scale, centerX, centerY,
+ /* animate */ true,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ }
+
+ private void zoomOff() {
+ mMagnificationController.reset(/* animate */ true);
+ }
+
+ private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) {
+ if (event != null) {
+ event.recycle();
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "MagnificationGestureHandler{" +
+ "mDetectingStateHandler=" + mDetectingStateHandler +
+ ", mMagnifiedInteractionStateHandler=" + mPanningScalingStateHandler +
+ ", mViewportDraggingStateHandler=" + mViewportDraggingStateHandler +
+ ", mDetectTripleTap=" + mDetectTripleTap +
+ ", mDetectShortcutTrigger=" + mDetectShortcutTrigger +
+ ", mCurrentState=" + stateToString(mCurrentState) +
+ ", mPreviousState=" + stateToString(mPreviousState) +
+ ", mShortcutTriggered=" + mShortcutTriggered +
+ ", mDelegatingStateDownTime=" + mDelegatingStateDownTime +
+ ", mMagnificationController=" + mMagnificationController +
+ '}';
+ }
+
private static final class MotionEventInfo {
private static final int MAX_POOL_SIZE = 10;
-
private static final Object sLock = new Object();
-
private static MotionEventInfo sPool;
-
private static int sPoolSize;
private MotionEventInfo mNext;
-
private boolean mInPool;
- public MotionEvent mEvent;
-
- public MotionEvent mRawEvent;
-
- public int mPolicyFlags;
+ public MotionEvent event;
+ public MotionEvent rawEvent;
+ public int policyFlags;
public static MotionEventInfo obtain(MotionEvent event, MotionEvent rawEvent,
int policyFlags) {
synchronized (sLock) {
- MotionEventInfo info;
- if (sPoolSize > 0) {
- sPoolSize--;
- info = sPool;
- sPool = info.mNext;
- info.mNext = null;
- info.mInPool = false;
- } else {
- info = new MotionEventInfo();
- }
+ MotionEventInfo info = obtainInternal();
info.initialize(event, rawEvent, policyFlags);
return info;
}
}
+ @NonNull
+ private static MotionEventInfo obtainInternal() {
+ MotionEventInfo info;
+ if (sPoolSize > 0) {
+ sPoolSize--;
+ info = sPool;
+ sPool = info.mNext;
+ info.mNext = null;
+ info.mInPool = false;
+ } else {
+ info = new MotionEventInfo();
+ }
+ return info;
+ }
+
private void initialize(MotionEvent event, MotionEvent rawEvent,
int policyFlags) {
- mEvent = MotionEvent.obtain(event);
- mRawEvent = MotionEvent.obtain(rawEvent);
- mPolicyFlags = policyFlags;
+ this.event = MotionEvent.obtain(event);
+ this.rawEvent = MotionEvent.obtain(rawEvent);
+ this.policyFlags = policyFlags;
}
public void recycle() {
@@ -897,11 +1010,22 @@ class MagnificationGestureHandler implements EventStreamTransformation {
}
private void clear() {
- mEvent.recycle();
- mEvent = null;
- mRawEvent.recycle();
- mRawEvent = null;
- mPolicyFlags = 0;
+ event = recycleAndNullify(event);
+ rawEvent = recycleAndNullify(rawEvent);
+ policyFlags = 0;
+ }
+
+ static int countOf(MotionEventInfo info, int eventType) {
+ if (info == null) return 0;
+ return (info.event.getAction() == eventType ? 1 : 0)
+ + countOf(info.mNext, eventType);
+ }
+
+ public static String toString(MotionEventInfo info) {
+ return info == null
+ ? ""
+ : MotionEvent.actionToString(info.event.getAction()).replace("ACTION_", "")
+ + " " + MotionEventInfo.toString(info.mNext);
}
}
@@ -927,7 +1051,7 @@ class MagnificationGestureHandler implements EventStreamTransformation {
@Override
public void onReceive(Context context, Intent intent) {
- mGestureHandler.setMagnificationShortcutTriggered(false);
+ mGestureHandler.setShortcutTriggered(false);
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 20ccee286fbc..2b8b25ef0fb4 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -522,10 +522,11 @@ final class AutofillManagerServiceImpl {
/**
* Updates the last fill selection when an authentication was selected.
*/
- void setAuthenticationSelected(int sessionId) {
+ void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState) {
synchronized (mLock) {
if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
- mEventHistory.addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null));
+ mEventHistory
+ .addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState));
}
}
}
@@ -533,11 +534,13 @@ final class AutofillManagerServiceImpl {
/**
* Updates the last fill selection when an dataset authentication was selected.
*/
- void setDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId) {
+ void setDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId,
+ @Nullable Bundle clientState) {
synchronized (mLock) {
if (isValidEventLocked("setDatasetAuthenticationSelected()", sessionId)) {
mEventHistory.addEvent(
- new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset));
+ new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
+ clientState));
}
}
}
@@ -545,10 +548,10 @@ final class AutofillManagerServiceImpl {
/**
* Updates the last fill selection when an save Ui is shown.
*/
- void setSaveShown(int sessionId) {
+ void setSaveShown(int sessionId, @Nullable Bundle clientState) {
synchronized (mLock) {
if (isValidEventLocked("setSaveShown()", sessionId)) {
- mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null));
+ mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState));
}
}
}
@@ -556,10 +559,12 @@ final class AutofillManagerServiceImpl {
/**
* Updates the last fill response when a dataset was selected.
*/
- void setDatasetSelected(@Nullable String selectedDataset, int sessionId) {
+ void setDatasetSelected(@Nullable String selectedDataset, int sessionId,
+ @Nullable Bundle clientState) {
synchronized (mLock) {
if (isValidEventLocked("setDatasetSelected()", sessionId)) {
- mEventHistory.addEvent(new Event(Event.TYPE_DATASET_SELECTED, selectedDataset));
+ mEventHistory.addEvent(
+ new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState));
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 95db6039b696..8d9f0aa2f49b 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -597,7 +597,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
getFillContextByRequestIdLocked(requestId).getStructure(), extras);
}
- mService.setAuthenticationSelected(id);
+ mService.setAuthenticationSelected(id, mClientState);
final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex);
mHandlerCaller.getHandler().post(() -> startAuthentication(authenticationId,
@@ -970,7 +970,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
if (sDebug) Slog.d(TAG, "Good news, everyone! All checks passed, show save UI!");
- mService.setSaveShown(id);
+ mService.setSaveShown(id, mClientState);
final IAutoFillManagerClient client = getClient();
mPendingSaveUi = new PendingUi(mActivityToken);
getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo,
@@ -1532,14 +1532,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
// Autofill it directly...
if (dataset.getAuthentication() == null) {
- mService.setDatasetSelected(dataset.getId(), id);
+ mService.setDatasetSelected(dataset.getId(), id, mClientState);
autoFillApp(dataset);
return;
}
// ...or handle authentication.
- mService.setDatasetAuthenticationSelected(dataset.getId(), id);
+ mService.setDatasetAuthenticationSelected(dataset.getId(), id, mClientState);
setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false);
final Intent fillInIntent = createAuthFillInIntent(
getFillContextByRequestIdLocked(requestId).getStructure(), mClientState);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 29d562b17deb..c1a7f6882d18 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -10475,7 +10475,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
public ComponentName[] listAllTransportComponents() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"listAllTransportComponents");
- return mTransportManager.getAllTransportCompenents();
+ return mTransportManager.getAllTransportComponents();
}
@Override
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index b01cfc572432..83b6693e7a70 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -2762,7 +2762,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
public ComponentName[] listAllTransportComponents() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"listAllTransportComponents");
- return mTransportManager.getAllTransportCompenents();
+ return mTransportManager.getAllTransportComponents();
}
@Override
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index fb2982eb0baa..321ef2652514 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -41,10 +41,12 @@ import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -57,7 +59,9 @@ public class TransportManager {
private static final String TAG = "BackupTransportManager";
- private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
+ @VisibleForTesting
+ /* package */ static final String SERVICE_ACTION_TRANSPORT_HOST =
+ "android.backup.TRANSPORT_HOST";
private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec
private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins
@@ -95,7 +99,11 @@ public class TransportManager {
TransportBoundListener listener, Looper looper) {
mContext = context;
mPackageManager = context.getPackageManager();
- mTransportWhitelist = (whitelist != null) ? whitelist : new ArraySet<>();
+ if (whitelist != null) {
+ mTransportWhitelist = whitelist;
+ } else {
+ mTransportWhitelist = new ArraySet<>();
+ }
mCurrentTransportName = defaultTransport;
mTransportBoundListener = listener;
mHandler = new RebindOnTimeoutHandler(looper);
@@ -186,7 +194,7 @@ public class TransportManager {
}
}
- ComponentName[] getAllTransportCompenents() {
+ ComponentName[] getAllTransportComponents() {
synchronized (mTransportLock) {
return mValidTransports.keySet().toArray(new ComponentName[mValidTransports.size()]);
}
@@ -208,7 +216,8 @@ public class TransportManager {
}
}
- void ensureTransportReady(ComponentName transportComponent, SelectBackupTransportCallback listener) {
+ void ensureTransportReady(ComponentName transportComponent,
+ SelectBackupTransportCallback listener) {
synchronized (mTransportLock) {
TransportConnection conn = mValidTransports.get(transportComponent);
if (conn == null) {
@@ -252,7 +261,7 @@ public class TransportManager {
intent, 0, UserHandle.USER_SYSTEM);
if (hosts != null) {
for (ResolveInfo host : hosts) {
- final ComponentName infoComponentName = host.serviceInfo.getComponentName();
+ final ComponentName infoComponentName = getComponentName(host.serviceInfo);
boolean shouldBind = false;
if (components != null && packageName != null) {
for (String component : components) {
@@ -310,7 +319,7 @@ public class TransportManager {
Intent intent = new Intent(mTransportServiceIntent)
.setComponent(componentName);
return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
- UserHandle.SYSTEM);
+ createSystemUserHandle());
}
private class TransportConnection implements ServiceConnection {
@@ -333,7 +342,7 @@ public class TransportManager {
boolean success = false;
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
- component.flattenToShortString(), 1);
+ component.flattenToShortString(), 1);
try {
mTransportName = mBinder.name();
@@ -437,7 +446,8 @@ public class TransportManager {
}
private long getRebindTimeout() {
- final boolean isDeviceProvisioned = Settings.Global.getInt(mContext.getContentResolver(),
+ final boolean isDeviceProvisioned = Settings.Global.getInt(
+ mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0) != 0;
return isDeviceProvisioned
? REBINDING_TIMEOUT_PROVISIONED_MS
@@ -465,7 +475,7 @@ public class TransportManager {
synchronized (mTransportLock) {
if (mBoundTransports.containsValue(transportComponent)) {
Slog.d(TAG, "Explicit rebinding timeout passed, but already bound to "
- + componentShortString + " so not attempting to rebind");
+ + componentShortString + " so not attempting to rebind");
return;
}
Slog.d(TAG, "Explicit rebinding timeout passed, attempting rebinding to: "
@@ -492,4 +502,18 @@ public class TransportManager {
Slog.v(TAG, message);
}
}
+
+ // These only exists to make it testable with Robolectric, which is not updated to API level 24
+ // yet.
+ // TODO: Get rid of this once Robolectric is updated.
+ private static ComponentName getComponentName(ServiceInfo serviceInfo) {
+ return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ }
+
+ // These only exists to make it testable with Robolectric, which is not updated to API level 24
+ // yet.
+ // TODO: Get rid of this once Robolectric is updated.
+ private static UserHandle createSystemUserHandle() {
+ return new UserHandle(UserHandle.USER_SYSTEM);
+ }
}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 473384081656..046eb761d1c0 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -728,6 +728,9 @@ public class VibratorService extends IVibratorService.Stub
return timeout;
}
}
+ if (!prebaked.shouldFallback()) {
+ return 0;
+ }
final int id = prebaked.getId();
if (id < 0 || id >= mFallbackEffects.length || mFallbackEffects[id] == null) {
Slog.w(TAG, "Failed to play prebaked effect, no fallback");
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index c481efc3b0ea..651d3a6a044e 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -653,14 +653,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
}
}
- void updatePictureInPictureMode(Rect targetStackBounds) {
+ void updatePictureInPictureMode(Rect targetStackBounds, boolean forceUpdate) {
if (task == null || task.getStack() == null || app == null || app.thread == null) {
return;
}
final boolean inPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID) &&
(targetStackBounds != null);
- if (inPictureInPictureMode != mLastReportedPictureInPictureMode) {
+ if (inPictureInPictureMode != mLastReportedPictureInPictureMode || forceUpdate) {
// Picture-in-picture mode changes also trigger a multi-window mode change as well, so
// update that here in order
mLastReportedPictureInPictureMode = inPictureInPictureMode;
@@ -675,8 +675,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
private void schedulePictureInPictureModeChanged(Configuration overrideConfig) {
try {
app.thread.schedulePictureInPictureModeChanged(appToken,
- mLastReportedPictureInPictureMode,
- overrideConfig);
+ mLastReportedPictureInPictureMode, overrideConfig);
} catch (Exception e) {
// If process died, no one cares.
}
@@ -1510,7 +1509,12 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
}
void notifyUnknownVisibilityLaunched() {
- mWindowContainerController.notifyUnknownVisibilityLaunched();
+
+ // No display activities never add a window, so there is no point in waiting them for
+ // relayout.
+ if (!noDisplay) {
+ mWindowContainerController.notifyUnknownVisibilityLaunched();
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 01a6b54522e3..e4a2273387be 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1269,6 +1269,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
r.app = app;
+ if (mKeyguardController.isKeyguardLocked()) {
+ r.notifyUnknownVisibilityLaunched();
+ }
+
// Have the window manager re-evaluate the orientation of the screen based on the new
// activity order. Note that as a result of this, it can call back into the activity
// manager with a new orientation. We don't care about that, because the activity is
@@ -1295,9 +1299,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
r.setVisibility(true);
}
- if (mKeyguardController.isKeyguardLocked()) {
- r.notifyUnknownVisibilityLaunched();
- }
final int applicationInfoUid =
(r.info.applicationInfo != null) ? r.info.applicationInfo.uid : -1;
if ((r.userId != app.userId) || (r.appInfo.uid != applicationInfoUid)) {
@@ -4120,31 +4121,29 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return;
}
- scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds, false /* immediate */);
+ scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds);
}
- void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds,
- boolean immediate) {
-
- if (immediate) {
- mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG);
- for (int i = task.mActivities.size() - 1; i >= 0; i--) {
- final ActivityRecord r = task.mActivities.get(i);
- if (r.app != null && r.app.thread != null) {
- r.updatePictureInPictureMode(targetStackBounds);
- }
- }
- } else {
- for (int i = task.mActivities.size() - 1; i >= 0; i--) {
- final ActivityRecord r = task.mActivities.get(i);
- if (r.app != null && r.app.thread != null) {
- mPipModeChangedActivities.add(r);
- }
+ void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds) {
+ for (int i = task.mActivities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = task.mActivities.get(i);
+ if (r.app != null && r.app.thread != null) {
+ mPipModeChangedActivities.add(r);
}
- mPipModeChangedTargetStackBounds = targetStackBounds;
+ }
+ mPipModeChangedTargetStackBounds = targetStackBounds;
- if (!mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) {
- mHandler.sendEmptyMessage(REPORT_PIP_MODE_CHANGED_MSG);
+ if (!mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) {
+ mHandler.sendEmptyMessage(REPORT_PIP_MODE_CHANGED_MSG);
+ }
+ }
+
+ void updatePictureInPictureMode(TaskRecord task, Rect targetStackBounds, boolean forceUpdate) {
+ mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG);
+ for (int i = task.mActivities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = task.mActivities.get(i);
+ if (r.app != null && r.app.thread != null) {
+ r.updatePictureInPictureMode(targetStackBounds, forceUpdate);
}
}
}
@@ -4206,7 +4205,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
synchronized (mService) {
for (int i = mPipModeChangedActivities.size() - 1; i >= 0; i--) {
final ActivityRecord r = mPipModeChangedActivities.remove(i);
- r.updatePictureInPictureMode(mPipModeChangedTargetStackBounds);
+ r.updatePictureInPictureMode(mPipModeChangedTargetStackBounds,
+ false /* forceUpdate */);
}
}
} break;
diff --git a/services/core/java/com/android/server/am/PinnedActivityStack.java b/services/core/java/com/android/server/am/PinnedActivityStack.java
index a1b95f9de05a..86ee3f4ba215 100644
--- a/services/core/java/com/android/server/am/PinnedActivityStack.java
+++ b/services/core/java/com/android/server/am/PinnedActivityStack.java
@@ -92,15 +92,16 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController>
return mWindowContainerController.deferScheduleMultiWindowModeChanged();
}
- public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {
+ public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
+ boolean forceUpdate) {
// It is guaranteed that the activities requiring the update will be in the pinned stack at
// this point (either reparented before the animation into PiP, or before reparenting after
// the animation out of PiP)
synchronized(this) {
ArrayList<TaskRecord> tasks = getAllTasks();
for (int i = 0; i < tasks.size(); i++ ) {
- mStackSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(tasks.get(i),
- targetStackBounds, true /* immediate */);
+ mStackSupervisor.updatePictureInPictureMode(tasks.get(i), targetStackBounds,
+ forceUpdate);
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index faf4729ead35..91b15912fcb5 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -801,12 +801,23 @@ public class AudioService extends IAudioService.Stub
public void systemReady() {
sendMsg(mAudioHandler, MSG_SYSTEM_READY, SENDMSG_QUEUE,
0, 0, null, 0);
- try {
- ActivityManager.getService().registerUidObserver(mUidObserver,
- ActivityManager.UID_OBSERVER_CACHED | ActivityManager.UID_OBSERVER_GONE,
- ActivityManager.PROCESS_STATE_UNKNOWN, null);
- } catch (RemoteException e) {
- // ignored; both services live in system_server
+ if (false) {
+ // This is turned off for now, because it is racy and thus causes apps to break.
+ // Currently banning a uid means that if an app tries to start playing an audio
+ // stream, that will be preventing, and unbanning it will not allow that stream
+ // to resume. However these changes in uid state are racy with what the app is doing,
+ // so that after taking a process out of the cached state we can't guarantee that
+ // we will unban the uid before the app actually tries to start playing audio.
+ // (To do that, the activity manager would need to wait until it knows for sure
+ // that the ban has been removed, before telling the app to do whatever it is
+ // supposed to do that caused it to go out of the cached state.)
+ try {
+ ActivityManager.getService().registerUidObserver(mUidObserver,
+ ActivityManager.UID_OBSERVER_CACHED | ActivityManager.UID_OBSERVER_GONE,
+ ActivityManager.PROCESS_STATE_UNKNOWN, null);
+ } catch (RemoteException e) {
+ // ignored; both services live in system_server
+ }
}
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 79bed73a0ca0..c6998d6a108c 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -520,11 +520,12 @@ public final class JobSchedulerService extends com.android.server.SystemService
if (DEBUG) {
Slog.d(TAG, "Receieved: " + action);
}
+ final String pkgName = getPackageName(intent);
+ final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+
if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
// Purge the app's jobs if the whole package was just disabled. When this is
// the case the component name will be a bare package name.
- final String pkgName = getPackageName(intent);
- final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
if (pkgName != null && pkgUid != -1) {
final String[] changedComponents = intent.getStringArrayExtra(
Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
@@ -544,7 +545,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
Slog.d(TAG, "Removing jobs for package " + pkgName
+ " in user " + userId);
}
- cancelJobsForUid(pkgUid, "app package state changed");
+ cancelJobsForPackageAndUid(pkgName, pkgUid,
+ "app disabled");
}
} catch (RemoteException|IllegalArgumentException e) {
/*
@@ -573,7 +575,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
if (DEBUG) {
Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
}
- cancelJobsForUid(uidRemoved, "app uninstalled");
+ cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled");
}
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
@@ -584,8 +586,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
} else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
// Has this package scheduled any jobs, such that we will take action
// if it were to be force-stopped?
- final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
- final String pkgName = intent.getData().getSchemeSpecificPart();
if (pkgUid != -1) {
List<JobStatus> jobsForUid;
synchronized (mLock) {
@@ -604,13 +604,11 @@ public final class JobSchedulerService extends com.android.server.SystemService
}
} else if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
// possible force-stop
- final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
- final String pkgName = intent.getData().getSchemeSpecificPart();
if (pkgUid != -1) {
if (DEBUG) {
Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
}
- cancelJobsForPackageAndUid(pkgName, pkgUid);
+ cancelJobsForPackageAndUid(pkgName, pkgUid, "app force stopped");
}
}
}
@@ -790,13 +788,17 @@ public final class JobSchedulerService extends com.android.server.SystemService
}
}
- void cancelJobsForPackageAndUid(String pkgName, int uid) {
+ void cancelJobsForPackageAndUid(String pkgName, int uid, String reason) {
+ if ("android".equals(pkgName)) {
+ Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
+ return;
+ }
synchronized (mLock) {
final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
for (int i = jobsForUid.size() - 1; i >= 0; i--) {
final JobStatus job = jobsForUid.get(i);
if (job.getSourcePackageName().equals(pkgName)) {
- cancelJobImplLocked(job, null, "app force stopped");
+ cancelJobImplLocked(job, null, reason);
}
}
}
@@ -811,8 +813,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
*/
public void cancelJobsForUid(int uid, String reason) {
if (uid == Process.SYSTEM_UID) {
- // This really shouldn't happen.
- Slog.wtfStack(TAG, "cancelJobsForUid() called for system uid");
+ Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
return;
}
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 922df1e3dba8..3795b7f3091c 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -18,9 +18,7 @@ package com.android.server.media;
import com.android.internal.util.DumpUtils;
import com.android.server.Watchdog;
-import com.android.server.media.AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener;
-import android.Manifest;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -96,9 +94,10 @@ public final class MediaRouterService extends IMediaRouterService.Stub
private final ArrayMap<IBinder, ClientRecord> mAllClientRecords =
new ArrayMap<IBinder, ClientRecord>();
private int mCurrentUserId = -1;
- private boolean mHasBluetoothRoute = false;
+ private boolean mGlobalBluetoothA2dpOn = false;
private final IAudioService mAudioService;
private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+ private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
public MediaRouterService(Context context) {
mContext = context;
@@ -137,13 +136,39 @@ public final class MediaRouterService extends IMediaRouterService.Stub
audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() {
@Override
public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
- mHasBluetoothRoute = newRoutes.bluetoothName != null;
+ synchronized (mLock) {
+ if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) {
+ if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
+ | AudioRoutesInfo.MAIN_HEADPHONES
+ | AudioRoutesInfo.MAIN_USB)) == 0) {
+ // headset was plugged out.
+ mGlobalBluetoothA2dpOn = newRoutes.bluetoothName != null;
+ } else {
+ // headset was plugged in.
+ mGlobalBluetoothA2dpOn = false;
+ }
+ mCurAudioRoutesInfo.mainType = newRoutes.mainType;
+ }
+ if (!TextUtils.equals(
+ newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
+ if (newRoutes.bluetoothName == null) {
+ // BT was disconnected.
+ mGlobalBluetoothA2dpOn = false;
+ } else {
+ // BT was connected or changed.
+ mGlobalBluetoothA2dpOn = true;
+ }
+ mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
+ }
+ }
}
});
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in the audio service.");
}
- mHasBluetoothRoute = (audioRoutes != null && audioRoutes.bluetoothName != null);
+ synchronized (mLock) {
+ mGlobalBluetoothA2dpOn = (audioRoutes != null && audioRoutes.bluetoothName != null);
+ }
}
public void systemRunning() {
@@ -246,6 +271,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
+ public boolean isGlobalBluetoothA2doOn() {
+ synchronized (mLock) {
+ return mGlobalBluetoothA2dpOn;
+ }
+ }
+
+ // Binder call
+ @Override
public void setDiscoveryRequest(IMediaRouterClient client,
int routeTypes, boolean activeScan) {
if (client == null) {
@@ -346,7 +379,12 @@ public final class MediaRouterService extends IMediaRouterService.Stub
void restoreBluetoothA2dp() {
try {
- mAudioService.setBluetoothA2dpOn(mHasBluetoothRoute);
+ boolean a2dpOn = false;
+ synchronized (mLock) {
+ a2dpOn = mGlobalBluetoothA2dpOn;
+ }
+ Slog.v(TAG, "restoreBluetoothA2dp( " + a2dpOn + ")");
+ mAudioService.setBluetoothA2dpOn(a2dpOn);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
}
@@ -354,12 +392,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub
void restoreRoute(int uid) {
ClientRecord clientRecord = null;
- UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid));
- if (userRecord != null && userRecord.mClientRecords != null) {
- for (ClientRecord cr : userRecord.mClientRecords) {
- if (validatePackageName(uid, cr.mPackageName)) {
- clientRecord = cr;
- break;
+ synchronized (mLock) {
+ UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid));
+ if (userRecord != null && userRecord.mClientRecords != null) {
+ for (ClientRecord cr : userRecord.mClientRecords) {
+ if (validatePackageName(uid, cr.mPackageName)) {
+ clientRecord = cr;
+ break;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 04d91f882d04..807c343d0d10 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -92,26 +92,10 @@ class IdmapManager {
return new File(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath())).isFile();
}
- boolean isDangerous(@NonNull final PackageInfo overlayPackage, final int userId) {
- // unused userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible
- return isDangerous(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath()));
- }
-
private String getIdmapPath(@NonNull final String baseCodePath) {
final StringBuilder sb = new StringBuilder("/data/resource-cache/");
sb.append(baseCodePath.substring(1).replace('/', '@'));
sb.append("@idmap");
return sb.toString();
}
-
- private boolean isDangerous(@NonNull final String idmapPath) {
- try (DataInputStream dis = new DataInputStream(new FileInputStream(idmapPath))) {
- final int magic = dis.readInt();
- final int version = dis.readInt();
- final int dangerous = dis.readInt();
- return dangerous != 0;
- } catch (IOException e) {
- return true;
- }
- }
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d21100c0cf72..68913c3a887a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -7820,13 +7820,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case HapticFeedbackConstants.VIRTUAL_KEY:
return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
- return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+ return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false);
case HapticFeedbackConstants.KEYBOARD_PRESS: // == HapticFeedbackConstants.KEYBOARD_TAP
return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
case HapticFeedbackConstants.KEYBOARD_RELEASE:
- return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+ return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false);
case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
- return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+ return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false);
default:
return null;
}
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index c2edc04db2e1..c76b90593ab3 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -450,7 +450,7 @@ public class AppWindowAnimator {
return isAnimating;
}
- void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+ void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken);
pw.print(prefix); pw.print("mAnimator="); pw.println(mAnimator);
pw.print(prefix); pw.print("freezingScreen="); pw.print(freezingScreen);
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index f628d5e6de27..66e0a152e09b 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -365,7 +365,6 @@ public class AppWindowContainerController
// Now that the app is going invisible, we can remove it. It will be restarted
// if made visible again.
wtoken.removeDeadWindows();
- mService.mUnknownAppVisibilityController.appRemovedOrHidden(wtoken);
} else {
if (!mService.mAppTransition.isTransitionSet()
&& mService.mAppTransition.isReady()) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index d625003305b6..f70035ca9c92 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -362,10 +362,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
boolean runningAppAnimation = false;
+ if (mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
+ mAppAnimator.setNullAnimation();
+ }
if (transit != AppTransition.TRANSIT_UNSET) {
- if (mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
- mAppAnimator.setNullAnimation();
- }
if (mService.applyAnimationLocked(this, lp, transit, visible, isVoiceInteraction)) {
delayed = runningAppAnimation = true;
}
@@ -1618,6 +1618,9 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
if (mRemovingFromDisplay) {
pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay);
}
+ if (mAppAnimator.isAnimating()) {
+ mAppAnimator.dump(pw, prefix + " ");
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index cff2fadd7649..7953ee430934 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -21,7 +21,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.animation.AnimationHandler;
-import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
@@ -32,13 +31,11 @@ import android.os.IBinder;
import android.os.Debug;
import android.util.ArrayMap;
import android.util.Slog;
-import android.view.Choreographer;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.WindowManagerInternal;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -142,9 +139,6 @@ public class BoundsAnimationController {
// True if this this animation was canceled and will be replaced the another animation from
// the same {@link #BoundsAnimationTarget} target.
private boolean mSkipFinalResize;
- // True if this animation replaced a previous animation of the same
- // {@link #BoundsAnimationTarget} target.
- private final boolean mSkipAnimationStart;
// True if this animation was canceled by the user, not as a part of a replacing animation
private boolean mSkipAnimationEnd;
@@ -159,6 +153,7 @@ public class BoundsAnimationController {
// Whether to schedule PiP mode changes on animation start/end
private @SchedulePipModeChangedState int mSchedulePipModeChangedState;
+ private @SchedulePipModeChangedState int mPrevSchedulePipModeChangedState;
// Depending on whether we are animating from
// a smaller to a larger size
@@ -171,14 +166,14 @@ public class BoundsAnimationController {
BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to,
@SchedulePipModeChangedState int schedulePipModeChangedState,
- boolean moveFromFullscreen, boolean moveToFullscreen,
- boolean replacingExistingAnimation) {
+ @SchedulePipModeChangedState int prevShedulePipModeChangedState,
+ boolean moveFromFullscreen, boolean moveToFullscreen) {
super();
mTarget = target;
mFrom.set(from);
mTo.set(to);
- mSkipAnimationStart = replacingExistingAnimation;
mSchedulePipModeChangedState = schedulePipModeChangedState;
+ mPrevSchedulePipModeChangedState = prevShedulePipModeChangedState;
mMoveFromFullscreen = moveFromFullscreen;
mMoveToFullscreen = moveToFullscreen;
addUpdateListener(this);
@@ -200,7 +195,7 @@ public class BoundsAnimationController {
@Override
public void onAnimationStart(Animator animation) {
if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
- + " mSkipAnimationStart=" + mSkipAnimationStart
+ + " mPrevSchedulePipModeChangedState=" + mPrevSchedulePipModeChangedState
+ " mSchedulePipModeChangedState=" + mSchedulePipModeChangedState);
mFinishAnimationAfterTransition = false;
mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth,
@@ -210,18 +205,26 @@ public class BoundsAnimationController {
// running
updateBooster();
- // Ensure that we have prepared the target for animation before
- // we trigger any size changes, so it can swap surfaces
- // in to appropriate modes, or do as it wishes otherwise.
- if (!mSkipAnimationStart) {
+ // Ensure that we have prepared the target for animation before we trigger any size
+ // changes, so it can swap surfaces in to appropriate modes, or do as it wishes
+ // otherwise.
+ if (mPrevSchedulePipModeChangedState == NO_PIP_MODE_CHANGED_CALLBACKS) {
mTarget.onAnimationStart(mSchedulePipModeChangedState ==
- SCHEDULE_PIP_MODE_CHANGED_ON_START);
+ SCHEDULE_PIP_MODE_CHANGED_ON_START, false /* forceUpdate */);
// When starting an animation from fullscreen, pause here and wait for the
// windows-drawn signal before we start the rest of the transition down into PiP.
if (mMoveFromFullscreen) {
pause();
}
+ } else if (mPrevSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END &&
+ mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
+ // We are replacing a running animation into PiP, but since it hasn't completed, the
+ // client will not currently receive any picture-in-picture mode change callbacks.
+ // However, we still need to report to them that they are leaving PiP, so this will
+ // force an update via a mode changed callback.
+ mTarget.onAnimationStart(true /* schedulePipModeChangedCallback */,
+ true /* forceUpdate */);
}
// Immediately update the task bounds if they have to become larger, but preserve
@@ -388,6 +391,8 @@ public class BoundsAnimationController {
boolean moveFromFullscreen, boolean moveToFullscreen) {
final BoundsAnimator existing = mRunningAnimations.get(target);
final boolean replacing = existing != null;
+ @SchedulePipModeChangedState int prevSchedulePipModeChangedState =
+ NO_PIP_MODE_CHANGED_CALLBACKS;
if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
+ " schedulePipModeChangedState=" + schedulePipModeChangedState
@@ -403,6 +408,9 @@ public class BoundsAnimationController {
return existing;
}
+ // Save the previous state
+ prevSchedulePipModeChangedState = existing.mSchedulePipModeChangedState;
+
// Update the PiP callback states if we are replacing the animation
if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
@@ -428,7 +436,8 @@ public class BoundsAnimationController {
existing.cancel();
}
final BoundsAnimator animator = new BoundsAnimator(target, from, to,
- schedulePipModeChangedState, moveFromFullscreen, moveToFullscreen, replacing);
+ schedulePipModeChangedState, prevSchedulePipModeChangedState,
+ moveFromFullscreen, moveToFullscreen);
mRunningAnimations.put(target, animator);
animator.setFloatValues(0f, 1f);
animator.setDuration((animationDuration != -1 ? animationDuration
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
index 8b1bf7bf77dc..647a2d6deac6 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
@@ -31,7 +31,7 @@ interface BoundsAnimationTarget {
* @param schedulePipModeChangedCallback whether or not to schedule the PiP mode changed
* callbacks
*/
- void onAnimationStart(boolean schedulePipModeChangedCallback);
+ void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate);
/**
* Sets the size of the target (without any intermediate steps, like scheduling animation)
diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
index 135f4000dd81..b5c9b99b35d6 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
@@ -204,10 +204,12 @@ public class PinnedStackWindowController extends StackWindowController {
*/
/** Calls directly into activity manager so window manager lock shouldn't held. */
- public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {
+ public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
+ boolean forceUpdate) {
if (mListener != null) {
PinnedStackWindowListener listener = (PinnedStackWindowListener) mListener;
- listener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds);
+ listener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds,
+ forceUpdate);
}
}
}
diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowListener.java b/services/core/java/com/android/server/wm/PinnedStackWindowListener.java
index 12b9c1f0c552..33e8a60329bf 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowListener.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowListener.java
@@ -28,5 +28,6 @@ public interface PinnedStackWindowListener extends StackWindowListener {
* Called when the stack container pinned stack animation will change the picture-in-picture
* mode. This is a direct call into ActivityManager.
*/
- default void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {}
+ default void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
+ boolean forceUpdate) {}
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 33eb0a92e872..6ec7565ab156 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1505,7 +1505,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
@Override // AnimatesBounds
- public void onAnimationStart(boolean schedulePipModeChangedCallback) {
+ public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) {
// Hold the lock since this is called from the BoundsAnimator running on the UiThread
synchronized (mService.mWindowMap) {
mBoundsAnimatingRequested = false;
@@ -1530,9 +1530,11 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
final PinnedStackWindowController controller =
(PinnedStackWindowController) getController();
if (schedulePipModeChangedCallback && controller != null) {
- // We need to schedule the PiP mode change after the animation down, so use the
- // final bounds
- controller.updatePictureInPictureModeForPinnedStackAnimation(null);
+ // We need to schedule the PiP mode change before the animation up. It is possible
+ // in this case for the animation down to not have been completed, so always
+ // force-schedule and update to the client to ensure that it is notified that it
+ // is no longer in picture-in-picture mode
+ controller.updatePictureInPictureModeForPinnedStackAnimation(null, forceUpdate);
}
}
}
@@ -1560,7 +1562,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
// We need to schedule the PiP mode change after the animation down, so use the
// final bounds
controller.updatePictureInPictureModeForPinnedStackAnimation(
- mBoundsAnimationTarget);
+ mBoundsAnimationTarget, false /* forceUpdate */);
}
if (finalStackSize != null) {
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 0370490668ec..d2f374dd9e08 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -153,9 +153,9 @@ static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength
if (status == Status::OK) {
return lengthMs;
} else if (status != Status::UNSUPPORTED_OPERATION) {
- // Don't warn on UNSUPPORTED_OPERATION, that's a normal even and just means the motor
- // doesn't have a pre-defined waveform to perform for it, so we should just fall back
- // to the framework waveforms.
+ // Don't warn on UNSUPPORTED_OPERATION, that's a normal event and just means the motor
+ // doesn't have a pre-defined waveform to perform for it, so we should just give the
+ // opportunity to fall back to the framework waveforms.
ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
", error=%" PRIu32 ").", static_cast<int64_t>(effect),
static_cast<int32_t>(strength), static_cast<uint32_t>(status));
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
new file mode 100644
index 000000000000..3e82d3ee1d2f
--- /dev/null
+++ b/services/robotests/Android.mk
@@ -0,0 +1,76 @@
+# 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.
+
+
+############################################################
+# FrameworksServicesLib app just for Robolectric test target. #
+############################################################
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := FrameworksServicesLib
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ frameworks-base-testutils \
+ services.backup \
+ services.core \
+ android-support-test \
+ mockito-target-minus-junit4 \
+ platform-test-annotations \
+ truth-prebuilt
+
+include $(BUILD_PACKAGE)
+
+#############################################
+# FrameworksServices Robolectric test target. #
+#############################################
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Include the testing libraries (JUnit4 + Robolectric libs).
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ platform-system-robolectric \
+ truth-prebuilt
+
+LOCAL_JAVA_LIBRARIES := \
+ junit \
+ platform-robolectric-prebuilt
+
+LOCAL_INSTRUMENTATION_FOR := FrameworksServicesLib
+LOCAL_MODULE := FrameworksServicesRoboTests
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+#############################################################
+# FrameworksServices runner target to run the previous target. #
+#############################################################
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := RunFrameworksServicesRoboTests
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ FrameworksServicesRoboTests
+
+LOCAL_TEST_PACKAGE := FrameworksServicesLib
+
+include prebuilts/misc/common/robolectric/run_robotests.mk
diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
new file mode 100644
index 000000000000..0f7a091a8315
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 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.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.RequiresPermission;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.UserHandle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.res.ResourceLoader;
+import org.robolectric.res.builder.DefaultPackageManager;
+import org.robolectric.res.builder.RobolectricPackageManager;
+import org.robolectric.shadows.ShadowContextImpl;
+import org.robolectric.shadows.ShadowLooper;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(
+ manifest = Config.NONE,
+ sdk = 23,
+ shadows = {TransportManagerTest.ShadowContextImplWithBindServiceAsUser.class}
+)
+public class TransportManagerTest {
+ private static final String PACKAGE_NAME = "some.package.name";
+ private static final String TRANSPORT1_NAME = "transport1.name";
+ private static final String TRANSPORT2_NAME = "transport2.name";
+ private static final ComponentName TRANSPORT1_COMPONENT_NAME = new ComponentName(PACKAGE_NAME,
+ TRANSPORT1_NAME);
+ private static final ComponentName TRANSPORT2_COMPONENT_NAME = new ComponentName(PACKAGE_NAME,
+ TRANSPORT2_NAME);
+ private static final List<ComponentName> TRANSPORTS_COMPONENT_NAMES = Arrays.asList(
+ TRANSPORT1_COMPONENT_NAME, TRANSPORT2_COMPONENT_NAME);
+
+ private RobolectricPackageManager mPackageManager;
+
+ @Mock private TransportManager.TransportBoundListener mTransportBoundListener;
+
+ @Before
+ public void setUp() {
+ mPackageManager = new DefaultPackageManagerWithQueryIntentServicesAsUser(
+ RuntimeEnvironment.getAppResourceLoader());
+ RuntimeEnvironment.setRobolectricPackageManager(mPackageManager);
+ }
+
+ @Test
+ public void onPackageAdded_bindsToAllTransports() {
+ Intent intent = new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST);
+ intent.setPackage(PACKAGE_NAME);
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = PACKAGE_NAME;
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+
+ mPackageManager.addPackage(packageInfo);
+
+ ResolveInfo transport1 = new ResolveInfo();
+ transport1.serviceInfo = new ServiceInfo();
+ transport1.serviceInfo.packageName = PACKAGE_NAME;
+ transport1.serviceInfo.name = TRANSPORT1_NAME;
+
+ ResolveInfo transport2 = new ResolveInfo();
+ transport2.serviceInfo = new ServiceInfo();
+ transport2.serviceInfo.packageName = PACKAGE_NAME;
+ transport2.serviceInfo.name = TRANSPORT2_NAME;
+
+ mPackageManager.addResolveInfoForIntent(intent, Arrays.asList(transport1, transport2));
+
+ TransportManager transportManager = new TransportManager(
+ RuntimeEnvironment.application.getApplicationContext(),
+ new HashSet<>(TRANSPORTS_COMPONENT_NAMES),
+ null,
+ mTransportBoundListener,
+ ShadowLooper.getMainLooper());
+ transportManager.onPackageAdded(PACKAGE_NAME);
+
+ assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
+ TRANSPORTS_COMPONENT_NAMES);
+ }
+
+ private static class DefaultPackageManagerWithQueryIntentServicesAsUser extends
+ DefaultPackageManager {
+
+ /* package */ DefaultPackageManagerWithQueryIntentServicesAsUser(
+ ResourceLoader appResourceLoader) {
+ super(appResourceLoader);
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
+ return super.queryIntentServices(intent, flags);
+ }
+ }
+
+ @Implements(className = ShadowContextImpl.CLASS_NAME)
+ public static class ShadowContextImplWithBindServiceAsUser extends ShadowContextImpl {
+
+ @Implementation
+ public boolean bindServiceAsUser(@RequiresPermission Intent service, ServiceConnection conn,
+ int flags, UserHandle user) {
+ return true;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
new file mode 100644
index 000000000000..50824e32e50d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2017 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.accessibility;
+
+import static android.util.ExceptionUtils.propagate;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
+
+import static com.android.server.testutils.TestUtils.strictMock;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.DebugUtils;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.IntConsumer;
+
+
+@RunWith(AndroidJUnit4.class)
+public class MagnificationGestureHandlerTest {
+
+ public static final int STATE_IDLE = 1;
+ public static final int STATE_ZOOMED = 2;
+ public static final int STATE_2TAPS = 3;
+ public static final int STATE_ZOOMED_2TAPS = 4;
+ public static final int STATE_SHORTCUT_TRIGGERED = 5;
+ public static final int STATE_DRAGGING_TMP = 6;
+ public static final int STATE_DRAGGING = 7;
+ public static final int STATE_PANNING = 8;
+ public static final int STATE_SCALING_AND_PANNING = 9;
+
+
+ public static final int FIRST_STATE = STATE_IDLE;
+ public static final int LAST_STATE = STATE_SCALING_AND_PANNING;
+
+ // Co-prime x and y, to potentially catch x-y-swapped errors
+ public static final float DEFAULT_X = 301;
+ public static final float DEFAULT_Y = 299;
+
+ private Context mContext;
+ private AccessibilityManagerService mAms;
+ private MagnificationController mMagnificationController;
+ private OffsettableClock mClock;
+ private MagnificationGestureHandler mMgh;
+ private TestHandler mHandler;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getContext();
+ mAms = new AccessibilityManagerService(mContext);
+ mMagnificationController = new MagnificationController(
+ mContext, mAms, /* lock */ new Object()) {
+ @Override
+ public boolean magnificationRegionContains(float x, float y) {
+ return true;
+ }
+
+ @Override
+ void setForceShowMagnifiableBounds(boolean show) {}
+ };
+ mMagnificationController.mRegistered = true;
+ mClock = new OffsettableClock.Stopped();
+
+ boolean detectTripleTap = true;
+ boolean detectShortcutTrigger = true;
+ mMgh = newInstance(detectTripleTap, detectShortcutTrigger);
+ }
+
+ @NonNull
+ public MagnificationGestureHandler newInstance(boolean detectTripleTap,
+ boolean detectShortcutTrigger) {
+ MagnificationGestureHandler h = new MagnificationGestureHandler(
+ mContext, mMagnificationController,
+ detectTripleTap, detectShortcutTrigger);
+ mHandler = new TestHandler(h.mDetectingStateHandler, mClock);
+ h.mDetectingStateHandler.mHandler = mHandler;
+ h.setNext(strictMock(EventStreamTransformation.class));
+ return h;
+ }
+
+ @Test
+ public void testInitialState_isIdle() {
+ assertIn(STATE_IDLE);
+ }
+
+ /**
+ * Covers paths to get to and back between each state and {@link #STATE_IDLE}
+ * This navigates between states using "canonical" paths, specified in
+ * {@link #goFromStateIdleTo} (for traversing away from {@link #STATE_IDLE}) and
+ * {@link #returnToNormalFrom} (for navigating back to {@link #STATE_IDLE})
+ */
+ @Test
+ public void testEachState_isReachableAndRecoverable() {
+ forEachState(state -> {
+ goFromStateIdleTo(state);
+ assertIn(state);
+
+ returnToNormalFrom(state);
+ try {
+ assertIn(STATE_IDLE);
+ } catch (AssertionError e) {
+ throw new AssertionError("Failed while testing state " + stateToString(state), e);
+ }
+ });
+ }
+
+ @Test
+ public void testStates_areMutuallyExclusive() {
+ forEachState(state1 -> {
+ forEachState(state2 -> {
+ if (state1 < state2) {
+ goFromStateIdleTo(state1);
+ try {
+ assertIn(state2);
+ fail("State " + stateToString(state1) + " also implies state "
+ + stateToString(state2) + stateDump());
+ } catch (AssertionError e) {
+ // expected
+ returnToNormalFrom(state1);
+ }
+ }
+ });
+ });
+ }
+
+ /**
+ * Covers edges of the graph not covered by "canonical" transitions specified in
+ * {@link #goFromStateIdleTo} and {@link #returnToNormalFrom}
+ */
+ @SuppressWarnings("Convert2MethodRef")
+ @Test
+ public void testAlternativeTransitions_areWorking() {
+ // A11y button followed by a tap&hold turns temporary "viewport dragging" zoom on
+ assertTransition(STATE_SHORTCUT_TRIGGERED, () -> {
+ send(downEvent());
+ fastForward1sec();
+ }, STATE_DRAGGING_TMP);
+
+ // A11y button followed by a tap turns zoom on
+ assertTransition(STATE_SHORTCUT_TRIGGERED, () -> tap(), STATE_ZOOMED);
+
+ // A11y button pressed second time negates the 1st press
+ assertTransition(STATE_SHORTCUT_TRIGGERED, () -> triggerShortcut(), STATE_IDLE);
+
+ // A11y button turns zoom off
+ assertTransition(STATE_ZOOMED, () -> triggerShortcut(), STATE_IDLE);
+
+
+ // Double tap times out while zoomed
+ assertTransition(STATE_ZOOMED_2TAPS, () -> {
+ allowEventDelegation();
+ fastForward1sec();
+ }, STATE_ZOOMED);
+
+ // tap+tap+swipe gets delegated
+ assertTransition(STATE_2TAPS, () -> {
+ allowEventDelegation();
+ swipe();
+ }, STATE_IDLE);
+ }
+
+ @Test
+ public void testNonTransitions_dontChangeState() {
+ // ACTION_POINTER_DOWN triggers event delegation if not magnifying
+ assertStaysIn(STATE_IDLE, () -> {
+ allowEventDelegation();
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
+ });
+
+ // Long tap breaks the triple-tap detection sequence
+ Runnable tapAndLongTap = () -> {
+ allowEventDelegation();
+ tap();
+ longTap();
+ };
+ assertStaysIn(STATE_IDLE, tapAndLongTap);
+ assertStaysIn(STATE_ZOOMED, tapAndLongTap);
+
+ // Triple tap with delays in between doesn't count
+ Runnable slow3tap = () -> {
+ tap();
+ fastForward1sec();
+ tap();
+ fastForward1sec();
+ tap();
+ };
+ assertStaysIn(STATE_IDLE, slow3tap);
+ assertStaysIn(STATE_ZOOMED, slow3tap);
+ }
+
+ @Test
+ public void testDisablingTripleTap_removesInputLag() {
+ mMgh = newInstance(/* detect3tap */ false, /* detectShortcut */ true);
+ goFromStateIdleTo(STATE_IDLE);
+ allowEventDelegation();
+ tap();
+ // no fast forward
+ verify(mMgh.mNext, times(2)).onMotionEvent(any(), any(), anyInt());
+ }
+
+ private void assertTransition(int fromState, Runnable transitionAction, int toState) {
+ goFromStateIdleTo(fromState);
+ transitionAction.run();
+ assertIn(toState);
+ returnToNormalFrom(toState);
+ }
+
+ private void assertStaysIn(int state, Runnable action) {
+ assertTransition(state, action, state);
+ }
+
+ private void forEachState(IntConsumer action) {
+ for (int state = FIRST_STATE; state <= LAST_STATE; state++) {
+ action.accept(state);
+ }
+ }
+
+ private void allowEventDelegation() {
+ doNothing().when(mMgh.mNext).onMotionEvent(any(), any(), anyInt());
+ }
+
+ private void fastForward1sec() {
+ fastForward(1000);
+ }
+
+ private void fastForward(int ms) {
+ mClock.fastForward(ms);
+ mHandler.timeAdvance();
+ }
+
+ /**
+ * Asserts that {@link #mMgh the handler} is in the given {@code state}
+ */
+ private void assertIn(int state) {
+ switch (state) {
+
+ // Asserts on separate lines for accurate stack traces
+
+ case STATE_IDLE: {
+ check(tapCount() < 2, state);
+ check(!mMgh.mShortcutTriggered, state);
+ check(!isZoomed(), state);
+ } break;
+ case STATE_ZOOMED: {
+ check(isZoomed(), state);
+ check(tapCount() < 2, state);
+ } break;
+ case STATE_2TAPS: {
+ check(!isZoomed(), state);
+ check(tapCount() == 2, state);
+ } break;
+ case STATE_ZOOMED_2TAPS: {
+ check(isZoomed(), state);
+ check(tapCount() == 2, state);
+ } break;
+ case STATE_DRAGGING: {
+ check(mMgh.mCurrentState == MagnificationGestureHandler.STATE_VIEWPORT_DRAGGING,
+ state);
+ check(mMgh.mViewportDraggingStateHandler.mZoomedInBeforeDrag, state);
+ } break;
+ case STATE_DRAGGING_TMP: {
+ check(mMgh.mCurrentState == MagnificationGestureHandler.STATE_VIEWPORT_DRAGGING,
+ state);
+ check(!mMgh.mViewportDraggingStateHandler.mZoomedInBeforeDrag, state);
+ } break;
+ case STATE_SHORTCUT_TRIGGERED: {
+ check(mMgh.mShortcutTriggered, state);
+ check(!isZoomed(), state);
+ } break;
+ case STATE_PANNING: {
+ check(mMgh.mCurrentState == MagnificationGestureHandler.STATE_PANNING_SCALING,
+ state);
+ check(!mMgh.mPanningScalingStateHandler.mScaling, state);
+ } break;
+ case STATE_SCALING_AND_PANNING: {
+ check(mMgh.mCurrentState == MagnificationGestureHandler.STATE_PANNING_SCALING,
+ state);
+ check(mMgh.mPanningScalingStateHandler.mScaling, state);
+ } break;
+ default: throw new IllegalArgumentException("Illegal state: " + state);
+ }
+ }
+
+ /**
+ * Defines a "canonical" path from {@link #STATE_IDLE} to {@code state}
+ */
+ private void goFromStateIdleTo(int state) {
+ try {
+ switch (state) {
+ case STATE_IDLE: {
+ mMgh.clearAndTransitionToStateDetecting();
+ } break;
+ case STATE_2TAPS: {
+ goFromStateIdleTo(STATE_IDLE);
+ tap();
+ tap();
+ } break;
+ case STATE_ZOOMED: {
+ if (mMgh.mDetectTripleTap) {
+ goFromStateIdleTo(STATE_2TAPS);
+ tap();
+ } else {
+ goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
+ tap();
+ }
+ } break;
+ case STATE_ZOOMED_2TAPS: {
+ goFromStateIdleTo(STATE_ZOOMED);
+ tap();
+ tap();
+ } break;
+ case STATE_DRAGGING: {
+ goFromStateIdleTo(STATE_ZOOMED_2TAPS);
+ send(downEvent());
+ fastForward1sec();
+ } break;
+ case STATE_DRAGGING_TMP: {
+ goFromStateIdleTo(STATE_2TAPS);
+ send(downEvent());
+ fastForward1sec();
+ } break;
+ case STATE_SHORTCUT_TRIGGERED: {
+ goFromStateIdleTo(STATE_IDLE);
+ triggerShortcut();
+ } break;
+ case STATE_PANNING: {
+ goFromStateIdleTo(STATE_ZOOMED);
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
+ } break;
+ case STATE_SCALING_AND_PANNING: {
+ goFromStateIdleTo(STATE_PANNING);
+ send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 3));
+ send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 4));
+ } break;
+ default:
+ throw new IllegalArgumentException("Illegal state: " + state);
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException("Failed to go to state " + stateToString(state), t);
+ }
+ }
+
+ /**
+ * Defines a "canonical" path from {@code state} to {@link #STATE_IDLE}
+ */
+ private void returnToNormalFrom(int state) {
+ switch (state) {
+ case STATE_IDLE: {
+ // no op
+ } break;
+ case STATE_2TAPS: {
+ allowEventDelegation();
+ fastForward1sec();
+ } break;
+ case STATE_ZOOMED: {
+ if (mMgh.mDetectTripleTap) {
+ tap();
+ tap();
+ returnToNormalFrom(STATE_ZOOMED_2TAPS);
+ } else {
+ triggerShortcut();
+ }
+ } break;
+ case STATE_ZOOMED_2TAPS: {
+ tap();
+ } break;
+ case STATE_DRAGGING: {
+ send(upEvent());
+ returnToNormalFrom(STATE_ZOOMED);
+ } break;
+ case STATE_DRAGGING_TMP: {
+ send(upEvent());
+ } break;
+ case STATE_SHORTCUT_TRIGGERED: {
+ triggerShortcut();
+ } break;
+ case STATE_PANNING: {
+ send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y));
+ send(upEvent());
+ returnToNormalFrom(STATE_ZOOMED);
+ } break;
+ case STATE_SCALING_AND_PANNING: {
+ returnToNormalFrom(STATE_PANNING);
+ } break;
+ default: throw new IllegalArgumentException("Illegal state: " + state);
+ }
+ }
+
+ private void check(boolean condition, int expectedState) {
+ if (!condition) {
+ fail("Expected to be in state " + stateToString(expectedState) + stateDump());
+ }
+ }
+
+ private boolean isZoomed() {
+ return mMgh.mMagnificationController.isMagnifying();
+ }
+
+ private int tapCount() {
+ return mMgh.mDetectingStateHandler.tapCount();
+ }
+
+ private static String stateToString(int state) {
+ return DebugUtils.valueToString(MagnificationGestureHandlerTest.class, "STATE_", state);
+ }
+
+ private void tap() {
+ MotionEvent downEvent = downEvent();
+ send(downEvent);
+ send(upEvent(downEvent.getDownTime()));
+ }
+
+ private void swipe() {
+ MotionEvent downEvent = downEvent();
+ send(downEvent);
+ send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2));
+ send(upEvent(downEvent.getDownTime()));
+ }
+
+ private void longTap() {
+ MotionEvent downEvent = downEvent();
+ send(downEvent);
+ fastForward(2000);
+ send(upEvent(downEvent.getDownTime()));
+ }
+
+ private void triggerShortcut() {
+ mMgh.notifyShortcutTriggered();
+ }
+
+ private void send(MotionEvent event) {
+ event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ try {
+ mMgh.onMotionEvent(event, event, /* policyFlags */ 0);
+ } catch (Throwable t) {
+ throw new RuntimeException("Exception while handling " + event, t);
+ }
+ fastForward(1);
+ }
+
+ private MotionEvent moveEvent(float x, float y) {
+ return MotionEvent.obtain(defaultDownTime(), mClock.now(), ACTION_MOVE, x, y, 0);
+ }
+
+ private MotionEvent downEvent() {
+ return MotionEvent.obtain(mClock.now(), mClock.now(),
+ ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0);
+ }
+
+ private MotionEvent upEvent() {
+ return upEvent(defaultDownTime());
+ }
+
+ private MotionEvent upEvent(long downTime) {
+ return MotionEvent.obtain(downTime, mClock.now(),
+ MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0);
+ }
+
+ private long defaultDownTime() {
+ MotionEvent lastDown = mMgh.mDetectingStateHandler.mLastDown;
+ return lastDown == null ? mClock.now() - 1 : lastDown.getDownTime();
+ }
+
+ private MotionEvent pointerEvent(int action, float x, float y) {
+ MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties();
+ defPointerProperties.id = 0;
+ defPointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
+ MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
+ pointerProperties.id = 1;
+ pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
+
+ MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords();
+ defPointerCoords.x = DEFAULT_X;
+ defPointerCoords.y = DEFAULT_Y;
+ MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
+ pointerCoords.x = x;
+ pointerCoords.y = y;
+
+ return MotionEvent.obtain(
+ /* downTime */ mClock.now(),
+ /* eventTime */ mClock.now(),
+ /* action */ action,
+ /* pointerCount */ 2,
+ /* pointerProperties */ new MotionEvent.PointerProperties[] {
+ defPointerProperties, pointerProperties },
+ /* pointerCoords */ new MotionEvent.PointerCoords[] { defPointerCoords, pointerCoords },
+ /* metaState */ 0,
+ /* buttonState */ 0,
+ /* xPrecision */ 1.0f,
+ /* yPrecision */ 1.0f,
+ /* deviceId */ 0,
+ /* edgeFlags */ 0,
+ /* source */ InputDevice.SOURCE_TOUCHSCREEN,
+ /* flags */ 0);
+ }
+
+ private String stateDump() {
+ return "\nCurrent state dump:\n" + mMgh + "\n" + mHandler.getPendingMessages();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupPasswordManagerTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupPasswordManagerTest.java
index 04c02510cb3d..bc162977de2b 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupPasswordManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupPasswordManagerTest.java
@@ -16,7 +16,7 @@
package com.android.server.backup;
-import static com.android.server.testutis.TestUtils.assertExpectException;
+import static com.android.server.testutils.TestUtils.assertExpectException;
import static com.google.common.truth.Truth.assertThat;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index e3faa5280859..0fda0fe8c738 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -22,7 +22,7 @@ import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY;
import static android.os.UserManagerInternal.CAMERA_NOT_DISABLED;
-import static com.android.server.testutis.TestUtils.assertExpectException;
+import static com.android.server.testutils.TestUtils.assertExpectException;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
@@ -54,7 +54,6 @@ import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.PasswordMetrics;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
diff --git a/services/tests/servicestests/src/com/android/server/testutils/OffsettableClock.java b/services/tests/servicestests/src/com/android/server/testutils/OffsettableClock.java
new file mode 100644
index 000000000000..8dabbc4d4356
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/testutils/OffsettableClock.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 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.testutils;
+
+import android.os.SystemClock;
+
+import java.util.function.LongSupplier;
+
+/**
+ * A time supplier (in the format of a {@code long} as the amount of milliseconds) similar
+ * to {@link SystemClock#uptimeMillis()}, but with the ability to {@link #fastForward}
+ * and {@link #rewind}
+ *
+ * Implements {@link LongSupplier} to be interchangeable with {@code SystemClock::uptimeMillis}
+ *
+ * Can be provided to {@link TestHandler} to "mock time" for the delayed execution testing
+ *
+ * @see OffsettableClock.Stopped for a version of this clock that does not advance on its own
+ */
+public class OffsettableClock implements LongSupplier {
+ private long mOffset = 0L;
+
+ /**
+ * @return Current time in milliseconds, according to this clock
+ */
+ public long now() {
+ return realNow() + mOffset;
+ }
+
+ /**
+ * Can be overriden with a constant for a clock that stands still, and is only ever moved
+ * manually
+ */
+ public long realNow() {
+ return SystemClock.uptimeMillis();
+ }
+
+ public void fastForward(long timeMs) {
+ mOffset += timeMs;
+ }
+ public void rewind(long timeMs) {
+ fastForward(-timeMs);
+ }
+ public void reset() {
+ mOffset = 0;
+ }
+
+ /** @deprecated Only present for {@link LongSupplier} contract */
+ @Override
+ @Deprecated
+ public long getAsLong() {
+ return now();
+ }
+
+ /**
+ * An {@link OffsettableClock} that does not advance with real time, and can only be
+ * advanced manually via {@link #fastForward}
+ */
+ public static class Stopped extends OffsettableClock {
+ @Override
+ public long realNow() {
+ return 0L;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java b/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java
new file mode 100644
index 000000000000..2d4bc0f8b7d0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 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.testutils;
+
+
+import static android.util.ExceptionUtils.getRootCause;
+import static android.util.ExceptionUtils.propagate;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.function.LongSupplier;
+
+/**
+ * A test {@link Handler} that stores incoming {@link Message}s and {@link Runnable callbacks}
+ * in a {@link PriorityQueue} based on time, to be manually processed later in a correct order
+ * either all together with {@link #flush}, or only those due at the current time with
+ * {@link #timeAdvance}.
+ *
+ * For the latter use case this also supports providing a custom clock (in a format of a
+ * milliseconds-returning {@link LongSupplier}), that will be used for storing the messages'
+ * timestamps to be posted at, and checked against during {@link #timeAdvance}.
+ *
+ * This allows to test code that uses {@link Handler}'s delayed invocation capabilities, such as
+ * {@link Handler#sendMessageDelayed} or {@link Handler#postDelayed} without resorting to
+ * synchronously {@link Thread#sleep}ing in your test.
+ *
+ * @see OffsettableClock for a useful custom clock implementation to use with this handler
+ */
+public class TestHandler extends Handler {
+ private static final LongSupplier DEFAULT_CLOCK = SystemClock::uptimeMillis;
+
+ private final PriorityQueue<MsgInfo> mMessages = new PriorityQueue<>();
+ /**
+ * Map of: {@code message id -> count of such messages currently pending }
+ */
+ // Boxing is ok here - both msg ids and their pending counts tend to be well below 128
+ private final Map<Integer, Integer> mPendingMsgTypeCounts = new ArrayMap<>();
+ private final LongSupplier mClock;
+
+ public TestHandler(Callback callback) {
+ this(callback, DEFAULT_CLOCK);
+ }
+
+ public TestHandler(Callback callback, LongSupplier clock) {
+ super(callback);
+ mClock = clock;
+ }
+
+ @Override
+ public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+ mPendingMsgTypeCounts.put(msg.what,
+ mPendingMsgTypeCounts.getOrDefault(msg.what, 0) + 1);
+
+ // uptimeMillis is an absolute time obtained as SystemClock.uptimeMillis() + offsetMillis
+ // if custom clock is given, recalculate the time with regards to it
+ if (mClock != DEFAULT_CLOCK) {
+ uptimeMillis = uptimeMillis - SystemClock.uptimeMillis() + mClock.getAsLong();
+ }
+
+ // post a dummy queue entry to keep track of message removal
+ return super.sendMessageAtTime(msg, Long.MAX_VALUE)
+ && mMessages.add(new MsgInfo(Message.obtain(msg), uptimeMillis));
+ }
+
+ /** @see TestHandler */
+ public void timeAdvance() {
+ long now = mClock.getAsLong();
+ while (!mMessages.isEmpty() && mMessages.peek().sendTime <= now) {
+ dispatch(mMessages.poll());
+ }
+ }
+
+ /**
+ * Dispatch all messages in order
+ *
+ * @see TestHandler
+ */
+ public void flush() {
+ MsgInfo msg;
+ while ((msg = mMessages.poll()) != null) {
+ dispatch(msg);
+ }
+ }
+
+ public PriorityQueue<MsgInfo> getPendingMessages() {
+ return new PriorityQueue<>(mMessages);
+ }
+
+ private void dispatch(MsgInfo msg) {
+ int msgId = msg.message.what;
+
+ if (!hasMessages(msgId)) {
+ // Handler.removeMessages(msgId) must have been called
+ return;
+ }
+
+ try {
+ Integer pendingMsgCount = mPendingMsgTypeCounts.getOrDefault(msgId, 0);
+ if (pendingMsgCount <= 1) {
+ removeMessages(msgId);
+ }
+ mPendingMsgTypeCounts.put(msgId, pendingMsgCount - 1);
+
+ dispatchMessage(msg.message);
+ } catch (Throwable t) {
+ // Append stack trace of this message being posted as a cause for a helpful
+ // test error message
+ throw propagate(getRootCause(t).initCause(msg.postPoint));
+ } finally {
+ msg.message.recycle();
+ }
+ }
+
+ private class MsgInfo implements Comparable<MsgInfo> {
+ public final Message message;
+ public final long sendTime;
+ public final RuntimeException postPoint;
+
+ private MsgInfo(Message message, long sendTime) {
+ this.message = message;
+ this.sendTime = sendTime;
+ this.postPoint = new RuntimeException("Message originated from here:");
+ }
+
+ @Override
+ public int compareTo(MsgInfo o) {
+ return (int) (sendTime - o.sendTime);
+ }
+
+ @Override
+ public String toString() {
+ return "MsgInfo{" +
+ "message=" + message +
+ ", sendTime=" + sendTime +
+ '}';
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java b/services/tests/servicestests/src/com/android/server/testutils/TestUtils.java
index 88289888b0dd..b200293ee916 100644
--- a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/testutils/TestUtils.java
@@ -13,12 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.server.testutis;
+package com.android.server.testutils;
import android.test.MoreAsserts;
import junit.framework.Assert;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
public class TestUtils {
private TestUtils() {
}
@@ -44,4 +47,17 @@ public class TestUtils {
Assert.fail("Expected exception type " + expectedExceptionType.getName()
+ " was not thrown");
}
+
+ /**
+ * EasyMock-style "strict" mock that throws immediately on any interaction that was not
+ * explicitly allowed.
+ *
+ * You can allow certain method calls on a whitelist basis by stubbing them e.g. with
+ * {@link Mockito#doAnswer}, {@link Mockito#doNothing}, etc.
+ */
+ public static <T> T strictMock(Class<T> c) {
+ return Mockito.mock(c, (Answer) invocation -> {
+ throw new AssertionError("Unexpected invocation: " + invocation);
+ });
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 9d32496c7817..0081214a24da 100644
--- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -126,6 +126,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
boolean mMovedToFullscreen;
boolean mAnimationStarted;
boolean mSchedulePipModeChangedOnStart;
+ boolean mForcePipModeChangedCallback;
boolean mAnimationEnded;
Rect mAnimationEndFinalStackBounds;
boolean mSchedulePipModeChangedOnEnd;
@@ -140,6 +141,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
mAnimationStarted = false;
mAnimationEnded = false;
mAnimationEndFinalStackBounds = null;
+ mForcePipModeChangedCallback = false;
mSchedulePipModeChangedOnStart = false;
mSchedulePipModeChangedOnEnd = false;
mStackBounds = from;
@@ -148,10 +150,11 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
}
@Override
- public void onAnimationStart(boolean schedulePipModeChangedCallback) {
+ public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) {
mAwaitingAnimationStart = false;
mAnimationStarted = true;
mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback;
+ mForcePipModeChangedCallback = forceUpdate;
}
@Override
@@ -232,7 +235,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
return this;
}
- BoundsAnimationDriver restart(Rect to) {
+ BoundsAnimationDriver restart(Rect to, boolean expectStartedAndPipModeChangedCallback) {
if (mAnimator == null) {
throw new IllegalArgumentException("Call start() to start a new animation");
}
@@ -251,8 +254,15 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
assertSame(oldAnimator, mAnimator);
}
- // No animation start for replacing animation
- assertTrue(!mTarget.mAnimationStarted);
+ if (expectStartedAndPipModeChangedCallback) {
+ // Replacing animation with pending pip mode changed callback, ensure we update
+ assertTrue(mTarget.mAnimationStarted);
+ assertTrue(mTarget.mSchedulePipModeChangedOnStart);
+ assertTrue(mTarget.mForcePipModeChangedCallback);
+ } else {
+ // No animation start for replacing animation
+ assertTrue(!mTarget.mAnimationStarted);
+ }
mTarget.mAnimationStarted = true;
return this;
}
@@ -467,7 +477,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
.expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
.update(0.25f)
- .restart(BOUNDS_FLOATING)
+ .restart(BOUNDS_FLOATING, false /* expectStartedAndPipModeChangedCallback */)
.end()
.expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
}
@@ -478,7 +488,8 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
.expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
.update(0.25f)
- .restart(BOUNDS_SMALLER_FLOATING)
+ .restart(BOUNDS_SMALLER_FLOATING,
+ false /* expectStartedAndPipModeChangedCallback */)
.end()
.expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
}
@@ -486,10 +497,12 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
@UiThreadTest
@Test
public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() throws Exception {
+ // When animating from fullscreen and the animation is interruped, we expect the animation
+ // start callback to be made, with a forced pip mode change callback
mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
.expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
.update(0.25f)
- .restart(BOUNDS_FULL)
+ .restart(BOUNDS_FULL, true /* expectStartedAndPipModeChangedCallback */)
.end()
.expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
}
@@ -512,7 +525,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
.expectStarted(SCHEDULE_PIP_MODE_CHANGED)
.update(0.25f)
- .restart(BOUNDS_FULL)
+ .restart(BOUNDS_FULL, false /* expectStartedAndPipModeChangedCallback */)
.end()
.expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
}
@@ -523,7 +536,8 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
.expectStarted(SCHEDULE_PIP_MODE_CHANGED)
.update(0.25f)
- .restart(BOUNDS_SMALLER_FLOATING)
+ .restart(BOUNDS_SMALLER_FLOATING,
+ false /* expectStartedAndPipModeChangedCallback */)
.end()
.expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
}
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index a0ffefad1e1c..6fb1793d90ed 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -293,6 +293,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
manifest_action["instrumentation"]["meta-data"] = meta_data_action;
manifest_action["original-package"];
+ manifest_action["overlay"];
manifest_action["protected-broadcast"];
manifest_action["uses-permission"];
manifest_action["uses-permission-sdk-23"];