diff options
118 files changed, 2691 insertions, 2303 deletions
diff --git a/Android.bp b/Android.bp index aae78a02e32e..b620634b485e 100644 --- a/Android.bp +++ b/Android.bp @@ -568,6 +568,7 @@ java_library { "libcore-platform-compat-config", "services-platform-compat-config", "documents-ui-compat-config", + "calendar-provider-compat-config", ], libs: ["framework-updatable-stubs-module_libs_api"], static_libs: [ diff --git a/StubLibraries.bp b/StubLibraries.bp index 56c568832ea9..decbb85efb7c 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -155,6 +155,9 @@ priv_apps = " " + module_libs = " " + " --show-annotation android.annotation.SystemApi\\(" + "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" + + "\\)" + + " --show-for-stub-purposes-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" + "\\) " droidstubs { @@ -232,9 +235,8 @@ droidstubs { } ///////////////////////////////////////////////////////////////////// -// Following droidstubs modules are for extra APIs for modules. -// The framework currently have two more API surfaces for modules: -// @SystemApi(client=MODULE_APPS) and @SystemApi(client=MODULE_LIBRARIES) +// Following droidstubs modules are for extra APIs for modules, +// namely @SystemApi(client=MODULE_LIBRARIES) APIs. ///////////////////////////////////////////////////////////////////// // TODO(b/146727827) remove the *-api module when we can teach metalava diff --git a/apex/Android.bp b/apex/Android.bp index 6d4dc8517237..410e21141f86 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -53,6 +53,9 @@ priv_apps = " " + module_libs = " " + " --show-annotation android.annotation.SystemApi\\(" + "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" + + "\\)" + + " --show-for-stub-purposes-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" + "\\) " mainline_service_stubs_args = diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 68b22c002d78..a67e928c8cbe 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -2287,7 +2287,8 @@ public class JobSchedulerService extends com.android.server.SystemService } // Everything else checked out so far, so this is the final yes/no check - final boolean appIsBad = mActivityManagerInternal.isAppBad(service.applicationInfo); + final boolean appIsBad = mActivityManagerInternal.isAppBad( + service.processName, service.applicationInfo.uid); if (DEBUG && appIsBad) { Slog.i(TAG, "App is bad for " + job.toShortString() + " so not runnable"); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index f2a55805d70a..7bd51b77a119 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -335,7 +335,7 @@ public final class JobStore { Slog.v(TAG, "Scheduling persist of jobs to disk."); } mIoHandler.postDelayed(mWriteRunnable, JOB_PERSIST_DELAY); - mWriteScheduled = mWriteInProgress = true; + mWriteScheduled = true; } } } @@ -353,7 +353,7 @@ public final class JobStore { throw new IllegalStateException("An asynchronous write is already scheduled."); } - mWriteScheduled = mWriteInProgress = true; + mWriteScheduled = true; mWriteRunnable.run(); } } @@ -369,7 +369,7 @@ public final class JobStore { final long start = SystemClock.uptimeMillis(); final long end = start + maxWaitMillis; synchronized (mWriteScheduleLock) { - while (mWriteInProgress) { + while (mWriteScheduled || mWriteInProgress) { final long now = SystemClock.uptimeMillis(); if (now >= end) { // still not done and we've hit the end; failure @@ -404,6 +404,12 @@ public final class JobStore { // a bit of lock contention. synchronized (mWriteScheduleLock) { mWriteScheduled = false; + if (mWriteInProgress) { + // Another runnable is currently writing. Postpone this new write task. + maybeWriteStatusToDiskAsync(); + return; + } + mWriteInProgress = true; } synchronized (mLock) { // Clone the jobs so we can release the lock before writing. diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index 1afe4493e33c..1803be384389 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -21,24 +21,6 @@ package android.graphics { package android.net { - public final class TetheredClient implements android.os.Parcelable { - ctor public TetheredClient(@NonNull android.net.MacAddress, @NonNull java.util.Collection<android.net.TetheredClient.AddressInfo>, int); - method public int describeContents(); - method @NonNull public java.util.List<android.net.TetheredClient.AddressInfo> getAddresses(); - method @NonNull public android.net.MacAddress getMacAddress(); - method public int getTetheringType(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient> CREATOR; - } - - public static final class TetheredClient.AddressInfo implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public android.net.LinkAddress getAddress(); - method @Nullable public String getHostname(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient.AddressInfo> CREATOR; - } - public final class TetheringConstants { field public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; field public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; @@ -58,69 +40,15 @@ package android.net { method @NonNull public String[] getTetheringErroredIfaces(); method public boolean isTetheringSupported(); method public boolean isTetheringSupported(@NonNull String); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback); - method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener); method public void requestLatestTetheringEntitlementResult(int, @NonNull android.os.ResultReceiver, boolean); method @Deprecated public int setUsbTethering(boolean); - method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback); - method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback); - method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering(); - method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int); + method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback); method @Deprecated public int tether(@NonNull String); - method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback); method @Deprecated public int untether(@NonNull String); - field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"; - field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY"; - field public static final String EXTRA_ACTIVE_TETHER = "tetherArray"; - field public static final String EXTRA_AVAILABLE_TETHER = "availableArray"; - field public static final String EXTRA_ERRORED_TETHER = "erroredArray"; - field public static final int TETHERING_BLUETOOTH = 2; // 0x2 - field public static final int TETHERING_ETHERNET = 5; // 0x5 - field public static final int TETHERING_INVALID = -1; // 0xffffffff - field public static final int TETHERING_NCM = 4; // 0x4 - field public static final int TETHERING_USB = 1; // 0x1 - field public static final int TETHERING_WIFI = 0; // 0x0 - field public static final int TETHERING_WIFI_P2P = 3; // 0x3 - field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc - field public static final int TETHER_ERROR_DISABLE_FORWARDING_ERROR = 9; // 0x9 - field public static final int TETHER_ERROR_ENABLE_FORWARDING_ERROR = 8; // 0x8 - field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd - field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa - field public static final int TETHER_ERROR_INTERNAL_ERROR = 5; // 0x5 - field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf - field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe - field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 - field public static final int TETHER_ERROR_PROVISIONING_FAILED = 11; // 0xb - field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2 - field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6 - field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4 - field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1 - field public static final int TETHER_ERROR_UNKNOWN_TYPE = 16; // 0x10 - field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3 - field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7 - field public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; // 0x2 - field public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; // 0x1 - field public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; // 0x0 - } - - public static interface TetheringManager.OnTetheringEntitlementResultListener { - method public void onTetheringEntitlementResult(int); - } - - public static interface TetheringManager.StartTetheringCallback { - method public default void onTetheringFailed(int); - method public default void onTetheringStarted(); } public static interface TetheringManager.TetheringEventCallback { - method public default void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>); - method public default void onError(@NonNull String, int); - method public default void onOffloadStatusChanged(int); method public default void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps); - method public default void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>); - method public default void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>); - method public default void onTetheringSupported(boolean); - method public default void onUpstreamChanged(@Nullable android.net.Network); } public static class TetheringManager.TetheringInterfaceRegexps { @@ -129,22 +57,6 @@ package android.net { method @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs(); } - public static class TetheringManager.TetheringRequest { - method @Nullable public android.net.LinkAddress getClientStaticIpv4Address(); - method @Nullable public android.net.LinkAddress getLocalIpv4Address(); - method public boolean getShouldShowEntitlementUi(); - method public int getTetheringType(); - method public boolean isExemptFromEntitlementCheck(); - } - - public static class TetheringManager.TetheringRequest.Builder { - ctor public TetheringManager.TetheringRequest.Builder(int); - method @NonNull public android.net.TetheringManager.TetheringRequest build(); - method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean); - method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setShouldShowEntitlementUi(boolean); - method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress); - } - } package android.os { diff --git a/core/java/android/annotation/SystemApi.java b/core/java/android/annotation/SystemApi.java index 4ac00983af13..a468439c8e74 100644 --- a/core/java/android/annotation/SystemApi.java +++ b/core/java/android/annotation/SystemApi.java @@ -23,7 +23,6 @@ import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PACKAGE; import static java.lang.annotation.ElementType.TYPE; -import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -41,7 +40,6 @@ import java.lang.annotation.Target; */ @Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE}) @Retention(RetentionPolicy.RUNTIME) -@Repeatable(SystemApi.Container.class) // TODO(b/146727827): make this non-repeatable public @interface SystemApi { enum Client { /** diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 505cfe7e6d28..7ba50cadd959 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -387,8 +387,8 @@ public abstract class ActivityManagerInternal { /** Returns true if the given uid is the app in the foreground. */ public abstract boolean isAppForeground(int uid); - /** Returns true if the given uid is currently marked 'bad' */ - public abstract boolean isAppBad(ApplicationInfo info); + /** Returns true if the given process name and uid is currently marked 'bad' */ + public abstract boolean isAppBad(String processName, int uid); /** Remove pending backup for the given userId. */ public abstract void clearPendingBackup(@UserIdInt int userId); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index d642f218cb71..3ca5f0deba1b 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -732,6 +732,10 @@ public class AppOpsManager { public static final int SAMPLING_STRATEGY_BOOT_TIME_SAMPLING = FrameworkStatsLog.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__BOOT_TIME_SAMPLING; + /** @hide */ + public static final int SAMPLING_STRATEGY_UNIFORM_OPS = + FrameworkStatsLog.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM_OPS; + /** * Strategies used for message sampling * @hide @@ -741,7 +745,8 @@ public class AppOpsManager { SAMPLING_STRATEGY_DEFAULT, SAMPLING_STRATEGY_UNIFORM, SAMPLING_STRATEGY_RARELY_USED, - SAMPLING_STRATEGY_BOOT_TIME_SAMPLING + SAMPLING_STRATEGY_BOOT_TIME_SAMPLING, + SAMPLING_STRATEGY_UNIFORM_OPS }) public @interface SamplingStrategy {} @@ -8350,7 +8355,7 @@ public class AppOpsManager { * @hide */ private static boolean isCollectingStackTraces() { - if (sConfig.getSampledOpCode() == OP_NONE && + if (sConfig.getSampledOpCode() == OP_NONE && sConfig.getAcceptableLeftDistance() == 0 && sConfig.getExpirationTimeSinceBootMillis() >= SystemClock.elapsedRealtime()) { return false; } diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 4b0d2f86ad34..bca6f39e1ded 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -175,14 +175,33 @@ import java.util.concurrent.atomic.AtomicLong; * * Caching can be disabled completely by initializing {@code sEnabled} to false and rebuilding. * + * To test a binder cache, create one or more tests that exercise the binder method. This + * should be done twice: once with production code and once with a special image that sets + * {@code DEBUG} and {@code VERIFY} true. In the latter case, verify that no cache + * inconsistencies are reported. If a cache inconsistency is reported, however, it might be a + * false positive. This happens if the server side data can be read and written non-atomically + * with respect to cache invalidation. + * * @param <Query> The class used to index cache entries: must be hashable and comparable * @param <Result> The class holding cache entries; use a boxed primitive if possible * * {@hide} */ public abstract class PropertyInvalidatedCache<Query, Result> { - private static final long NONCE_UNSET = 0; - private static final long NONCE_DISABLED = -1; + /** + * Reserved nonce values. The code is written assuming that these + * values are contiguous. + */ + private static final int NONCE_UNSET = 0; + private static final int NONCE_DISABLED = 1; + private static final int NONCE_CORKED = 2; + private static final int NONCE_RESERVED = NONCE_CORKED + 1; + + /** + * The names of the nonces + */ + private static final String[] sNonceName = + new String[]{ "unset", "disabled", "corked" }; private static final String TAG = "PropertyInvalidatedCache"; private static final boolean DEBUG = false; @@ -195,11 +214,28 @@ public abstract class PropertyInvalidatedCache<Query, Result> { @GuardedBy("mLock") private long mMisses = 0; + @GuardedBy("mLock") + private long mMissDisabled[] = new long[]{ 0, 0, 0 }; + + @GuardedBy("mLock") + private long mMissOverflow = 0; + + @GuardedBy("mLock") + private long mHighWaterMark = 0; + // Most invalidation is done in a static context, so the counters need to be accessible. @GuardedBy("sCorkLock") private static final HashMap<String, Long> sInvalidates = new HashMap<>(); /** + * Record the number of invalidate or cork calls that were nops because + * the cache was already corked. This is static because invalidation is + * done in a static context. + */ + @GuardedBy("sCorkLock") + private static final HashMap<String, Long> sCorkedInvalidates = new HashMap<>(); + + /** * If sEnabled is false then all cache operations are stubbed out. Set * it to false inside test processes. */ @@ -271,7 +307,15 @@ public abstract class PropertyInvalidatedCache<Query, Result> { true /* LRU access order */) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > maxEntries; + final int size = size(); + if (size > mHighWaterMark) { + mHighWaterMark = size; + } + if (size > maxEntries) { + mMissOverflow++; + return true; + } + return false; } }; synchronized (sCorkLock) { @@ -363,14 +407,21 @@ public abstract class PropertyInvalidatedCache<Query, Result> { // Let access to mDisabled race: it's atomic anyway. long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED; for (;;) { - if (currentNonce == NONCE_DISABLED || currentNonce == NONCE_UNSET) { + if (currentNonce == NONCE_DISABLED || currentNonce == NONCE_UNSET || + currentNonce == NONCE_CORKED) { + if (!mDisabled) { + // Do not bother collecting statistics if the cache is + // locally disabled. + synchronized (mLock) { + mMissDisabled[(int) currentNonce]++; + } + } + if (DEBUG) { if (!mDisabled) { Log.d(TAG, String.format( "cache %s %s for %s", - cacheName(), - currentNonce == NONCE_DISABLED ? "disabled" : "unset", - queryToString(query))); + cacheName(), sNonceName[(int) currentNonce], queryToString(query))); } } return recompute(query); @@ -383,10 +434,10 @@ public abstract class PropertyInvalidatedCache<Query, Result> { if (cachedResult != null) mHits++; } else { if (DEBUG) { - Log.d(TAG, - String.format("clearing cache %s because nonce changed [%s] -> [%s]", - cacheName(), - mLastSeenNonce, currentNonce)); + Log.d(TAG, String.format( + "clearing cache %s of %d entries because nonce changed [%s] -> [%s]", + cacheName(), mCache.size(), + mLastSeenNonce, currentNonce)); } mCache.clear(); mLastSeenNonce = currentNonce; @@ -517,6 +568,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> { if (DEBUG) { Log.d(TAG, "ignoring invalidation due to cork: " + name); } + final long count = sCorkedInvalidates.getOrDefault(name, (long) 0); + sCorkedInvalidates.put(name, count + 1); return; } invalidateCacheLocked(name); @@ -538,7 +591,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { long newValue; do { newValue = NoPreloadHolder.next(); - } while (newValue == NONCE_UNSET || newValue == NONCE_DISABLED); + } while (newValue >= 0 && newValue < NONCE_RESERVED); final String newValueString = Long.toString(newValue); if (DEBUG) { Log.d(TAG, @@ -567,13 +620,21 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * @param name Name of the cache-key property to cork */ public static void corkInvalidations(@NonNull String name) { + if (!sEnabled) { + if (DEBUG) { + Log.w(TAG, String.format( + "cache cork %s suppressed", name)); + } + return; + } + synchronized (sCorkLock) { int numberCorks = sCorks.getOrDefault(name, 0); if (DEBUG) { Log.d(TAG, String.format("corking %s: numberCorks=%s", name, numberCorks)); } - // If we're the first ones to cork this cache, set the cache to the unset state so + // If we're the first ones to cork this cache, set the cache to the corked state so // existing caches talk directly to their services while we've corked updates. // Make sure we don't clobber a disabled cache value. @@ -584,8 +645,11 @@ public abstract class PropertyInvalidatedCache<Query, Result> { if (numberCorks == 0) { final long nonce = SystemProperties.getLong(name, NONCE_UNSET); if (nonce != NONCE_UNSET && nonce != NONCE_DISABLED) { - SystemProperties.set(name, Long.toString(NONCE_UNSET)); + SystemProperties.set(name, Long.toString(NONCE_CORKED)); } + } else { + final long count = sCorkedInvalidates.getOrDefault(name, (long) 0); + sCorkedInvalidates.put(name, count + 1); } sCorks.put(name, numberCorks + 1); if (DEBUG) { @@ -602,6 +666,14 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * @param name Name of the cache-key property to uncork */ public static void uncorkInvalidations(@NonNull String name) { + if (!sEnabled) { + if (DEBUG) { + Log.w(TAG, String.format( + "cache uncork %s suppressed", name)); + } + return; + } + synchronized (sCorkLock) { int numberCorks = sCorks.getOrDefault(name, 0); if (DEBUG) { @@ -729,8 +801,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> { boolean nonceChanged = (getCurrentNonce() != mLastSeenNonce); if (!nonceChanged && !debugCompareQueryResults(proposedResult, resultToCompare)) { Log.e(TAG, String.format( - "cache %s inconsistent for %s", - cacheName(), queryToString(query))); + "cache %s inconsistent for %s is %s should be %s", + cacheName(), queryToString(query), + proposedResult, resultToCompare)); } // Always return the "true" result in verification mode. return resultToCompare; @@ -784,18 +857,23 @@ public abstract class PropertyInvalidatedCache<Query, Result> { private void dumpContents(PrintWriter pw, String[] args) { long invalidateCount; - + long corkedInvalidates; synchronized (sCorkLock) { invalidateCount = sInvalidates.getOrDefault(mPropertyName, (long) 0); + corkedInvalidates = sCorkedInvalidates.getOrDefault(mPropertyName, (long) 0); } synchronized (mLock) { pw.println(String.format(" Cache Property Name: %s", cacheName())); - pw.println(String.format(" Hits: %d, Misses: %d, Invalidates: %d", - mHits, mMisses, invalidateCount)); + pw.println(String.format(" Hits: %d, Misses: %d, Invalidates: %d, Overflows: %d", + mHits, mMisses, invalidateCount, mMissOverflow)); + pw.println(String.format(" Miss-corked: %d, Miss-unset: %d, Miss-other: %d," + + " CorkedInvalidates: %d", + mMissDisabled[NONCE_CORKED], mMissDisabled[NONCE_UNSET], + mMissDisabled[NONCE_DISABLED], corkedInvalidates)); pw.println(String.format(" Last Observed Nonce: %d", mLastSeenNonce)); - pw.println(String.format(" Current Size: %d, Max Size: %d", - mCache.entrySet().size(), mMaxEntries)); + pw.println(String.format(" Current Size: %d, Max Size: %d, HW Mark: %d", + mCache.size(), mMaxEntries, mHighWaterMark)); pw.println(String.format(" Enabled: %s", mDisabled ? "false" : "true")); Set<Map.Entry<Query, Result>> cacheEntries = mCache.entrySet(); diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java index 4f5c4feb3684..df11d3adac01 100644 --- a/core/java/android/bluetooth/BluetoothMapClient.java +++ b/core/java/android/bluetooth/BluetoothMapClient.java @@ -52,6 +52,18 @@ public final class BluetoothMapClient implements BluetoothProfile { public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY = "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY"; + /** + * Action to notify read status changed + */ + public static final String ACTION_MESSAGE_READ_STATUS_CHANGED = + "android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED"; + + /** + * Action to notify deleted status changed + */ + public static final String ACTION_MESSAGE_DELETED_STATUS_CHANGED = + "android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED"; + /* Extras used in ACTION_MESSAGE_RECEIVED intent. * NOTE: HANDLE is only valid for a single session with the device. */ public static final String EXTRA_MESSAGE_HANDLE = @@ -65,6 +77,25 @@ public final class BluetoothMapClient implements BluetoothProfile { public static final String EXTRA_SENDER_CONTACT_NAME = "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME"; + /** + * Used as a boolean extra in ACTION_MESSAGE_DELETED_STATUS_CHANGED + * Contains the MAP message deleted status + * Possible values are: + * true: deleted + * false: undeleted + */ + public static final String EXTRA_MESSAGE_DELETED_STATUS = + "android.bluetooth.mapmce.profile.extra.MESSAGE_DELETED_STATUS"; + + /** + * Extra used in ACTION_MESSAGE_READ_STATUS_CHANGED or ACTION_MESSAGE_DELETED_STATUS_CHANGED + * Possible values are: + * 0: failure + * 1: success + */ + public static final String EXTRA_RESULT_CODE = + "android.bluetooth.device.extra.RESULT_CODE"; + /** There was an error trying to obtain the state */ public static final int STATE_ERROR = -1; @@ -75,6 +106,12 @@ public final class BluetoothMapClient implements BluetoothProfile { private static final int UPLOADING_FEATURE_BITMASK = 0x08; + /** Parameters in setMessageStatus */ + public static final int UNREAD = 0; + public static final int READ = 1; + public static final int UNDELETED = 2; + public static final int DELETED = 3; + private BluetoothAdapter mAdapter; private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT, @@ -405,6 +442,38 @@ public final class BluetoothMapClient implements BluetoothProfile { return false; } + /** + * Set message status of message on MSE + * <p> + * When read status changed, the result will be published via + * {@link #ACTION_MESSAGE_READ_STATUS_CHANGED} + * When deleted status changed, the result will be published via + * {@link #ACTION_MESSAGE_DELETED_STATUS_CHANGED} + * + * @param device Bluetooth device + * @param handle message handle + * @param status <code>UNREAD</code> for "unread", <code>READ</code> for + * "read", <code>UNDELETED</code> for "undeleted", <code>DELETED</code> for + * "deleted", otherwise return error + * @return <code>true</code> if request has been sent, <code>false</code> on error + * + */ + @RequiresPermission(Manifest.permission.READ_SMS) + public boolean setMessageStatus(BluetoothDevice device, String handle, int status) { + if (DBG) Log.d(TAG, "setMessageStatus(" + device + ", " + handle + ", " + status + ")"); + final IBluetoothMapClient service = getService(); + if (service != null && isEnabled() && isValidDevice(device) && handle != null && + (status == READ || status == UNREAD || status == UNDELETED || status == DELETED)) { + try { + return service.setMessageStatus(device, handle, status); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + return false; + } + private boolean isEnabled() { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index f533760de84d..9c7dc9610522 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -8179,7 +8179,7 @@ public abstract class PackageManager { private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo> sPackageInfoCache = new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>( - 16, PermissionManager.CACHE_KEY_PACKAGE_INFO) { + 32, PermissionManager.CACHE_KEY_PACKAGE_INFO) { @Override protected PackageInfo recompute(PackageInfoQuery query) { return getPackageInfoAsUserUncached( diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index b4546a49a07c..efe71f15007c 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -71,7 +71,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan private static final int MSG_SET_FEATURE_COMPLETED = 107; private static final int MSG_CHALLENGE_GENERATED = 108; - private IFaceService mService; + private final IFaceService mService; private final Context mContext; private IBinder mToken = new Binder(); private AuthenticationCallback mAuthenticationCallback; diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 4ca75d9a09cf..7b1048ed1de5 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -19,6 +19,7 @@ package android.hardware.fingerprint; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.MANAGE_FINGERPRINT; import static android.Manifest.permission.USE_BIOMETRIC; +import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.Manifest.permission.USE_FINGERPRINT; import android.annotation.NonNull; @@ -685,6 +686,75 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } /** + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public boolean isUdfps() { + if (mService == null) { + Slog.w(TAG, "isUdfps: no fingerprint service"); + return false; + } + + try { + return mService.isUdfps(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + + /** + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void setUdfpsOverlayController(IUdfpsOverlayController controller) { + if (mService == null) { + Slog.w(TAG, "setUdfpsOverlayController: no fingerprint service"); + return; + } + + try { + mService.setUdfpsOverlayController(controller); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void onFingerDown(int x, int y, float minor, float major) { + if (mService == null) { + Slog.w(TAG, "onFingerDown: no fingerprint service"); + return; + } + + try { + mService.onFingerDown(x, y, minor, major); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void onFingerUp() { + if (mService == null) { + Slog.w(TAG, "onFingerDown: no fingerprint service"); + return; + } + + try { + mService.onFingerUp(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** * Determine if there is at least one fingerprint enrolled. * * @return true if at least one fingerprint is enrolled, false otherwise diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 38d7d2bc9e27..21ac687166f0 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -114,8 +114,8 @@ interface IFingerprintService { // Notifies about a finger leaving the sensor area. void onFingerUp(); - // Returns whether the specified sensor is an under-display fingerprint sensor (UDFPS). - boolean isUdfps(int sensorId); + // Returns whether the sensor is an under-display fingerprint sensor (UDFPS). + boolean isUdfps(); // Sets the controller for managing the UDFPS overlay. void setUdfpsOverlayController(in IUdfpsOverlayController controller); diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index dea932dd6178..43bd76de1f46 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -208,8 +208,15 @@ public final class PermissionControllerManager { ServiceConnector<IPermissionController> remoteService = sRemoteServices.get(key); if (remoteService == null) { Intent intent = new Intent(SERVICE_INTERFACE); - intent.setPackage(context.getPackageManager().getPermissionControllerPackageName()); + String pkgName = context.getPackageManager().getPermissionControllerPackageName(); + intent.setPackage(pkgName); ResolveInfo serviceInfo = context.getPackageManager().resolveService(intent, 0); + if (serviceInfo == null) { + String errorMsg = "No PermissionController package (" + pkgName + ") for user " + + context.getUserId(); + Log.wtf(TAG, errorMsg); + throw new IllegalStateException(errorMsg); + } remoteService = new ServiceConnector.Impl<IPermissionController>( ActivityThread.currentApplication() /* context */, new Intent(SERVICE_INTERFACE) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index feebb9485167..e1d48860b1e2 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8298,6 +8298,13 @@ public final class Settings { public static final String CAMERA_GESTURE_DISABLED = "camera_gesture_disabled"; /** + * Whether the panic button (emergency sos) gesture should be enabled. + * + * @hide + */ + public static final String PANIC_GESTURE_ENABLED = "panic_gesture_enabled"; + + /** * Whether the camera launch gesture to double tap the power button when the screen is off * should be disabled. * diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 31da83ad5137..69220722fc05 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -308,7 +308,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll false /* isScreenRound */, false /* alwaysConsumeSystemBars */, null /* displayCutout */, LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/, - 0 /* legacySystemUiFlags */, typeSideMap) + 0 /* legacyWindowFlags */, 0 /* legacySystemUiFlags */, typeSideMap) .getInsets(mTypes); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index a679b3740fd9..c383bc7a4d70 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -501,6 +501,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private PendingControlRequest mPendingImeControlRequest; private int mLastLegacySoftInputMode; + private int mLastLegacyWindowFlags; private int mLastLegacySystemUiFlags; private DisplayCutout mLastDisplayCutout; private boolean mStartingAnimation; @@ -569,8 +570,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/, mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeSystemBars(), - mLastDisplayCutout, mLastLegacySoftInputMode, mLastLegacySystemUiFlags, - null /* typeSideMap */); + mLastDisplayCutout, mLastLegacySoftInputMode, mLastLegacyWindowFlags, + mLastLegacySystemUiFlags, null /* typeSideMap */); mHost.dispatchWindowInsetsAnimationProgress(insets, mUnmodifiableTmpRunningAnims); if (DEBUG) { for (WindowInsetsAnimation anim : mUnmodifiableTmpRunningAnims) { @@ -706,13 +707,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars, DisplayCutout cutout, - int legacySoftInputMode, int legacySystemUiFlags) { + int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags) { mLastLegacySoftInputMode = legacySoftInputMode; + mLastLegacyWindowFlags = legacyWindowFlags; mLastLegacySystemUiFlags = legacySystemUiFlags; mLastDisplayCutout = cutout; mLastInsets = mState.calculateInsets(mFrame, null /* ignoringVisibilityState*/, isScreenRound, alwaysConsumeSystemBars, cutout, - legacySoftInputMode, legacySystemUiFlags, + legacySoftInputMode, legacyWindowFlags, legacySystemUiFlags, null /* typeSideMap */); return mLastInsets; } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 91e7591193f1..6b0b509932a8 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -22,13 +22,14 @@ import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; import static android.view.ViewRootImpl.sNewInsetsMode; import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES; -import static android.view.WindowInsets.Type.SIZE; import static android.view.WindowInsets.Type.SYSTEM_GESTURES; import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.indexOf; import static android.view.WindowInsets.Type.isVisibleInsetsType; +import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowInsets.Type.systemBars; +import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; @@ -38,7 +39,6 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseIntArray; import android.view.WindowInsets.Type; @@ -171,7 +171,7 @@ public class InsetsState implements Parcelable { */ public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState, boolean isScreenRound, boolean alwaysConsumeSystemBars, DisplayCutout cutout, - int legacySoftInputMode, int legacySystemUiFlags, + int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags, @Nullable @InternalInsetsSide SparseIntArray typeSideMap) { Insets[] typeInsetsMap = new Insets[Type.SIZE]; Insets[] typeMaxInsetsMap = new Insets[Type.SIZE]; @@ -218,10 +218,17 @@ public class InsetsState implements Parcelable { } } final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST; + + @InsetsType int compatInsetsTypes = systemBars() | displayCutout(); + if (softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE) { + compatInsetsTypes |= ime(); + } + if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) { + compatInsetsTypes &= ~statusBars(); + } + return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound, - alwaysConsumeSystemBars, cutout, softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE - ? systemBars() | displayCutout() | ime() - : systemBars() | displayCutout(), + alwaysConsumeSystemBars, cutout, compatInsetsTypes, sNewInsetsMode == NEW_INSETS_MODE_FULL && (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f2cec250d47f..fe6f33da6d2a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2257,7 +2257,8 @@ public final class ViewRootImpl implements ViewParent, mLastWindowInsets = mInsetsController.calculateInsets( mContext.getResources().getConfiguration().isScreenRound(), mAttachInfo.mAlwaysConsumeSystemBars, mPendingDisplayCutout.get(), - mWindowAttributes.softInputMode, (mWindowAttributes.systemUiVisibility + mWindowAttributes.softInputMode, mWindowAttributes.flags, + (mWindowAttributes.systemUiVisibility | mWindowAttributes.subtreeSystemUiVisibility)); Rect visibleInsets = mInsetsController.calculateVisibleInsets( diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 4d6b72f96aab..5e94758b003c 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -17,7 +17,6 @@ package android.view; -import static android.view.WindowInsets.Type.CAPTION_BAR; import static android.view.WindowInsets.Type.DISPLAY_CUTOUT; import static android.view.WindowInsets.Type.FIRST; import static android.view.WindowInsets.Type.IME; @@ -95,7 +94,7 @@ public final class WindowInsets { private final boolean mStableInsetsConsumed; private final boolean mDisplayCutoutConsumed; - private final int mCompatInsetTypes; + private final int mCompatInsetsTypes; private final boolean mCompatIgnoreVisibility; /** @@ -150,8 +149,8 @@ public final class WindowInsets { @Nullable Insets[] typeMaxInsetsMap, boolean[] typeVisibilityMap, boolean isRound, - boolean alwaysConsumeSystemBars, DisplayCutout displayCutout, int compatInsetTypes, - boolean compatIgnoreVisibility) { + boolean alwaysConsumeSystemBars, DisplayCutout displayCutout, + @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) { mSystemWindowInsetsConsumed = typeInsetsMap == null; mTypeInsetsMap = mSystemWindowInsetsConsumed ? new Insets[SIZE] @@ -165,7 +164,7 @@ public final class WindowInsets { mTypeVisibilityMap = typeVisibilityMap; mIsRound = isRound; mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; - mCompatInsetTypes = compatInsetTypes; + mCompatInsetsTypes = compatInsetsTypes; mCompatIgnoreVisibility = compatIgnoreVisibility; mDisplayCutoutConsumed = displayCutout == null; @@ -183,7 +182,7 @@ public final class WindowInsets { src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap, src.mTypeVisibilityMap, src.mIsRound, src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src), - src.mCompatInsetTypes, + src.mCompatInsetsTypes, src.mCompatIgnoreVisibility); } @@ -310,11 +309,11 @@ public final class WindowInsets { @NonNull public Insets getSystemWindowInsets() { Insets result = mCompatIgnoreVisibility - ? getInsetsIgnoringVisibility(mCompatInsetTypes & ~ime()) - : getInsets(mCompatInsetTypes); + ? getInsetsIgnoringVisibility(mCompatInsetsTypes & ~ime()) + : getInsets(mCompatInsetsTypes); // We can't query max insets for IME, so we need to add it manually after. - if ((mCompatInsetTypes & ime()) != 0 && mCompatIgnoreVisibility) { + if ((mCompatInsetsTypes & ime()) != 0 && mCompatIgnoreVisibility) { result = Insets.max(result, getInsets(ime())); } return result; @@ -503,7 +502,7 @@ public final class WindowInsets { mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, null /* displayCutout */, - mCompatInsetTypes, mCompatIgnoreVisibility); + mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -554,7 +553,7 @@ public final class WindowInsets { mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(this), - mCompatInsetTypes, mCompatIgnoreVisibility); + mCompatInsetsTypes, mCompatIgnoreVisibility); } // TODO(b/119190588): replace @code with @link below @@ -627,7 +626,7 @@ public final class WindowInsets { @Deprecated @NonNull public Insets getStableInsets() { - return getInsets(mTypeMaxInsetsMap, mCompatInsetTypes); + return getInsets(mTypeMaxInsetsMap, mCompatInsetsTypes); } /** @@ -939,7 +938,7 @@ public final class WindowInsets { : mDisplayCutout == null ? DisplayCutout.NO_CUTOUT : mDisplayCutout.inset(left, top, right, bottom), - mCompatInsetTypes, mCompatIgnoreVisibility); + mCompatInsetsTypes, mCompatIgnoreVisibility); } @Override diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 28a18da37b3e..2fe7c021bb21 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -276,7 +276,7 @@ public final class WindowManagerImpl implements WindowManager { if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL) { return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState*/, isScreenRound, alwaysConsumeSystemBars, displayCutout.get(), - SOFT_INPUT_ADJUST_NOTHING, + SOFT_INPUT_ADJUST_NOTHING, attrs.flags, SYSTEM_UI_FLAG_VISIBLE, null /* typeSideMap */); } else { return new WindowInsets.Builder() diff --git a/core/java/com/android/internal/view/IInlineSuggestionsRequestCallback.aidl b/core/java/com/android/internal/view/IInlineSuggestionsRequestCallback.aidl index 03948a92bcab..6c97962ac057 100644 --- a/core/java/com/android/internal/view/IInlineSuggestionsRequestCallback.aidl +++ b/core/java/com/android/internal/view/IInlineSuggestionsRequestCallback.aidl @@ -22,41 +22,54 @@ import android.view.inputmethod.InlineSuggestionsRequest; import com.android.internal.view.IInlineSuggestionsResponseCallback; /** - * Binder interface for the IME service to send an inline suggestion request to the system. + * Binder interface for the IME service to send {@link InlineSuggestionsRequest} or notify other IME + * service events to the system. * {@hide} */ oneway interface IInlineSuggestionsRequestCallback { - // Indicates that the current IME does not support inline suggestion. + /** Indicates that the current IME does not support inline suggestion. */ void onInlineSuggestionsUnsupported(); - // Sends the inline suggestions request from IME to Autofill. Calling this method indicates - // that the IME input is started on the view corresponding to the request. + /** + * Sends the inline suggestions request from IME to Autofill. Calling this method indicates + * that the IME input is started on the view corresponding to the request. + */ void onInlineSuggestionsRequest(in InlineSuggestionsRequest request, in IInlineSuggestionsResponseCallback callback); - // Signals that {@link android.inputmethodservice.InputMethodService - // #onStartInput(EditorInfo, boolean)} is called on the given focused field. + /** + * Signals that {@link android.inputmethodservice.InputMethodService + * #onStartInput(EditorInfo, boolean)} is called on the given focused field. + */ void onInputMethodStartInput(in AutofillId imeFieldId); - // Signals that {@link android.inputmethodservice.InputMethodService - // #dispatchOnShowInputRequested(int, boolean)} is called and shares the call result. - // The true value of {@code requestResult} means the IME is about to be shown, while - // false value means the IME will not be shown. + /** + * Signals that {@link android.inputmethodservice.InputMethodService + * #dispatchOnShowInputRequested(int, boolean)} is called and shares the call result. + * The true value of {@code requestResult} means the IME is about to be shown, while + * false value means the IME will not be shown. + */ void onInputMethodShowInputRequested(boolean requestResult); - // Signals that {@link android.inputmethodservice.InputMethodService - // #onStartInputView(EditorInfo, boolean)} is called on the field specified by the earlier - // {@link #onInputMethodStartInput(AutofillId)}. + /** + * Signals that {@link android.inputmethodservice.InputMethodService + * #onStartInputView(EditorInfo, boolean)} is called on the field specified by the earlier + * {@link #onInputMethodStartInput(AutofillId)}. + */ void onInputMethodStartInputView(); - // Signals that {@link android.inputmethodservice.InputMethodService - // #onFinishInputView(boolean)} is called on the field specified by the earlier - // {@link #onInputMethodStartInput(AutofillId)}. + /** + * Signals that {@link android.inputmethodservice.InputMethodService + * #onFinishInputView(boolean)} is called on the field specified by the earlier + * {@link #onInputMethodStartInput(AutofillId)}. + */ void onInputMethodFinishInputView(); - // Signals that {@link android.inputmethodservice.InputMethodService - // #onFinishInput()} is called on the field specified by the earlier - // {@link #onInputMethodStartInput(AutofillId)}. + /** + * Signals that {@link android.inputmethodservice.InputMethodService + * #onFinishInput()} is called on the field specified by the earlier + * {@link #onInputMethodStartInput(AutofillId)}. + */ void onInputMethodFinishInput(); // Indicates that the current IME changes inline suggestion session. diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index edafb0c19da8..ca4dc18689bc 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -208,6 +208,13 @@ message SecureSettingsProto { optional Doze doze = 21; optional SettingProto emergency_assistance_application = 22 [ (android.privacy).dest = DEST_AUTOMATIC ]; + + message EmergencyResponse { + optional SettingProto panic_gesture_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; + } + + optional EmergencyResponse emergency_response = 83; + optional SettingProto enhanced_voice_privacy_enabled = 23 [ (android.privacy).dest = DEST_AUTOMATIC ]; message Gesture { @@ -606,5 +613,5 @@ message SecureSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 83; + // Next tag = 84; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0985fea3d26a..ef50fc8e2b6e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -227,6 +227,8 @@ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED" /> <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY" /> <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY" /> + <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED" /> + <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED" /> <protected-broadcast android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT" /> <protected-broadcast diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml index 3ecb1dddd916..0382dd3b9103 100644 --- a/core/res/res/values-television/config.xml +++ b/core/res/res/values-television/config.xml @@ -42,4 +42,13 @@ <!-- Allow SystemUI to show the shutdown dialog --> <bool name="config_showSysuiShutdown">true</bool> + + <!-- Control the behavior when the user long presses the power button. + 0 - Nothing + 1 - Global actions menu + 2 - Power off (with confirmation) + 3 - Power off (without confirmation) + 4 - Go to voice assist + 5 - Go to assistant (Settings.Secure.ASSISTANT --> + <integer name="config_longPressOnPowerBehavior">3</integer> </resources> diff --git a/core/tests/bluetoothtests/AndroidManifest.xml b/core/tests/bluetoothtests/AndroidManifest.xml index 7f9d8749358c..6849a90f5010 100644 --- a/core/tests/bluetoothtests/AndroidManifest.xml +++ b/core/tests/bluetoothtests/AndroidManifest.xml @@ -15,14 +15,18 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.bluetooth.tests" > + package="com.android.bluetooth.tests" + android:sharedUserId="android.uid.bluetooth" > <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> + <uses-permission android:name="android.permission.RECEIVE_SMS" /> + <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java index 4b32ceae0617..89dbe3f75b56 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java @@ -360,6 +360,30 @@ public class BluetoothStressTest extends InstrumentationTestCase { mTestUtils.unpair(mAdapter, device); } + /* Make sure there is at least 1 unread message in the last week on remote device */ + public void testMceSetMessageStatus() { + int iterations = BluetoothTestRunner.sMceSetMessageStatusIterations; + if (iterations == 0) { + return; + } + + BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.enable(mAdapter); + mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.MAP_CLIENT, null); + mTestUtils.mceGetUnreadMessage(mAdapter, device); + + for (int i = 0; i < iterations; i++) { + mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.READ); + mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.UNREAD); + } + + /** + * It is hard to find device to support set undeleted status, so just + * set deleted in 1 iteration + **/ + mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.DELETED); + } + private void sleep(long time) { try { Thread.sleep(time); diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java index 56e691d8c246..d19c2c3e7e24 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java @@ -40,6 +40,7 @@ import android.util.Log; * [-e connect_input_iterations <iterations>] \ * [-e connect_pan_iterations <iterations>] \ * [-e start_stop_sco_iterations <iterations>] \ + * [-e mce_set_message_status_iterations <iterations>] \ * [-e pair_address <address>] \ * [-e headset_address <address>] \ * [-e a2dp_address <address>] \ @@ -64,6 +65,7 @@ public class BluetoothTestRunner extends InstrumentationTestRunner { public static int sConnectInputIterations = 100; public static int sConnectPanIterations = 100; public static int sStartStopScoIterations = 100; + public static int sMceSetMessageStatusIterations = 100; public static String sDeviceAddress = ""; public static byte[] sDevicePairPin = {'1', '2', '3', '4'}; @@ -173,6 +175,15 @@ public class BluetoothTestRunner extends InstrumentationTestRunner { } } + val = arguments.getString("mce_set_message_status_iterations"); + if (val != null) { + try { + sMceSetMessageStatusIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + val = arguments.getString("device_address"); if (val != null) { sDeviceAddress = val; diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java index ed613c36b89b..409025bc670d 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java @@ -56,6 +56,10 @@ public class BluetoothTestUtils extends Assert { private static final int CONNECT_PROXY_TIMEOUT = 5000; /** Time between polls in ms. */ private static final int POLL_TIME = 100; + /** Timeout to get map message in ms. */ + private static final int GET_UNREAD_MESSAGE_TIMEOUT = 10000; + /** Timeout to set map message status in ms. */ + private static final int SET_MESSAGE_STATUS_TIMEOUT = 2000; private abstract class FlagReceiver extends BroadcastReceiver { private int mExpectedFlags = 0; @@ -98,6 +102,8 @@ public class BluetoothTestUtils extends Assert { private static final int STATE_TURNING_ON_FLAG = 1 << 6; private static final int STATE_ON_FLAG = 1 << 7; private static final int STATE_TURNING_OFF_FLAG = 1 << 8; + private static final int STATE_GET_MESSAGE_FINISHED_FLAG = 1 << 9; + private static final int STATE_SET_MESSAGE_STATUS_FINISHED_FLAG = 1 << 10; public BluetoothReceiver(int expectedFlags) { super(expectedFlags); @@ -231,6 +237,9 @@ public class BluetoothTestUtils extends Assert { case BluetoothProfile.PAN: mConnectionAction = BluetoothPan.ACTION_CONNECTION_STATE_CHANGED; break; + case BluetoothProfile.MAP_CLIENT: + mConnectionAction = BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED; + break; default: mConnectionAction = null; } @@ -308,6 +317,34 @@ public class BluetoothTestUtils extends Assert { } } + + private class MceSetMessageStatusReceiver extends FlagReceiver { + private static final int MESSAGE_RECEIVED_FLAG = 1; + private static final int STATUS_CHANGED_FLAG = 1 << 1; + + public MceSetMessageStatusReceiver(int expectedFlags) { + super(expectedFlags); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (BluetoothMapClient.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) { + String handle = intent.getStringExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE); + assertNotNull(handle); + setFiredFlag(MESSAGE_RECEIVED_FLAG); + mMsgHandle = handle; + } else if (BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED.equals(intent.getAction())) { + int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE); + assertEquals(result, BluetoothMapClient.RESULT_SUCCESS); + setFiredFlag(STATUS_CHANGED_FLAG); + } else if (BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED.equals(intent.getAction())) { + int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE); + assertEquals(result, BluetoothMapClient.RESULT_SUCCESS); + setFiredFlag(STATUS_CHANGED_FLAG); + } + } + } + private BluetoothProfile.ServiceListener mServiceListener = new BluetoothProfile.ServiceListener() { @Override @@ -326,6 +363,9 @@ public class BluetoothTestUtils extends Assert { case BluetoothProfile.PAN: mPan = (BluetoothPan) proxy; break; + case BluetoothProfile.MAP_CLIENT: + mMce = (BluetoothMapClient) proxy; + break; } } } @@ -346,6 +386,9 @@ public class BluetoothTestUtils extends Assert { case BluetoothProfile.PAN: mPan = null; break; + case BluetoothProfile.MAP_CLIENT: + mMce = null; + break; } } } @@ -362,6 +405,8 @@ public class BluetoothTestUtils extends Assert { private BluetoothHeadset mHeadset = null; private BluetoothHidHost mInput = null; private BluetoothPan mPan = null; + private BluetoothMapClient mMce = null; + private String mMsgHandle = null; /** * Creates a utility instance for testing Bluetooth. @@ -898,7 +943,7 @@ public class BluetoothTestUtils extends Assert { * @param adapter The BT adapter. * @param device The remote device. * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#HID_HOST}. + * {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#HID_HOST} or {@link BluetoothProfile#MAP_CLIENT}.. * @param methodName The method name to printed in the logs. If null, will be * "connectProfile(profile=<profile>, device=<device>)" */ @@ -941,6 +986,8 @@ public class BluetoothTestUtils extends Assert { assertTrue(((BluetoothHeadset)proxy).connect(device)); } else if (profile == BluetoothProfile.HID_HOST) { assertTrue(((BluetoothHidHost)proxy).connect(device)); + } else if (profile == BluetoothProfile.MAP_CLIENT) { + assertTrue(((BluetoothMapClient)proxy).connect(device)); } break; default: @@ -1016,6 +1063,8 @@ public class BluetoothTestUtils extends Assert { assertTrue(((BluetoothHeadset)proxy).disconnect(device)); } else if (profile == BluetoothProfile.HID_HOST) { assertTrue(((BluetoothHidHost)proxy).disconnect(device)); + } else if (profile == BluetoothProfile.MAP_CLIENT) { + assertTrue(((BluetoothMapClient)proxy).disconnect(device)); } break; case BluetoothProfile.STATE_DISCONNECTED: @@ -1373,6 +1422,89 @@ public class BluetoothTestUtils extends Assert { } } + public void mceGetUnreadMessage(BluetoothAdapter adapter, BluetoothDevice device) { + int mask; + String methodName = "getUnreadMessage"; + + if (!adapter.isEnabled()) { + fail(String.format("%s bluetooth not enabled", methodName)); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail(String.format("%s device not paired", methodName)); + } + + mMce = (BluetoothMapClient) connectProxy(adapter, BluetoothProfile.MAP_CLIENT); + assertNotNull(mMce); + + if (mMce.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { + fail(String.format("%s device is not connected", methodName)); + } + + mMsgHandle = null; + mask = MceSetMessageStatusReceiver.MESSAGE_RECEIVED_FLAG; + MceSetMessageStatusReceiver receiver = getMceSetMessageStatusReceiver(device, mask); + assertTrue(mMce.getUnreadMessages(device)); + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < GET_UNREAD_MESSAGE_TIMEOUT) { + if ((receiver.getFiredFlags() & mask) == mask) { + writeOutput(String.format("%s completed", methodName)); + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", + methodName, mMce.getConnectionState(device), BluetoothMapClient.STATE_CONNECTED, firedFlags, mask)); + } + + /** + * Set a message to read/unread/deleted/undeleted + */ + public void mceSetMessageStatus(BluetoothAdapter adapter, BluetoothDevice device, int status) { + int mask; + String methodName = "setMessageStatus"; + + if (!adapter.isEnabled()) { + fail(String.format("%s bluetooth not enabled", methodName)); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail(String.format("%s device not paired", methodName)); + } + + mMce = (BluetoothMapClient) connectProxy(adapter, BluetoothProfile.MAP_CLIENT); + assertNotNull(mMce); + + if (mMce.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { + fail(String.format("%s device is not connected", methodName)); + } + + assertNotNull(mMsgHandle); + mask = MceSetMessageStatusReceiver.STATUS_CHANGED_FLAG; + MceSetMessageStatusReceiver receiver = getMceSetMessageStatusReceiver(device, mask); + + assertTrue(mMce.setMessageStatus(device, mMsgHandle, status)); + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < SET_MESSAGE_STATUS_TIMEOUT) { + if ((receiver.getFiredFlags() & mask) == mask) { + writeOutput(String.format("%s completed", methodName)); + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", + methodName, mMce.getConnectionState(device), BluetoothPan.STATE_CONNECTED, firedFlags, mask)); + } + private void addReceiver(BroadcastReceiver receiver, String[] actions) { IntentFilter filter = new IntentFilter(); for (String action: actions) { @@ -1408,7 +1540,8 @@ public class BluetoothTestUtils extends Assert { String[] actions = { BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED, - BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED}; + BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED, + BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED}; ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile, expectedFlags); addReceiver(receiver, actions); @@ -1430,6 +1563,16 @@ public class BluetoothTestUtils extends Assert { return receiver; } + private MceSetMessageStatusReceiver getMceSetMessageStatusReceiver(BluetoothDevice device, + int expectedFlags) { + String[] actions = {BluetoothMapClient.ACTION_MESSAGE_RECEIVED, + BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED, + BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED}; + MceSetMessageStatusReceiver receiver = new MceSetMessageStatusReceiver(expectedFlags); + addReceiver(receiver, actions); + return receiver; + } + private void removeReceiver(BroadcastReceiver receiver) { mContext.unregisterReceiver(receiver); mReceivers.remove(receiver); @@ -1456,6 +1599,10 @@ public class BluetoothTestUtils extends Assert { if (mPan != null) { return mPan; } + case BluetoothProfile.MAP_CLIENT: + if (mMce != null) { + return mMce; + } break; default: return null; @@ -1483,6 +1630,11 @@ public class BluetoothTestUtils extends Assert { sleep(POLL_TIME); } return mPan; + case BluetoothProfile.MAP_CLIENT: + while (mMce == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { + sleep(POLL_TIME); + } + return mMce; default: return null; } diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index bfcf52af80bf..eb695258142a 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -86,7 +86,7 @@ public class ImeInsetsSourceConsumerTest { false, new DisplayCutout( Insets.of(10, 10, 10, 10), rect, rect, rect, rect), - SOFT_INPUT_ADJUST_RESIZE, 0); + SOFT_INPUT_ADJUST_RESIZE, 0, 0); mImeConsumer = (ImeInsetsSourceConsumer) mController.getSourceConsumer(ITYPE_IME); }); } diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index c36f1067149e..801cd4ddb94e 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -165,7 +165,7 @@ public class InsetsControllerTest { false, new DisplayCutout( Insets.of(10, 10, 10, 10), rect, rect, rect, rect), - SOFT_INPUT_ADJUST_RESIZE, 0); + SOFT_INPUT_ADJUST_RESIZE, 0, 0); mController.onFrameChanged(new Rect(0, 0, 100, 100)); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index 5260ef83cc4f..c7d835ca7c7e 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -29,6 +29,7 @@ import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; +import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; @@ -77,7 +78,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_IME).setVisible(true); SparseIntArray typeSideMap = new SparseIntArray(); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_RESIZE, 0, typeSideMap); + false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_RESIZE, 0, 0, typeSideMap); assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all())); assertEquals(ISIDE_TOP, typeSideMap.get(ITYPE_STATUS_BAR)); @@ -96,7 +97,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_IME).setFrame(new Rect(0, 100, 100, 300)); mState.getSource(ITYPE_IME).setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_RESIZE, 0, null); + false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_RESIZE, 0, 0, null); assertEquals(100, insets.getStableInsetBottom()); assertEquals(Insets.of(0, 0, 0, 100), insets.getInsetsIgnoringVisibility(Type.systemBars())); assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets()); @@ -115,7 +116,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300)); mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, DisplayCutout.NO_CUTOUT, 0, 0, null); + false, DisplayCutout.NO_CUTOUT, 0, 0, 0, null); assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars())); assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars())); @@ -131,7 +132,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300)); mState.getSource(ITYPE_IME).setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0, null); + false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0, 0, null); assertEquals(0, insets.getSystemWindowInsetBottom()); assertEquals(100, insets.getInsets(ime()).bottom); assertTrue(insets.isVisible(ime())); @@ -147,11 +148,28 @@ public class InsetsStateTest { mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300)); mState.getSource(ITYPE_IME).setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, + false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_LAYOUT_STABLE, null); assertEquals(100, insets.getSystemWindowInsetTop()); insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false, - DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, + DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0, + 0 /* legacySystemUiFlags */, null); + assertEquals(0, insets.getSystemWindowInsetTop()); + } + } + + @Test + public void testCalculateInsets_systemUiFlagLayoutStable_windowFlagFullscreen() { + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) { + mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(ITYPE_STATUS_BAR).setVisible(false); + WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, + false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN, + SYSTEM_UI_FLAG_LAYOUT_STABLE, null); + assertEquals(0, insets.getSystemWindowInsetTop()); + insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false, + DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0, 0 /* legacySystemUiFlags */, null); assertEquals(0, insets.getSystemWindowInsetTop()); } @@ -195,7 +213,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300)); mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, DisplayCutout.NO_CUTOUT, 0, 0, null); + false, DisplayCutout.NO_CUTOUT, 0, 0, 0, null); assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars())); assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars())); @@ -211,7 +229,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300)); mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, DisplayCutout.NO_CUTOUT, 0, 0, null); + false, DisplayCutout.NO_CUTOUT, 0, 0, 0, null); assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars())); assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars())); @@ -226,7 +244,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_IME).setVisible(true); mState.removeSource(ITYPE_IME); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false, - DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_RESIZE, 0, null); + DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_RESIZE, 0, 0, null); assertEquals(0, insets.getSystemWindowInsetBottom()); } diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml index ecd7d40ff21d..84c1897a2b62 100644 --- a/data/etc/preinstalled-packages-platform-overlays.xml +++ b/data/etc/preinstalled-packages-platform-overlays.xml @@ -19,32 +19,250 @@ <config> <install-in-user-type package="com.android.internal.display.cutout.emulation.corner"> <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> </install-in-user-type> <install-in-user-type package="com.android.internal.display.cutout.emulation.double"> <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.internal.display.cutout.emulation.hole"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> </install-in-user-type> <install-in-user-type package="com.android.internal.display.cutout.emulation.tall"> <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.internal.display.cutout.emulation.waterfall"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.internal.systemui.navbar.twobutton"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.internal.systemui.navbar.threebutton"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> </install-in-user-type> <install-in-user-type package="com.android.internal.systemui.navbar.gestural"> <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> </install-in-user-type> <install-in-user-type package="com.android.internal.systemui.navbar.gestural_extra_wide_back"> <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> </install-in-user-type> <install-in-user-type package="com.android.internal.systemui.navbar.gestural_narrow_back"> <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> </install-in-user-type> <install-in-user-type package="com.android.internal.systemui.navbar.gestural_wide_back"> <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> </install-in-user-type> - <install-in-user-type package="com.android.internal.systemui.navbar.threebutton"> + <install-in-user-type package="com.android.internal.systemui.onehanded.gestural"> <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> </install-in-user-type> - <install-in-user-type package="com.android.internal.systemui.navbar.twobutton"> + <install-in-user-type package="com.android.theme.color.amethyst"> <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> </install-in-user-type> - <install-in-user-type package="com.android.internal.systemui.onehanded.gestural"> + <install-in-user-type package="com.android.theme.color.aquamarine"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.color.black"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.color.carbon"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.color.cinnamon"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.color.green"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.color.ocean"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.color.orchid"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.color.palette"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.color.purple"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.color.sand"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.color.space"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.color.tangerine"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.font.notoserifsource"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.circular.android"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.circular.launcher"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.circular.settings"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.circular.systemui"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.circular.themepicker"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.filled.android"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.filled.launcher"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.filled.settings"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.filled.systemui"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.filled.themepicker"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.kai.android"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.kai.launcher"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.kai.settings"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.kai.systemui"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.kai.themepicker"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.rounded.android"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.rounded.launcher"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.rounded.settings"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.rounded.systemui"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.rounded.themepicker"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.sam.android"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.sam.launcher"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.sam.settings"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.sam.systemui"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.sam.themepicker"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.victor.android"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.victor.launcher"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.victor.settings"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.victor.systemui"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon_pack.victor.themepicker"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon.pebble"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon.roundedrect"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon.squircle"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon.taperedrect"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon.teardrop"> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> + <install-in-user-type package="com.android.theme.icon.vessel"> <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> </install-in-user-type> </config> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index c710bed29361..06791421d60f 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -198,6 +198,8 @@ applications that come with the platform <permission name="android.permission.MANAGE_USERS" /> <permission name="android.permission.UPDATE_APP_OPS_STATS"/> <permission name="android.permission.USE_RESERVED_DISK"/> + <permission name="android.permission.LOG_COMPAT_CHANGE" /> + <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> </privapp-permissions> <privapp-permissions package="com.android.providers.contacts"> diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp index 6a12a203b9f8..a6137b073d5a 100644 --- a/libs/hwui/hwui/MinikinSkia.cpp +++ b/libs/hwui/hwui/MinikinSkia.cpp @@ -125,22 +125,22 @@ const std::vector<minikin::FontVariation>& MinikinFontSkia::GetAxes() const { std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation( const std::vector<minikin::FontVariation>& variations) const { - SkFontArguments params; + SkFontArguments args; int ttcIndex; std::unique_ptr<SkStreamAsset> stream(mTypeface->openStream(&ttcIndex)); LOG_ALWAYS_FATAL_IF(stream == nullptr, "openStream failed"); - params.setCollectionIndex(ttcIndex); - std::vector<SkFontArguments::Axis> skAxes; - skAxes.resize(variations.size()); + args.setCollectionIndex(ttcIndex); + std::vector<SkFontArguments::VariationPosition::Coordinate> skVariation; + skVariation.resize(variations.size()); for (size_t i = 0; i < variations.size(); i++) { - skAxes[i].fTag = variations[i].axisTag; - skAxes[i].fStyleValue = SkFloatToScalar(variations[i].value); + skVariation[i].axis = variations[i].axisTag; + skVariation[i].value = SkFloatToScalar(variations[i].value); } - params.setAxes(skAxes.data(), skAxes.size()); + args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())}); sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); - sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), params)); + sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args)); return std::make_shared<MinikinFontSkia>(std::move(face), mFontData, mFontSize, mFilePath, ttcIndex, variations); diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp index a2fef1e19328..68eaa0a3ca54 100644 --- a/libs/hwui/jni/FontFamily.cpp +++ b/libs/hwui/jni/FontFamily.cpp @@ -104,21 +104,21 @@ static jlong FontFamily_getFamilyReleaseFunc(CRITICAL_JNI_PARAMS) { static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, int ttcIndex, jint weight, jint italic) { - FatVector<SkFontArguments::Axis, 2> skiaAxes; + FatVector<SkFontArguments::VariationPosition::Coordinate, 2> skVariation; for (const auto& axis : builder->axes) { - skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value}); + skVariation.push_back({axis.axisTag, axis.value}); } const size_t fontSize = data->size(); const void* fontPtr = data->data(); std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data))); - SkFontArguments params; - params.setCollectionIndex(ttcIndex); - params.setAxes(skiaAxes.data(), skiaAxes.size()); + SkFontArguments args; + args.setCollectionIndex(ttcIndex); + args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())}); sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); - sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), params)); + sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), args)); if (face == NULL) { ALOGE("addFont failed to create font, invalid request"); builder->axes.clear(); diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp index 5714cd1d0390..996cdceed8a7 100644 --- a/libs/hwui/jni/fonts/Font.cpp +++ b/libs/hwui/jni/fonts/Font.cpp @@ -93,19 +93,19 @@ static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jo sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize, release_global_ref, reinterpret_cast<void*>(fontRef))); - FatVector<SkFontArguments::Axis, 2> skiaAxes; + FatVector<SkFontArguments::VariationPosition::Coordinate, 2> skVariation; for (const auto& axis : builder->axes) { - skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value}); + skVariation.push_back({axis.axisTag, axis.value}); } std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data))); - SkFontArguments params; - params.setCollectionIndex(ttcIndex); - params.setAxes(skiaAxes.data(), skiaAxes.size()); + SkFontArguments args; + args.setCollectionIndex(ttcIndex); + args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())}); sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); - sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), params)); + sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), args)); if (face == nullptr) { jniThrowException(env, "java/lang/IllegalArgumentException", "Failed to create internal object. maybe invalid font data."); diff --git a/location/java/android/location/package.html b/location/java/android/location/package.html index 2355e725b6c5..20c5c54d6921 100644 --- a/location/java/android/location/package.html +++ b/location/java/android/location/package.html @@ -6,7 +6,7 @@ <p class="warning"> <strong>This API is not the recommended method for accessing Android location.</strong><br> The -<a href="{@docRoot}reference/com/google/android/gms/location/package-summary.html">Google Location Services API</a>, +<a href="https://developers.google.com/android/reference/com/google/android/gms/location/package-summary">Google Location Services API</a>, part of Google Play services, is the preferred way to add location-awareness to your app. It offers a simpler API, higher accuracy, low-power geofencing, and more. If you are currently using the android.location API, you are strongly diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 482a3812b838..18c2957b1adc 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -173,5 +173,6 @@ public class SecureSettings { Settings.Secure.ONE_HANDED_MODE_TIMEOUT, Settings.Secure.TAPS_APP_TO_EXIT, Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, + Settings.Secure.PANIC_GESTURE_ENABLED, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index b337e609e281..91f3f4af0566 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -260,5 +260,6 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ONE_HANDED_MODE_TIMEOUT, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Secure.TAPS_APP_TO_EXIT, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.PANIC_GESTURE_ENABLED, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index a994fdaa4591..27576b107efd 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -2026,6 +2026,13 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION, SecureSettingsProto.EMERGENCY_ASSISTANCE_APPLICATION); + + final long emergencyResponseToken = p.start(SecureSettingsProto.EMERGENCY_RESPONSE); + dumpSetting(s, p, + Settings.Secure.PANIC_GESTURE_ENABLED, + SecureSettingsProto.EmergencyResponse.PANIC_GESTURE_ENABLED); + p.end(emergencyResponseToken); + dumpSetting(s, p, Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED, SecureSettingsProto.ENHANCED_VOICE_PRIVACY_ENABLED); diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 6ecf303c6dc8..dfc47587b91c 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -140,7 +140,8 @@ android_library { "testables", "truth-prebuilt", "dagger2", - "jsr330" + "jsr330", + "WindowManager-Shell", ], libs: [ "android.test.runner", diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index 2a2ba1b0ccaa..14097b12e730 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -39,4 +39,6 @@ -keep public class * extends com.android.systemui.SystemUI { public <init>(android.content.Context); -}
\ No newline at end of file +} + +-keep class com.android.wm.shell.*
\ No newline at end of file diff --git a/packages/SystemUI/res/values/config_tv.xml b/packages/SystemUI/res/values/config_tv.xml index ffd58dcfd50d..d8c9428f2676 100644 --- a/packages/SystemUI/res/values/config_tv.xml +++ b/packages/SystemUI/res/values/config_tv.xml @@ -22,4 +22,13 @@ <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, when the PIP menu is shown in center. --> <string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string> + + <!-- Whether to enable microphone disclosure indicator + (com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar). --> + <bool name="audio_recording_disclosure_enabled">true</bool> + + <!-- Whether the indicator should expand and show the recording application's label. + When disabled (false) the "minimized" indicator would appear on the screen whenever an + application is recording, but will not reveal to the user what application this is. --> + <bool name="audio_recording_disclosure_reveal_packages">false</bool> </resources> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 8212d6159737..a56f6f56836a 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -92,6 +92,8 @@ <item type="id" name="requires_remeasuring"/> + <item type="id" name="secondary_home_handle" /> + <!-- Whether the icon is from a notification for which targetSdk < L --> <item type="id" name="icon_is_pre_L"/> diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java index 332a00d1d4e7..398a2c9c9d41 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java @@ -16,6 +16,7 @@ package com.android.systemui.accessibility; +import android.annotation.NonNull; import android.content.Context; import android.graphics.PixelFormat; import android.provider.Settings; @@ -23,6 +24,7 @@ import android.view.Gravity; import android.view.WindowManager; import android.widget.ImageView; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; /** @@ -42,23 +44,36 @@ class MagnificationModeSwitch { private boolean mIsVisible = false; MagnificationModeSwitch(Context context) { + this(context, createView(context)); + } + + @VisibleForTesting + MagnificationModeSwitch(Context context, @NonNull ImageView imageView) { mContext = context; mWindowManager = (WindowManager) mContext.getSystemService( Context.WINDOW_SERVICE); mParams = createLayoutParams(); - mImageView = createView(mContext, mMagnificationMode); + mImageView = imageView; + applyResourcesValues(); mImageView.setOnClickListener( view -> { removeButton(); toggleMagnificationMode(); }); + mImageView.setImageResource(getIconResId(mMagnificationMode)); + } + + private void applyResourcesValues() { + final int padding = mContext.getResources().getDimensionPixelSize( + R.dimen.magnification_switch_button_padding); + mImageView.setPadding(padding, padding, padding, padding); } void removeButton() { - mImageView.animate().cancel(); if (!mIsVisible) { return; } + mImageView.animate().cancel(); mWindowManager.removeView(mImageView); mIsVisible = false; } @@ -72,7 +87,7 @@ class MagnificationModeSwitch { mWindowManager.addView(mImageView, mParams); mIsVisible = true; } - + mImageView.setAlpha(1.0f); // TODO(b/143852371): use accessibility timeout as a delay. // Dismiss the magnification switch button after the button is displayed for a period of // time. @@ -82,30 +97,29 @@ class MagnificationModeSwitch { .setStartDelay(START_DELAY_MS) .setDuration(DURATION_MS) .withEndAction( - () -> removeButton()); + () -> removeButton()) + .start(); } private void toggleMagnificationMode() { final int newMode = mMagnificationMode ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; mMagnificationMode = newMode; + mImageView.setImageResource(getIconResId(newMode)); Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, newMode); } - private static ImageView createView(Context context, int mode) { - final int padding = context.getResources().getDimensionPixelSize( - R.dimen.magnification_switch_button_padding); + private static ImageView createView(Context context) { ImageView imageView = new ImageView(context); - imageView.setImageResource(getIconResId(mode)); imageView.setClickable(true); imageView.setFocusable(true); - imageView.setPadding(padding, padding, padding, padding); imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); return imageView; } - private static int getIconResId(int mode) { + @VisibleForTesting + static int getIconResId(int mode) { return (mode == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) ? R.drawable.ic_open_in_new_window : R.drawable.ic_open_in_new_fullscreen; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 7e9faff8c296..8b6581ff1856 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -258,8 +258,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); params.gravity = Gravity.TOP | Gravity.LEFT; - params.x = mMagnificationFrame.left; - params.y = mMagnificationFrame.top; + params.x = mMagnificationFrame.left - mMirrorSurfaceMargin; + params.y = mMagnificationFrame.top - mMirrorSurfaceMargin; params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; params.setTitle(mContext.getString(R.string.magnification_window_title)); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 7b441d64af06..42911374dc87 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -246,11 +246,6 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, IActivityTaskManager getActivityTaskManager() { return ActivityTaskManager.getService(); } - - IFingerprintService getFingerprintService() { - return IFingerprintService.Stub.asInterface( - ServiceManager.getService(Context.FINGERPRINT_SERVICE)); - } } @Inject @@ -276,23 +271,12 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mActivityTaskManager = mInjector.getActivityTaskManager(); - if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { - IFingerprintService fingerprintService = mInjector.getFingerprintService(); - if (fingerprintService == null) { - Log.e(TAG, "FEATURE_FINGERPRINT is available, but FingerprintService is null"); - } else { - boolean isUdfps = false; - try { - // TODO(b/160024833): Enumerate through all of the sensors and check whether - // at least one of them is UDFPS. - isUdfps = fingerprintService.isUdfps(0 /* sensorId */); - } catch (RemoteException e) { - Log.w(TAG, "Unable to check whether the sensor is a UDFPS", e); - } - if (isUdfps) { - mUdfpsController = new UdfpsController(mContext, fingerprintService, - mWindowManager); - } + final FingerprintManager fpm = mContext.getSystemService(FingerprintManager.class); + if (fpm != null && fpm.isHardwareDetected()) { + // TODO(b/160024833): Enumerate through all of the sensors and check whether + // at least one of them is UDFPS. + if (fpm.isUdfps()) { + mUdfpsController = new UdfpsController(mContext, mWindowManager); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 97e97ffaae0c..739c2b155444 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -20,6 +20,7 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.PixelFormat; import android.graphics.Point; +import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IFingerprintService; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Handler; @@ -44,7 +45,7 @@ class UdfpsController { private static final String TAG = "UdfpsController"; private final Context mContext; - private final IFingerprintService mFingerprintService; + private final FingerprintManager mFingerprintManager; private final WindowManager mWindowManager; private final Handler mHandler; @@ -94,10 +95,9 @@ class UdfpsController { } }; - UdfpsController(Context context, IFingerprintService fingerprintService, - WindowManager windowManager) { + UdfpsController(Context context, WindowManager windowManager) { mContext = context; - mFingerprintService = fingerprintService; + mFingerprintManager = context.getSystemService(FingerprintManager.class); mWindowManager = windowManager; mHandler = new Handler(Looper.getMainLooper()); start(); @@ -133,11 +133,7 @@ class UdfpsController { mHbmEnableCommand = mContext.getResources().getString(R.string.udfps_hbm_enable_command); mHbmDisableCommand = mContext.getResources().getString(R.string.udfps_hbm_disable_command); - try { - mFingerprintService.setUdfpsOverlayController(new UdfpsOverlayController()); - } catch (RemoteException e) { - Log.e(TAG, "start | failed to set UDFPS controller", e); - } + mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController()); mIsOverlayShowing = false; } @@ -156,6 +152,7 @@ class UdfpsController { } private void hideUdfpsOverlay() { + onFingerUp(); mHandler.post(() -> { Log.v(TAG, "hideUdfpsOverlay | removing window"); if (mIsOverlayShowing) { @@ -174,19 +171,11 @@ class UdfpsController { Log.e(TAG, "onFingerDown | failed to enable HBM: " + e.getMessage()); } mView.onFingerDown(); - try { - mFingerprintService.onFingerDown(x, y, minor, major); - } catch (RemoteException e) { - Log.e(TAG, "onFingerDown | failed to propagate onFingerDown", e); - } + mFingerprintManager.onFingerDown(x, y, minor, major); } private void onFingerUp() { - try { - mFingerprintService.onFingerUp(); - } catch (RemoteException e) { - Log.e(TAG, "onFingeUp | failed to propagate onFingerUp", e); - } + mFingerprintManager.onFingerUp(); mView.onFingerUp(); try { FileWriter fw = new FileWriter(mHbmPath); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java index dfc7d53f08a7..8190550a74fc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java @@ -143,12 +143,12 @@ public class UdfpsView extends View { void onFingerDown() { mIsFingerDown = true; mSensorPaint.setStyle(Paint.Style.FILL); - invalidate(); + postInvalidate(); } void onFingerUp() { mIsFingerDown = false; mSensorPaint.setStyle(Paint.Style.STROKE); - invalidate(); + postInvalidate(); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java index 57e2362bd511..d017bc0e31c2 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java @@ -24,6 +24,8 @@ import android.content.pm.ShortcutInfo; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -40,15 +42,15 @@ import com.android.systemui.R; */ public class BubbleIconFactory extends BaseIconFactory { + private int mBadgeSize; + protected BubbleIconFactory(Context context) { super(context, context.getResources().getConfiguration().densityDpi, context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size)); - } - - int getBadgeSize() { - return mContext.getResources().getDimensionPixelSize( + mBadgeSize = mContext.getResources().getDimensionPixelSize( com.android.launcher3.icons.R.dimen.profile_badge_size); } + /** * Returns the drawable that the developer has provided to display in the bubble. */ @@ -78,18 +80,20 @@ public class BubbleIconFactory extends BaseIconFactory { * will include the workprofile indicator on the badge if appropriate. */ BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation) { - Bitmap userBadgedBitmap = createIconBitmap( - userBadgedAppIcon, 1f, getBadgeSize()); - ShadowGenerator shadowGenerator = new ShadowGenerator(getBadgeSize()); - if (!isImportantConversation) { - Canvas c = new Canvas(); - c.setBitmap(userBadgedBitmap); - shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c); - return createIconBitmap(userBadgedBitmap); - } else { - float ringStrokeWidth = mContext.getResources().getDimensionPixelSize( + ShadowGenerator shadowGenerator = new ShadowGenerator(mBadgeSize); + Bitmap userBadgedBitmap = createIconBitmap(userBadgedAppIcon, 1f, mBadgeSize); + + if (userBadgedAppIcon instanceof AdaptiveIconDrawable) { + userBadgedBitmap = Bitmap.createScaledBitmap( + getCircleBitmap((AdaptiveIconDrawable) userBadgedAppIcon, /* size */ + userBadgedAppIcon.getIntrinsicWidth()), + mBadgeSize, mBadgeSize, /* filter */ true); + } + + if (isImportantConversation) { + final float ringStrokeWidth = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.importance_ring_stroke_width); - int importantConversationColor = mContext.getResources().getColor( + final int importantConversationColor = mContext.getResources().getColor( com.android.settingslib.R.color.important_conversation, null); Bitmap badgeAndRing = Bitmap.createBitmap(userBadgedBitmap.getWidth(), userBadgedBitmap.getHeight(), userBadgedBitmap.getConfig()); @@ -114,9 +118,45 @@ public class BubbleIconFactory extends BaseIconFactory { shadowGenerator.recreateIcon(Bitmap.createBitmap(badgeAndRing), c); return createIconBitmap(badgeAndRing); + } else { + Canvas c = new Canvas(); + c.setBitmap(userBadgedBitmap); + shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c); + return createIconBitmap(userBadgedBitmap); } } + public Bitmap getCircleBitmap(AdaptiveIconDrawable icon, int size) { + Drawable foreground = icon.getForeground(); + Drawable background = icon.getBackground(); + Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(); + canvas.setBitmap(bitmap); + + // Clip canvas to circle. + Path circlePath = new Path(); + circlePath.addCircle(/* x */ size / 2f, + /* y */ size / 2f, + /* radius */ size / 2f, + Path.Direction.CW); + canvas.clipPath(circlePath); + + // Draw background. + background.setBounds(0, 0, size, size); + background.draw(canvas); + + // Draw foreground. The foreground and background drawables are derived from adaptive icons + // Some icon shapes fill more space than others, so adaptive icons are normalized to about + // the same size. This size is smaller than the original bounds, so we estimate + // the difference in this offset. + int offset = size / 5; + foreground.setBounds(-offset, -offset, size + offset, size + offset); + foreground.draw(canvas); + + canvas.setBitmap(null); + return bitmap; + } + /** * Returns a {@link BitmapInfo} for the entire bubble icon including the badge. */ diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt index 78d70877a90e..e8dba8f3b585 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt @@ -217,10 +217,10 @@ class LogBuffer( private fun dumpMessage(message: LogMessage, pw: PrintWriter) { pw.print(DATE_FORMAT.format(message.timestamp)) pw.print(" ") - pw.print(message.level) + pw.print(message.level.shortString) pw.print(" ") pw.print(message.tag) - pw.print(" ") + pw.print(": ") pw.println(message.printer(message)) } diff --git a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt index 7b9af0f91200..53f231c9f9d2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt @@ -21,11 +21,14 @@ import android.util.Log /** * Enum version of @Log.Level */ -enum class LogLevel(@Log.Level val nativeLevel: Int) { - VERBOSE(Log.VERBOSE), - DEBUG(Log.DEBUG), - INFO(Log.INFO), - WARNING(Log.WARN), - ERROR(Log.ERROR), - WTF(Log.ASSERT) +enum class LogLevel( + @Log.Level val nativeLevel: Int, + val shortString: String +) { + VERBOSE(Log.VERBOSE, "V"), + DEBUG(Log.DEBUG, "D"), + INFO(Log.INFO, "I"), + WARNING(Log.WARN, "W"), + ERROR(Log.ERROR, "E"), + WTF(Log.ASSERT, "WTF") } diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java index 370f9a762402..d9d46605f209 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java @@ -529,6 +529,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, updateVisibility(false /* visible */); mMinimized = false; removeDivider(); + mImePositionProcessor.reset(); } void ensureMinimizedSplit() { diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java index 9db389eba3d8..5aeca5e07bdd 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java @@ -117,6 +117,18 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder()); } + void reset() { + mPaused = true; + mPausedTargetAdjusted = false; + mAdjustedWhileHidden = false; + mAnimation = null; + mAdjusted = mTargetAdjusted = false; + mImeWasShown = mTargetShown = false; + mTargetPrimaryDim = mTargetSecondaryDim = mLastPrimaryDim = mLastSecondaryDim = 0.f; + mSecondaryHasFocus = false; + mLastAdjustTop = -1; + } + private void updateDimTargets() { final boolean splitIsVisible = !getView().isHidden(); mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible) @@ -126,18 +138,20 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor } @Override - public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop, - boolean imeShouldShow, SurfaceControl.Transaction t) { + @ImeAnimationFlags + public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop, + boolean imeShouldShow, boolean imeIsFloating, SurfaceControl.Transaction t) { mHiddenTop = hiddenTop; mShownTop = shownTop; mTargetShown = imeShouldShow; if (!isDividerVisible()) { - return; + return 0; } final boolean splitIsVisible = !getView().isHidden(); mSecondaryHasFocus = getSecondaryHasFocus(displayId); final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus - && !getLayout().mDisplayLayout.isLandscape() && !mSplits.mDivider.isMinimized(); + && !imeIsFloating && !getLayout().mDisplayLayout.isLandscape() + && !mSplits.mDivider.isMinimized(); if (mLastAdjustTop < 0) { mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop; } else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) { @@ -155,7 +169,7 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor if (mPaused) { mPausedTargetAdjusted = targetAdjusted; if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState()); - return; + return (targetAdjusted || mAdjusted) ? IME_ANIMATION_NO_ALPHA : 0; } mTargetAdjusted = targetAdjusted; updateDimTargets(); @@ -174,6 +188,7 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor } else { mAdjustedWhileHidden = true; } + return (mTargetAdjusted || mAdjusted) ? IME_ANIMATION_NO_ALPHA : 0; } private void updateImeAdjustState() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index cdcfac87f1c8..d264af94947d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -84,7 +84,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl private final Map<View, MenuItem> mMenuItemsByView = new ArrayMap<>(); private OnMenuEventListener mMenuListener; private boolean mDismissRtl; - private boolean mIsForeground; private ValueAnimator mFadeAnimator; private boolean mAnimating; @@ -191,9 +190,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl @Override public void createMenu(ViewGroup parent, StatusBarNotification sbn) { mParent = (ExpandableNotificationRow) parent; - createMenuViews(true /* resetState */, - sbn != null && (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) - != 0); + createMenuViews(true /* resetState */); } @Override @@ -237,8 +234,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl // Menu hasn't been created yet, no need to do anything. return; } - createMenuViews(!isMenuVisible() /* resetState */, - (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0); + createMenuViews(!isMenuVisible() /* resetState */); } @Override @@ -253,9 +249,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl mParent.removeListener(); } - private void createMenuViews(boolean resetState, final boolean isForeground) { - mIsForeground = isForeground; - + private void createMenuViews(boolean resetState) { final Resources res = mContext.getResources(); mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size); mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height); @@ -266,7 +260,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl SHOW_NOTIFICATION_SNOOZE, 0) == 1; // Construct the menu items based on the notification - if (!isForeground && showSnooze) { + if (showSnooze) { // Only show snooze for non-foreground notifications, and if the setting is on mSnoozeItem = createSnoozeItem(mContext); } @@ -283,7 +277,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl mInfoItem = createInfoItem(mContext); } - if (!isForeground && showSnooze) { + if (showSnooze) { mRightMenuItems.add(mSnoozeItem); } mRightMenuItems.add(mInfoItem); @@ -789,7 +783,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl public void setDismissRtl(boolean dismissRtl) { mDismissRtl = dismissRtl; if (mMenuContainer != null) { - createMenuViews(true, mIsForeground); + createMenuViews(true); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 27daf8615a31..837543c9bdae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -591,6 +591,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback .registerDisplayListener(this, new Handler(Looper.getMainLooper())); mOrientationHandle = new QuickswitchOrientedNavHandle(getContext()); + mOrientationHandle.setId(R.id.secondary_home_handle); getBarTransitions().addDarkIntensityListener(mOrientationHandleIntensityListener); mOrientationParams = new WindowManager.LayoutParams(0, 0, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java index 8fcaa67e7614..23d03a4b225a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java @@ -80,6 +80,22 @@ public class KeyButtonDrawable extends Drawable { private final Paint mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private final ShadowDrawableState mState; private AnimatedVectorDrawable mAnimatedDrawable; + private final Callback mAnimatedDrawableCallback = new Callback() { + @Override + public void invalidateDrawable(@NonNull Drawable who) { + invalidateSelf(); + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + unscheduleSelf(what); + } + }; public KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor, boolean horizontalFlip, Color ovalBackgroundColor) { @@ -97,6 +113,7 @@ public class KeyButtonDrawable extends Drawable { } if (canAnimate()) { mAnimatedDrawable = (AnimatedVectorDrawable) mState.mChildState.newDrawable().mutate(); + mAnimatedDrawable.setCallback(mAnimatedDrawableCallback); setDrawableBounds(mAnimatedDrawable); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index 6bc0565510a2..c0602762ef29 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -26,6 +26,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.assist.AssistManager; import com.android.systemui.statusbar.CommandQueue; @@ -72,6 +73,11 @@ public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks { } catch (RemoteException ex) { // If the system process isn't there we're doomed anyway. } + + if (mContext.getResources().getBoolean(R.bool.audio_recording_disclosure_enabled)) { + // Creating AudioRecordingDisclosureBar and just letting it run + new AudioRecordingDisclosureBar(mContext); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java index 36e360da857f..8e4e12358836 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java @@ -112,6 +112,13 @@ public class AudioRecordingDisclosureBar implements */ private final AudioActivityObserver[] mAudioActivityObservers; /** + * Whether the indicator should expand and show the recording application's label. + * If disabled ({@code false}) the "minimized" ({@link #STATE_MINIMIZED}) indicator would appear + * on the screen whenever an application is recording, but will not reveal to the user what + * application this is. + */ + private final boolean mRevealRecordingPackages; + /** * Set of applications that we've notified the user about since the indicator came up. Meaning * that if an application is in this list then at some point since the indicator came up, it * was expanded showing this application's title. @@ -137,6 +144,8 @@ public class AudioRecordingDisclosureBar implements public AudioRecordingDisclosureBar(Context context) { mContext = context; + mRevealRecordingPackages = mContext.getResources().getBoolean( + R.bool.audio_recording_disclosure_reveal_packages); mExemptPackages = new ArraySet<>( Arrays.asList(mContext.getResources().getStringArray( R.array.audio_recording_disclosure_exempt_apps))); @@ -149,11 +158,6 @@ public class AudioRecordingDisclosureBar implements }; } - private String[] getGlobalStringArray(String setting) { - String result = Settings.Global.getString(mContext.getContentResolver(), setting); - return TextUtils.isEmpty(result) ? new String[0] : result.split(","); - } - @UiThread @Override public void onAudioActivityStateChange(boolean active, String packageName) { @@ -190,7 +194,9 @@ public class AudioRecordingDisclosureBar implements break; case STATE_MINIMIZED: - expand(packageName); + if (mRevealRecordingPackages) { + expand(packageName); + } break; case STATE_DISAPPEARING: @@ -228,9 +234,8 @@ public class AudioRecordingDisclosureBar implements @UiThread private void show(String packageName) { - final String label = getApplicationLabel(packageName); if (DEBUG) { - Log.d(TAG, "Showing indicator for " + packageName + " (" + label + ")..."); + Log.d(TAG, "Showing indicator for " + packageName); } mIsLtr = mContext.getResources().getConfiguration().getLayoutDirection() @@ -247,19 +252,31 @@ public class AudioRecordingDisclosureBar implements mTextView = mTextsContainers.findViewById(R.id.text); mBgEnd = mIndicatorView.findViewById(R.id.bg_end); - // Swap background drawables depending on layout directions (both drawables have rounded - // corners only on one side) - if (mIsLtr) { - mBgEnd.setBackgroundResource(R.drawable.tv_rect_dark_right_rounded); - mIconContainerBg.setBackgroundResource(R.drawable.tv_rect_dark_left_rounded); + // Set up the notification text + if (mRevealRecordingPackages) { + // Swap background drawables depending on layout directions (both drawables have rounded + // corners only on one side) + if (mIsLtr) { + mBgEnd.setBackgroundResource(R.drawable.tv_rect_dark_right_rounded); + mIconContainerBg.setBackgroundResource(R.drawable.tv_rect_dark_left_rounded); + } else { + mBgEnd.setBackgroundResource(R.drawable.tv_rect_dark_left_rounded); + mIconContainerBg.setBackgroundResource(R.drawable.tv_rect_dark_right_rounded); + } + + final String label = getApplicationLabel(packageName); + mTextView.setText(mContext.getString(R.string.app_accessed_mic, label)); } else { - mBgEnd.setBackgroundResource(R.drawable.tv_rect_dark_left_rounded); - mIconContainerBg.setBackgroundResource(R.drawable.tv_rect_dark_right_rounded); + mTextsContainers.setVisibility(View.GONE); + mIconContainerBg.setVisibility(View.GONE); + mTextView.setVisibility(View.GONE); + mBgEnd.setVisibility(View.GONE); + mTextsContainers = null; + mIconContainerBg = null; + mTextView = null; + mBgEnd = null; } - // Set up the notification text - mTextView.setText(mContext.getString(R.string.app_accessed_mic, label)); - // Initially change the visibility to INVISIBLE, wait until and receives the size and // then animate it moving from "off" the screen correctly mIndicatorView.setVisibility(View.INVISIBLE); @@ -296,7 +313,11 @@ public class AudioRecordingDisclosureBar implements @Override public void onAnimationEnd(Animator animation) { startPulsatingAnimation(); - onExpanded(); + if (mRevealRecordingPackages) { + onExpanded(); + } else { + onMinimized(); + } } }); set.start(); @@ -321,6 +342,8 @@ public class AudioRecordingDisclosureBar implements @UiThread private void expand(String packageName) { + assertRevealingRecordingPackages(); + final String label = getApplicationLabel(packageName); if (DEBUG) { Log.d(TAG, "Expanding for " + packageName + " (" + label + ")..."); @@ -348,6 +371,8 @@ public class AudioRecordingDisclosureBar implements @UiThread private void minimize() { + assertRevealingRecordingPackages(); + if (DEBUG) Log.d(TAG, "Minimizing..."); final int targetOffset = (mIsLtr ? 1 : -1) * mTextsContainers.getWidth(); final AnimatorSet set = new AnimatorSet(); @@ -393,6 +418,8 @@ public class AudioRecordingDisclosureBar implements @UiThread private void onExpanded() { + assertRevealingRecordingPackages(); + if (DEBUG) Log.d(TAG, "Expanded"); mState = STATE_SHOWN; @@ -404,11 +431,13 @@ public class AudioRecordingDisclosureBar implements if (DEBUG) Log.d(TAG, "Minimized"); mState = STATE_MINIMIZED; - if (!mPendingNotificationPackages.isEmpty()) { - // There is a new application that started recording, tell the user about it. - expand(mPendingNotificationPackages.poll()); - } else { - hideIndicatorIfNeeded(); + if (mRevealRecordingPackages) { + if (!mPendingNotificationPackages.isEmpty()) { + // There is a new application that started recording, tell the user about it. + expand(mPendingNotificationPackages.poll()); + } else { + hideIndicatorIfNeeded(); + } } } @@ -451,7 +480,14 @@ public class AudioRecordingDisclosureBar implements animator.start(); } + private String[] getGlobalStringArray(String setting) { + String result = Settings.Global.getString(mContext.getContentResolver(), setting); + return TextUtils.isEmpty(result) ? new String[0] : result.split(","); + } + private String getApplicationLabel(String packageName) { + assertRevealingRecordingPackages(); + final PackageManager pm = mContext.getPackageManager(); final ApplicationInfo appInfo; try { @@ -461,4 +497,11 @@ public class AudioRecordingDisclosureBar implements } return pm.getApplicationLabel(appInfo).toString(); } + + private void assertRevealingRecordingPackages() { + if (!mRevealRecordingPackages) { + Log.e(TAG, "Not revealing recording packages", + DEBUG ? new RuntimeException("Should not be called") : null); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java index 3402a52f056a..89f469a438a9 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -19,6 +19,7 @@ package com.android.systemui.wm; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.annotation.IntDef; import android.content.Context; import android.content.res.Configuration; import android.graphics.Point; @@ -136,12 +137,16 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } } - private void dispatchStartPositioning(int displayId, int hiddenTop, int shownTop, - boolean show, SurfaceControl.Transaction t) { + @ImePositionProcessor.ImeAnimationFlags + private int dispatchStartPositioning(int displayId, int hiddenTop, int shownTop, + boolean show, boolean isFloating, SurfaceControl.Transaction t) { synchronized (mPositionProcessors) { + int flags = 0; for (ImePositionProcessor pp : mPositionProcessors) { - pp.onImeStartPositioning(displayId, hiddenTop, shownTop, show, t); + flags |= pp.onImeStartPositioning( + displayId, hiddenTop, shownTop, show, isFloating, t); } + return flags; } } @@ -184,6 +189,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged int mRotation = Surface.ROTATION_0; boolean mImeShowing = false; final Rect mImeFrame = new Rect(); + boolean mAnimateAlpha = true; PerDisplay(int displayId, int initialRotation) { mDisplayId = displayId; @@ -273,15 +279,29 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged return mImeFrame.top + (int) surfaceOffset; } + private boolean calcIsFloating(InsetsSource imeSource) { + final Rect frame = imeSource.getFrame(); + if (frame.height() == 0) { + return true; + } + // Some Floating Input Methods will still report a frame, but the frame is actually + // a nav-bar inset created by WM and not part of the IME (despite being reported as + // an IME inset). For now, we assume that no non-floating IME will be <= this nav bar + // frame height so any reported frame that is <= nav-bar frame height is assumed to + // be floating. + return frame.height() <= mSystemWindows.mDisplayController.getDisplayLayout(mDisplayId) + .navBarFrameHeight(); + } + private void startAnimation(final boolean show, final boolean forceRestart) { final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME); if (imeSource == null || mImeSourceControl == null) { return; } final Rect newFrame = imeSource.getFrame(); - final boolean isFloating = newFrame.height() == 0 && show; + final boolean isFloating = calcIsFloating(imeSource) && show; if (isFloating) { - // This is likely a "floating" or "expanded" IME, so to get animations, just + // This is a "floating" or "expanded" IME, so to get animations, just // pretend the ime has some size just below the screen. mImeFrame.set(newFrame); final int floatingInset = (int) ( @@ -334,7 +354,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged SurfaceControl.Transaction t = mTransactionPool.acquire(); float value = (float) animation.getAnimatedValue(); t.setPosition(mImeSourceControl.getLeash(), x, value); - final float alpha = isFloating ? (value - hiddenY) / (shownY - hiddenY) : 1.f; + final float alpha = (mAnimateAlpha || isFloating) + ? (value - hiddenY) / (shownY - hiddenY) : 1.f; t.setAlpha(mImeSourceControl.getLeash(), alpha); dispatchPositionChanged(mDisplayId, imeTop(value), t); t.apply(); @@ -347,16 +368,18 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged public void onAnimationStart(Animator animation) { SurfaceControl.Transaction t = mTransactionPool.acquire(); t.setPosition(mImeSourceControl.getLeash(), x, startY); - final float alpha = isFloating ? (startY - hiddenY) / (shownY - hiddenY) : 1.f; - t.setAlpha(mImeSourceControl.getLeash(), alpha); if (DEBUG) { Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:" + imeTop(hiddenY) + "->" + imeTop(shownY) + " showing:" + (mAnimationDirection == DIRECTION_SHOW)); } - dispatchStartPositioning(mDisplayId, imeTop(hiddenY), - imeTop(shownY), mAnimationDirection == DIRECTION_SHOW, - t); + int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY), + imeTop(shownY), mAnimationDirection == DIRECTION_SHOW, isFloating, t); + mAnimateAlpha = (flags & ImePositionProcessor.IME_ANIMATION_NO_ALPHA) == 0; + final float alpha = (mAnimateAlpha || isFloating) + ? (startY - hiddenY) / (shownY - hiddenY) + : 1.f; + t.setAlpha(mImeSourceControl.getLeash(), alpha); if (mAnimationDirection == DIRECTION_SHOW) { t.show(mImeSourceControl.getLeash()); } @@ -419,15 +442,33 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged */ public interface ImePositionProcessor { /** + * Indicates that ime shouldn't animate alpha. It will always be opaque. Used when stuff + * behind the IME shouldn't be visible (for example during split-screen adjustment where + * there is nothing behind the ime). + */ + int IME_ANIMATION_NO_ALPHA = 1; + + /** @hide */ + @IntDef(prefix = { "IME_ANIMATION_" }, value = { + IME_ANIMATION_NO_ALPHA, + }) + @interface ImeAnimationFlags {} + + /** * Called when the IME position is starting to animate. * * @param hiddenTop The y position of the top of the IME surface when it is hidden. * @param shownTop The y position of the top of the IME surface when it is shown. * @param showing {@code true} when we are animating from hidden to shown, {@code false} * when animating from shown to hidden. + * @param isFloating {@code true} when the ime is a floating ime (doesn't inset). + * @return flags that may alter how ime itself is animated (eg. no-alpha). */ - default void onImeStartPositioning(int displayId, int hiddenTop, int shownTop, - boolean showing, SurfaceControl.Transaction t) {} + @ImeAnimationFlags + default int onImeStartPositioning(int displayId, int hiddenTop, int shownTop, + boolean showing, boolean isFloating, SurfaceControl.Transaction t) { + return 0; + } /** * Called when the ime position changed. This is expected to be a synchronous call on the diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java index cfec1c07ff1d..a341f3050ea6 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java @@ -79,6 +79,7 @@ public class DisplayLayout { private final Rect mStableInsets = new Rect(); private boolean mHasNavigationBar = false; private boolean mHasStatusBar = false; + private int mNavBarFrameHeight = 0; /** * Create empty layout. @@ -146,6 +147,7 @@ public class DisplayLayout { if (mHasStatusBar) { convertNonDecorInsetsToStableInsets(res, mStableInsets, mWidth, mHeight, mHasStatusBar); } + mNavBarFrameHeight = getNavigationBarFrameHeight(res, mWidth > mHeight); } /** @@ -214,6 +216,11 @@ public class DisplayLayout { return mWidth > mHeight; } + /** Get the navbar frame height (used by ime). */ + public int navBarFrameHeight() { + return mNavBarFrameHeight; + } + /** Gets the orientation of this layout */ public int getOrientation() { return (mWidth > mHeight) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; @@ -483,6 +490,7 @@ public class DisplayLayout { } else { return res.getDimensionPixelSize(R.dimen.navigation_bar_width_car_mode); } + } else { if (navBarSide == NAV_BAR_BOTTOM) { return res.getDimensionPixelSize(landscape @@ -493,4 +501,11 @@ public class DisplayLayout { } } } + + /** @see com.android.server.wm.DisplayPolicy#getNavigationBarFrameHeight */ + public static int getNavigationBarFrameHeight(Resources res, boolean landscape) { + return res.getDimensionPixelSize(landscape + ? R.dimen.navigation_bar_frame_height_landscape + : R.dimen.navigation_bar_frame_height); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java new file mode 100644 index 000000000000..71f3d5bee225 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020 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.accessibility; + +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; + +import static com.android.systemui.accessibility.MagnificationModeSwitch.getIconResId; + +import static junit.framework.Assert.assertEquals; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.view.ViewPropertyAnimator; +import android.view.WindowManager; +import android.widget.ImageView; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class MagnificationModeSwitchTest extends SysuiTestCase { + + @Mock + private ImageView mMockImageView; + @Mock + private WindowManager mWindowManager; + @Mock + private ViewPropertyAnimator mViewPropertyAnimator; + private MagnificationModeSwitch mMagnificationModeSwitch; + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); + + when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator); + when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator); + when(mViewPropertyAnimator.setStartDelay(anyLong())).thenReturn(mViewPropertyAnimator); + when(mViewPropertyAnimator.withEndAction(any(Runnable.class))).thenReturn( + mViewPropertyAnimator); + + when(mMockImageView.animate()).thenReturn(mViewPropertyAnimator); + + mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mMockImageView); + } + + @Test + public void removeButton_removeView() { + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); + + mMagnificationModeSwitch.removeButton(); + + verify(mWindowManager).removeView(mMockImageView); + // First invocation is in showButton. + verify(mViewPropertyAnimator, times(2)).cancel(); + } + + @Test + public void showWindowModeButton_fullscreenMode_addViewAndSetImageResource() { + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); + + verify(mMockImageView).setAlpha(1.0f); + verify(mMockImageView).setImageResource( + getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)); + verify(mViewPropertyAnimator).cancel(); + verify(mViewPropertyAnimator).setDuration(anyLong()); + verify(mViewPropertyAnimator).setStartDelay(anyLong()); + verify(mViewPropertyAnimator).alpha(anyFloat()); + ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class); + verify(mViewPropertyAnimator).withEndAction(captor.capture()); + verify(mWindowManager).addView(eq(mMockImageView), any(WindowManager.LayoutParams.class)); + + captor.getValue().run(); + + // First invocation is in showButton. + verify(mViewPropertyAnimator, times(2)).cancel(); + verify(mWindowManager).removeView(mMockImageView); + } + + @Test + public void performClick_fullscreenMode_removeViewAndChangeSettingsValue() { + ArgumentCaptor<View.OnClickListener> captor = ArgumentCaptor.forClass( + View.OnClickListener.class); + verify(mMockImageView).setOnClickListener(captor.capture()); + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + + captor.getValue().onClick(mMockImageView); + + // First invocation is in showButton. + verify(mViewPropertyAnimator, times(2)).cancel(); + verify(mMockImageView).setImageResource( + getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)); + verify(mWindowManager).removeView(mMockImageView); + final int actualMode = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0); + assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, actualMode); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizerTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizerTest.java index 7cf351f3efdd..84a261b6e7d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizerTest.java @@ -44,6 +44,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.wm.DisplayController; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -135,6 +136,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { assertThat(mDisplayAreaOrganizer.mDisplayAreaMap.containsKey(mDisplayAreaInfo)).isTrue(); } + @Ignore("b/160848002") @Test public void testScheduleOffset() { final int xOffSet = 0; @@ -148,6 +150,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { OneHandedDisplayAreaOrganizer.MSG_OFFSET_ANIMATE)).isEqualTo(true); } + @Ignore("b/160848002") @Test public void testRotation_portraitToLandscape() { when(mMockLeash.isValid()).thenReturn(false); @@ -180,6 +183,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true); } + @Ignore("b/160848002") @Test public void testRotation_landscapeToPortrait() { when(mMockLeash.isValid()).thenReturn(false); @@ -212,6 +216,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true); } + @Ignore("b/160848002") @Test public void testRotation_portraitToPortrait() { when(mMockLeash.isValid()).thenReturn(false); @@ -244,6 +249,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false); } + @Ignore("b/160848002") @Test public void testRotation_landscapeToLandscape() { when(mMockLeash.isValid()).thenReturn(false); diff --git a/packages/Tethering/common/TetheringLib/api/module-lib-current.txt b/packages/Tethering/common/TetheringLib/api/module-lib-current.txt index 754584e70fad..6ddb122936e7 100644 --- a/packages/Tethering/common/TetheringLib/api/module-lib-current.txt +++ b/packages/Tethering/common/TetheringLib/api/module-lib-current.txt @@ -1,24 +1,6 @@ // Signature format: 2.0 package android.net { - public final class TetheredClient implements android.os.Parcelable { - ctor public TetheredClient(@NonNull android.net.MacAddress, @NonNull java.util.Collection<android.net.TetheredClient.AddressInfo>, int); - method public int describeContents(); - method @NonNull public java.util.List<android.net.TetheredClient.AddressInfo> getAddresses(); - method @NonNull public android.net.MacAddress getMacAddress(); - method public int getTetheringType(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient> CREATOR; - } - - public static final class TetheredClient.AddressInfo implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public android.net.LinkAddress getAddress(); - method @Nullable public String getHostname(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient.AddressInfo> CREATOR; - } - public final class TetheringConstants { field public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; field public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; @@ -38,69 +20,15 @@ package android.net { method @NonNull public String[] getTetheringErroredIfaces(); method public boolean isTetheringSupported(); method public boolean isTetheringSupported(@NonNull String); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback); - method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener); method public void requestLatestTetheringEntitlementResult(int, @NonNull android.os.ResultReceiver, boolean); method @Deprecated public int setUsbTethering(boolean); - method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback); method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback); - method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering(); - method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int); method @Deprecated public int tether(@NonNull String); - method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback); method @Deprecated public int untether(@NonNull String); - field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"; - field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY"; - field public static final String EXTRA_ACTIVE_TETHER = "tetherArray"; - field public static final String EXTRA_AVAILABLE_TETHER = "availableArray"; - field public static final String EXTRA_ERRORED_TETHER = "erroredArray"; - field public static final int TETHERING_BLUETOOTH = 2; // 0x2 - field public static final int TETHERING_ETHERNET = 5; // 0x5 - field public static final int TETHERING_INVALID = -1; // 0xffffffff - field public static final int TETHERING_NCM = 4; // 0x4 - field public static final int TETHERING_USB = 1; // 0x1 - field public static final int TETHERING_WIFI = 0; // 0x0 - field public static final int TETHERING_WIFI_P2P = 3; // 0x3 - field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc - field public static final int TETHER_ERROR_DISABLE_FORWARDING_ERROR = 9; // 0x9 - field public static final int TETHER_ERROR_ENABLE_FORWARDING_ERROR = 8; // 0x8 - field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd - field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa - field public static final int TETHER_ERROR_INTERNAL_ERROR = 5; // 0x5 - field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf - field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe - field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 - field public static final int TETHER_ERROR_PROVISIONING_FAILED = 11; // 0xb - field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2 - field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6 - field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4 - field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1 - field public static final int TETHER_ERROR_UNKNOWN_TYPE = 16; // 0x10 - field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3 - field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7 - field public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; // 0x2 - field public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; // 0x1 - field public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; // 0x0 - } - - public static interface TetheringManager.OnTetheringEntitlementResultListener { - method public void onTetheringEntitlementResult(int); - } - - public static interface TetheringManager.StartTetheringCallback { - method public default void onTetheringFailed(int); - method public default void onTetheringStarted(); } public static interface TetheringManager.TetheringEventCallback { - method public default void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>); - method public default void onError(@NonNull String, int); - method public default void onOffloadStatusChanged(int); method public default void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps); - method public default void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>); - method public default void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>); - method public default void onTetheringSupported(boolean); - method public default void onUpstreamChanged(@Nullable android.net.Network); } public static class TetheringManager.TetheringInterfaceRegexps { @@ -109,21 +37,5 @@ package android.net { method @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs(); } - public static class TetheringManager.TetheringRequest { - method @Nullable public android.net.LinkAddress getClientStaticIpv4Address(); - method @Nullable public android.net.LinkAddress getLocalIpv4Address(); - method public boolean getShouldShowEntitlementUi(); - method public int getTetheringType(); - method public boolean isExemptFromEntitlementCheck(); - } - - public static class TetheringManager.TetheringRequest.Builder { - ctor public TetheringManager.TetheringRequest.Builder(int); - method @NonNull public android.net.TetheringManager.TetheringRequest build(); - method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean); - method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setShouldShowEntitlementUi(boolean); - method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress); - } - } diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java index 48be0d96425c..645b00001330 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java @@ -16,8 +16,6 @@ package android.net; -import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -36,7 +34,6 @@ import java.util.Objects; * @hide */ @SystemApi -@SystemApi(client = MODULE_LIBRARIES) @TestApi public final class TetheredClient implements Parcelable { @NonNull diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index 7483bac11662..db8436859281 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -55,7 +55,6 @@ import java.util.function.Supplier; * @hide */ @SystemApi -@SystemApi(client = MODULE_LIBRARIES) @TestApi public class TetheringManager { private static final String TAG = TetheringManager.class.getSimpleName(); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index dad061262d49..d50e9d720861 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -175,7 +175,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH */ public FullScreenMagnificationGestureHandler(Context context, FullScreenMagnificationController fullScreenMagnificationController, - MagnificationGestureHandler.ScaleChangedListener listener, + ScaleChangedListener listener, boolean detectTripleTap, boolean detectShortcutTrigger, int displayId) { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java index f38c38ff7d26..d6f53d2c225c 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java @@ -23,7 +23,7 @@ import com.android.server.accessibility.BaseEventStreamTransformation; */ public abstract class MagnificationGestureHandler extends BaseEventStreamTransformation { - protected final MagnificationGestureHandler.ScaleChangedListener mListener; + protected final ScaleChangedListener mListener; protected MagnificationGestureHandler(ScaleChangedListener listener) { mListener = listener; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index 56c05199d0ca..bd25f2bea881 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -100,7 +100,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl */ public WindowMagnificationGestureHandler(Context context, WindowMagnificationManager windowMagnificationMgr, - MagnificationGestureHandler.ScaleChangedListener listener, boolean detectTripleTap, + ScaleChangedListener listener, boolean detectTripleTap, boolean detectShortcutTrigger, int displayId) { super(listener); if (DEBUG_ALL) { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index a3c5d1edd351..a08c2dd4292d 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -17,7 +17,10 @@ package com.android.server.accessibility.magnification; import android.annotation.Nullable; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; @@ -52,17 +55,28 @@ public class WindowMagnificationManager implements static final float MAX_SCALE = FullScreenMagnificationController.MAX_SCALE; static final float MIN_SCALE = FullScreenMagnificationController.MIN_SCALE; - private final Object mLock = new Object();; + private final Object mLock = new Object(); private final Context mContext; @VisibleForTesting @GuardedBy("mLock") - @Nullable WindowMagnificationConnectionWrapper mConnectionWrapper; + @Nullable + WindowMagnificationConnectionWrapper mConnectionWrapper; @GuardedBy("mLock") private ConnectionCallback mConnectionCallback; @GuardedBy("mLock") private SparseArray<WindowMagnifier> mWindowMagnifiers = new SparseArray<>(); private int mUserId; + @VisibleForTesting + protected final BroadcastReceiver mScreenStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int displayId = context.getDisplayId(); + removeMagnificationButton(displayId); + disableWindowMagnification(displayId); + } + }; + public WindowMagnificationManager(Context context, int userId) { mContext = context; mUserId = userId; @@ -133,8 +147,12 @@ public class WindowMagnificationManager implements if (connect == isConnected()) { return false; } - if (!connect) { + if (connect) { + final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF); + mContext.registerReceiver(mScreenStateReceiver, intentFilter); + } else { disableAllWindowMagnifiers(); + mContext.unregisterReceiver(mScreenStateReceiver); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 25e6eb6e943f..fa18ccb53408 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -6012,9 +6012,9 @@ public class ActivityManagerService extends IActivityManager.Stub } } - private boolean isAppBad(ApplicationInfo info) { + private boolean isAppBad(final String processName, final int uid) { synchronized (this) { - return mAppErrors.isBadProcessLocked(info); + return mAppErrors.isBadProcessLocked(processName, uid); } } @@ -6082,7 +6082,7 @@ public class ActivityManagerService extends IActivityManager.Stub mPendingStartActivityUids.isPendingTopPid(pr.uid, pids[i]); states[i] = isPendingTop ? PROCESS_STATE_TOP : pr.getCurProcState(); if (scores != null) { - scores[i] = isPendingTop ? ProcessList.FOREGROUND_APP_ADJ : pr.curAdj; + scores[i] = isPendingTop ? (ProcessList.FOREGROUND_APP_ADJ - 1) : pr.curAdj; } } else { states[i] = PROCESS_STATE_NONEXISTENT; @@ -19713,8 +19713,8 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public boolean isAppBad(ApplicationInfo info) { - return ActivityManagerService.this.isAppBad(info); + public boolean isAppBad(final String processName, final int uid) { + return ActivityManagerService.this.isAppBad(processName, uid); } @Override diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 50d2cab0af81..a36a18b4cf5c 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -33,7 +33,6 @@ import android.app.ApplicationExitInfo; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.content.pm.VersionedPackage; import android.net.Uri; import android.os.Binder; @@ -263,16 +262,16 @@ class AppErrors { return needSep; } - boolean isBadProcessLocked(ApplicationInfo info) { - return mBadProcesses.get(info.processName, info.uid) != null; + boolean isBadProcessLocked(final String processName, final int uid) { + return mBadProcesses.get(processName, uid) != null; } - void clearBadProcessLocked(ApplicationInfo info) { - mBadProcesses.remove(info.processName, info.uid); + void clearBadProcessLocked(final String processName, final int uid) { + mBadProcesses.remove(processName, uid); } - void resetProcessCrashTimeLocked(ApplicationInfo info) { - mProcessCrashTimes.remove(info.processName, info.uid); + void resetProcessCrashTimeLocked(final String processName, final int uid) { + mProcessCrashTimes.remove(processName, uid); } void resetProcessCrashTimeLocked(boolean resetEntireUser, int appId, int userId) { @@ -548,7 +547,7 @@ class AppErrors { if (r != null && !r.isolated && res != AppErrorDialog.RESTART) { // XXX Can't keep track of crash time for isolated processes, // since they don't have a persistent identity. - mProcessCrashTimes.put(r.info.processName, r.uid, + mProcessCrashTimes.put(r.processName, r.uid, SystemClock.uptimeMillis()); } } @@ -695,8 +694,8 @@ class AppErrors { boolean tryAgain = false; if (!app.isolated) { - crashTime = mProcessCrashTimes.get(app.info.processName, app.uid); - crashTimePersistent = mProcessCrashTimesPersistent.get(app.info.processName, app.uid); + crashTime = mProcessCrashTimes.get(app.processName, app.uid); + crashTimePersistent = mProcessCrashTimesPersistent.get(app.processName, app.uid); } else { crashTime = crashTimePersistent = null; } @@ -723,10 +722,10 @@ class AppErrors { if (crashTime != null && now < crashTime + ProcessList.MIN_CRASH_INTERVAL) { // The process crashed again very quickly. If it was a bound foreground service, let's // try to restart again in a while, otherwise the process loses! - Slog.w(TAG, "Process " + app.info.processName + Slog.w(TAG, "Process " + app.processName + " has crashed too many times: killing!"); EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH, - app.userId, app.info.processName, app.uid); + app.userId, app.processName, app.uid); mService.mAtmInternal.onHandleAppCrash(app.getWindowProcessController()); if (!app.isPersistent()) { // We don't want to start this process again until the user @@ -734,13 +733,13 @@ class AppErrors { // need to keep it running. If a persistent process is actually // repeatedly crashing, then badness for everyone. EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.userId, app.uid, - app.info.processName); + app.processName); if (!app.isolated) { // XXX We don't have a way to mark isolated processes // as bad, since they don't have a peristent identity. - mBadProcesses.put(app.info.processName, app.uid, + mBadProcesses.put(app.processName, app.uid, new BadProcessInfo(now, shortMsg, longMsg, stackTrace)); - mProcessCrashTimes.remove(app.info.processName, app.uid); + mProcessCrashTimes.remove(app.processName, app.uid); } app.bad = true; app.removed = true; @@ -785,8 +784,8 @@ class AppErrors { if (!app.isolated) { // XXX Can't keep track of crash times for isolated processes, // because they don't have a persistent identity. - mProcessCrashTimes.put(app.info.processName, app.uid, now); - mProcessCrashTimesPersistent.put(app.info.processName, app.uid, now); + mProcessCrashTimes.put(app.processName, app.uid, now); + mProcessCrashTimesPersistent.put(app.processName, app.uid, now); } if (app.crashHandler != null) mService.mHandler.post(app.crashHandler); @@ -829,7 +828,7 @@ class AppErrors { } Long crashShowErrorTime = null; if (!proc.isolated) { - crashShowErrorTime = mProcessCrashShowDialogTimes.get(proc.info.processName, + crashShowErrorTime = mProcessCrashShowDialogTimes.get(proc.processName, proc.uid); } final boolean showFirstCrash = Settings.Global.getInt( @@ -850,7 +849,7 @@ class AppErrors { && (showFirstCrash || showFirstCrashDevOption || data.repeating)) { proc.getDialogController().showCrashDialogs(data); if (!proc.isolated) { - mProcessCrashShowDialogTimes.put(proc.info.processName, proc.uid, now); + mProcessCrashShowDialogTimes.put(proc.processName, proc.uid, now); } } else { // The device is asleep, so just pretend that the user diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index ebff0691c1f7..b6ad1a526165 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2359,9 +2359,9 @@ public final class ProcessList { if ((intentFlags & Intent.FLAG_FROM_BACKGROUND) != 0) { // If we are in the background, then check to see if this process // is bad. If so, we will just silently fail. - if (mService.mAppErrors.isBadProcessLocked(info)) { + if (mService.mAppErrors.isBadProcessLocked(processName, info.uid)) { if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid - + "/" + info.processName); + + "/" + processName); return null; } } else { @@ -2370,13 +2370,13 @@ public final class ProcessList { // least one crash dialog again, and make the process good again // if it had been bad. if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid - + "/" + info.processName); - mService.mAppErrors.resetProcessCrashTimeLocked(info); - if (mService.mAppErrors.isBadProcessLocked(info)) { + + "/" + processName); + mService.mAppErrors.resetProcessCrashTimeLocked(processName, info.uid); + if (mService.mAppErrors.isBadProcessLocked(processName, info.uid)) { EventLog.writeEvent(EventLogTags.AM_PROC_GOOD, UserHandle.getUserId(info.uid), info.uid, info.processName); - mService.mAppErrors.clearBadProcessLocked(info); + mService.mAppErrors.clearBadProcessLocked(processName, info.uid); if (app != null) { app.bad = false; } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 2eca00e27ddf..b3231909e4b7 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -44,6 +44,7 @@ import static android.app.AppOpsManager.RestrictionBypass; import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING; import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM; +import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS; import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE; import static android.app.AppOpsManager.UID_STATE_BACKGROUND; import static android.app.AppOpsManager.UID_STATE_CACHED; @@ -5936,11 +5937,13 @@ public class AppOpsService extends IAppOpsService.Stub { int newLeftDistance = AppOpsManager.leftCircularDistance(opCode, mSampledAppOpCode, _NUM_OP); - if (mAcceptableLeftDistance < newLeftDistance) { + if (mAcceptableLeftDistance < newLeftDistance + && mSamplingStrategy != SAMPLING_STRATEGY_UNIFORM_OPS) { return; } - if (mAcceptableLeftDistance > newLeftDistance) { + if (mAcceptableLeftDistance > newLeftDistance + && mSamplingStrategy != SAMPLING_STRATEGY_UNIFORM_OPS) { mAcceptableLeftDistance = newLeftDistance; mMessagesCollectedCount = 0.0f; } @@ -5978,13 +5981,13 @@ public class AppOpsService extends IAppOpsService.Stub { if (mSampledPackage == null) { if (ThreadLocalRandom.current().nextFloat() < 0.5f) { mSamplingStrategy = SAMPLING_STRATEGY_BOOT_TIME_SAMPLING; - resampleAppOpForPackageLocked(packageName); + resampleAppOpForPackageLocked(packageName, true); } } else if (mRarelyUsedPackages.contains(packageName)) { mRarelyUsedPackages.remove(packageName); if (ThreadLocalRandom.current().nextFloat() < 0.5f) { mSamplingStrategy = SAMPLING_STRATEGY_RARELY_USED; - resampleAppOpForPackageLocked(packageName); + resampleAppOpForPackageLocked(packageName, true); } } } @@ -6001,16 +6004,22 @@ public class AppOpsService extends IAppOpsService.Stub { /** Resamples package and appop to watch from the list provided. */ private void resamplePackageAndAppOpLocked(@NonNull List<String> packageNames) { if (!packageNames.isEmpty()) { - mSamplingStrategy = SAMPLING_STRATEGY_UNIFORM; - resampleAppOpForPackageLocked(packageNames.get( - ThreadLocalRandom.current().nextInt(packageNames.size()))); + if (ThreadLocalRandom.current().nextFloat() < 0.5f) { + mSamplingStrategy = SAMPLING_STRATEGY_UNIFORM; + resampleAppOpForPackageLocked(packageNames.get( + ThreadLocalRandom.current().nextInt(packageNames.size())), true); + } else { + mSamplingStrategy = SAMPLING_STRATEGY_UNIFORM_OPS; + resampleAppOpForPackageLocked(packageNames.get( + ThreadLocalRandom.current().nextInt(packageNames.size())), false); + } } } /** Resamples appop for the chosen package and initializes sampling state */ - private void resampleAppOpForPackageLocked(@NonNull String packageName) { + private void resampleAppOpForPackageLocked(@NonNull String packageName, boolean pickOp) { mMessagesCollectedCount = 0.0f; - mSampledAppOpCode = ThreadLocalRandom.current().nextInt(_NUM_OP); + mSampledAppOpCode = pickOp ? ThreadLocalRandom.current().nextInt(_NUM_OP) : OP_NONE; mAcceptableLeftDistance = _NUM_OP; mSampledPackage = packageName; } diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 29b8493b8035..03ca8fa28a9c 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -31,6 +31,7 @@ import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE; import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; @@ -52,6 +53,7 @@ import android.provider.Settings; import android.util.Slog; import com.android.internal.R; +import com.android.server.biometrics.sensors.ClientMonitor; public class Utils { @@ -395,4 +397,8 @@ public class Utils { ? keyguardComponent.getPackageName() : null; return hasPermission && keyguardPackage != null && keyguardPackage.equals(clientPackage); } + + public static String getClientName(@Nullable ClientMonitor<?> client) { + return client != null ? client.getClass().getSimpleName() : "null"; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java index 14baaa72494f..f8e8dd979e4d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java @@ -45,7 +45,7 @@ public abstract class AcquisitionClient<T> extends ClientMonitor<T> implements I private final PowerManager mPowerManager; private final VibrationEffect mSuccessVibrationEffect; private final VibrationEffect mErrorVibrationEffect; - private boolean mErrorAlreadySent; + private boolean mShouldSendErrorToClient; /** * Stops the HAL operation specific to the ClientMonitor subclass. @@ -84,11 +84,11 @@ public abstract class AcquisitionClient<T> extends ClientMonitor<T> implements I // case (success, failure, or error) is received from the HAL (e.g. versions of fingerprint // that do not handle lockout under the HAL. In these cases, ensure that the framework only // sends errors once per ClientMonitor. - if (!mErrorAlreadySent) { + if (!mShouldSendErrorToClient) { logOnError(getContext(), errorCode, vendorCode, getTargetUserId()); try { if (getListener() != null) { - mErrorAlreadySent = true; + mShouldSendErrorToClient = true; getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode); } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 5392f0ff89af..fdc3def93d06 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -17,6 +17,7 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.TaskStackListener; @@ -41,7 +42,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> { private final boolean mIsStrongBiometric; private final boolean mRequireConfirmation; private final IActivityTaskManager mActivityTaskManager; - private final TaskStackListener mTaskStackListener; + @Nullable private final TaskStackListener mTaskStackListener; private final LockoutTracker mLockoutTracker; private final boolean mIsRestricted; @@ -56,7 +57,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> { @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation, int sensorId, boolean isStrongBiometric, - int statsModality, int statsClient, @NonNull TaskStackListener taskStackListener, + int statsModality, int statsClient, @Nullable TaskStackListener taskStackListener, @NonNull LockoutTracker lockoutTracker) { super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId, statsModality, BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); @@ -133,10 +134,12 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> { vibrateSuccess(); } - try { - mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); - } catch (RemoteException e) { - Slog.e(TAG, "Could not unregister task stack listener", e); + if (mTaskStackListener != null) { + try { + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Slog.e(TAG, "Could not unregister task stack listener", e); + } } final byte[] byteToken = new byte[hardwareAuthToken.size()]; @@ -221,10 +224,12 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> { return; } - try { - mActivityTaskManager.registerTaskStackListener(mTaskStackListener); - } catch (RemoteException e) { - Slog.e(TAG, "Could not register task stack listener", e); + if (mTaskStackListener != null) { + try { + mActivityTaskManager.registerTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Slog.e(TAG, "Could not register task stack listener", e); + } } if (DEBUG) Slog.w(TAG, "Requesting auth for " + getOwnerString()); @@ -241,10 +246,12 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> { return; } - try { - mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); - } catch (RemoteException e) { - Slog.e(TAG, "Could not unregister task stack listener", e); + if (mTaskStackListener != null) { + try { + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Slog.e(TAG, "Could not unregister task stack listener", e); + } } if (DEBUG) Slog.w(TAG, "Requesting cancel for " + getOwnerString()); diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index c9ab3138b410..6bdd7834c000 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -28,7 +28,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.util.Slog; -import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityTracker; +import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -37,7 +37,6 @@ import java.text.SimpleDateFormat; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Date; -import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Queue; @@ -133,7 +132,7 @@ public class BiometricScheduler { @Override public void run() { if (operation.state != Operation.STATE_FINISHED) { - Slog.e(tag, "[Watchdog] Running for: " + operation); + Slog.e(tag, "[Watchdog Triggered]: " + operation); operation.clientMonitor.mFinishCallback .onClientFinished(operation.clientMonitor, false /* success */); } @@ -173,7 +172,7 @@ public class BiometricScheduler { } @NonNull private final String mBiometricTag; - @Nullable private final GestureAvailabilityTracker mGestureAvailabilityTracker; + @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; @NonNull private final IBiometricService mBiometricService; @NonNull private final Handler mHandler = new Handler(Looper.getMainLooper()); @NonNull private final InternalFinishCallback mInternalFinishCallback; @@ -195,6 +194,8 @@ public class BiometricScheduler { return; } + mCurrentOperation.state = Operation.STATE_FINISHED; + if (mCurrentOperation.clientFinishCallback != null) { mCurrentOperation.clientFinishCallback.onClientFinished(clientMonitor, success); } @@ -206,12 +207,11 @@ public class BiometricScheduler { } Slog.d(getTag(), "[Finished] " + clientMonitor + ", success: " + success); - if (mGestureAvailabilityTracker != null) { - mGestureAvailabilityTracker.markSensorActive( + if (mGestureAvailabilityDispatcher != null) { + mGestureAvailabilityDispatcher.markSensorActive( mCurrentOperation.clientMonitor.getSensorId(), false /* active */); } - mCurrentOperation.state = Operation.STATE_FINISHED; mCurrentOperation = null; startNextOperationIfIdle(); }); @@ -221,15 +221,15 @@ public class BiometricScheduler { /** * Creates a new scheduler. * @param tag for the specific instance of the scheduler. Should be unique. - * @param gestureAvailabilityTracker may be null if the sensor does not support gestures (such - * as fingerprint swipe). + * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures + * (such as fingerprint swipe). */ public BiometricScheduler(@NonNull String tag, - @Nullable GestureAvailabilityTracker gestureAvailabilityTracker) { + @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { mBiometricTag = tag; mInternalFinishCallback = new InternalFinishCallback(); - mGestureAvailabilityTracker = gestureAvailabilityTracker; - mPendingOperations = new LinkedList<>(); + mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher; + mPendingOperations = new ArrayDeque<>(); mBiometricService = IBiometricService.Stub.asInterface( ServiceManager.getService(Context.BIOMETRIC_SERVICE)); mCrashStates = new ArrayDeque<>(); @@ -268,9 +268,9 @@ public class BiometricScheduler { return; } - if (mGestureAvailabilityTracker != null + if (mGestureAvailabilityDispatcher != null && mCurrentOperation.clientMonitor instanceof AcquisitionClient) { - mGestureAvailabilityTracker.markSensorActive( + mGestureAvailabilityDispatcher.markSensorActive( mCurrentOperation.clientMonitor.getSensorId(), true /* active */); } @@ -297,15 +297,23 @@ public class BiometricScheduler { * Starts the {@link #mCurrentOperation} if * 1) its state is {@link Operation#STATE_WAITING_FOR_COOKIE} and * 2) its cookie matches this cookie + * + * This is currently only used by {@link com.android.server.biometrics.BiometricService}, which + * requests sensors to prepare for authentication with a cookie. Once sensor(s) are ready (e.g. + * the BiometricService client becomes the current client in the scheduler), the cookie is + * returned to BiometricService. Once BiometricService decides that authentication can start, + * it invokes this code path. + * * @param cookie of the operation to be started */ public void startPreparedClient(int cookie) { if (mCurrentOperation == null) { - Slog.e(getTag(), "Current operation null"); + Slog.e(getTag(), "Current operation is null"); return; } if (mCurrentOperation.state != Operation.STATE_WAITING_FOR_COOKIE) { - Slog.e(getTag(), "Operation in wrong state: " + mCurrentOperation); + Slog.e(getTag(), "Operation is in the wrong state: " + mCurrentOperation + + ", expected STATE_WAITING_FOR_COOKIE"); return; } if (mCurrentOperation.clientMonitor.getCookie() != cookie) { @@ -354,7 +362,8 @@ public class BiometricScheduler { // If the current operation is cancellable, start the cancellation process. if (mCurrentOperation != null && mCurrentOperation.clientMonitor instanceof Interruptable - && mCurrentOperation.state != Operation.STATE_STARTED_CANCELING) { + && mCurrentOperation.state == Operation.STATE_STARTED) { + Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation); cancelInternal(mCurrentOperation); } diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceBase.java deleted file mode 100644 index 9aa72a76aed2..000000000000 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceBase.java +++ /dev/null @@ -1,681 +0,0 @@ -/* - * Copyright (C) 2020 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.biometrics.sensors; - -import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; - -import android.app.ActivityManager; -import android.app.ActivityTaskManager; -import android.app.AppOpsManager; -import android.app.IActivityTaskManager; -import android.app.SynchronousUserSwitchObserver; -import android.app.TaskStackListener; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.UserInfo; -import android.hardware.biometrics.BiometricAuthenticator; -import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.BiometricsProtoEnums; -import android.hardware.biometrics.IBiometricService; -import android.os.Handler; -import android.os.IBinder; -import android.os.IHwBinder; -import android.os.Looper; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.Slog; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.util.FrameworkStatsLog; -import com.android.server.SystemService; -import com.android.server.biometrics.Utils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Abstract base class containing all of the business logic for biometric services, e.g. - * Fingerprint, Face, Iris. - * - * @hide - */ -public abstract class BiometricServiceBase<T> extends SystemService - implements IHwBinder.DeathRecipient { - - protected static final boolean DEBUG = true; - - private static final int MSG_USER_SWITCHING = 10; - private static final long CANCEL_TIMEOUT_LIMIT = 3000; // max wait for onCancel() from HAL,in ms - - private final Context mContext; - protected final IActivityTaskManager mActivityTaskManager; - protected final BiometricTaskStackListener mTaskStackListener = - new BiometricTaskStackListener(); - private final ResetClientStateRunnable mResetClientState = new ResetClientStateRunnable(); - - protected final IStatusBarService mStatusBarService; - protected final Map<Integer, Long> mAuthenticatorIds = - Collections.synchronizedMap(new HashMap<>()); - protected final AppOpsManager mAppOps; - - /** - * Handler which all subclasses should post events to. - */ - protected final Handler mHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(android.os.Message msg) { - switch (msg.what) { - case MSG_USER_SWITCHING: - handleUserSwitching(msg.arg1); - break; - default: - Slog.w(getTag(), "Unknown message:" + msg.what); - } - } - }; - - protected final ClientMonitor.FinishCallback mClientFinishCallback = - (clientMonitor, success) -> { - removeClient(clientMonitor); - // When enrollment finishes, update this group's authenticator id, as the HAL has - // already generated a new authenticator id when the new biometric is enrolled. - if (clientMonitor instanceof EnrollClient) { - updateActiveGroup(clientMonitor.getTargetUserId()); - } - }; - - private IBiometricService mBiometricService; - private ClientMonitor<T> mCurrentClient; - private ClientMonitor<T> mPendingClient; - private PerformanceTracker mPerformanceTracker; - private int mSensorId; - protected int mCurrentUserId = UserHandle.USER_NULL; - - /** - * @return the log tag. - */ - protected abstract String getTag(); - - /** - * @return a fresh reference to the biometric HAL - */ - protected abstract T getDaemon(); - - /** - * @return the biometric utilities for a specific implementation. - */ - protected abstract BiometricUtils getBiometricUtils(); - - /** - * @param userId - * @return true if the enrollment limit has been reached. - */ - protected abstract boolean hasReachedEnrollmentLimit(int userId); - - /** - * Notifies the HAL that the user has changed. - * @param userId - */ - protected abstract void updateActiveGroup(int userId); - - /** - * @param userId - * @return Returns true if the user has any enrolled biometrics. - */ - protected abstract boolean hasEnrolledBiometrics(int userId); - - /** - * @return Returns the MANAGE_* permission string, which is required for enrollment, removal - * etc. - */ - protected abstract String getManageBiometricPermission(); - - protected abstract List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates( - int userId); - - /** - * Notifies clients of any change in the biometric state (active / idle). This is mainly for - * Fingerprint navigation gestures. - * @param isActive - */ - protected void notifyClientActiveCallbacks(boolean isActive) {} - - protected abstract int statsModality(); - - private final Runnable mOnTaskStackChangedRunnable = new Runnable() { - @Override - public void run() { - try { - if (!(mCurrentClient instanceof AuthenticationClient)) { - return; - } - final String currentClient = mCurrentClient.getOwnerString(); - if (isKeyguard(currentClient)) { - return; // Keyguard is always allowed - } - List<ActivityManager.RunningTaskInfo> runningTasks = - mActivityTaskManager.getTasks(1); - if (!runningTasks.isEmpty()) { - final String topPackage = runningTasks.get(0).topActivity.getPackageName(); - if (!topPackage.contentEquals(currentClient) - && !mCurrentClient.isAlreadyDone()) { - Slog.e(getTag(), "Stopping background authentication, top: " - + topPackage + " currentClient: " + currentClient); - ((AuthenticationClient) mCurrentClient).cancel(); - } - } - } catch (RemoteException e) { - Slog.e(getTag(), "Unable to get running tasks", e); - } - } - }; - - private final class BiometricTaskStackListener extends TaskStackListener { - @Override - public void onTaskStackChanged() { - mHandler.post(mOnTaskStackChangedRunnable); - } - } - - private final class ResetClientStateRunnable implements Runnable { - @Override - public void run() { - /** - * Warning: if we get here, the driver never confirmed our call to cancel the current - * operation (authenticate, enroll, remove, enumerate, etc), which is - * really bad. The result will be a 3-second delay in starting each new client. - * If you see this on a device, make certain the driver notifies with - * {@link BiometricConstants#BIOMETRIC_ERROR_CANCELED} in response to cancel() - * once it has successfully switched to the IDLE state in the HAL. - * Additionally,{@link BiometricConstants#BIOMETRIC_ERROR_CANCELED} should only be sent - * in response to an actual cancel() call. - */ - Slog.w(getTag(), "Client " - + (mCurrentClient != null ? mCurrentClient.getOwnerString() : "null") - + " failed to respond to cancel, starting client " - + (mPendingClient != null ? mPendingClient.getOwnerString() : "null")); - - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, - statsModality(), BiometricsProtoEnums.ISSUE_CANCEL_TIMED_OUT); - - ClientMonitor<T> newClient = mPendingClient; - mCurrentClient = null; - mPendingClient = null; - startClient(newClient, false); - } - } - - /** - * Initializes the system service. - * <p> - * Subclasses must define a single argument constructor that accepts the context - * and passes it to super. - * </p> - * - * @param context The system server context. - */ - public BiometricServiceBase(Context context) { - super(context); - mContext = context; - mStatusBarService = IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE)); - mAppOps = context.getSystemService(AppOpsManager.class); - mActivityTaskManager = ActivityTaskManager.getService(); - mPerformanceTracker = PerformanceTracker.getInstanceForSensorId(getSensorId()); - } - - @Override - public void onStart() { - listenForUserSwitches(); - } - - @Override - public void serviceDied(long cookie) { - Slog.e(getTag(), "HAL died"); - mPerformanceTracker.incrementHALDeathCount(); - mCurrentUserId = UserHandle.USER_NULL; - - // All client lifecycle must be managed on the handler. - mHandler.post(() -> { - Slog.e(getTag(), "Sending BIOMETRIC_ERROR_HW_UNAVAILABLE after HAL crash"); - handleError(BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); - }); - - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, - statsModality(), BiometricsProtoEnums.ISSUE_HAL_DEATH); - } - - protected void initializeConfigurationInternal(int sensorId) { - if (DEBUG) { - Slog.d(getTag(), "initializeConfigurationInternal(" + sensorId + ")"); - } - mSensorId = sensorId; - } - - protected ClientMonitor<?> getCurrentClient() { - return mCurrentClient; - } - - protected boolean isStrongBiometric() { - return Utils.isStrongBiometric(mSensorId); - } - - protected int getSensorId() { - return mSensorId; - } - - /** - * Callback handlers from the daemon. The caller must put this on a handler. - */ - - protected void handleAcquired(int acquiredInfo, int vendorCode) { - final ClientMonitor<?> client = mCurrentClient; - if (!(client instanceof AcquisitionClient)) { - final String clientName = client != null ? client.getClass().getSimpleName() : "null"; - Slog.e(getTag(), "handleAcquired for non-acquire consumer: " + clientName); - return; - } - - final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client; - acquisitionClient.onAcquired(acquiredInfo, vendorCode); - } - - protected void handleAuthenticated(BiometricAuthenticator.Identifier identifier, - ArrayList<Byte> token) { - final ClientMonitor<?> client = mCurrentClient; - if (!(client instanceof AuthenticationClient)) { - final String clientName = client != null ? client.getClass().getSimpleName() : "null"; - Slog.e(getTag(), "handleAuthenticated for non-authentication client: " + clientName); - return; - } - - final AuthenticationClient<?> authenticationClient = (AuthenticationClient<?>) client; - final boolean authenticated = identifier.getBiometricId() != 0; - authenticationClient.onAuthenticated(identifier, authenticated, token); - } - - protected void handleEnrollResult(BiometricAuthenticator.Identifier identifier, - int remaining) { - final ClientMonitor<?> client = mCurrentClient; - if (!(client instanceof EnrollClient)) { - final String clientName = client != null ? client.getClass().getSimpleName() : "null"; - Slog.e(getTag(), "handleEnrollResult for non-enroll client: " + clientName); - return; - } - - final EnrollClient<?> enrollClient = (EnrollClient<?>) client; - enrollClient.onEnrollResult(identifier, remaining); - } - - protected void handleError(int error, int vendorCode) { - final ClientMonitor<?> client = mCurrentClient; - - if (DEBUG) Slog.v(getTag(), "handleError(client=" - + (client != null ? client.getOwnerString() : "null") + ", error = " + error + ")"); - - if (!(client instanceof Interruptable)) { - Slog.e(getTag(), "error received for non-ErrorConsumer"); - return; - } - - final Interruptable interruptable = (Interruptable) client; - interruptable.onError(error, vendorCode); - - if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { - mHandler.removeCallbacks(mResetClientState); - if (mPendingClient != null) { - if (DEBUG) Slog.v(getTag(), "start pending client " + - mPendingClient.getOwnerString()); - startClient(mPendingClient, false); - mPendingClient = null; - } - } - } - - protected void handleRemoved(BiometricAuthenticator.Identifier identifier, - final int remaining) { - if (DEBUG) Slog.w(getTag(), "Removed: fid=" + identifier.getBiometricId() - + ", dev=" + identifier.getDeviceId() - + ", rem=" + remaining); - - final ClientMonitor<?> client = mCurrentClient; - if (!(client instanceof RemovalConsumer)) { - final String clientName = client != null ? client.getClass().getSimpleName() : "null"; - Slog.e(getTag(), "handleRemoved for non-removal consumer: " + clientName); - return; - } - - final RemovalConsumer removalConsumer = (RemovalConsumer) client; - removalConsumer.onRemoved(identifier, remaining); - } - - protected void handleEnumerate(BiometricAuthenticator.Identifier identifier, int remaining) { - final ClientMonitor<?> client = mCurrentClient; - if (!(client instanceof EnumerateConsumer)) { - final String clientName = client != null ? client.getClass().getSimpleName() : "null"; - Slog.e(getTag(), "handleEnumerate for non-enumerate consumer: " - + clientName); - return; - } - - final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client; - enumerateConsumer.onEnumerationResult(identifier, remaining); - } - - /** - * Calls from the Manager. These are still on the calling binder's thread. - */ - - protected void enrollInternal(EnrollClient<T> client, int userId) { - if (hasReachedEnrollmentLimit(userId)) { - return; - } - - // Group ID is arbitrarily set to parent profile user ID. It just represents - // the default biometrics for the user. - if (!Utils.isCurrentUserOrProfile(mContext, userId)) { - return; - } - - mHandler.post(() -> { - startClient(client, true /* initiatedByClient */); - }); - } - - protected void cancelEnrollmentInternal(IBinder token) { - mHandler.post(() -> { - ClientMonitor<?> client = mCurrentClient; - if (client instanceof EnrollClient && client.getToken() == token) { - if (DEBUG) Slog.v(getTag(), "Cancelling enrollment"); - ((EnrollClient<?>) client).cancel(); - } - }); - } - - protected void generateChallengeInternal(GenerateChallengeClient<T> client) { - mHandler.post(() -> { - startClient(client, true /* initiatedByClient */); - }); - } - - protected void revokeChallengeInternal(RevokeChallengeClient<T> client) { - mHandler.post(() -> { - startClient(client, true /* initiatedByClient */); - }); - } - - protected void authenticateInternal(AuthenticationClient<T> client, String opPackageName) { - mHandler.post(() -> { - startAuthentication(client, opPackageName); - }); - } - - protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName, - boolean fromClient) { - - if (DEBUG) Slog.v(getTag(), "cancelAuthentication(" + opPackageName + ")"); - - mHandler.post(() -> { - ClientMonitor<?> client = mCurrentClient; - if (client instanceof AuthenticationClient) { - if (client.getToken() == token || !fromClient) { - if (DEBUG) Slog.v(getTag(), "Stopping client " + client.getOwnerString() - + ", fromClient: " + fromClient); - // If cancel was from BiometricService, it means the dialog was dismissed - // and authentication should be canceled. - ((AuthenticationClient<?>) client).cancel(); - } else { - if (DEBUG) Slog.v(getTag(), "Can't stop client " + client.getOwnerString() - + " since tokens don't match. fromClient: " + fromClient); - } - } else if (client != null) { - if (DEBUG) Slog.v(getTag(), "Can't cancel non-authenticating client " - + client.getOwnerString()); - } - }); - } - - protected void removeInternal(RemovalClient<T> client) { - mHandler.post(() -> { - startClient(client, true /* initiatedByClient */); - }); - } - - protected void cleanupInternal( - InternalCleanupClient<? extends BiometricAuthenticator.Identifier, T> client) { - mHandler.post(() -> { - if (DEBUG) { - Slog.v(getTag(), "Cleaning up templates for user(" - + client.getTargetUserId() + ")"); - } - startClient(client, true /* initiatedByClient */); - }); - } - - // Should be done on a handler thread - not on the Binder's thread. - private void startAuthentication(AuthenticationClient<T> client, String opPackageName) { - if (DEBUG) Slog.v(getTag(), "startAuthentication(" + opPackageName + ")"); - - startClient(client, true /* initiatedByClient */); - } - - /** - * Helper methods. - */ - - /** - * @return true if this is keyguard package - */ - public boolean isKeyguard(String clientPackage) { - return Utils.isKeyguard(mContext, clientPackage); - } - - /** - * Calls the HAL to switch states to the new task. If there's already a current task, - * it calls cancel() and sets mPendingClient to begin when the current task finishes - * ({@link BiometricConstants#BIOMETRIC_ERROR_CANCELED}). - * - * @param newClient the new client that wants to connect - * @param initiatedByClient true for authenticate, remove and enroll - */ - @VisibleForTesting - protected void startClient(ClientMonitor<T> newClient, boolean initiatedByClient) { - ClientMonitor<?> currentClient = mCurrentClient; - if (currentClient != null) { - if (DEBUG) Slog.v(getTag(), "request stop current client " + - currentClient.getOwnerString()); - if (currentClient instanceof InternalCleanupClient) { - // This condition means we're currently running internal diagnostics to - // remove extra templates in the hardware and/or the software - // TODO: design an escape hatch in case client never finishes - if (newClient != null) { - Slog.w(getTag(), "Internal cleanup in progress but trying to start client " - + newClient.getClass().getSuperclass().getSimpleName() - + "(" + newClient.getOwnerString() + ")" - + ", initiatedByClient = " + initiatedByClient); - } - } else if (currentClient instanceof Interruptable) { - ((Interruptable) currentClient).cancel(); - - // Only post the reset runnable for non-cleanup clients. Cleanup clients should - // never be forcibly stopped since they ensure synchronization between HAL and - // framework. Thus, we should instead just start the pending client once cleanup - // finishes instead of using the reset runnable. - mHandler.removeCallbacks(mResetClientState); - mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT); - } - mPendingClient = newClient; - } else if (newClient != null) { - // For BiometricPrompt clients, do not start until - // <Biometric>Service#startPreparedClient is called. BiometricService waits until all - // modalities are ready before initiating authentication. - if (newClient instanceof AuthenticationClient) { - AuthenticationClient<?> client = (AuthenticationClient<?>) newClient; - if (client.isBiometricPrompt()) { - if (DEBUG) Slog.v(getTag(), "Returning cookie: " + client.getCookie()); - mCurrentClient = newClient; - if (mBiometricService == null) { - mBiometricService = IBiometricService.Stub.asInterface( - ServiceManager.getService(Context.BIOMETRIC_SERVICE)); - } - try { - mBiometricService.onReadyForAuthentication(client.getCookie()); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception", e); - } - return; - } - } - - // We are not a BiometricPrompt client, start the client immediately - mCurrentClient = newClient; - startCurrentClient(mCurrentClient.getCookie()); - } - } - - protected void startCurrentClient(int cookie) { - if (mCurrentClient == null) { - Slog.e(getTag(), "Trying to start null client!"); - return; - } - - if (DEBUG) Slog.v(getTag(), "Starting client " - + mCurrentClient.getClass().getSimpleName() - + "(" + mCurrentClient.getOwnerString() + ")" - + " targetUserId: " + mCurrentClient.getTargetUserId() - + " currentUserId: " + mCurrentUserId - + " cookie: " + cookie + "/" + mCurrentClient.getCookie()); - - if (cookie != mCurrentClient.getCookie()) { - Slog.e(getTag(), "Mismatched cookie"); - return; - } - - final T daemon = mCurrentClient.getFreshDaemon(); - if (daemon == null) { - Slog.e(getTag(), "Daemon null, unable to start: " - + mCurrentClient.getClass().getSimpleName()); - mCurrentClient.unableToStart(); - mCurrentClient = null; - return; - } - - mCurrentClient.start(mClientFinishCallback); - notifyClientActiveCallbacks(true); - } - - protected void removeClient(ClientMonitor<?> client) { - if (client != null) { - client.destroy(); - if (client != mCurrentClient && mCurrentClient != null) { - Slog.w(getTag(), "Unexpected client: " + client.getOwnerString() + "expected: " - + mCurrentClient.getOwnerString()); - } - } - if (mCurrentClient != null) { - if (DEBUG) Slog.v(getTag(), "Done with client: " - + mCurrentClient.getClass().getSimpleName() - + "(" + mCurrentClient.getOwnerString() + ")"); - mCurrentClient = null; - } - if (mPendingClient == null) { - notifyClientActiveCallbacks(false); - } - } - - /** - * Populates existing authenticator ids. To be used only during the start of the service. - */ - protected void loadAuthenticatorIds() { - // This operation can be expensive, so keep track of the elapsed time. Might need to move to - // background if it takes too long. - long t = System.currentTimeMillis(); - mAuthenticatorIds.clear(); - for (UserInfo user : UserManager.get(getContext()).getUsers(true /* excludeDying */)) { - int userId = user.id; - if (!mAuthenticatorIds.containsKey(userId)) { - updateActiveGroup(userId); - } - } - - t = System.currentTimeMillis() - t; - if (t > 1000) { - Slog.w(getTag(), "loadAuthenticatorIds() taking too long: " + t + "ms"); - } - } - - protected boolean isRestricted() { - // Only give privileged apps (like Settings) access to biometric info - final boolean restricted = !hasPermission(getManageBiometricPermission()); - return restricted; - } - - protected boolean hasPermission(String permission) { - return getContext().checkCallingOrSelfPermission(permission) - == PackageManager.PERMISSION_GRANTED; - } - - protected void checkPermission(String permission) { - getContext().enforceCallingOrSelfPermission(permission, - "Must have " + permission + " permission."); - } - - /** - * @return authenticator id for the calling user - */ - protected long getAuthenticatorId(int callingUserId) { - return mAuthenticatorIds.getOrDefault(callingUserId, 0L); - } - - /** - * This method should be called upon connection to the daemon, and when user switches. - */ - protected abstract void doTemplateCleanupForUser(int userId); - - /** - * This method is called when the user switches. Implementations should probably notify the - * HAL. - */ - protected void handleUserSwitching(int userId) { - if (getCurrentClient() instanceof InternalCleanupClient) { - Slog.w(getTag(), "User switched while performing cleanup"); - } - updateActiveGroup(userId); - doTemplateCleanupForUser(userId); - } - - private void listenForUserSwitches() { - try { - ActivityManager.getService().registerUserSwitchObserver( - new SynchronousUserSwitchObserver() { - @Override - public void onUserSwitching(int newUserId) { - mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */) - .sendToTarget(); - } - }, getTag()); - } catch (RemoteException e) { - Slog.w(getTag(), "Failed to listen for user switching event" ,e); - } - } -} diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java index 8cabdf52314c..8b27781940ac 100644 --- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java @@ -34,7 +34,7 @@ import java.util.NoSuchElementException; public abstract class ClientMonitor<T> extends LoggableMonitor implements IBinder.DeathRecipient { private static final String TAG = "Biometrics/ClientMonitor"; - protected static final boolean DEBUG = BiometricServiceBase.DEBUG; + protected static final boolean DEBUG = true; // Counter used to distinguish between ClientMonitor instances to help debugging. private static int sCount = 0; diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java index a3d96778d177..163f29ea5184 100644 --- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java @@ -35,12 +35,17 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> { protected final byte[] mHardwareAuthToken; protected final int mTimeoutSec; - private final BiometricUtils mBiometricUtils; + protected final BiometricUtils mBiometricUtils; private final boolean mShouldVibrate; private long mEnrollmentStartTimeMs; private boolean mAlreadyCancelled; + /** + * @return true if the user has already enrolled the maximum number of templates. + */ + protected abstract boolean hasReachedEnrollmentLimit(); + public EnrollClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils, @@ -82,6 +87,12 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> { public void start(@NonNull FinishCallback finishCallback) { super.start(finishCallback); + if (hasReachedEnrollmentLimit()) { + Slog.e(TAG, "Reached enrollment limit"); + finishCallback.onClientFinished(this, false /* success */); + return; + } + mEnrollmentStartTimeMs = System.currentTimeMillis(); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutResetTracker.java b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java index 22c10b350e7d..f4997d4abe24 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LockoutResetTracker.java +++ b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java @@ -32,7 +32,7 @@ import java.util.ArrayList; * ends. This class keeps track of all client callbacks. Individual sensors should notify this * when lockout for a specific sensor has been reset. */ -public class LockoutResetTracker implements IBinder.DeathRecipient { +public class LockoutResetDispatcher implements IBinder.DeathRecipient { private static final String TAG = "LockoutResetTracker"; @@ -79,7 +79,7 @@ public class LockoutResetTracker implements IBinder.DeathRecipient { } } - public LockoutResetTracker(Context context) { + public LockoutResetDispatcher(Context context) { mContext = context; mClientCallbacks = new ArrayList<>(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java index a7c63f75d707..1a4216f9d43c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java @@ -39,10 +39,6 @@ public abstract class LoggableMonitor { private final int mStatsClient; private long mFirstAcquireTimeMs; - protected long getFirstAcquireTimeMs() { - return mFirstAcquireTimeMs; - } - /** * Only valid for AuthenticationClient. * @return true if the client is authenticating for a crypto operation. @@ -62,6 +58,12 @@ public abstract class LoggableMonitor { mStatsClient = statsClient; } + private boolean isAnyFieldUnknown() { + return mStatsModality == BiometricsProtoEnums.MODALITY_UNKNOWN + || mStatsAction == BiometricsProtoEnums.ACTION_UNKNOWN + || mStatsClient == BiometricsProtoEnums.CLIENT_UNKNOWN; + } + protected final void logOnAcquired(Context context, int acquiredInfo, int vendorCode, int targetUserId) { @@ -86,6 +88,11 @@ public abstract class LoggableMonitor { + ", AcquiredInfo: " + acquiredInfo + ", VendorCode: " + vendorCode); } + + if (isAnyFieldUnknown()) { + return; + } + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED, mStatsModality, targetUserId, @@ -114,6 +121,11 @@ public abstract class LoggableMonitor { } else { Slog.v(TAG, "Error latency: " + latency); } + + if (isAnyFieldUnknown()) { + return; + } + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED, mStatsModality, targetUserId, @@ -157,6 +169,10 @@ public abstract class LoggableMonitor { Slog.v(TAG, "Authentication latency: " + latency); } + if (isAnyFieldUnknown()) { + return; + } + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED, mStatsModality, targetUserId, @@ -179,6 +195,10 @@ public abstract class LoggableMonitor { Slog.v(TAG, "Enroll latency: " + latency); } + if (isAnyFieldUnknown()) { + return; + } + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED, mStatsModality, targetUserId, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java new file mode 100644 index 000000000000..c4ea0a8f8fc7 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java @@ -0,0 +1,649 @@ +/* + * Copyright (C) 2020 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.biometrics.sensors.face; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.NotificationManager; +import android.app.SynchronousUserSwitchObserver; +import android.app.UserSwitchObserver; +import android.content.Context; +import android.content.pm.UserInfo; +import android.hardware.biometrics.BiometricConstants; +import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; +import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; +import android.hardware.face.Face; +import android.hardware.face.IFaceServiceReceiver; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.os.IHwBinder; +import android.os.Looper; +import android.os.NativeHandle; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.util.Slog; + +import com.android.internal.util.FrameworkStatsLog; +import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.ClientMonitor; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.EnumerateConsumer; +import com.android.server.biometrics.sensors.Interruptable; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.LockoutTracker; +import com.android.server.biometrics.sensors.PerformanceTracker; +import com.android.server.biometrics.sensors.RemovalConsumer; +import com.android.server.biometrics.sensors.fingerprint.FingerprintUpdateActiveUserClient; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Supports a single instance of the {@link android.hardware.biometrics.face.V1_0} or + * its extended minor versions. + */ +class Face10 implements IHwBinder.DeathRecipient { + + private static final String TAG = "Face10"; + private static final int ENROLL_TIMEOUT_SEC = 75; + static final String NOTIFICATION_TAG = "FaceService"; + static final int NOTIFICATION_ID = 1; + + @NonNull private final Context mContext; + @NonNull private final BiometricScheduler mScheduler; + @NonNull private final Handler mHandler; + @NonNull private final ClientMonitor.LazyDaemon<IBiometricsFace> mLazyDaemon; + @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher; + @NonNull private final LockoutHalImpl mLockoutTracker; + @NonNull private final UsageStats mUsageStats; + @NonNull private NotificationManager mNotificationManager; + private final int mSensorId; + @NonNull private final Map<Integer, Long> mAuthenticatorIds; + + @Nullable private IBiometricsFace mDaemon; + private int mCurrentUserId = UserHandle.USER_NULL; + + private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { + @Override + public void onUserSwitching(int newUserId) { + scheduleInternalCleanup(newUserId); + } + }; + + private final IBiometricsFaceClientCallback mDaemonCallback = + new IBiometricsFaceClientCallback.Stub() { + @Override + public void onEnrollResult(long deviceId, int faceId, int userId, int remaining) { + mHandler.post(() -> { + final CharSequence name = FaceUtils.getInstance() + .getUniqueName(mContext, userId); + final Face face = new Face(name, faceId, deviceId); + + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof FaceEnrollClient)) { + Slog.e(TAG, "onEnrollResult for non-enroll client: " + + Utils.getClientName(client)); + return; + } + + final FaceEnrollClient enrollClient = (FaceEnrollClient) client; + enrollClient.onEnrollResult(face, remaining); + }); + } + + @Override + public void onAuthenticated(long deviceId, int faceId, int userId, ArrayList<Byte> token) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof FaceAuthenticationClient)) { + Slog.e(TAG, "onAuthenticated for non-authentication client: " + + Utils.getClientName(client)); + return; + } + + final FaceAuthenticationClient authenticationClient = + (FaceAuthenticationClient) client; + final boolean authenticated = faceId != 0; + final Face face = new Face("", faceId, deviceId); + authenticationClient.onAuthenticated(face, authenticated, token); + }); + } + + @Override + public void onAcquired(long deviceId, int userId, int acquiredInfo, int vendorCode) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof AcquisitionClient)) { + Slog.e(TAG, "onAcquired for non-acquire client: " + + Utils.getClientName(client)); + return; + } + + final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client; + acquisitionClient.onAcquired(acquiredInfo, vendorCode); + }); + } + + @Override + public void onError(long deviceId, int userId, int error, int vendorCode) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + Slog.d(TAG, "handleError" + + ", client: " + (client != null ? client.getOwnerString() : null) + + ", error: " + error + + ", vendorCode: " + vendorCode); + if (!(client instanceof Interruptable)) { + Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(client)); + return; + } + + final Interruptable interruptable = (Interruptable) client; + interruptable.onError(error, vendorCode); + + if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) { + Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE"); + mDaemon = null; + mCurrentUserId = UserHandle.USER_NULL; + } + }); + } + + @Override + public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof RemovalConsumer)) { + Slog.e(TAG, "onRemoved for non-removal consumer: " + + Utils.getClientName(client)); + return; + } + + final RemovalConsumer removalConsumer = (RemovalConsumer) client; + + if (!removed.isEmpty()) { + // Convert to old fingerprint-like behavior, where remove() receives one removal + // at a time. This way, remove can share some more common code. + for (int i = 0; i < removed.size(); i++) { + final int id = removed.get(i); + final Face face = new Face("", id, deviceId); + final int remaining = removed.size() - i - 1; + Slog.d(TAG, "Removed, faceId: " + id + ", remaining: " + remaining); + removalConsumer.onRemoved(face, remaining); + } + } else { + final Face face = new Face("", 0 /* identifier */, deviceId); + removalConsumer.onRemoved(face, 0 /* remaining */); + } + + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT); + }); + } + + @Override + public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof EnumerateConsumer)) { + Slog.e(TAG, "onEnumerate for non-enumerate consumer: " + + Utils.getClientName(client)); + return; + } + + final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client; + + if (!faceIds.isEmpty()) { + // Convert to old fingerprint-like behavior, where enumerate() receives one + // template at a time. This way, enumerate can share some more common code. + for (int i = 0; i < faceIds.size(); i++) { + final Face face = new Face("", faceIds.get(i), deviceId); + enumerateConsumer.onEnumerationResult(face, faceIds.size() - i - 1); + } + } else { + // For face, the HIDL contract is to receive an empty list when there are no + // templates enrolled. Send a null identifier since we don't consume them + // anywhere, and send remaining == 0 so this code can be shared with + // Fingerprint@2.1 + enumerateConsumer.onEnumerationResult(null /* identifier */, 0); + } + }); + } + + @Override + public void onLockoutChanged(long duration) { + mHandler.post(() -> { + Slog.d(TAG, "onLockoutChanged: " + duration); + final @LockoutTracker.LockoutMode int lockoutMode; + if (duration == 0) { + lockoutMode = LockoutTracker.LOCKOUT_NONE; + } else if (duration == -1 || duration == Long.MAX_VALUE) { + lockoutMode = LockoutTracker.LOCKOUT_PERMANENT; + } else { + lockoutMode = LockoutTracker.LOCKOUT_TIMED; + } + + mLockoutTracker.setCurrentUserLockoutMode(lockoutMode); + + if (duration == 0) { + mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId); + } + }); + } + }; + + Face10(@NonNull Context context, int sensorId, + @NonNull LockoutResetDispatcher lockoutResetDispatcher) { + mContext = context; + mSensorId = sensorId; + mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */); + mHandler = new Handler(Looper.getMainLooper()); + mUsageStats = new UsageStats(context); + mAuthenticatorIds = new HashMap<>(); + mLazyDaemon = Face10.this::getDaemon; + mNotificationManager = mContext.getSystemService(NotificationManager.class); + mLockoutTracker = new LockoutHalImpl(); + mLockoutResetDispatcher = lockoutResetDispatcher; + + try { + ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to register user switch observer"); + } + } + + @Override + public void serviceDied(long cookie) { + Slog.e(TAG, "HAL died"); + mHandler.post(() -> { + PerformanceTracker.getInstanceForSensorId(mSensorId) + .incrementHALDeathCount(); + mDaemon = null; + mCurrentUserId = UserHandle.USER_NULL; + + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (client instanceof Interruptable) { + Slog.e(TAG, "Sending ERROR_HW_UNAVAILABLE for client: " + client); + final Interruptable interruptable = (Interruptable) client; + interruptable.onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */); + + mScheduler.recordCrashState(); + + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, + BiometricsProtoEnums.MODALITY_FACE, + BiometricsProtoEnums.ISSUE_HAL_DEATH); + } + }); + } + + private synchronized IBiometricsFace getDaemon() { + if (mDaemon != null) { + return mDaemon; + } + + Slog.d(TAG, "Daemon was null, reconnecting, current operation: " + + mScheduler.getCurrentClient()); + + try { + mDaemon = IBiometricsFace.getService(); + } catch (java.util.NoSuchElementException e) { + // Service doesn't exist or cannot be opened. + Slog.w(TAG, "NoSuchElementException", e); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to get face HAL", e); + } + + if (mDaemon == null) { + Slog.w(TAG, "Face HAL not available"); + return null; + } + + mDaemon.asBinder().linkToDeath(this, 0 /* flags */); + + // HAL ID for these HIDL versions are only used to determine if callbacks have been + // successfully set. + long halId = 0; + try { + halId = mDaemon.setCallback(mDaemonCallback).value; + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set callback for face HAL", e); + mDaemon = null; + } + + Slog.d(TAG, "Face HAL ready, HAL ID: " + halId); + if (halId != 0) { + scheduleLoadAuthenticatorIds(); + scheduleInternalCleanup(ActivityManager.getCurrentUser()); + } else { + Slog.e(TAG, "Unable to set callback"); + mDaemon = null; + } + + return mDaemon; + } + + @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) { + return mLockoutTracker.getLockoutModeForUser(userId); + } + + private void scheduleLoadAuthenticatorIds() { + // Note that this can be performed on the scheduler (as opposed to being done immediately + // when the HAL is (re)loaded, since + // 1) If this is truly the first time it's being performed (e.g. system has just started), + // this will be run very early and way before any applications need to generate keys. + // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has + // just been reloaded), the framework already has a cache of the authenticatorIds. This + // is safe because authenticatorIds only change when A) new template has been enrolled, + // or B) all templates are removed. + mHandler.post(() -> { + for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) { + final int targetUserId = user.id; + if (!mAuthenticatorIds.containsKey(targetUserId)) { + scheduleUpdateActiveUserWithoutHandler(targetUserId); + } + } + }); + } + + /** + * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the + * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser" + * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule + * this operation on the same lambda/runnable as those operations so that the ordering is + * correct. + */ + private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) { + final boolean hasEnrolled = !getEnrolledFaces(targetUserId).isEmpty(); + final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext, + mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId, mCurrentUserId, + hasEnrolled, mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, (clientMonitor, success) -> { + if (success) { + mCurrentUserId = targetUserId; + } + }); + } + + void scheduleResetLockout(int userId, @NonNull byte[] hardwareAuthToken) { + mHandler.post(() -> { + if (getEnrolledFaces(userId).isEmpty()) { + Slog.w(TAG, "Ignoring lockout reset, no templates enrolled for user: " + userId); + return; + } + + scheduleUpdateActiveUserWithoutHandler(userId); + + final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext, + mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, + hardwareAuthToken); + mScheduler.scheduleClientMonitor(client); + }); + } + + void scheduleSetFeature(@NonNull IBinder token, int userId, int feature, boolean enabled, + @NonNull byte[] hardwareAuthToken, @NonNull IFaceServiceReceiver receiver, + @NonNull String opPackageName) { + mHandler.post(() -> { + final List<Face> faces = getEnrolledFaces(userId); + if (faces.isEmpty()) { + Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId); + return; + } + + scheduleUpdateActiveUserWithoutHandler(userId); + + final int faceId = faces.get(0).getBiometricId(); + final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, + mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, + opPackageName, mSensorId, feature, enabled, hardwareAuthToken, faceId); + mScheduler.scheduleClientMonitor(client); + }); + } + + void scheduleGetFeature(@NonNull IBinder token, int userId, int feature, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + mHandler.post(() -> { + final List<Face> faces = getEnrolledFaces(userId); + if (faces.isEmpty()) { + Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId); + return; + } + + scheduleUpdateActiveUserWithoutHandler(userId); + + final int faceId = faces.get(0).getBiometricId(); + final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, + mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, + opPackageName, mSensorId, feature, faceId); + mScheduler.scheduleClientMonitor(client); + }); + } + + void scheduleGenerateChallenge(@NonNull IBinder token, @NonNull IFaceServiceReceiver receiver, + @NonNull String opPackageName) { + mHandler.post(() -> { + final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, + mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName, + mSensorId); + mScheduler.scheduleClientMonitor(client); + }); + } + + void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String owner) { + mHandler.post(() -> { + final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, + mLazyDaemon, token, owner, mSensorId); + mScheduler.scheduleClientMonitor(client); + }); + } + + void scheduleEnroll(@NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, + @NonNull int[] disabledFeatures, @Nullable NativeHandle surfaceHandle) { + mHandler.post(() -> { + scheduleUpdateActiveUserWithoutHandler(userId); + + mNotificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID, + UserHandle.CURRENT); + + final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token, + new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, + opPackageName, FaceUtils.getInstance(), disabledFeatures, ENROLL_TIMEOUT_SEC, + surfaceHandle, mSensorId); + + mScheduler.scheduleClientMonitor(client, ((clientMonitor, success) -> { + if (success) { + // Update authenticatorIds + scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId()); + } + })); + }); + } + + void cancelEnrollment(@NonNull IBinder token) { + mHandler.post(() -> { + mScheduler.cancelEnrollment(token); + }); + } + + void scheduleAuthenticate(@NonNull IBinder token, long operationId, int userId, int cookie, + @NonNull ClientMonitorCallbackConverter receiver, @NonNull String opPackageName, + boolean restricted, int statsClient) { + mHandler.post(() -> { + scheduleUpdateActiveUserWithoutHandler(userId); + + final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId); + final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext, + mLazyDaemon, token, receiver, userId, operationId, restricted, opPackageName, + cookie, false /* requireConfirmation */, mSensorId, isStrongBiometric, + statsClient, mLockoutTracker, mUsageStats); + mScheduler.scheduleClientMonitor(client); + }); + } + + void startPreparedClient(int cookie) { + mHandler.post(() -> { + mScheduler.startPreparedClient(cookie); + }); + } + + void cancelAuthentication(@NonNull IBinder token) { + mHandler.post(() -> { + mScheduler.cancelAuthentication(token); + }); + } + + void scheduleRemove(@NonNull IBinder token, int faceId, int userId, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + mHandler.post(() -> { + scheduleUpdateActiveUserWithoutHandler(userId); + + final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, + new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName, + FaceUtils.getInstance(), mSensorId, mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client); + }); + } + + private void scheduleInternalCleanup(int userId) { + mHandler.post(() -> { + scheduleUpdateActiveUserWithoutHandler(userId); + + final List<Face> enrolledList = getEnrolledFaces(userId); + final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, + mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, enrolledList, + FaceUtils.getInstance(), mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client); + }); + } + + boolean isHardwareDetected() { + final IBiometricsFace daemon = getDaemon(); + return daemon != null; + } + + List<Face> getEnrolledFaces(int userId) { + return FaceUtils.getInstance().getBiometricsForUser(mContext, userId); + } + + long getAuthenticatorId(int userId) { + return mAuthenticatorIds.get(userId); + } + + public void dump(@NonNull PrintWriter pw) { + PerformanceTracker performanceTracker = + PerformanceTracker.getInstanceForSensorId(mSensorId); + + JSONObject dump = new JSONObject(); + try { + dump.put("service", "Face Manager"); + + JSONArray sets = new JSONArray(); + for (UserInfo user : UserManager.get(mContext).getUsers()) { + final int userId = user.getUserHandle().getIdentifier(); + final int N = FaceUtils.getInstance().getBiometricsForUser(mContext, userId).size(); + JSONObject set = new JSONObject(); + set.put("id", userId); + set.put("count", N); + set.put("accept", performanceTracker.getAcceptForUser(userId)); + set.put("reject", performanceTracker.getRejectForUser(userId)); + set.put("acquire", performanceTracker.getAcquireForUser(userId)); + set.put("lockout", performanceTracker.getTimedLockoutForUser(userId)); + set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId)); + // cryptoStats measures statistics about secure face transactions + // (e.g. to unlock password storage, make secure purchases, etc.) + set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId)); + set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId)); + set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId)); + sets.put(set); + } + + dump.put("prints", sets); + } catch (JSONException e) { + Slog.e(TAG, "dump formatting failure", e); + } + pw.println(dump); + pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount()); + + mUsageStats.print(pw); + } + + public void dumpHal(@NonNull FileDescriptor fd, @NonNull String[] args) { + // WARNING: CDD restricts image data from leaving TEE unencrypted on + // production devices: + // [C-1-10] MUST not allow unencrypted access to identifiable biometric + // data or any data derived from it (such as embeddings) to the + // Application Processor outside the context of the TEE. + // As such, this API should only be enabled for testing purposes on + // engineering and userdebug builds. All modules in the software stack + // MUST enforce final build products do NOT have this functionality. + // Additionally, the following check MUST NOT be removed. + if (!(Build.IS_ENG || Build.IS_USERDEBUG)) { + return; + } + + // Additionally, this flag allows turning off face for a device + // (either permanently through the build or on an individual device). + if (SystemProperties.getBoolean("ro.face.disable_debug_data", false) + || SystemProperties.getBoolean("persist.face.disable_debug_data", false)) { + return; + } + + // The debug method takes two file descriptors. The first is for text + // output, which we will drop. The second is for binary data, which + // will be the protobuf data. + final IBiometricsFace daemon = getDaemon(); + if (daemon != null) { + FileOutputStream devnull = null; + try { + devnull = new FileOutputStream("/dev/null"); + final NativeHandle handle = new NativeHandle( + new FileDescriptor[] { devnull.getFD(), fd }, + new int[0], false); + daemon.debug(handle, new ArrayList<String>(Arrays.asList(args))); + } catch (IOException | RemoteException ex) { + Slog.d(TAG, "error while reading face debugging data", ex); + } finally { + if (devnull != null) { + try { + devnull.close(); + } catch (IOException ex) { + } + } + } + } + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java index 118cadc9f9fa..21bda74bc6b9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java @@ -21,7 +21,6 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.TaskStackListener; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -67,11 +66,10 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId, boolean isStrongBiometric, int statsClient, - @NonNull TaskStackListener taskStackListener, @NonNull LockoutTracker lockoutTracker, @NonNull UsageStats usageStats) { super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner, cookie, requireConfirmation, sensorId, isStrongBiometric, - BiometricsProtoEnums.MODALITY_FACE, statsClient, taskStackListener, + BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */, lockoutTracker); mNotificationManager = context.getSystemService(NotificationManager.class); mUsageStats = usageStats; @@ -221,8 +219,8 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { .build(); mNotificationManager.createNotificationChannel(channel); - mNotificationManager.notifyAsUser(FaceService.NOTIFICATION_TAG, - FaceService.NOTIFICATION_ID, notification, + mNotificationManager.notifyAsUser(Face10.NOTIFICATION_TAG, + Face10.NOTIFICATION_ID, notification, UserHandle.CURRENT); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java index 4f9f46aff4c0..52a822675c2f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java @@ -68,6 +68,19 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> { } @Override + protected boolean hasReachedEnrollmentLimit() { + final int limit = getContext().getResources().getInteger( + com.android.internal.R.integer.config_faceMaxTemplatesPerUser); + final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()) + .size(); + if (enrolled >= limit) { + Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId()); + return true; + } + return false; + } + + @Override public void onAcquired(int acquireInfo, int vendorCode) { final boolean shouldSend; if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 7a051921f403..1c81fe1b484b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -16,64 +16,32 @@ package com.android.server.biometrics.sensors.face; -import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.MANAGE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; -import android.app.ActivityManager; -import android.app.NotificationManager; +import android.annotation.NonNull; import android.content.Context; -import android.content.pm.UserInfo; -import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; -import android.hardware.biometrics.face.V1_0.IBiometricsFace; -import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; import android.hardware.face.Face; import android.hardware.face.IFaceService; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; -import android.os.Build; -import android.os.Environment; import android.os.IBinder; import android.os.NativeHandle; -import android.os.RemoteException; -import android.os.SELinux; -import android.os.SystemProperties; -import android.os.UserHandle; -import android.os.UserManager; -import android.provider.Settings; import android.util.Slog; import android.view.Surface; -import com.android.internal.annotations.GuardedBy; -import com.android.internal.logging.MetricsLogger; import com.android.internal.util.DumpUtils; -import com.android.server.SystemServerInitThreadPool; -import com.android.server.biometrics.sensors.AuthenticationClient; -import com.android.server.biometrics.sensors.BiometricServiceBase; -import com.android.server.biometrics.sensors.BiometricUtils; -import com.android.server.biometrics.sensors.ClientMonitor; +import com.android.server.SystemService; +import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; -import com.android.server.biometrics.sensors.EnrollClient; -import com.android.server.biometrics.sensors.GenerateChallengeClient; -import com.android.server.biometrics.sensors.LockoutResetTracker; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; -import com.android.server.biometrics.sensors.PerformanceTracker; -import com.android.server.biometrics.sensors.RemovalClient; -import com.android.server.biometrics.sensors.RevokeChallengeClient; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -82,177 +50,115 @@ import java.util.List; * The service is responsible for maintaining a list of clients and dispatching all * face-related events. */ -public class FaceService extends BiometricServiceBase<IBiometricsFace> { +public class FaceService extends SystemService { protected static final String TAG = "FaceService"; - private static final boolean DEBUG = true; - private static final String FACE_DATA_DIR = "facedata"; - private static final String ACTION_LOCKOUT_RESET = - "com.android.server.biometrics.face.ACTION_LOCKOUT_RESET"; - - - static final String NOTIFICATION_TAG = "FaceService"; - static final int NOTIFICATION_ID = 1; - private final LockoutResetTracker mLockoutResetTracker; - private final ClientMonitor.LazyDaemon<IBiometricsFace> mLazyDaemon; + private Face10 mFace10; + private final LockoutResetDispatcher mLockoutResetDispatcher; /** * Receives the incoming binder calls from FaceManager. */ private final class FaceServiceWrapper extends IFaceService.Stub { - private static final int ENROLL_TIMEOUT_SEC = 75; - - /** - * The following methods contain common code which is shared in biometrics/common. - */ - @Override // Binder call public void generateChallenge(IBinder token, IFaceServiceReceiver receiver, String opPackageName) { - checkPermission(MANAGE_BIOMETRIC); - - final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(getContext(), - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName, - getSensorId()); - generateChallengeInternal(client); + Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); + mFace10.scheduleGenerateChallenge(token, receiver, opPackageName); } @Override // Binder call public void revokeChallenge(IBinder token, String owner) { - checkPermission(MANAGE_BIOMETRIC); - - final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(getContext(), - mLazyDaemon, token, owner, getSensorId()); - - // TODO(b/137106905): Schedule binder calls in FaceService to avoid deadlocks. - if (getCurrentClient() == null) { - // if we aren't handling any other HIDL calls (mCurrentClient == null), revoke - // the challenge right away. - revokeChallengeInternal(client); - } else { - // postpone revoking the challenge until we finish processing the current HIDL - // call. - mPendingRevokeChallenge = client; - } + Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); + mFace10.scheduleRevokeChallenge(token, owner); } @Override // Binder call public void enroll(int userId, final IBinder token, final byte[] hardwareAuthToken, final IFaceServiceReceiver receiver, final String opPackageName, final int[] disabledFeatures, Surface surface) { - checkPermission(MANAGE_BIOMETRIC); - updateActiveGroup(userId); - - mHandler.post(() -> { - mNotificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID, - UserHandle.CURRENT); - }); - - final FaceEnrollClient client = new FaceEnrollClient(getContext(), mLazyDaemon, token, - new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, - opPackageName, getBiometricUtils(), disabledFeatures, ENROLL_TIMEOUT_SEC, - convertSurfaceToNativeHandle(surface), getSensorId()); - - enrollInternal(client, userId); + Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); + mFace10.scheduleEnroll(token, hardwareAuthToken, userId, receiver, opPackageName, + disabledFeatures, convertSurfaceToNativeHandle(surface)); } @Override // Binder call public void enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken, final IFaceServiceReceiver receiver, final String opPackageName, final int[] disabledFeatures) { - checkPermission(MANAGE_BIOMETRIC); + Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); // TODO(b/145027036): Implement this. } @Override // Binder call public void cancelEnrollment(final IBinder token) { - checkPermission(MANAGE_BIOMETRIC); - cancelEnrollmentInternal(token); + Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); + mFace10.cancelEnrollment(token); } @Override // Binder call - public void authenticate(final IBinder token, final long opId, int userId, + public void authenticate(final IBinder token, final long operationId, int userId, final IFaceServiceReceiver receiver, final String opPackageName) { - checkPermission(USE_BIOMETRIC_INTERNAL); - updateActiveGroup(userId); + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - final boolean restricted = isRestricted(); - final int statsClient = isKeyguard(opPackageName) ? BiometricsProtoEnums.CLIENT_KEYGUARD + final boolean restricted = false; // Face APIs are private + final int statsClient = Utils.isKeyguard(getContext(), opPackageName) + ? BiometricsProtoEnums.CLIENT_KEYGUARD : BiometricsProtoEnums.CLIENT_UNKNOWN; - final FaceAuthenticationClient client = new FaceAuthenticationClient(getContext(), - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, opId, - restricted, opPackageName, 0 /* cookie */, false /* requireConfirmation */, - getSensorId(), isStrongBiometric(), statsClient, mTaskStackListener, - mLockoutTracker, mUsageStats); - authenticateInternal(client, opPackageName); + mFace10.scheduleAuthenticate(token, operationId, userId, 0 /* cookie */, + new ClientMonitorCallbackConverter(receiver), opPackageName, restricted, + statsClient); } @Override // Binder call - public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long opId, - int userId, IBiometricSensorReceiver sensorReceiver, + public void prepareForAuthentication(boolean requireConfirmation, IBinder token, + long operationId, int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId) { - checkPermission(USE_BIOMETRIC_INTERNAL); - updateActiveGroup(userId); + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); final boolean restricted = true; // BiometricPrompt is always restricted - final FaceAuthenticationClient client = new FaceAuthenticationClient(getContext(), - mLazyDaemon, token, new ClientMonitorCallbackConverter(sensorReceiver), userId, - opId, restricted, opPackageName, cookie, requireConfirmation, getSensorId(), - isStrongBiometric(), BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, - mTaskStackListener, mLockoutTracker, mUsageStats); - authenticateInternal(client, opPackageName); + mFace10.scheduleAuthenticate(token, operationId, userId, cookie, + new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, + restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT); } @Override // Binder call public void startPreparedClient(int cookie) { - checkPermission(MANAGE_BIOMETRIC); - startCurrentClient(cookie); + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + mFace10.startPreparedClient(cookie); } @Override // Binder call public void cancelAuthentication(final IBinder token, final String opPackageName) { - checkPermission(USE_BIOMETRIC_INTERNAL); - cancelAuthenticationInternal(token, opPackageName, true /* fromClient */); + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + mFace10.cancelAuthentication(token); } @Override // Binder call public void cancelAuthenticationFromService(final IBinder token, final String opPackageName, int callingUid, int callingPid, int callingUserId) { - checkPermission(USE_BIOMETRIC_INTERNAL); - // Cancellation is from system server in this case. - cancelAuthenticationInternal(token, opPackageName, false /* fromClient */); + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + mFace10.cancelAuthentication(token); } @Override // Binder call public void remove(final IBinder token, final int faceId, final int userId, final IFaceServiceReceiver receiver, final String opPackageName) { - checkPermission(MANAGE_BIOMETRIC); - updateActiveGroup(userId); - - if (token == null) { - Slog.w(TAG, "remove(): token is null"); - return; - } - - final FaceRemovalClient client = new FaceRemovalClient(getContext(), mLazyDaemon, token, - new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName, - getBiometricUtils(), getSensorId(), mAuthenticatorIds); - removeInternal(client); + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + mFace10.scheduleRemove(token, faceId, userId, receiver, opPackageName); } @Override public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback, final String opPackageName) { - checkPermission(USE_BIOMETRIC_INTERNAL); - mHandler.post(() -> { - mLockoutResetTracker.addCallback(callback, opPackageName); - }); + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + mLockoutResetDispatcher.addCallback(callback, opPackageName); } @Override // Binder call - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) { return; } @@ -260,28 +166,25 @@ public class FaceService extends BiometricServiceBase<IBiometricsFace> { final long ident = Binder.clearCallingIdentity(); try { if (args.length > 1 && "--hal".equals(args[0])) { - dumpHal(fd, Arrays.copyOfRange(args, 1, args.length, args.getClass())); + mFace10.dumpHal(fd, Arrays.copyOfRange(args, 1, args.length, args.getClass())); } else { - dumpInternal(pw); + mFace10.dump(pw); } } finally { Binder.restoreCallingIdentity(ident); } } - /** - * The following methods don't use any common code from BiometricService - */ - - // TODO: refactor out common code here @Override // Binder call public boolean isHardwareDetected(String opPackageName) { - checkPermission(USE_BIOMETRIC_INTERNAL); - + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + if (mFace10 == null) { + Slog.wtf(TAG, "No HAL, caller: " + opPackageName); + return false; + } final long token = Binder.clearCallingIdentity(); try { - IBiometricsFace daemon = getFaceDaemon(); - return daemon != null; + return mFace10.isHardwareDetected(); } finally { Binder.restoreCallingIdentity(token); } @@ -289,512 +192,66 @@ public class FaceService extends BiometricServiceBase<IBiometricsFace> { @Override // Binder call public List<Face> getEnrolledFaces(int userId, String opPackageName) { - checkPermission(MANAGE_BIOMETRIC); - return FaceService.this.getEnrolledTemplates(userId); + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + return mFace10.getEnrolledFaces(userId); } @Override // Binder call public boolean hasEnrolledFaces(int userId, String opPackageName) { - checkPermission(USE_BIOMETRIC_INTERNAL); - return FaceService.this.hasEnrolledBiometrics(userId); + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + return !mFace10.getEnrolledFaces(userId).isEmpty(); } @Override public @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) { - checkPermission(USE_BIOMETRIC_INTERNAL); - return mLockoutTracker.getLockoutModeForUser(userId); + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + return mFace10.getLockoutModeForUser(userId); } @Override // Binder call - public long getAuthenticatorId(int callingUserId) { - checkPermission(USE_BIOMETRIC_INTERNAL); - return FaceService.this.getAuthenticatorId(callingUserId); + public long getAuthenticatorId(int userId) { + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + return mFace10.getAuthenticatorId(userId); } @Override // Binder call public void resetLockout(int userId, byte[] hardwareAuthToken) { - checkPermission(MANAGE_BIOMETRIC); - mHandler.post(() -> { - if (!FaceService.this.hasEnrolledBiometrics(userId)) { - Slog.w(TAG, "Ignoring lockout reset, no templates enrolled"); - return; - } - - Slog.d(TAG, "Resetting lockout for user: " + userId); - - updateActiveGroup(userId); - final FaceResetLockoutClient client = new FaceResetLockoutClient(getContext(), - mLazyDaemon, userId, getContext().getOpPackageName(), getSensorId(), - hardwareAuthToken); - startClient(client, true /* initiatedByClient */); - }); + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + mFace10.scheduleResetLockout(userId, hardwareAuthToken); } @Override public void setFeature(final IBinder token, int userId, int feature, boolean enabled, final byte[] hardwareAuthToken, IFaceServiceReceiver receiver, final String opPackageName) { - checkPermission(MANAGE_BIOMETRIC); - - mHandler.post(() -> { - if (DEBUG) { - Slog.d(TAG, "setFeature for user(" + userId + ")"); - } - updateActiveGroup(userId); - if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) { - Slog.e(TAG, "No enrolled biometrics while setting feature: " + feature); - return; - } - - final int faceId = getFirstTemplateForUser(mCurrentUserId); - - final FaceSetFeatureClient client = new FaceSetFeatureClient(getContext(), - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, - opPackageName, getSensorId(), feature, enabled, hardwareAuthToken, faceId); - startClient(client, true /* initiatedByClient */); - }); - + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + mFace10.scheduleSetFeature(token, userId, feature, enabled, hardwareAuthToken, receiver, + opPackageName); } @Override public void getFeature(final IBinder token, int userId, int feature, IFaceServiceReceiver receiver, final String opPackageName) { - checkPermission(MANAGE_BIOMETRIC); - - mHandler.post(() -> { - if (DEBUG) { - Slog.d(TAG, "getFeature for user(" + userId + ")"); - } - updateActiveGroup(userId); - // This should ideally return tri-state, but the user isn't shown settings unless - // they are enrolled so it's fine for now. - if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) { - Slog.e(TAG, "No enrolled biometrics while getting feature: " + feature); - return; - } - - // TODO: Support multiple faces - final int faceId = getFirstTemplateForUser(mCurrentUserId); - - final FaceGetFeatureClient client = new FaceGetFeatureClient(getContext(), - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, - opPackageName, getSensorId(), feature, faceId); - startClient(client, true /* initiatedByClient */); - }); - - } - - // TODO: Support multiple faces - private int getFirstTemplateForUser(int user) { - final List<Face> faces = FaceService.this.getEnrolledTemplates(user); - if (!faces.isEmpty()) { - return faces.get(0).getBiometricId(); - } - return 0; + Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); + mFace10.scheduleGetFeature(token, userId, feature, receiver, opPackageName); } @Override // Binder call public void initializeConfiguration(int sensorId) { - checkPermission(USE_BIOMETRIC_INTERNAL); - initializeConfigurationInternal(sensorId); + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + mFace10 = new Face10(getContext(), sensorId, mLockoutResetDispatcher); } } - private final LockoutHalImpl mLockoutTracker; - - @GuardedBy("this") - private IBiometricsFace mDaemon; - private UsageStats mUsageStats; - private FaceRevokeChallengeClient mPendingRevokeChallenge; - - private NotificationManager mNotificationManager; - - /** - * Receives callbacks from the HAL. - */ - private IBiometricsFaceClientCallback mDaemonCallback = - new IBiometricsFaceClientCallback.Stub() { - @Override - public void onEnrollResult(final long deviceId, int faceId, int userId, - int remaining) { - mHandler.post(() -> { - final Face face = new Face(getBiometricUtils() - .getUniqueName(getContext(), userId), faceId, deviceId); - FaceService.super.handleEnrollResult(face, remaining); - - // Enrollment changes the authenticatorId, so update it here. - IBiometricsFace daemon = getFaceDaemon(); - if (remaining == 0 && daemon != null) { - try { - mAuthenticatorIds.put(userId, - hasEnrolledBiometrics(userId) ? daemon.getAuthenticatorId().value - : 0L); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to get authenticatorId", e); - } - } - }); - } - - @Override - public void onAcquired(final long deviceId, final int userId, final int acquiredInfo, - final int vendorCode) { - mHandler.post(() -> { - FaceService.super.handleAcquired(acquiredInfo, vendorCode); - }); - } - - @Override - public void onAuthenticated(final long deviceId, final int faceId, final int userId, - ArrayList<Byte> token) { - mHandler.post(() -> { - Face face = new Face("", faceId, deviceId); - FaceService.super.handleAuthenticated(face, token); - }); - } - - @Override - public void onError(final long deviceId, final int userId, final int error, - final int vendorCode) { - mHandler.post(() -> { - FaceService.super.handleError(error, vendorCode); - - // TODO: this chunk of code should be common to all biometric services - if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) { - // If we get HW_UNAVAILABLE, try to connect again later... - Slog.w(TAG, "Got ERROR_HW_UNAVAILABLE; try reconnecting next client."); - synchronized (this) { - mDaemon = null; - mCurrentUserId = UserHandle.USER_NULL; - } - } - }); - } - - @Override - public void onRemoved(final long deviceId, ArrayList<Integer> faceIds, final int userId) { - mHandler.post(() -> { - if (!faceIds.isEmpty()) { - for (int i = 0; i < faceIds.size(); i++) { - final Face face = new Face("", faceIds.get(i), deviceId); - // Convert to old behavior - FaceService.super.handleRemoved(face, faceIds.size() - i - 1); - } - } else { - final Face face = new Face("", 0 /* identifier */, deviceId); - FaceService.super.handleRemoved(face, 0 /* remaining */); - } - Settings.Secure.putIntForUser(getContext().getContentResolver(), - Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT); - }); - } - - @Override - public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) - throws RemoteException { - mHandler.post(() -> { - if (!faceIds.isEmpty()) { - for (int i = 0; i < faceIds.size(); i++) { - final Face face = new Face("", faceIds.get(i), deviceId); - // Convert to old old behavior - FaceService.super.handleEnumerate(face, faceIds.size() - i - 1); - } - } else { - // For face, the HIDL contract is to receive an empty list when there are no - // templates enrolled. Send a null identifier since we don't consume them - // anywhere, and send remaining == 0 to plumb this with existing common code. - FaceService.super.handleEnumerate(null /* identifier */, 0); - } - }); - } - - @Override - public void onLockoutChanged(long duration) { - Slog.d(TAG, "onLockoutChanged: " + duration); - - final @LockoutTracker.LockoutMode int lockoutMode; - if (duration == 0) { - lockoutMode = LockoutTracker.LOCKOUT_NONE; - } else if (duration == -1 || duration == Long.MAX_VALUE) { - lockoutMode = LockoutTracker.LOCKOUT_PERMANENT; - } else { - lockoutMode = LockoutTracker.LOCKOUT_TIMED; - } - mLockoutTracker.setCurrentUserLockoutMode(lockoutMode); - - mHandler.post(() -> { - if (duration == 0) { - mLockoutResetTracker.notifyLockoutResetCallbacks(getSensorId()); - } - }); - } - }; - public FaceService(Context context) { super(context); - mLazyDaemon = FaceService.this::getFaceDaemon; - mLockoutResetTracker = new LockoutResetTracker(context); - mLockoutTracker = new LockoutHalImpl(); - mUsageStats = new UsageStats(context); - mNotificationManager = getContext().getSystemService(NotificationManager.class); - } - - @Override - protected void removeClient(ClientMonitor<?> client) { - super.removeClient(client); - if (mPendingRevokeChallenge != null) { - revokeChallengeInternal(mPendingRevokeChallenge); - mPendingRevokeChallenge = null; - } + mLockoutResetDispatcher = new LockoutResetDispatcher(context); } @Override public void onStart() { - super.onStart(); publishBinderService(Context.FACE_SERVICE, new FaceServiceWrapper()); - // Get the face daemon on FaceService's on thread so SystemServerInitThreadPool isn't - // blocked - SystemServerInitThreadPool.submit(() -> mHandler.post(this::getFaceDaemon), - TAG + ".onStart"); - } - - @Override - public String getTag() { - return TAG; - } - - @Override - protected IBiometricsFace getDaemon() { - return getFaceDaemon(); - } - - @Override - protected BiometricUtils getBiometricUtils() { - return FaceUtils.getInstance(); - } - - @Override - protected boolean hasReachedEnrollmentLimit(int userId) { - final int limit = getContext().getResources().getInteger( - com.android.internal.R.integer.config_faceMaxTemplatesPerUser); - final int enrolled = FaceService.this.getEnrolledTemplates(userId).size(); - if (enrolled >= limit) { - Slog.w(TAG, "Too many faces registered, user: " + userId); - return true; - } - return false; - } - - @Override - public void serviceDied(long cookie) { - super.serviceDied(cookie); - mDaemon = null; - - mCurrentUserId = UserHandle.USER_NULL; // Force updateActiveGroup() to re-evaluate - } - - @Override - protected void updateActiveGroup(int userId) { - IBiometricsFace daemon = getFaceDaemon(); - - if (daemon != null) { - try { - if (userId != mCurrentUserId) { - final File baseDir = Environment.getDataVendorDeDirectory(userId); - final File faceDir = new File(baseDir, FACE_DATA_DIR); - if (!faceDir.exists()) { - if (!faceDir.mkdir()) { - Slog.v(TAG, "Cannot make directory: " + faceDir.getAbsolutePath()); - return; - } - // Calling mkdir() from this process will create a directory with our - // permissions (inherited from the containing dir). This command fixes - // the label. - if (!SELinux.restorecon(faceDir)) { - Slog.w(TAG, "Restorecons failed. Directory will have wrong label."); - return; - } - } - - daemon.setActiveUser(userId, faceDir.getAbsolutePath()); - mCurrentUserId = userId; - mAuthenticatorIds.put(userId, - hasEnrolledBiometrics(userId) ? daemon.getAuthenticatorId().value : 0L); - } - } catch (RemoteException e) { - Slog.e(TAG, "Failed to setActiveUser():", e); - } - } - } - - @Override - protected void handleUserSwitching(int userId) { - super.handleUserSwitching(userId); - // Will be updated when we get the callback from HAL - mLockoutTracker.setCurrentUserLockoutMode(LockoutTracker.LOCKOUT_NONE); - } - - @Override - protected boolean hasEnrolledBiometrics(int userId) { - if (userId != UserHandle.getCallingUserId()) { - checkPermission(INTERACT_ACROSS_USERS); - } - return getBiometricUtils().getBiometricsForUser(getContext(), userId).size() > 0; - } - - @Override - protected String getManageBiometricPermission() { - return MANAGE_BIOMETRIC; - } - - @Override - protected List<Face> getEnrolledTemplates(int userId) { - return FaceUtils.getInstance().getBiometricsForUser(getContext(), userId); - } - - @Override - protected void notifyClientActiveCallbacks(boolean isActive) { - // noop for Face. - } - - @Override - protected int statsModality() { - return BiometricsProtoEnums.MODALITY_FACE; - } - - @Override - protected void doTemplateCleanupForUser(int userId) { - final List<Face> enrolledList = getEnrolledTemplates(userId); - final FaceInternalCleanupClient client = new FaceInternalCleanupClient(getContext(), - mLazyDaemon, userId, getContext().getOpPackageName(), getSensorId(), enrolledList, - getBiometricUtils(), mAuthenticatorIds); - cleanupInternal(client); - } - - - /** Gets the face daemon */ - private synchronized IBiometricsFace getFaceDaemon() { - if (mDaemon == null) { - Slog.v(TAG, "mDaemon was null, reconnect to face"); - try { - mDaemon = IBiometricsFace.getService(); - } catch (java.util.NoSuchElementException e) { - // Service doesn't exist or cannot be opened. Logged below. - } catch (RemoteException e) { - Slog.e(TAG, "Failed to get biometric interface", e); - } - if (mDaemon == null) { - Slog.w(TAG, "face HIDL not available"); - return null; - } - - mDaemon.asBinder().linkToDeath(this, 0); - - long halId = 0; - try { - halId = mDaemon.setCallback(mDaemonCallback).value; - } catch (RemoteException e) { - Slog.e(TAG, "Failed to open face HAL", e); - mDaemon = null; // try again later! - } - - if (DEBUG) Slog.v(TAG, "Face HAL id: " + halId); - if (halId != 0) { - loadAuthenticatorIds(); - updateActiveGroup(ActivityManager.getCurrentUser()); - doTemplateCleanupForUser(ActivityManager.getCurrentUser()); - } else { - Slog.w(TAG, "Failed to open Face HAL!"); - MetricsLogger.count(getContext(), "faced_openhal_error", 1); - mDaemon = null; - } - } - return mDaemon; } private native NativeHandle convertSurfaceToNativeHandle(Surface surface); - - private void dumpInternal(PrintWriter pw) { - PerformanceTracker performanceTracker = - PerformanceTracker.getInstanceForSensorId(getSensorId()); - - JSONObject dump = new JSONObject(); - try { - dump.put("service", "Face Manager"); - - JSONArray sets = new JSONArray(); - for (UserInfo user : UserManager.get(getContext()).getUsers()) { - final int userId = user.getUserHandle().getIdentifier(); - final int N = getBiometricUtils().getBiometricsForUser(getContext(), userId).size(); - JSONObject set = new JSONObject(); - set.put("id", userId); - set.put("count", N); - set.put("accept", performanceTracker.getAcceptForUser(userId)); - set.put("reject", performanceTracker.getRejectForUser(userId)); - set.put("acquire", performanceTracker.getAcquireForUser(userId)); - set.put("lockout", performanceTracker.getTimedLockoutForUser(userId)); - set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId)); - // cryptoStats measures statistics about secure face transactions - // (e.g. to unlock password storage, make secure purchases, etc.) - set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId)); - set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId)); - set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId)); - sets.put(set); - } - - dump.put("prints", sets); - } catch (JSONException e) { - Slog.e(TAG, "dump formatting failure", e); - } - pw.println(dump); - pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount()); - - mUsageStats.print(pw); - } - - private void dumpHal(FileDescriptor fd, String[] args) { - // WARNING: CDD restricts image data from leaving TEE unencrypted on - // production devices: - // [C-1-10] MUST not allow unencrypted access to identifiable biometric - // data or any data derived from it (such as embeddings) to the - // Application Processor outside the context of the TEE. - // As such, this API should only be enabled for testing purposes on - // engineering and userdebug builds. All modules in the software stack - // MUST enforce final build products do NOT have this functionality. - // Additionally, the following check MUST NOT be removed. - if (!(Build.IS_ENG || Build.IS_USERDEBUG)) { - return; - } - - // Additionally, this flag allows turning off face for a device - // (either permanently through the build or on an individual device). - if (SystemProperties.getBoolean("ro.face.disable_debug_data", false) - || SystemProperties.getBoolean("persist.face.disable_debug_data", false)) { - return; - } - - // The debug method takes two file descriptors. The first is for text - // output, which we will drop. The second is for binary data, which - // will be the protobuf data. - final IBiometricsFace daemon = getFaceDaemon(); - if (daemon != null) { - FileOutputStream devnull = null; - try { - devnull = new FileOutputStream("/dev/null"); - final NativeHandle handle = new NativeHandle( - new FileDescriptor[] { devnull.getFD(), fd }, - new int[0], false); - daemon.debug(handle, new ArrayList<String>(Arrays.asList(args))); - } catch (IOException | RemoteException ex) { - Slog.d(TAG, "error while reading face debugging data", ex); - } finally { - if (devnull != null) { - try { - devnull.close(); - } catch (IOException ex) { - } - } - } - } - } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java new file mode 100644 index 000000000000..bcf304e47816 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 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.biometrics.sensors.face; + +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; +import android.os.Environment; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.server.biometrics.sensors.ClientMonitor; + +import java.io.File; +import java.util.Map; + +public class FaceUpdateActiveUserClient extends ClientMonitor<IBiometricsFace> { + private static final String TAG = "FaceUpdateActiveUserClient"; + private static final String FACE_DATA_DIR = "facedata"; + + private final int mCurrentUserId; + private final boolean mHasEnrolledBiometrics; + @NonNull private final Map<Integer, Long> mAuthenticatorIds; + + FaceUpdateActiveUserClient(@NonNull Context context, + @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner, + int sensorId, int currentUserId, boolean hasEnrolledBIometrics, + @NonNull Map<Integer, Long> authenticatorIds) { + super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, + 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, + BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + mCurrentUserId = currentUserId; + mHasEnrolledBiometrics = hasEnrolledBIometrics; + mAuthenticatorIds = authenticatorIds; + } + + @Override + public void start(@NonNull FinishCallback finishCallback) { + super.start(finishCallback); + + if (mCurrentUserId == getTargetUserId()) { + Slog.d(TAG, "Already user: " + mCurrentUserId + ", refreshing authenticatorId"); + try { + mAuthenticatorIds.put(getTargetUserId(), mHasEnrolledBiometrics + ? getFreshDaemon().getAuthenticatorId().value : 0L); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to refresh authenticatorId", e); + } + finishCallback.onClientFinished(this, true /* success */); + return; + } + + startHalOperation(); + } + + @Override + public void unableToStart() { + // Nothing to do here + } + + @Override + protected void startHalOperation() { + final File storePath = new File(Environment.getDataVendorDeDirectory(getTargetUserId()), + FACE_DATA_DIR); + if (!storePath.exists()) { + Slog.e(TAG, "vold has not created the directory?"); + mFinishCallback.onClientFinished(this, false /* success */); + return; + } + + try { + getFreshDaemon().setActiveUser(getTargetUserId(), storePath.getAbsolutePath()); + mFinishCallback.onClientFinished(this, true /* success */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to setActiveUser: " + e); + mFinishCallback.onClientFinished(this, false /* success */); + } + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java index 0a9497291f37..9f94c88193ff 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java @@ -56,7 +56,7 @@ import com.android.server.biometrics.sensors.ClientMonitor; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.EnumerateConsumer; import com.android.server.biometrics.sensors.Interruptable; -import com.android.server.biometrics.sensors.LockoutResetTracker; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.PerformanceTracker; import com.android.server.biometrics.sensors.RemovalConsumer; @@ -83,17 +83,17 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { private static final int ENROLL_TIMEOUT_SEC = 60; private final Context mContext; - private final IActivityTaskManager mActivityTaskManager;; + private final IActivityTaskManager mActivityTaskManager; private final SensorProperties mSensorProperties; private final BiometricScheduler mScheduler; private final Handler mHandler; - private final LockoutResetTracker mLockoutResetTracker; + private final LockoutResetDispatcher mLockoutResetDispatcher; private final LockoutFrameworkImpl mLockoutTracker; private final BiometricTaskStackListener mTaskStackListener; private final ClientMonitor.LazyDaemon<IBiometricsFingerprint> mLazyDaemon; private final Map<Integer, Long> mAuthenticatorIds; - private IBiometricsFingerprint mDaemon; + @Nullable private IBiometricsFingerprint mDaemon; @Nullable private IUdfpsOverlayController mUdfpsOverlayController; private int mCurrentUserId = UserHandle.USER_NULL; @@ -102,9 +102,9 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { */ private static final class SensorProperties { final int sensorId; - final Boolean isUdfps; + final boolean isUdfps; - SensorProperties(int sensorId, Boolean isUdfps) { + SensorProperties(int sensorId, boolean isUdfps) { this.sensorId = sensorId; this.isUdfps = isUdfps; } @@ -146,7 +146,7 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { new LockoutFrameworkImpl.LockoutResetCallback() { @Override public void onLockoutReset(int userId) { - mLockoutResetTracker.notifyLockoutResetCallbacks(mSensorProperties.sensorId); + mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorProperties.sensorId); } }; @@ -157,9 +157,8 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { } }; - private IBiometricsFingerprintClientCallback mDaemonCallback = + private final IBiometricsFingerprintClientCallback mDaemonCallback = new IBiometricsFingerprintClientCallback.Stub() { - @Override public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { mHandler.post(() -> { @@ -169,19 +168,13 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { final ClientMonitor<?> client = mScheduler.getCurrentClient(); if (!(client instanceof FingerprintEnrollClient)) { - final String clientName = client != null - ? client.getClass().getSimpleName(): "null"; - Slog.e(TAG, "onEnrollResult for non-enroll client: " + clientName); + Slog.e(TAG, "onEnrollResult for non-enroll client: " + + Utils.getClientName(client)); return; } final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client; enrollClient.onEnrollResult(fingerprint, remaining); - - if (remaining == 0) { - // Update the framework's authenticatorId cache for this user - scheduleUpdateActiveUserWithoutHandler(mCurrentUserId); - } }); } @@ -195,9 +188,8 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { mHandler.post(() -> { final ClientMonitor<?> client = mScheduler.getCurrentClient(); if (!(client instanceof AcquisitionClient)) { - final String clientName = client != null - ? client.getClass().getSimpleName(): "null"; - Slog.e(TAG, "onAcquired for non-acquisition client: " + clientName); + Slog.e(TAG, "onAcquired for non-acquisition client: " + + Utils.getClientName(client)); return; } @@ -212,9 +204,8 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { mHandler.post(() -> { final ClientMonitor<?> client = mScheduler.getCurrentClient(); if (!(client instanceof FingerprintAuthenticationClient)) { - final String clientName = client != null - ? client.getClass().getSimpleName(): "null"; - Slog.e(TAG, "onAuthenticated for non-authentication client: " + clientName); + Slog.e(TAG, "onAuthenticated for non-authentication client: " + + Utils.getClientName(client)); return; } @@ -235,9 +226,7 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { + ", error: " + error + ", vendorCode: " + vendorCode); if (!(client instanceof Interruptable)) { - final String clientName = client != null - ? client.getClass().getSimpleName(): "null"; - Slog.e(TAG, "onError for non-error consumer: " + clientName); + Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(client)); return; } @@ -258,9 +247,8 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { Slog.d(TAG, "Removed, fingerId: " + fingerId + ", remaining: " + remaining); final ClientMonitor<?> client = mScheduler.getCurrentClient(); if (!(client instanceof RemovalConsumer)) { - final String clientName = client != null - ? client.getClass().getSimpleName(): "null"; - Slog.e(TAG, "onRemoved for non-removal consumer: " + clientName); + Slog.e(TAG, "onRemoved for non-removal consumer: " + + Utils.getClientName(client)); return; } @@ -275,9 +263,8 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { mHandler.post(() -> { final ClientMonitor<?> client = mScheduler.getCurrentClient(); if (!(client instanceof EnumerateConsumer)) { - final String clientName = client != null - ? client.getClass().getSimpleName(): "null"; - Slog.e(TAG, "onEnumerate for non-enumerate consumer: " + clientName); + Slog.e(TAG, "onEnumerate for non-enumerate consumer: " + + Utils.getClientName(client)); return; } @@ -289,17 +276,17 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { }; Fingerprint21(@NonNull Context context, int sensorId, - @NonNull LockoutResetTracker lockoutResetTracker, - @NonNull GestureAvailabilityTracker gestureAvailabilityTracker) { + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { mContext = context; mActivityTaskManager = ActivityTaskManager.getService(); mHandler = new Handler(Looper.getMainLooper()); mTaskStackListener = new BiometricTaskStackListener(); mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>()); mLazyDaemon = Fingerprint21.this::getDaemon; - mLockoutResetTracker = lockoutResetTracker; + mLockoutResetDispatcher = lockoutResetDispatcher; mLockoutTracker = new LockoutFrameworkImpl(context, mLockoutResetCallback); - mScheduler = new BiometricScheduler(TAG, gestureAvailabilityTracker); + mScheduler = new BiometricScheduler(TAG, gestureAvailabilityDispatcher); try { ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG); @@ -308,7 +295,7 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { } final IBiometricsFingerprint daemon = getDaemon(); - Boolean isUdfps = false; + boolean isUdfps = false; android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint extension = android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint.castFrom( daemon); @@ -317,7 +304,7 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { isUdfps = extension.isUdfps(sensorId); } catch (RemoteException e) { Slog.e(TAG, "Remote exception while quering udfps", e); - isUdfps = null; + isUdfps = false; } } mSensorProperties = new SensorProperties(sensorId, isUdfps); @@ -414,9 +401,15 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { } } }); - } + /** + * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the + * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser" + * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule + * this operation on the same lambda/runnable as those operations so that the ordering is + * correct. + */ private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) { final boolean hasEnrolled = !getEnrolledFingerprints(targetUserId).isEmpty(); final FingerprintUpdateActiveUserClient client = @@ -467,9 +460,13 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, FingerprintUtils.getInstance(), ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController); - mScheduler.scheduleClientMonitor(client); + mScheduler.scheduleClientMonitor(client, ((clientMonitor, success) -> { + if (success) { + // Update authenticatorIds + scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId()); + } + })); }); - } void cancelEnrollment(@NonNull IBinder token) { @@ -533,7 +530,7 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { } boolean isHardwareDetected() { - IBiometricsFingerprint daemon = getDaemon(); + final IBiometricsFingerprint daemon = getDaemon(); return daemon != null; } @@ -619,7 +616,7 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { tracker.clear(); } - void dumpInternal(PrintWriter pw) { + void dumpInternal(@NonNull PrintWriter pw) { PerformanceTracker performanceTracker = PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java index c26dbfdaa75b..25e949a6fa60 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java @@ -76,6 +76,19 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint } @Override + protected boolean hasReachedEnrollmentLimit() { + final int limit = getContext().getResources().getInteger( + com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser); + final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()) + .size(); + if (enrolled >= limit) { + Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId()); + return true; + } + return false; + } + + @Override protected void startHalOperation() { showUdfpsOverlay(); try { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index d092bfd02b7b..ce9565ac0f7d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -25,6 +25,7 @@ import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.Manifest.permission.USE_FINGERPRINT; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.Context; @@ -50,7 +51,7 @@ import com.android.internal.util.DumpUtils; import com.android.server.SystemService; import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; -import com.android.server.biometrics.sensors.LockoutResetTracker; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import java.io.FileDescriptor; @@ -68,8 +69,8 @@ public class FingerprintService extends SystemService { protected static final String TAG = "FingerprintService"; private final AppOpsManager mAppOps; - private final LockoutResetTracker mLockoutResetTracker; - private final GestureAvailabilityTracker mGestureAvailabilityTracker; + private final LockoutResetDispatcher mLockoutResetDispatcher; + private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; private Fingerprint21 mFingerprint21; /** @@ -181,11 +182,11 @@ public class FingerprintService extends SystemService { public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback, final String opPackageName) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - mLockoutResetTracker.addCallback(callback, opPackageName); + mLockoutResetDispatcher.addCallback(callback, opPackageName); } @Override // Binder call - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) { return; } @@ -212,6 +213,10 @@ public class FingerprintService extends SystemService { final long token = Binder.clearCallingIdentity(); try { + if (mFingerprint21 == null) { + Slog.e(TAG, "No HAL"); + return false; + } return mFingerprint21.isHardwareDetected(); } finally { Binder.restoreCallingIdentity(token); @@ -277,26 +282,26 @@ public class FingerprintService extends SystemService { @Override public boolean isClientActive() { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); - return mGestureAvailabilityTracker.isAnySensorActive(); + return mGestureAvailabilityDispatcher.isAnySensorActive(); } @Override public void addClientActiveCallback(IFingerprintClientActiveCallback callback) { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); - mGestureAvailabilityTracker.registerCallback(callback); + mGestureAvailabilityDispatcher.registerCallback(callback); } @Override public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); - mGestureAvailabilityTracker.removeCallback(callback); + mGestureAvailabilityDispatcher.removeCallback(callback); } @Override // Binder call public void initializeConfiguration(int sensorId) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - mFingerprint21 = new Fingerprint21(getContext(), sensorId, mLockoutResetTracker, - mGestureAvailabilityTracker); + mFingerprint21 = new Fingerprint21(getContext(), sensorId, mLockoutResetDispatcher, + mGestureAvailabilityDispatcher); } @Override @@ -312,7 +317,7 @@ public class FingerprintService extends SystemService { } @Override - public boolean isUdfps(int sensorId) { + public boolean isUdfps() { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); return mFingerprint21.isUdfps(); } @@ -327,8 +332,8 @@ public class FingerprintService extends SystemService { public FingerprintService(Context context) { super(context); mAppOps = context.getSystemService(AppOpsManager.class); - mGestureAvailabilityTracker = new GestureAvailabilityTracker(); - mLockoutResetTracker = new LockoutResetTracker(context); + mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher(); + mLockoutResetDispatcher = new LockoutResetDispatcher(context); } @Override @@ -336,6 +341,9 @@ public class FingerprintService extends SystemService { publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper()); } + /** + * Checks for public API invocations to ensure that permissions, etc are granted/correct. + */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean canUseFingerprint(String opPackageName, boolean requireForeground, int uid, int pid, int userId) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityTracker.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityDispatcher.java index 5023c83cd585..7bda33da8126 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityTracker.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityDispatcher.java @@ -28,7 +28,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * Keeps track of sensor gesture availability (e.g. swipe), and notifies clients when its * availability changes */ -public class GestureAvailabilityTracker { +public class GestureAvailabilityDispatcher { private static final String TAG = "GestureAvailabilityTracker"; private final CopyOnWriteArrayList<IFingerprintClientActiveCallback> mClientActiveCallbacks; @@ -36,7 +36,7 @@ public class GestureAvailabilityTracker { private boolean mIsActive; - GestureAvailabilityTracker() { + GestureAvailabilityDispatcher() { mClientActiveCallbacks = new CopyOnWriteArrayList<>(); mActiveSensors = new HashMap<>(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java index 58ab20d8d56d..bcf63dcdd67f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java +++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java @@ -18,17 +18,12 @@ package com.android.server.biometrics.sensors.iris; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; +import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricAuthenticator; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.iris.IIrisService; -import com.android.server.biometrics.sensors.BiometricServiceBase; -import com.android.server.biometrics.sensors.BiometricUtils; -import com.android.server.biometrics.sensors.LockoutTracker; -import com.android.server.biometrics.sensors.fingerprint.FingerprintService; - -import java.util.List; +import com.android.server.SystemService; +import com.android.server.biometrics.Utils; /** * A service to manage multiple clients that want to access the Iris HAL API. @@ -36,11 +31,9 @@ import java.util.List; * iris-related events. * * TODO: The vendor is expected to fill in the service. See - * {@link FingerprintService} - * - * @hide + * {@link com.android.server.biometrics.sensors.face.FaceService} */ -public class IrisService extends BiometricServiceBase { +public class IrisService extends SystemService { private static final String TAG = "IrisService"; @@ -50,77 +43,16 @@ public class IrisService extends BiometricServiceBase { private final class IrisServiceWrapper extends IIrisService.Stub { @Override // Binder call public void initializeConfiguration(int sensorId) { - checkPermission(USE_BIOMETRIC_INTERNAL); - initializeConfigurationInternal(sensorId); + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); } } - /** - * Initializes the system service. - * <p> - * Subclasses must define a single argument constructor that accepts the context - * and passes it to super. - * </p> - * - * @param context The system server context. - */ - public IrisService(Context context) { + public IrisService(@NonNull Context context) { super(context); } @Override public void onStart() { - super.onStart(); publishBinderService(Context.IRIS_SERVICE, new IrisServiceWrapper()); } - - @Override - protected void doTemplateCleanupForUser(int userId) { - - } - - @Override - protected String getTag() { - return TAG; - } - - @Override - protected Object getDaemon() { - return null; - } - - @Override - protected BiometricUtils getBiometricUtils() { - return null; - } - - @Override - protected boolean hasReachedEnrollmentLimit(int userId) { - return false; - } - - @Override - protected void updateActiveGroup(int userId) { - - } - - @Override - protected boolean hasEnrolledBiometrics(int userId) { - return false; - } - - @Override - protected String getManageBiometricPermission() { - return null; - } - - @Override - protected List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates(int userId) { - return null; - } - - @Override - protected int statsModality() { - return BiometricsProtoEnums.MODALITY_IRIS; - } } diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index b279b370c611..ed3a223b5dd7 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -400,7 +400,7 @@ public class ClipboardService extends SystemService { final int intendingUid = getIntendingUid(callingPackage, userId); final int intendingUserId = UserHandle.getUserId(intendingUid); if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage, - intendingUid, intendingUserId) + intendingUid, intendingUserId, false) || isDeviceLocked(intendingUserId)) { return null; } @@ -416,7 +416,7 @@ public class ClipboardService extends SystemService { final int intendingUid = getIntendingUid(callingPackage, userId); final int intendingUserId = UserHandle.getUserId(intendingUid); if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage, - intendingUid, intendingUserId) + intendingUid, intendingUserId, false) || isDeviceLocked(intendingUserId)) { return false; } @@ -450,7 +450,7 @@ public class ClipboardService extends SystemService { final int intendingUid = getIntendingUid(callingPackage, userId); final int intendingUserId = UserHandle.getUserId(intendingUid); if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage, - intendingUid, intendingUserId) + intendingUid, intendingUserId, false) || isDeviceLocked(intendingUserId)) { return false; } @@ -740,14 +740,21 @@ public class ClipboardService extends SystemService { private boolean clipboardAccessAllowed(int op, String callingPackage, int uid, @UserIdInt int userId) { - // Check the AppOp. - if (mAppOps.noteOp(op, uid, callingPackage) != AppOpsManager.MODE_ALLOWED) { - return false; - } + return clipboardAccessAllowed(op, callingPackage, uid, userId, true); + } + + private boolean clipboardAccessAllowed(int op, String callingPackage, int uid, + @UserIdInt int userId, boolean shouldNoteOp) { + + boolean allowed = false; + + // First, verify package ownership to ensure use below is safe. + mAppOps.checkPackage(uid, callingPackage); + // Shell can access the clipboard for testing purposes. if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND, callingPackage) == PackageManager.PERMISSION_GRANTED) { - return true; + allowed = true; } // The default IME is always allowed to access the clipboard. String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(), @@ -755,7 +762,7 @@ public class ClipboardService extends SystemService { if (!TextUtils.isEmpty(defaultIme)) { final String imePkg = ComponentName.unflattenFromString(defaultIme).getPackageName(); if (imePkg.equals(callingPackage)) { - return true; + allowed = true; } } @@ -766,8 +773,10 @@ public class ClipboardService extends SystemService { // at the same time. e.x. SystemUI. It needs to check the window focus of // Binder.getCallingUid(). Without checking, the user X can't copy any thing from // INTERNAL_SYSTEM_WINDOW to the other applications. - boolean allowed = mWm.isUidFocused(uid) - || isInternalSysWindowAppWithWindowFocus(callingPackage); + if (!allowed) { + allowed = mWm.isUidFocused(uid) + || isInternalSysWindowAppWithWindowFocus(callingPackage); + } if (!allowed && mContentCaptureInternal != null) { // ...or the Content Capture Service // The uid parameter of mContentCaptureInternal.isContentCaptureServiceForUser @@ -786,17 +795,28 @@ public class ClipboardService extends SystemService { // userId must pass intending userId. i.e. user#10. allowed = mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId); } - if (!allowed) { - Slog.e(TAG, "Denying clipboard access to " + callingPackage - + ", application is not in focus neither is a system service for " - + "user " + userId); - } - return allowed; + break; case AppOpsManager.OP_WRITE_CLIPBOARD: // Writing is allowed without focus. - return true; + allowed = true; + break; default: throw new IllegalArgumentException("Unknown clipboard appop " + op); } + if (!allowed) { + Slog.e(TAG, "Denying clipboard access to " + callingPackage + + ", application is not in focus nor is it a system service for " + + "user " + userId); + return false; + } + // Finally, check the app op. + int appOpsResult; + if (shouldNoteOp) { + appOpsResult = mAppOps.noteOp(op, uid, callingPackage); + } else { + appOpsResult = mAppOps.checkOp(op, uid, callingPackage); + } + + return appOpsResult == AppOpsManager.MODE_ALLOWED; } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index ca16d5730f78..a864aa65fe4a 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -403,6 +403,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private boolean mVerityFound; + @GuardedBy("mLock") private boolean mDataLoaderFinished = false; @GuardedBy("mLock") @@ -1118,7 +1119,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (hasParentSessionId()) { throw new IllegalStateException( "Session " + sessionId + " is a child of multi-package session " - + mParentSessionId + " and may not be committed directly."); + + getParentSessionId() + " and may not be committed directly."); } if (!markAsSealed(statusReceiver, forTransfer)) { @@ -1589,12 +1590,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } private void onStorageUnhealthy() { - if (TextUtils.isEmpty(mPackageName)) { + final String packageName = getPackageName(); + if (TextUtils.isEmpty(packageName)) { // The package has not been installed. return; } final PackageManagerService packageManagerService = mPm; - final String packageName = mPackageName; mHandler.post(() -> { if (packageManagerService.deletePackageX(packageName, PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM, @@ -1686,7 +1687,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } private void handleInstall() { - if (isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked()) { + final boolean needsLogging; + synchronized (mLock) { + needsLogging = isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(); + } + if (needsLogging) { DevicePolicyEventLogger .createEvent(DevicePolicyEnums.INSTALL_PACKAGE) .setAdmin(mInstallSource.installerPackageName) @@ -1929,19 +1934,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Skip logging the side-loaded app installations, as those are private and aren't reported // anywhere; app stores already have a record of the installation and that's why reporting // it here is fine + final String packageName = getPackageName(); final String packageNameToLog = - (params.installFlags & PackageManager.INSTALL_FROM_ADB) == 0 ? mPackageName : ""; + (params.installFlags & PackageManager.INSTALL_FROM_ADB) == 0 ? packageName : ""; final long currentTimestamp = System.currentTimeMillis(); FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLER_V2_REPORTED, isIncrementalInstallation(), packageNameToLog, currentTimestamp - createdMillis, returnCode, - getApksSize()); + getApksSize(packageName)); } - private long getApksSize() { - final PackageSetting ps = mPm.getPackageSetting(mPackageName); + private long getApksSize(String packageName) { + final PackageSetting ps = mPm.getPackageSetting(packageName); if (ps == null) { return 0; } @@ -2577,7 +2583,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } void setPermissionsResult(boolean accepted) { - if (!mSealed) { + if (!isSealed()) { throw new SecurityException("Must be sealed to accept permissions"); } @@ -2653,7 +2659,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (hasParentSessionId()) { throw new IllegalStateException( "Session " + sessionId + " is a child of multi-package session " - + mParentSessionId + " and may not be abandoned directly."); + + getParentSessionId() + " and may not be abandoned directly."); } List<PackageInstallerSession> childSessions = getChildSessionsNotLocked(); @@ -2823,7 +2829,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return; } - if (mDestroyed || mDataLoaderFinished) { + final boolean isDestroyedOrDataLoaderFinished; + synchronized (mLock) { + isDestroyedOrDataLoaderFinished = mDestroyed || mDataLoaderFinished; + } + if (isDestroyedOrDataLoaderFinished) { switch (status) { case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE: onStorageUnhealthy(); @@ -2835,7 +2845,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { try { IDataLoader dataLoader = dataLoaderManager.getDataLoader(dataLoaderId); if (dataLoader == null) { - mDataLoaderFinished = true; + synchronized (mLock) { + mDataLoaderFinished = true; + } dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE, "Failure to obtain data loader"); return; @@ -2868,10 +2880,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { break; } case IDataLoaderStatusListener.DATA_LOADER_IMAGE_READY: { - mDataLoaderFinished = true; + synchronized (mLock) { + mDataLoaderFinished = true; + } if (hasParentSessionId()) { mSessionProvider.getSession( - mParentSessionId).dispatchStreamValidateAndCommit(); + getParentSessionId()).dispatchStreamValidateAndCommit(); } else { dispatchStreamValidateAndCommit(); } @@ -2881,7 +2895,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { break; } case IDataLoaderStatusListener.DATA_LOADER_IMAGE_NOT_READY: { - mDataLoaderFinished = true; + synchronized (mLock) { + mDataLoaderFinished = true; + } dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE, "Failed to prepare image."); if (manualStartAndDestroy) { @@ -2895,7 +2911,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { break; } case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE: - mDataLoaderFinished = true; + synchronized (mLock) { + mDataLoaderFinished = true; + } dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE, "DataLoader reported unrecoverable failure."); break; @@ -2919,7 +2937,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final IStorageHealthListener healthListener = new IStorageHealthListener.Stub() { @Override public void onHealthStatus(int storageId, int status) { - if (mDestroyed || mDataLoaderFinished) { + final boolean isDestroyedOrDataLoaderFinished; + synchronized (mLock) { + isDestroyedOrDataLoaderFinished = mDestroyed || mDataLoaderFinished; + } + if (isDestroyedOrDataLoaderFinished) { // App's installed. switch (status) { case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY: @@ -2941,7 +2963,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // fallthrough case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY: // Even ADB installation can't wait for missing pages for too long. - mDataLoaderFinished = true; + synchronized (mLock) { + mDataLoaderFinished = true; + } dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE, "Image is missing pages required for installation."); break; @@ -2984,13 +3008,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return EMPTY_CHILD_SESSION_ARRAY; } + private boolean canBeAddedAsChild(int parentCandidate) { + synchronized (mLock) { + return (!hasParentSessionId() || mParentSessionId == parentCandidate) + && !mCommitted && !mDestroyed; + } + } + @Override public void addChildSessionId(int childSessionId) { final PackageInstallerSession childSession = mSessionProvider.getSession(childSessionId); - if (childSession == null - || (childSession.hasParentSessionId() && childSession.mParentSessionId != sessionId) - || childSession.mCommitted - || childSession.mDestroyed) { + if (childSession == null || !childSession.canBeAddedAsChild(sessionId)) { throw new IllegalStateException("Unable to add child session " + childSessionId + " as it does not exist or is in an invalid state."); } @@ -3039,12 +3067,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } boolean hasParentSessionId() { - return mParentSessionId != SessionInfo.INVALID_ID; + synchronized (mLock) { + return mParentSessionId != SessionInfo.INVALID_ID; + } } @Override public int getParentSessionId() { - return mParentSessionId; + synchronized (mLock) { + return mParentSessionId; + } } private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) { @@ -3134,27 +3166,37 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** {@hide} */ boolean isStagedSessionReady() { - return mStagedSessionReady; + synchronized (mLock) { + return mStagedSessionReady; + } } /** {@hide} */ boolean isStagedSessionApplied() { - return mStagedSessionApplied; + synchronized (mLock) { + return mStagedSessionApplied; + } } /** {@hide} */ boolean isStagedSessionFailed() { - return mStagedSessionFailed; + synchronized (mLock) { + return mStagedSessionFailed; + } } /** {@hide} */ @StagedSessionErrorCode int getStagedSessionErrorCode() { - return mStagedSessionErrorCode; + synchronized (mLock) { + return mStagedSessionErrorCode; + } } /** {@hide} */ String getStagedSessionErrorMessage() { - return mStagedSessionErrorMessage; + synchronized (mLock) { + return mStagedSessionErrorMessage; + } } private void destroyInternal() { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 9f3db704253e..17b805f4dfa3 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -7158,8 +7158,9 @@ public class PackageManagerService extends IPackageManager.Stub && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp) || (matchVisibleToInstantAppOnly && isCallerInstantApp && isTargetHiddenFromInstantApp)); - final boolean blockNormalResolution = !isTargetInstantApp && !isCallerInstantApp - && shouldFilterApplicationLocked( + final boolean blockNormalResolution = + !resolveForStart && !isTargetInstantApp && !isCallerInstantApp + && shouldFilterApplicationLocked( getPackageSettingInternal(ai.applicationInfo.packageName, Process.SYSTEM_UID), filterCallingUid, userId); if (!blockInstantResolution && !blockNormalResolution) { @@ -7252,8 +7253,8 @@ public class PackageManagerService extends IPackageManager.Stub final PackageSetting setting = getPackageSettingInternal(pkgName, Process.SYSTEM_UID); result = null; - if (setting != null && setting.pkg != null - && !shouldFilterApplicationLocked(setting, filterCallingUid, userId)) { + if (setting != null && setting.pkg != null && (resolveForStart + || !shouldFilterApplicationLocked(setting, filterCallingUid, userId))) { result = filterIfNotSystemUser(mComponentResolver.queryActivities( intent, resolvedType, flags, setting.pkg.getActivities(), userId), userId); @@ -25545,7 +25546,8 @@ public class PackageManagerService extends IPackageManager.Stub private void applyMimeGroupChanges(String packageName, String mimeGroup) { if (mComponentResolver.updateMimeGroup(packageName, mimeGroup)) { - clearPackagePreferredActivities(packageName, UserHandle.USER_ALL); + Binder.withCleanCallingIdentity(() -> + clearPackagePreferredActivities(packageName, UserHandle.USER_ALL)); } mPmInternal.writeSettings(false); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 9963cf7e212d..bc7554c54eb0 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -4224,6 +4224,20 @@ public class PermissionManagerService extends IPermissionManager.Stub { revokePermissionFromPackageForUser(p.getPackageName(), bp.getName(), true, userId, callback)); } + } else { + mPackageManagerInt.forEachPackage(p -> { + PackageSetting ps = mPackageManagerInt.getPackageSetting( + p.getPackageName()); + if (ps == null) { + return; + } + PermissionsState permissionsState = ps.getPermissionsState(); + if (permissionsState.getInstallPermissionState(bp.getName()) != null) { + permissionsState.revokeInstallPermission(bp); + permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, + MASK_PERMISSION_FLAGS_ALL, 0); + } + }); } it.remove(); } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java index 72f8027e664f..761858ccd238 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java @@ -192,10 +192,7 @@ public class SoundTriggerHw2Enforcer implements ISoundTriggerHw2 { } private static RuntimeException handleException(RuntimeException e) { - // TODO(b/160169016): There is currently no other way to distinguish dead object from other - // exceptions. - if (e.getCause() instanceof RemoteException && - e.getCause().getMessage().equals("HwBinder Error: (-32)")) { + if (e.getCause() instanceof DeadObjectException) { // Server is dead, no need to reboot. Log.e(TAG, "HAL died"); } else { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 4e355f0dadfc..bec0ce944e3f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7465,7 +7465,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** * Determines whether this ActivityRecord can turn the screen on. It checks whether the flag * {@link ActivityRecord#getTurnScreenOnFlag} is set and checks whether the ActivityRecord - * should be visible depending on Keyguard state + * should be visible depending on Keyguard and window state. * * @return true if the screen can be turned on, false otherwise. */ @@ -7474,8 +7474,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return false; } final ActivityStack stack = getRootTask(); - return stack != null && - stack.checkKeyguardVisibility(this, true /* shouldBeVisible */, + return stack != null + && !stack.inMultiWindowMode() + && stack.checkKeyguardVisibility(this, true /* shouldBeVisible */, stack.topRunningActivity() == this /* isTop */); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 1d1c31b8226c..de91af752512 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3459,9 +3459,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp InsetsControlTarget getImeFallback() { // host is in non-default display that doesn't support system decor, default to // default display's StatusBar to control IME (when available), else let system control it. - WindowState statusBar = - mWmService.getDefaultDisplayContentLocked().getDisplayPolicy().getStatusBar(); - return statusBar != null ? statusBar : mRemoteInsetsControlTarget; + final DisplayContent defaultDc = mWmService.getDefaultDisplayContentLocked(); + WindowState statusBar = defaultDc.getDisplayPolicy().getStatusBar(); + return statusBar != null ? statusBar : defaultDc.mRemoteInsetsControlTarget; } boolean canShowIme() { @@ -3521,7 +3521,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ @VisibleForTesting InsetsControlTarget computeImeControlTarget() { - if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null) { + if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null + || (mInputMethodInputTarget != null + && getImeHostOrFallback(mInputMethodInputTarget.getWindow()) + == mRemoteInsetsControlTarget)) { return mRemoteInsetsControlTarget; } else { // Now, a special case -- if the last target's window is in the process of exiting, but diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 99ee5e121b7a..e7fbc334306e 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -32,7 +32,7 @@ import java.io.PrintWriter; */ class ImeInsetsSourceProvider extends InsetsSourceProvider { - private WindowState mImeTargetFromIme; + private InsetsControlTarget mImeTargetFromIme; private Runnable mShowImeRunner; private boolean mIsImeLayoutDrawn; @@ -47,10 +47,12 @@ class ImeInsetsSourceProvider extends InsetsSourceProvider { * * @param imeTarget imeTarget on which IME request is coming from. */ - void scheduleShowImePostLayout(WindowState imeTarget) { + void scheduleShowImePostLayout(InsetsControlTarget imeTarget) { boolean targetChanged = mImeTargetFromIme != imeTarget && mImeTargetFromIme != null && imeTarget != null && mShowImeRunner != null - && mImeTargetFromIme.mActivityRecord == imeTarget.mActivityRecord; + && imeTarget.getWindow() != null && mImeTargetFromIme.getWindow() != null + && mImeTargetFromIme.getWindow().mActivityRecord + == imeTarget.getWindow().mActivityRecord; mImeTargetFromIme = imeTarget; if (targetChanged) { // target changed, check if new target can show IME. @@ -62,7 +64,8 @@ class ImeInsetsSourceProvider extends InsetsSourceProvider { return; } - ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeTargetFromIme.getName()); + ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeTargetFromIme.getWindow() == null + ? mImeTargetFromIme : mImeTargetFromIme.getWindow().getName()); mShowImeRunner = () -> { ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner"); // Target should still be the same. @@ -127,13 +130,17 @@ class ImeInsetsSourceProvider extends InsetsSourceProvider { return false; } ProtoLog.d(WM_DEBUG_IME, "dcTarget: %s mImeTargetFromIme: %s", - dcTarget.getName(), mImeTargetFromIme.getName()); + dcTarget.getName(), mImeTargetFromIme.getWindow() == null + ? mImeTargetFromIme : mImeTargetFromIme.getWindow().getName()); return (!dcTarget.isClosing() && mImeTargetFromIme == dcTarget) - || (mImeTargetFromIme != null && dcTarget.getParentWindow() == mImeTargetFromIme - && dcTarget.mSubLayer > mImeTargetFromIme.mSubLayer) + || (mImeTargetFromIme != null && mImeTargetFromIme.getWindow() != null + && dcTarget.getParentWindow() == mImeTargetFromIme + && dcTarget.mSubLayer > mImeTargetFromIme.getWindow().mSubLayer) || mImeTargetFromIme == mDisplayContent.getImeFallback() - || (!mImeTargetFromIme.isClosing() && controlTarget == mImeTargetFromIme); + || controlTarget == mImeTargetFromIme + && (mImeTargetFromIme.getWindow() == null + || !mImeTargetFromIme.getWindow().isClosing()); } @Override diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index ab2151974ce9..f3e23169fee0 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2269,6 +2269,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) { final DisplayContent display = getChildAt(displayNdx); + if (display.shouldSleep()) { + continue; + } + final boolean curResult = result; boolean resumedOnDisplay = display.reduceOnAllTaskDisplayAreas( (taskDisplayArea, resumed) -> { @@ -2360,7 +2364,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // process the keyguard going away, which can happen before the sleep // token is released. As a result, it is important we resume the // activity here. - resumeFocusedStacksTopActivities(); + stack.resumeTopActivityUncheckedLocked(null, null); } // The visibility update must not be called before resuming the top, so the // display orientation can be updated first if needed. Otherwise there may diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index f13dfdd1156f..68445f6970fb 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -615,8 +615,9 @@ class TaskSnapshotController { static Rect getSystemBarInsets(Rect frame, InsetsState state) { return state.calculateInsets(frame, null /* ignoringVisibilityState */, false /* isScreenRound */, false /* alwaysConsumeSystemBars */, - null /* displayCutout */, 0 /* legacySoftInputMode */, 0 /* legacySystemUiFlags */, - null /* typeSideMap */).getInsets(WindowInsets.Type.systemBars()).toRect(); + null /* displayCutout */, 0 /* legacySoftInputMode */, 0 /* legacyWindowFlags */, + 0 /* legacySystemUiFlags */, null /* typeSideMap */).getInsets( + WindowInsets.Type.systemBars()).toRect(); } void dump(PrintWriter pw, String prefix) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 85972bffd755..4df48dc11636 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7599,13 +7599,14 @@ public class WindowManagerService extends IWindowManager.Stub if (imeTarget == null) { return; } - imeTarget = imeTarget.getImeControlTarget().getWindow(); + final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget(); + imeTarget = controlTarget.getWindow(); // If InsetsControlTarget doesn't have a window, its using remoteControlTarget which // is controlled by default display final DisplayContent dc = imeTarget != null ? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked(); dc.getInsetsStateController().getImeSourceProvider() - .scheduleShowImePostLayout(imeTarget); + .scheduleShowImePostLayout(controlTarget); } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index c9e628480a52..70e6a340816a 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -32,7 +32,10 @@ import static org.testng.Assert.assertTrue; import static java.lang.Float.NaN; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.graphics.PointF; import android.graphics.Rect; import android.os.IBinder; @@ -300,6 +303,26 @@ public class WindowMagnificationManagerTest { assertFalse(mWindowMagnificationManager.isConnected()); } + @Test + public void requestConnection_registerAndUnregisterBroadcastReceiver() { + assertTrue(mWindowMagnificationManager.requestConnection(true)); + verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class)); + + assertTrue(mWindowMagnificationManager.requestConnection(false)); + verify(mContext).unregisterReceiver(any(BroadcastReceiver.class)); + } + + @Test + public void onReceiveScreenOff_removeMagnificationButtonAndDisableWindowMagnification() + throws RemoteException { + mWindowMagnificationManager.requestConnection(true); + mWindowMagnificationManager.mScreenStateReceiver.onReceive(mContext, + new Intent(Intent.ACTION_SCREEN_OFF)); + + verify(mMockConnection.getConnection()).removeMagnificationButton(TEST_DISPLAY); + verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY); + } + private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) { final int len = pointersLocation.length; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricServiceBaseTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricServiceBaseTest.java deleted file mode 100644 index 0c66ee75eab0..000000000000 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricServiceBaseTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2020 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.biometrics.sensors; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.res.Resources; -import android.hardware.biometrics.BiometricAuthenticator; -import android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.List; - -@Presubmit -@SmallTest -public class BiometricServiceBaseTest { - private static class TestableBiometricServiceBase extends BiometricServiceBase { - TestableBiometricServiceBase(Context context) { - super(context); - } - - @Override - protected void doTemplateCleanupForUser(int userId) { - } - - @Override - protected String getTag() { - return null; - } - - @Override - protected Object getDaemon() { - return null; - } - - @Override - protected BiometricUtils getBiometricUtils() { - return null; - } - - @Override - protected boolean hasReachedEnrollmentLimit(int userId) { - return false; - } - - @Override - protected void updateActiveGroup(int userId) { - } - - @Override - protected boolean hasEnrolledBiometrics(int userId) { - return false; - } - - @Override - protected String getManageBiometricPermission() { - return null; - } - - @Override - protected List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates( - int userId) { - return null; - } - - @Override - protected int statsModality() { - return 0; - } - } - - private static final int CLIENT_COOKIE = 0xc00c1e; - - private BiometricServiceBase mBiometricServiceBase; - - @Mock - private Context mContext; - @Mock - private Resources mResources; - @Mock - private BiometricAuthenticator.Identifier mIdentifier; - @Mock - private ClientMonitor mClient; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - when(mContext.getResources()).thenReturn(mResources); - when(mResources.getString(anyInt())).thenReturn(""); - when(mClient.getCookie()).thenReturn(CLIENT_COOKIE); - - mBiometricServiceBase = new TestableBiometricServiceBase(mContext); - } - - @Test - public void testHandleEnumerate_doesNotCrash_withNullClient() { - mBiometricServiceBase.handleEnumerate(mIdentifier, 0 /* remaining */); - } -} diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index c7b45efb2de1..76b1a4d69f05 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -17,6 +17,8 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; @@ -65,9 +67,13 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import android.app.ActivityManager.TaskSnapshot; @@ -405,7 +411,7 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void ignoreRequestedOrientationInFreeformWindows() { - mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); + mStack.setWindowingMode(WINDOWING_MODE_FREEFORM); final Rect stableRect = new Rect(); mStack.getDisplay().mDisplayContent.getStableRect(stableRect); @@ -1657,6 +1663,26 @@ public class ActivityRecordTests extends ActivityTestsBase { .diff(wpc.getRequestedOverrideConfiguration())); } + @Test + public void testCanTurnScreenOn() { + mStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + doReturn(true).when(mStack).checkKeyguardVisibility( + same(mActivity), eq(true) /* shouldBeVisible */, anyBoolean()); + doReturn(true).when(mActivity).getTurnScreenOnFlag(); + + assertTrue(mActivity.canTurnScreenOn()); + } + + @Test + public void testFreeformWindowCantTurnScreenOn() { + mStack.setWindowingMode(WINDOWING_MODE_FREEFORM); + doReturn(true).when(mStack).checkKeyguardVisibility( + same(mActivity), eq(true) /* shouldBeVisible */, anyBoolean()); + doReturn(true).when(mActivity).getTurnScreenOnFlag(); + + assertFalse(mActivity.canTurnScreenOn()); + } + /** * Creates an activity on display. For non-default display request it will also create a new * display with custom DisplayInfo. diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java index 4f5b3ec8bdac..4aac47cf006a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java @@ -891,6 +891,24 @@ public class RootActivityContainerTests extends ActivityTestsBase { assertEquals(taskDisplayArea.getTopStack(), taskDisplayArea.getRootHomeTask()); } + @Test + public void testResumeFocusedStackOnSleepingDisplay() { + // Create an activity on secondary display. + final TestDisplayContent secondDisplay = addNewDisplayContentAt( + DisplayContent.POSITION_TOP); + final ActivityStack stack = secondDisplay.getDefaultTaskDisplayArea() + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityRecord activity = new ActivityBuilder(mService).setStack(stack).build(); + spyOn(activity); + spyOn(stack); + + // Cannot resumed activities on secondary display if the display should sleep. + doReturn(true).when(secondDisplay).shouldSleep(); + mRootWindowContainer.resumeFocusedStacksTopActivities(); + verify(stack, never()).resumeTopActivityUncheckedLocked(any(), any()); + verify(activity, never()).makeActiveIfNeeded(any()); + } + /** * Mock {@link RootWindowContainer#resolveHomeActivity} for returning consistent activity * info for test cases. diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index bc987a6282c7..71a1964210b0 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -623,6 +623,10 @@ public final class TelephonyPermissions { } private static int getCarrierPrivilegeStatus(Context context, int subId, int uid) { + if (uid == Process.SYSTEM_UID || uid == Process.PHONE_UID) { + // Skip the check if it's one of these special uids + return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; + } final long identity = Binder.clearCallingIdentity(); try { TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService( diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index b376660f839e..183fdcce1ca4 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -670,10 +670,12 @@ public final class SmsManager { } if (priority < 0x00 || priority > 0x03) { + Log.e(TAG, "Invalid Priority " + priority); priority = SMS_MESSAGE_PRIORITY_NOT_SPECIFIED; } if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) { + Log.e(TAG, "Invalid Validity Period " + validityPeriod); validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED; } @@ -1231,10 +1233,12 @@ public final class SmsManager { } if (priority < 0x00 || priority > 0x03) { + Log.e(TAG, "Invalid Priority " + priority); priority = SMS_MESSAGE_PRIORITY_NOT_SPECIFIED; } if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) { + Log.e(TAG, "Invalid Validity Period " + validityPeriod); validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED; } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index d96e024d4c60..f9148d0c44c4 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -113,8 +113,6 @@ import com.android.internal.telephony.RILConstants; import com.android.internal.telephony.SmsApplication; import com.android.telephony.Rlog; -import java.io.FileInputStream; -import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -127,8 +125,6 @@ import java.util.Objects; import java.util.UUID; import java.util.concurrent.Executor; import java.util.function.Consumer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Provides access to information about the telephony services on @@ -2540,7 +2536,8 @@ public class TelephonyManager { return PhoneConstants.PHONE_TYPE_CDMA; case RILConstants.NETWORK_MODE_LTE_ONLY: - if (getLteOnCdmaModeStatic() == PhoneConstants.LTE_ON_CDMA_TRUE) { + if (TelephonyProperties.lte_on_cdma_device().orElse( + PhoneConstants.LTE_ON_CDMA_FALSE) == PhoneConstants.LTE_ON_CDMA_TRUE) { return PhoneConstants.PHONE_TYPE_CDMA; } else { return PhoneConstants.PHONE_TYPE_GSM; @@ -2551,35 +2548,6 @@ public class TelephonyManager { } /** - * The contents of the /proc/cmdline file - */ - @UnsupportedAppUsage - private static String getProcCmdLine() - { - String cmdline = ""; - FileInputStream is = null; - try { - is = new FileInputStream("/proc/cmdline"); - byte [] buffer = new byte[2048]; - int count = is.read(buffer); - if (count > 0) { - cmdline = new String(buffer, 0, count); - } - } catch (IOException e) { - Rlog.d(TAG, "No /proc/cmdline exception=" + e); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - } - } - } - Rlog.d(TAG, "/proc/cmdline=" + cmdline); - return cmdline; - } - - /** * @return The max value for the timeout passed in {@link #requestNumberVerification}. * @hide */ @@ -2588,56 +2556,6 @@ public class TelephonyManager { return MAX_NUMBER_VERIFICATION_TIMEOUT_MILLIS; } - /** Kernel command line */ - private static final String sKernelCmdLine = getProcCmdLine(); - - /** Pattern for selecting the product type from the kernel command line */ - private static final Pattern sProductTypePattern = - Pattern.compile("\\sproduct_type\\s*=\\s*(\\w+)"); - - /** The ProductType used for LTE on CDMA devices */ - private static final String sLteOnCdmaProductType = - TelephonyProperties.lte_on_cdma_product_type().orElse(""); - - /** - * Return if the current radio is LTE on CDMA. This - * is a tri-state return value as for a period of time - * the mode may be unknown. - * - * @return {@link PhoneConstants#LTE_ON_CDMA_UNKNOWN}, {@link PhoneConstants#LTE_ON_CDMA_FALSE} - * or {@link PhoneConstants#LTE_ON_CDMA_TRUE} - * - * @hide - */ - @UnsupportedAppUsage - public static int getLteOnCdmaModeStatic() { - int retVal; - int curVal; - String productType = ""; - - curVal = TelephonyProperties.lte_on_cdma_device().orElse( - PhoneConstants.LTE_ON_CDMA_UNKNOWN); - retVal = curVal; - if (retVal == PhoneConstants.LTE_ON_CDMA_UNKNOWN) { - Matcher matcher = sProductTypePattern.matcher(sKernelCmdLine); - if (matcher.find()) { - productType = matcher.group(1); - if (sLteOnCdmaProductType.equals(productType)) { - retVal = PhoneConstants.LTE_ON_CDMA_TRUE; - } else { - retVal = PhoneConstants.LTE_ON_CDMA_FALSE; - } - } else { - retVal = PhoneConstants.LTE_ON_CDMA_FALSE; - } - } - - Rlog.d(TAG, "getLteOnCdmaMode=" + retVal + " curVal=" + curVal + - " product_type='" + productType + - "' lteOnCdmaProductType='" + sLteOnCdmaProductType + "'"); - return retVal; - } - // // // Current Network diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index c7ad2bbb043f..d186fcf63cfe 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -1100,7 +1100,7 @@ public final class BearerData { bData.hasUserDataHeader = (inStream.read(1) == 1); inStream.skip(3); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1469,7 +1469,7 @@ public final class BearerData { bData.reportReq = (inStream.read(1) == 1); inStream.skip(4); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "REPLY_OPTION decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1488,7 +1488,7 @@ public final class BearerData { decodeSuccess = true; bData.numberOfMessages = IccUtils.cdmaBcdByteToInt((byte)inStream.read(8)); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "NUMBER_OF_MESSAGES decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1507,7 +1507,7 @@ public final class BearerData { decodeSuccess = true; bData.depositIndex = (inStream.read(8) << 8) | inStream.read(8); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "MESSAGE_DEPOSIT_INDEX decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1594,7 +1594,7 @@ public final class BearerData { bData.errorClass = inStream.read(2); bData.messageStatus = inStream.read(6); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "MESSAGE_STATUS decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1614,7 +1614,7 @@ public final class BearerData { decodeSuccess = true; bData.msgCenterTimeStamp = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8)); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "MESSAGE_CENTER_TIME_STAMP decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1633,7 +1633,7 @@ public final class BearerData { decodeSuccess = true; bData.validityPeriodAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8)); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "VALIDITY_PERIOD_ABSOLUTE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1653,7 +1653,7 @@ public final class BearerData { bData.deferredDeliveryTimeAbsolute = TimeStamp.fromByteArray( inStream.readByteArray(6 * 8)); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_ABSOLUTE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1672,7 +1672,7 @@ public final class BearerData { decodeSuccess = true; bData.deferredDeliveryTimeRelative = inStream.read(8); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "VALIDITY_PERIOD_RELATIVE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1692,7 +1692,7 @@ public final class BearerData { decodeSuccess = true; bData.validityPeriodRelative = inStream.read(8); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_RELATIVE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1713,7 +1713,7 @@ public final class BearerData { bData.privacy = inStream.read(2); inStream.skip(6); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "PRIVACY_INDICATOR decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1733,7 +1733,7 @@ public final class BearerData { decodeSuccess = true; bData.language = inStream.read(8); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "LANGUAGE_INDICATOR decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1754,7 +1754,7 @@ public final class BearerData { bData.displayMode = inStream.read(2); inStream.skip(6); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "DISPLAY_MODE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1775,7 +1775,7 @@ public final class BearerData { bData.priority = inStream.read(2); inStream.skip(6); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "PRIORITY_INDICATOR decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1796,7 +1796,7 @@ public final class BearerData { bData.alert = inStream.read(2); inStream.skip(6); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "ALERT_ON_MESSAGE_DELIVERY decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1816,7 +1816,7 @@ public final class BearerData { decodeSuccess = true; bData.userResponseCode = inStream.read(8); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "USER_RESPONSE_CODE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1878,7 +1878,7 @@ public final class BearerData { decodeSuccess = true; } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ')'); diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java index f297ba74914e..7e31c4633050 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -92,14 +92,15 @@ public class SmsMessage extends SmsMessageBase { private int mVoiceMailCount = 0; + /** TP-Validity-Period-Format (TP-VPF). See TS 23.040, 9.2.3.3 */ private static final int VALIDITY_PERIOD_FORMAT_NONE = 0x00; private static final int VALIDITY_PERIOD_FORMAT_ENHANCED = 0x01; private static final int VALIDITY_PERIOD_FORMAT_RELATIVE = 0x02; private static final int VALIDITY_PERIOD_FORMAT_ABSOLUTE = 0x03; - //Validity Period min - 5 mins + // Validity Period min - 5 mins private static final int VALIDITY_PERIOD_MIN = 5; - //Validity Period max - 63 weeks + // Validity Period max - 63 weeks private static final int VALIDITY_PERIOD_MAX = 635040; private static final int INVALID_VALIDITY_PERIOD = -1; @@ -193,20 +194,20 @@ public class SmsMessage extends SmsMessageBase { } /** - * Get Encoded Relative Validty Period Value from Validity period in mins. + * Gets Encoded Relative Validity Period Value from Validity period in mins. * * @param validityPeriod Validity period in mins. * * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. - * ||relValidityPeriod (TP-VP) || || validityPeriod || - * - * 0 to 143 ---> (TP-VP + 1) x 5 minutes - * - * 144 to 167 ---> 12 hours + ((TP-VP -143) x 30 minutes) - * - * 168 to 196 ---> (TP-VP - 166) x 1 day - * - * 197 to 255 ---> (TP-VP - 192) x 1 week + * ------------------------------------------------------------ + * TP-VP | Validity period + * (Relative format) | value + * ------------------------------------------------------------ + * 0 to 143 | (TP-VP + 1) x 5 minutes + * 144 to 167 | 12 hours + ((TP-VP -143) x 30 minutes) + * 168 to 196 | (TP-VP - 166) x 1 day + * 197 to 255 | (TP-VP - 192) x 1 week + * ------------------------------------------------------------ * * @return relValidityPeriod Encoded Relative Validity Period Value. * @hide @@ -214,19 +215,16 @@ public class SmsMessage extends SmsMessageBase { public static int getRelativeValidityPeriod(int validityPeriod) { int relValidityPeriod = INVALID_VALIDITY_PERIOD; - if (validityPeriod < VALIDITY_PERIOD_MIN || validityPeriod > VALIDITY_PERIOD_MAX) { - Rlog.e(LOG_TAG,"Invalid Validity Period" + validityPeriod); - return relValidityPeriod; - } - - if (validityPeriod <= 720) { - relValidityPeriod = (validityPeriod / 5) - 1; - } else if (validityPeriod <= 1440) { - relValidityPeriod = ((validityPeriod - 720) / 30) + 143; - } else if (validityPeriod <= 43200) { - relValidityPeriod = (validityPeriod / 1440) + 166; - } else if (validityPeriod <= 635040) { - relValidityPeriod = (validityPeriod / 10080) + 192; + if (validityPeriod >= VALIDITY_PERIOD_MIN) { + if (validityPeriod <= 720) { + relValidityPeriod = (validityPeriod / 5) - 1; + } else if (validityPeriod <= 1440) { + relValidityPeriod = ((validityPeriod - 720) / 30) + 143; + } else if (validityPeriod <= 43200) { + relValidityPeriod = (validityPeriod / 1440) + 166; + } else if (validityPeriod <= VALIDITY_PERIOD_MAX) { + relValidityPeriod = (validityPeriod / 10080) + 192; + } } return relValidityPeriod; } @@ -336,17 +334,19 @@ public class SmsMessage extends SmsMessageBase { SubmitPdu ret = new SubmitPdu(); - int validityPeriodFormat = VALIDITY_PERIOD_FORMAT_NONE; - int relativeValidityPeriod = INVALID_VALIDITY_PERIOD; + int relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod); + + byte mtiByte = 0x01; // SMS-SUBMIT - // TP-Validity-Period-Format (TP-VPF) in 3GPP TS 23.040 V6.8.1 section 9.2.3.3 - //bit 4:3 = 10 - TP-VP field present - relative format - if((relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod)) >= 0) { - validityPeriodFormat = VALIDITY_PERIOD_FORMAT_RELATIVE; + if (header != null) { + // Set TP-UDHI + mtiByte |= 0x40; } - byte mtiByte = (byte)(0x01 | (validityPeriodFormat << 0x03) | - (header != null ? 0x40 : 0x00)); + if (relativeValidityPeriod != INVALID_VALIDITY_PERIOD) { + // Set TP-Validity-Period-Format (TP-VPF) + mtiByte |= VALIDITY_PERIOD_FORMAT_RELATIVE << 3; + } ByteArrayOutputStream bo = getSubmitPduHead( scAddress, destinationAddress, mtiByte, @@ -418,8 +418,8 @@ public class SmsMessage extends SmsMessageBase { bo.write(0x08); } - if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) { - // ( TP-Validity-Period - relative format) + // TP-Validity-Period (TP-VP) + if (relativeValidityPeriod != INVALID_VALIDITY_PERIOD) { bo.write(relativeValidityPeriod); } @@ -1352,23 +1352,17 @@ public class SmsMessage extends SmsMessageBase { // TP-Validity-Period-Format int validityPeriodLength = 0; - int validityPeriodFormat = ((firstByte>>3) & 0x3); - if (0x0 == validityPeriodFormat) /* 00, TP-VP field not present*/ - { + int validityPeriodFormat = ((firstByte >> 3) & 0x3); + if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_NONE) { validityPeriodLength = 0; - } - else if (0x2 == validityPeriodFormat) /* 10, TP-VP: relative format*/ - { + } else if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) { validityPeriodLength = 1; - } - else /* other case, 11 or 01, TP-VP: absolute or enhanced format*/ - { + } else { // VALIDITY_PERIOD_FORMAT_ENHANCED or VALIDITY_PERIOD_FORMAT_ABSOLUTE validityPeriodLength = 7; } // TP-Validity-Period is not used on phone, so just ignore it for now. - while (validityPeriodLength-- > 0) - { + while (validityPeriodLength-- > 0) { p.getByte(); } diff --git a/tools/validatekeymaps/Android.bp b/tools/validatekeymaps/Android.bp index 819e75ba1fed..61ce44c3b5b9 100644 --- a/tools/validatekeymaps/Android.bp +++ b/tools/validatekeymaps/Android.bp @@ -20,6 +20,7 @@ cc_binary_host { "libutils", "libcutils", "liblog", + "libui-types", ], // This tool is prebuilt if we're doing an app-only build. diff --git a/wifi/TEST_MAPPING b/wifi/TEST_MAPPING new file mode 100644 index 000000000000..fde3a6aa993c --- /dev/null +++ b/wifi/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "CtsWifiTestCases", + "options": [ + { + "exclude-annotation": "android.net.wifi.cts.VirtualDeviceNotSupported" + } + ] + } + ] +} |