diff --git a/core/api/system-current.txt b/core/api/system-current.txt index c723fb757aee..89688565c441 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -11830,11 +11830,23 @@ package android.service.translation { package android.service.trust { + public final class GrantTrustResult implements android.os.Parcelable { + method public int describeContents(); + method public int getStatus(); + method @NonNull public static String statusToString(int); + method @NonNull public static android.service.trust.GrantTrustResult withStatus(int); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int STATUS_UNKNOWN = 0; // 0x0 + field public static final int STATUS_UNLOCKED_BY_GRANT = 1; // 0x1 + } + public class TrustAgentService extends android.app.Service { ctor public TrustAgentService(); method public final void addEscrowToken(byte[], android.os.UserHandle); method @Deprecated public final void grantTrust(CharSequence, long, boolean); - method public final void grantTrust(CharSequence, long, int); + method @Deprecated public final void grantTrust(CharSequence, long, int); + method public final void grantTrust(@NonNull CharSequence, long, int, @Nullable java.util.function.Consumer); method public final void isEscrowTokenActive(long, android.os.UserHandle); method public final void lockUser(); method public final android.os.IBinder onBind(android.content.Intent); @@ -11847,7 +11859,8 @@ package android.service.trust { method public void onEscrowTokenStateReceived(long, int); method public void onTrustTimeout(); method public void onUnlockAttempt(boolean); - method public void onUserRequestedUnlock(); + method public void onUserMayRequestUnlock(); + method public void onUserRequestedUnlock(boolean); method public final void removeEscrowToken(long, android.os.UserHandle); method public final void revokeTrust(); method public final void setManagingTrust(boolean); diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index b786444faa8c..45146fd95104 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -26,7 +26,8 @@ import android.hardware.biometrics.BiometricSourceType; */ interface ITrustManager { void reportUnlockAttempt(boolean successful, int userId); - void reportUserRequestedUnlock(int userId); + void reportUserRequestedUnlock(int userId, boolean dismissKeyguard); + void reportUserMayRequestUnlock(int userId); void reportUnlockLockout(int timeoutMs, int userId); void reportEnabledTrustAgentsChanged(int userId); void registerTrustListener(in ITrustListener trustListener); diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index 70b7de0767e4..9e825b7207e0 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -92,10 +92,25 @@ public class TrustManager { * Reports that the user {@code userId} is likely interested in unlocking the device. * * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. + * + * @param dismissKeyguard whether the user wants to dismiss keyguard */ - public void reportUserRequestedUnlock(int userId) { + public void reportUserRequestedUnlock(int userId, boolean dismissKeyguard) { try { - mService.reportUserRequestedUnlock(userId); + mService.reportUserRequestedUnlock(userId, dismissKeyguard); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Reports that the user {@code userId} may want to unlock the device soon. + * + * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. + */ + public void reportUserMayRequestUnlock(int userId) { + try { + mService.reportUserMayRequestUnlock(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/service/trust/GrantTrustResult.aidl b/core/java/android/service/trust/GrantTrustResult.aidl new file mode 100644 index 000000000000..d24a6bc62f17 --- /dev/null +++ b/core/java/android/service/trust/GrantTrustResult.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.trust; + +parcelable GrantTrustResult; diff --git a/core/java/android/service/trust/GrantTrustResult.java b/core/java/android/service/trust/GrantTrustResult.java new file mode 100644 index 000000000000..7cf708a3d6e3 --- /dev/null +++ b/core/java/android/service/trust/GrantTrustResult.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.trust; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Result type for a callback in a call to + * {@link TrustAgentService#grantTrust(CharSequence, long, int)}. + * + * @hide + */ +@DataClass(genHiddenConstructor = true) +@SystemApi +public final class GrantTrustResult implements Parcelable { + + /** Result status is unknown to this version of the SDK. */ + public static final int STATUS_UNKNOWN = 0; + + /** The device went from locked to unlocked as a result of the call. */ + public static final int STATUS_UNLOCKED_BY_GRANT = 1; + + @Status + private int mStatus; + + /** Returns a new {@link GrantTrustResult} with the specified status. */ + @NonNull + public static GrantTrustResult withStatus(@Status int status) { + return new GrantTrustResult(status); + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/trust/GrantTrustResult.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** @hide */ + @IntDef(prefix = "STATUS_", value = { + STATUS_UNKNOWN, + STATUS_UNLOCKED_BY_GRANT + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface Status {} + + @NonNull + @DataClass.Generated.Member + public static String statusToString(@Status int value) { + switch (value) { + case STATUS_UNKNOWN: + return "STATUS_UNKNOWN"; + case STATUS_UNLOCKED_BY_GRANT: + return "STATUS_UNLOCKED_BY_GRANT"; + default: return Integer.toHexString(value); + } + } + + /** + * Creates a new GrantTrustResult. + * + * @hide + */ + @DataClass.Generated.Member + public GrantTrustResult( + @Status int status) { + this.mStatus = status; + + if (!(mStatus == STATUS_UNKNOWN) + && !(mStatus == STATUS_UNLOCKED_BY_GRANT)) { + throw new java.lang.IllegalArgumentException( + "status was " + mStatus + " but must be one of: " + + "STATUS_UNKNOWN(" + STATUS_UNKNOWN + "), " + + "STATUS_UNLOCKED_BY_GRANT(" + STATUS_UNLOCKED_BY_GRANT + ")"); + } + + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @Status int getStatus() { + return mStatus; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeInt(mStatus); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ GrantTrustResult(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int status = in.readInt(); + + this.mStatus = status; + + if (!(mStatus == STATUS_UNKNOWN) + && !(mStatus == STATUS_UNLOCKED_BY_GRANT)) { + throw new java.lang.IllegalArgumentException( + "status was " + mStatus + " but must be one of: " + + "STATUS_UNKNOWN(" + STATUS_UNKNOWN + "), " + + "STATUS_UNLOCKED_BY_GRANT(" + STATUS_UNLOCKED_BY_GRANT + ")"); + } + + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public GrantTrustResult[] newArray(int size) { + return new GrantTrustResult[size]; + } + + @Override + public GrantTrustResult createFromParcel(@NonNull android.os.Parcel in) { + return new GrantTrustResult(in); + } + }; + + @DataClass.Generated( + time = 1647878197834L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/service/trust/GrantTrustResult.java", + inputSignatures = "public static final int STATUS_UNKNOWN\npublic static final int STATUS_UNLOCKED_BY_GRANT\nprivate @android.service.trust.GrantTrustResult.Status int mStatus\npublic static @android.annotation.NonNull android.service.trust.GrantTrustResult withStatus(int)\nclass GrantTrustResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/service/trust/ITrustAgentService.aidl b/core/java/android/service/trust/ITrustAgentService.aidl index ec3b8575ed36..70c29a682cd1 100644 --- a/core/java/android/service/trust/ITrustAgentService.aidl +++ b/core/java/android/service/trust/ITrustAgentService.aidl @@ -25,7 +25,8 @@ import android.service.trust.ITrustAgentServiceCallback; */ interface ITrustAgentService { oneway void onUnlockAttempt(boolean successful); - oneway void onUserRequestedUnlock(); + oneway void onUserRequestedUnlock(boolean dismissKeyguard); + oneway void onUserMayRequestUnlock(); oneway void onUnlockLockout(int timeoutMs); oneway void onTrustTimeout(); oneway void onDeviceLocked(); diff --git a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl index 6b11e7463abc..e9e40c0175ac 100644 --- a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl +++ b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl @@ -18,13 +18,15 @@ package android.service.trust; import android.os.Bundle; import android.os.IBinder; import android.os.UserHandle; +import com.android.internal.infra.AndroidFuture; /** * Communication channel from the TrustAgentService back to TrustManagerService. * @hide */ oneway interface ITrustAgentServiceCallback { - void grantTrust(CharSequence message, long durationMs, int flags); + void grantTrust( + CharSequence message, long durationMs, int flags, in AndroidFuture resultCallback); void revokeTrust(); void lockUser(); void setManagingTrust(boolean managingTrust); diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java index 8f6e1e00c3f4..559313a30dfa 100644 --- a/core/java/android/service/trust/TrustAgentService.java +++ b/core/java/android/service/trust/TrustAgentService.java @@ -19,6 +19,7 @@ package android.service.trust; import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.Service; @@ -39,9 +40,12 @@ import android.os.UserManager; import android.util.Log; import android.util.Slog; +import com.android.internal.infra.AndroidFuture; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.function.Consumer; /** * A service that notifies the system about whether it believes the environment of the device @@ -183,6 +187,7 @@ public class TrustAgentService extends Service { private static final int MSG_ESCROW_TOKEN_STATE_RECEIVED = 8; private static final int MSG_ESCROW_TOKEN_REMOVED = 9; private static final int MSG_USER_REQUESTED_UNLOCK = 10; + private static final int MSG_USER_MAY_REQUEST_UNLOCK = 11; private static final String EXTRA_TOKEN = "token"; private static final String EXTRA_TOKEN_HANDLE = "token_handle"; @@ -217,7 +222,10 @@ public class TrustAgentService extends Service { onUnlockAttempt(msg.arg1 != 0); break; case MSG_USER_REQUESTED_UNLOCK: - onUserRequestedUnlock(); + onUserRequestedUnlock(msg.arg1 != 0); + break; + case MSG_USER_MAY_REQUEST_UNLOCK: + onUserMayRequestUnlock(); break; case MSG_UNLOCK_LOCKOUT: onDeviceUnlockLockout(msg.arg1); @@ -296,17 +304,38 @@ public class TrustAgentService extends Service { public void onUnlockAttempt(boolean successful) { } + /** + * Called when the user has interacted with the locked device such that they are likely to want + * it to be unlocked soon. This approximates the timing when, for example, the platform would + * check for face authentication to unlock the device. + * + * This signal can be used for the agent to make preparations to quickly unlock the device + * with {@link #onUserRequestedUnlock}. Agents should not unlock the device based solely on this + * signal. There is no guarantee that this method will be called before + * {@link #onUserRequestedUnlock(boolean)}. + */ + public void onUserMayRequestUnlock() { + } + /** * Called when the user has interacted with the locked device such that they likely want it - * to be unlocked. This approximates the timing when, for example, the platform would check for - * face authentication to unlock the device. + * to be unlocked. + * + * When this is called, there is a high probability that the user wants to unlock the device and + * that a biometric method is either not available or not the optimal method at this time. For + * example, this may be called after some kinds of biometric authentication failure. + * + * A call to this method may be preceded by a call to {@link #onUserMayRequestUnlock} which + * the agent can use as a signal to prepare for a subsequent call to this method. * * To attempt to unlock the device, the agent needs to call * {@link #grantTrust(CharSequence, long, int)}. * + * @param dismissKeyguard true when the user wants keyguard dismissed + * * @see #FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE */ - public void onUserRequestedUnlock() { + public void onUserRequestedUnlock(boolean dismissKeyguard) { } /** @@ -399,26 +428,10 @@ public class TrustAgentService extends Service { } /** - * Call to grant trust on the device. + * Attempts to grant trust on the device. * - * @param message describes why the device is trusted, e.g. "Trusted by location". - * @param durationMs amount of time in milliseconds to keep the device in a trusted state. - * Trust for this agent will automatically be revoked when the timeout expires unless - * extended by a subsequent call to this function. The timeout is measured from the - * invocation of this function as dictated by {@link SystemClock#elapsedRealtime())}. - * For security reasons, the value should be no larger than necessary. - * The value may be adjusted by the system as necessary to comply with a policy controlled - * by the system or {@link DevicePolicyManager} restrictions. See {@link #onTrustTimeout()} - * for determining when trust expires. - * @param initiatedByUser this is a hint to the system that trust is being granted as the - * direct result of user action - such as solving a security challenge. The hint is used - * by the system to optimize the experience. Behavior may vary by device and release, so - * one should only set this parameter if it meets the above criteria rather than relying on - * the behavior of any particular device or release. Corresponds to - * {@link #FLAG_GRANT_TRUST_INITIATED_BY_USER}. - * @throws IllegalStateException if the agent is not currently managing trust. - * - * @deprecated use {@link #grantTrust(CharSequence, long, int)} instead. + * @param initiatedByUser see {@link #FLAG_GRANT_TRUST_INITIATED_BY_USER} + * @deprecated use {@link #grantTrust(CharSequence, long, int, Consumer)} instead. */ @Deprecated public final void grantTrust( @@ -427,7 +440,17 @@ public class TrustAgentService extends Service { } /** - * Call to grant trust on the device. + * Attempts to grant trust on the device. + * @deprecated use {@link #grantTrust(CharSequence, long, int, Consumer)} instead. + */ + @Deprecated + public final void grantTrust( + final CharSequence message, final long durationMs, @GrantTrustFlags final int flags) { + grantTrust(message, durationMs, flags, null); + } + + /** + * Attempts to grant trust on the device. * * @param message describes why the device is trusted, e.g. "Trusted by location". * @param durationMs amount of time in milliseconds to keep the device in a trusted state. @@ -438,19 +461,36 @@ public class TrustAgentService extends Service { * The value may be adjusted by the system as necessary to comply with a policy controlled * by the system or {@link DevicePolicyManager} restrictions. See {@link #onTrustTimeout()} * for determining when trust expires. - * @param flags TBDocumented + * @param flags flags to control call: see constants prefixed by {@code FLAG_GRANT_TRUST_}. + * @param resultCallback may be called with the results of the grant * @throws IllegalStateException if the agent is not currently managing trust. + * + * See {@link GrantTrustResult} for the cases where {@code resultCallback} will be called. */ public final void grantTrust( - final CharSequence message, final long durationMs, @GrantTrustFlags final int flags) { + @NonNull final CharSequence message, + final long durationMs, + @GrantTrustFlags final int flags, + @Nullable final Consumer resultCallback) { synchronized (mLock) { if (!mManagingTrust) { throw new IllegalStateException("Cannot grant trust if agent is not managing trust." + " Call setManagingTrust(true) first."); } + + // Prepare future for the IPC + AndroidFuture future = new AndroidFuture<>(); + future.thenAccept(result -> { + if (resultCallback != null) { + // Instead of taking an explicit executor, we post this to mHandler to be + // consistent with the other event methods in this class. + mHandler.post(() -> resultCallback.accept(result)); + } + }); + if (mCallback != null) { try { - mCallback.grantTrust(message.toString(), durationMs, flags); + mCallback.grantTrust(message.toString(), durationMs, flags, future); } catch (RemoteException e) { onError("calling enableTrust()"); } @@ -460,7 +500,7 @@ public class TrustAgentService extends Service { mPendingGrantTrustTask = new Runnable() { @Override public void run() { - grantTrust(message, durationMs, flags); + grantTrust(message, durationMs, flags, resultCallback); } }; } @@ -666,8 +706,14 @@ public class TrustAgentService extends Service { } @Override - public void onUserRequestedUnlock() { - mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK).sendToTarget(); + public void onUserRequestedUnlock(boolean dismissKeyguard) { + mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK, dismissKeyguard ? 1 : 0, 0) + .sendToTarget(); + } + + @Override + public void onUserMayRequestUnlock() { + mHandler.obtainMessage(MSG_USER_MAY_REQUEST_UNLOCK).sendToTarget(); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 3858f9cd7a82..74a88bd5f19a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1486,6 +1486,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationFailed() { handleFingerprintAuthFailed(); + + // TODO(b/225231929): Refactor as needed, add tests, etc. + mTrustManager.reportUserRequestedUnlock( + KeyguardUpdateMonitor.getCurrentUser(), true); } @Override @@ -2258,7 +2262,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } if (shouldTriggerActiveUnlock()) { - mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser()); + // TODO(b/225231929): Refactor surrounding code to reflect calling of new method + mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser()); } } diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index adca21676f9d..d3748140a5a5 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -40,12 +40,16 @@ import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; +import android.service.trust.GrantTrustResult; import android.service.trust.ITrustAgentService; import android.service.trust.ITrustAgentServiceCallback; import android.service.trust.TrustAgentService; import android.util.Log; +import android.util.Pair; import android.util.Slog; +import com.android.internal.infra.AndroidFuture; + import java.util.Collections; import java.util.List; @@ -156,7 +160,9 @@ public class TrustAgentWrapper { } mTrusted = true; mTrustable = false; - mMessage = (CharSequence) msg.obj; + Pair> pair = (Pair) msg.obj; + mMessage = pair.first; + AndroidFuture resultCallback = pair.second; int flags = msg.arg1; mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0; if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) { @@ -189,7 +195,7 @@ public class TrustAgentWrapper { mTrustManagerService.mArchive.logGrantTrust(mUserId, mName, (mMessage != null ? mMessage.toString() : null), durationMs, flags); - mTrustManagerService.updateTrust(mUserId, flags); + mTrustManagerService.updateTrust(mUserId, flags, resultCallback); break; case MSG_TRUST_TIMEOUT: if (DEBUG) Slog.d(TAG, "Trust timed out : " + mName.flattenToShortString()); @@ -314,13 +320,18 @@ public class TrustAgentWrapper { private ITrustAgentServiceCallback mCallback = new ITrustAgentServiceCallback.Stub() { @Override - public void grantTrust(CharSequence message, long durationMs, int flags) { + public void grantTrust( + CharSequence message, + long durationMs, + int flags, + AndroidFuture resultCallback) { if (DEBUG) { Slog.d(TAG, "enableTrust(" + message + ", durationMs = " + durationMs + ", flags = " + flags + ")"); } - Message msg = mHandler.obtainMessage(MSG_GRANT_TRUST, flags, 0, message); + Message msg = mHandler.obtainMessage( + MSG_GRANT_TRUST, flags, 0, Pair.create(message, resultCallback)); msg.getData().putLong(DATA_DURATION, durationMs); msg.sendToTarget(); } @@ -514,12 +525,23 @@ public class TrustAgentWrapper { } /** - * @see android.service.trust.TrustAgentService#onUserRequestedUnlock() + * @see android.service.trust.TrustAgentService#onUserRequestedUnlock(boolean) */ - public void onUserRequestedUnlock() { + public void onUserRequestedUnlock(boolean dismissKeyguard) { try { if (mTrustAgentService != null) { - mTrustAgentService.onUserRequestedUnlock(); + mTrustAgentService.onUserRequestedUnlock(dismissKeyguard); + } + } catch (RemoteException e) { + onError(e); + } + } + + /** @see android.service.trust.TrustAgentService#onUserMayRequestUnlock() */ + public void onUserMayRequestUnlock() { + try { + if (mTrustAgentService != null) { + mTrustAgentService.onUserMayRequestUnlock(); } } catch (RemoteException e) { onError(e); diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index cc1d0e2ea6c8..a486364518b9 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -61,6 +61,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.security.Authorization; +import android.service.trust.GrantTrustResult; import android.service.trust.TrustAgentService; import android.text.TextUtils; import android.util.ArrayMap; @@ -77,6 +78,7 @@ import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; +import com.android.internal.infra.AndroidFuture; import com.android.internal.util.DumpUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; @@ -131,6 +133,7 @@ public class TrustManagerService extends SystemService { private static final int MSG_SCHEDULE_TRUST_TIMEOUT = 15; private static final int MSG_USER_REQUESTED_UNLOCK = 16; private static final int MSG_REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH = 17; + private static final int MSG_USER_MAY_REQUEST_UNLOCK = 18; private static final String REFRESH_DEVICE_LOCKED_EXCEPT_USER = "except"; @@ -495,13 +498,28 @@ public class TrustManagerService extends SystemService { } } - public void updateTrust(int userId, int flags) { - updateTrust(userId, flags, false /* isFromUnlock */); + /** Triggers a trust update. */ + public void updateTrust( + int userId, + int flags) { + updateTrust(userId, flags, null); } - private void updateTrust(int userId, int flags, boolean isFromUnlock) { + /** Triggers a trust update. */ + public void updateTrust( + int userId, + int flags, + @Nullable AndroidFuture resultCallback) { + updateTrust(userId, flags, false /* isFromUnlock */, resultCallback); + } + + private void updateTrust( + int userId, + int flags, + boolean isFromUnlock, + @Nullable AndroidFuture resultCallback) { if (ENABLE_ACTIVE_UNLOCK_FLAG) { - updateTrustWithRenewableUnlock(userId, flags, isFromUnlock); + updateTrustWithRenewableUnlock(userId, flags, isFromUnlock, resultCallback); } else { updateTrustWithNonrenewableTrust(userId, flags, isFromUnlock); } @@ -553,7 +571,11 @@ public class TrustManagerService extends SystemService { } } - private void updateTrustWithRenewableUnlock(int userId, int flags, boolean isFromUnlock) { + private void updateTrustWithRenewableUnlock( + int userId, + int flags, + boolean isFromUnlock, + @Nullable AndroidFuture resultCallback) { boolean managed = aggregateIsTrustManaged(userId); dispatchOnTrustManagedChanged(managed, userId); if (mStrongAuthTracker.isTrustAllowedForUser(userId) @@ -614,8 +636,18 @@ public class TrustManagerService extends SystemService { isTrustableTimeout /* isTrustableTimeout */); } } - } + boolean wasLocked = !alreadyUnlocked; + boolean shouldSendCallback = wasLocked && pendingTrustState == TrustState.TRUSTED; + if (shouldSendCallback) { + if (resultCallback != null) { + if (DEBUG) Slog.d(TAG, "calling back with UNLOCKED_BY_GRANT"); + resultCallback.complete( + GrantTrustResult.withStatus( + GrantTrustResult.STATUS_UNLOCKED_BY_GRANT)); + } + } + } private void updateTrustUsuallyManaged(int userId, boolean managed) { synchronized (mTrustUsuallyManagedForUser) { @@ -1190,7 +1222,7 @@ public class TrustManagerService extends SystemService { if (successful) { mStrongAuthTracker.allowTrustFromUnlock(userId); // Allow the presence of trust on a successful unlock attempt to extend unlock - updateTrust(userId, 0 /* flags */, true); + updateTrust(userId, 0 /* flags */, true, null); mHandler.obtainMessage(MSG_REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH, userId).sendToTarget(); } @@ -1202,11 +1234,27 @@ public class TrustManagerService extends SystemService { } } - private void dispatchUserRequestedUnlock(int userId) { + private void dispatchUserRequestedUnlock(int userId, boolean dismissKeyguard) { + if (DEBUG) { + Slog.d(TAG, "dispatchUserRequestedUnlock(user=" + userId + ", dismissKeyguard=" + + dismissKeyguard + ")"); + } for (int i = 0; i < mActiveAgents.size(); i++) { AgentInfo info = mActiveAgents.valueAt(i); if (info.userId == userId) { - info.agent.onUserRequestedUnlock(); + info.agent.onUserRequestedUnlock(dismissKeyguard); + } + } + } + + private void dispatchUserMayRequestUnlock(int userId) { + if (DEBUG) { + Slog.d(TAG, "dispatchUserMayRequestUnlock(user=" + userId + ")"); + } + for (int i = 0; i < mActiveAgents.size(); i++) { + AgentInfo info = mActiveAgents.valueAt(i); + if (info.userId == userId) { + info.agent.onUserMayRequestUnlock(); } } } @@ -1341,9 +1389,17 @@ public class TrustManagerService extends SystemService { } @Override - public void reportUserRequestedUnlock(int userId) throws RemoteException { + public void reportUserRequestedUnlock(int userId, boolean dismissKeyguard) + throws RemoteException { enforceReportPermission(); - mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK, userId).sendToTarget(); + mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK, userId, dismissKeyguard ? 1 : 0) + .sendToTarget(); + } + + @Override + public void reportUserMayRequestUnlock(int userId) throws RemoteException { + enforceReportPermission(); + mHandler.obtainMessage(MSG_USER_MAY_REQUEST_UNLOCK, userId).sendToTarget(); } @Override @@ -1683,7 +1739,10 @@ public class TrustManagerService extends SystemService { dispatchUnlockAttempt(msg.arg1 != 0, msg.arg2); break; case MSG_USER_REQUESTED_UNLOCK: - dispatchUserRequestedUnlock(msg.arg1); + dispatchUserRequestedUnlock(msg.arg1, msg.arg2 != 0); + break; + case MSG_USER_MAY_REQUEST_UNLOCK: + dispatchUserMayRequestUnlock(msg.arg1); break; case MSG_DISPATCH_UNLOCK_LOCKOUT: dispatchUnlockLockout(msg.arg1, msg.arg2); @@ -1725,7 +1784,7 @@ public class TrustManagerService extends SystemService { break; case MSG_REFRESH_DEVICE_LOCKED_FOR_USER: if (msg.arg2 == 1) { - updateTrust(msg.arg1, 0 /* flags */, true /* isFromUnlock */); + updateTrust(msg.arg1, 0 /* flags */, true /* isFromUnlock */, null); } final int unlockedUser = msg.getData().getInt( REFRESH_DEVICE_LOCKED_EXCEPT_USER, UserHandle.USER_NULL); diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp index c9c6c5cf193b..77f98e88f1eb 100644 --- a/tests/TrustTests/Android.bp +++ b/tests/TrustTests/Android.bp @@ -25,6 +25,8 @@ android_test { "androidx.test.rules", "androidx.test.ext.junit", "androidx.test.uiautomator", + "mockito-target-minus-junit4", + "servicestests-utils", "truth-prebuilt", ], libs: [ diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt index af7a98c22ad1..f864fedf4e62 100644 --- a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt +++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt @@ -16,6 +16,7 @@ package android.trust.test +import android.service.trust.GrantTrustResult import android.trust.BaseTrustAgentService import android.trust.TrustTestActivity import android.trust.test.lib.LockStateTrackingRule @@ -25,11 +26,13 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.uiautomator.UiDevice +import com.android.server.testutils.mock import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain import org.junit.runner.RunWith +import org.mockito.Mockito.verifyZeroInteractions /** * Test for testing revokeTrust & grantTrust for non-renewable trust. @@ -66,7 +69,7 @@ class GrantAndRevokeTrustTest { @Test fun grantKeepsDeviceUnlocked() { - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) + trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) {} uiDevice.sleep() lockStateTrackingRule.assertUnlocked() @@ -74,7 +77,7 @@ class GrantAndRevokeTrustTest { @Test fun grantKeepsDeviceUnlocked_untilRevoked() { - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0) + trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0) {} await() uiDevice.sleep() trustAgentRule.agent.revokeTrust() @@ -82,6 +85,15 @@ class GrantAndRevokeTrustTest { lockStateTrackingRule.assertLocked() } + @Test + fun grantDoesNotCallBack() { + val callback = mock<(GrantTrustResult) -> Unit>() + trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0, callback) + await() + + verifyZeroInteractions(callback) + } + companion object { private const val TAG = "GrantAndRevokeTrustTest" private const val GRANT_MESSAGE = "granted by test" diff --git a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt index 14c227b1f678..3c6d54d24291 100644 --- a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt +++ b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt @@ -16,16 +16,20 @@ package android.trust.test +import android.service.trust.GrantTrustResult +import android.service.trust.GrantTrustResult.STATUS_UNLOCKED_BY_GRANT import android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE import android.trust.BaseTrustAgentService import android.trust.TrustTestActivity import android.trust.test.lib.LockStateTrackingRule import android.trust.test.lib.ScreenLockRule import android.trust.test.lib.TrustAgentRule +import android.util.Log import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.uiautomator.UiDevice +import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test @@ -70,7 +74,8 @@ class TemporaryAndRenewableTrustTest { uiDevice.sleep() lockStateTrackingRule.assertLocked() - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} uiDevice.wakeUp() lockStateTrackingRule.assertLocked() @@ -78,7 +83,8 @@ class TemporaryAndRenewableTrustTest { @Test fun grantTrustUnlockedDevice_deviceLocksOnScreenOff() { - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} uiDevice.sleep() lockStateTrackingRule.assertLocked() @@ -86,20 +92,48 @@ class TemporaryAndRenewableTrustTest { @Test fun grantTrustLockedDevice_grantTrustOnLockedDeviceUnlocksDevice() { - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} uiDevice.sleep() lockStateTrackingRule.assertLocked() - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} uiDevice.wakeUp() lockStateTrackingRule.assertUnlocked() } + @Test + fun grantTrustLockedDevice_callsBackWhenUnlocked() { + Log.i(TAG, "Granting renewable trust while unlocked") + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} + await(1000) + + Log.i(TAG, "Locking device") + uiDevice.sleep() + + lockStateTrackingRule.assertLocked() + + Log.i(TAG, "Renewing trust and unlocking") + var result: GrantTrustResult? = null + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) { + Log.i(TAG, "Callback received; status=${it.status}") + result = it + } + uiDevice.wakeUp() + lockStateTrackingRule.assertUnlocked() + + assertThat(result?.status).isEqualTo(STATUS_UNLOCKED_BY_GRANT) + } + @Test fun grantTrustLockedDevice_revokeTrustPreventsSubsequentUnlock() { - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} uiDevice.sleep() lockStateTrackingRule.assertLocked() @@ -109,7 +143,8 @@ class TemporaryAndRenewableTrustTest { uiDevice.wakeUp() await(500) - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} lockStateTrackingRule.assertLocked() } diff --git a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt index f8783fbaf121..8bd8340a04b1 100644 --- a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt +++ b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt @@ -32,7 +32,7 @@ import org.junit.rules.RuleChain import org.junit.runner.RunWith /** - * Test for testing the user unlock trigger. + * Test for the user unlock triggers. * * atest TrustTests:UserUnlockRequestTest */ @@ -53,13 +53,32 @@ class UserUnlockRequestTest { @Test fun reportUserRequestedUnlock_propagatesToAgent() { val oldCount = trustAgentRule.agent.onUserRequestedUnlockCallCount - trustManager.reportUserRequestedUnlock(userId) + trustManager.reportUserRequestedUnlock(userId, false) await() assertThat(trustAgentRule.agent.onUserRequestedUnlockCallCount) .isEqualTo(oldCount + 1) } + @Test + fun reportUserRequestedUnlock_propagatesToAgentWithDismissKeyguard() { + trustManager.reportUserRequestedUnlock(userId, true) + await() + + assertThat(trustAgentRule.agent.lastCallDismissKeyguard) + .isTrue() + } + + @Test + fun reportUserMayRequestUnlock_propagatesToAgent() { + val oldCount = trustAgentRule.agent.onUserMayRequestUnlockCallCount + trustManager.reportUserMayRequestUnlock(userId) + await() + + assertThat(trustAgentRule.agent.onUserMayRequestUnlockCallCount) + .isEqualTo(oldCount + 1) + } + companion object { private const val TAG = "UserUnlockRequestTest" private fun await() = Thread.sleep(250) @@ -69,10 +88,20 @@ class UserUnlockRequestTest { class UserUnlockRequestTrustAgent : BaseTrustAgentService() { var onUserRequestedUnlockCallCount: Long = 0 private set + var onUserMayRequestUnlockCallCount: Long = 0 + private set + var lastCallDismissKeyguard: Boolean = false + private set - override fun onUserRequestedUnlock() { - Log.i(TAG, "onUserRequestedUnlock") + override fun onUserRequestedUnlock(dismissKeyguard: Boolean) { + Log.i(TAG, "onUserRequestedUnlock($dismissKeyguard)") onUserRequestedUnlockCallCount++ + lastCallDismissKeyguard = dismissKeyguard + } + + override fun onUserMayRequestUnlock() { + Log.i(TAG, "onUserMayRequestUnlock") + onUserMayRequestUnlockCallCount++ } companion object { diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt index 834f2122a21b..00f457b5328a 100644 --- a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt +++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt @@ -56,12 +56,22 @@ class LockStateTrackingRule : TestRule { val maxWaits = 50 var waitCount = 0 + // First verify we get the call in LockState via TrustListener while ((lockState.locked == false) && waitCount < maxWaits) { - Log.i(TAG, "phone still locked, wait 50ms more ($waitCount)") + Log.i(TAG, "phone still unlocked (TrustListener), wait 50ms more ($waitCount)") Thread.sleep(50) waitCount++ } assertThat(lockState.locked).isTrue() + + // TODO(b/225231929): refactor checks into one loop and re-use for assertUnlocked + // Then verify we get the window manager locked + while (!windowManager.isKeyguardLocked && waitCount < maxWaits) { + Log.i(TAG, "phone still unlocked (WindowManager), wait 50ms more ($waitCount)") + Thread.sleep(50) + waitCount++ + } + assertThat(windowManager.isKeyguardLocked).isTrue() } fun assertUnlocked() { diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt index 006525d857ac..127653d13b10 100644 --- a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt +++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt @@ -69,6 +69,17 @@ class ScreenLockRule : TestRule { while (windowManager.isKeyguardLocked && waitCount < maxWaits) { Log.i(TAG, "Keyguard still showing; attempting to dismiss and wait 50ms ($waitCount)") windowManager.dismissKeyguard(null, null) + + // Sometimes, bouncer gets shown due to a race, so we have to put display to sleep + // and wake it back up to get it to go away + if (waitCount >= 10 && waitCount % 5 == 0) { + Log.i(TAG, "Escalation: attempting screen off/on to get rid of bouncer (+500ms)") + uiDevice.sleep() + Thread.sleep(250) + uiDevice.wakeUp() + Thread.sleep(250) + } + Thread.sleep(50) waitCount++ }