diff options
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 & 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 & 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"]; |