diff options
106 files changed, 2963 insertions, 735 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 589b055692bc..211f60350cfa 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -40645,6 +40645,7 @@ package android.service.credentials { public class CredentialEntry implements android.os.Parcelable { ctor public CredentialEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice); ctor public CredentialEntry(@NonNull android.service.credentials.BeginGetCredentialOption, @NonNull android.app.slice.Slice); + ctor public CredentialEntry(@NonNull String, @NonNull android.app.slice.Slice); method public int describeContents(); method @NonNull public String getBeginGetCredentialOptionId(); method @NonNull public android.app.slice.Slice getSlice(); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index c3456ee2d976..a3bc3922126b 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1072,6 +1072,40 @@ package android.content.rollback { } +package android.credentials { + + public final class CredentialManager { + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.QUERY_ALL_PACKAGES, "android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS"}) public java.util.List<android.credentials.CredentialProviderInfo> getCredentialProviderServicesForTesting(int); + method public static boolean isServiceEnabled(@NonNull android.content.Context); + field public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0; // 0x0 + field public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1; // 0x1 + field public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2; // 0x2 + } + + public final class CredentialProviderInfo implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getCapabilities(); + method @NonNull public android.content.ComponentName getComponentName(); + method @Nullable public CharSequence getLabel(@NonNull android.content.Context); + method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context); + method @NonNull public android.content.pm.ServiceInfo getServiceInfo(); + method @NonNull public boolean hasCapability(@NonNull String); + method public boolean isEnabled(); + method public boolean isSystemProvider(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CredentialProviderInfo> CREATOR; + } + + public static final class CredentialProviderInfo.Builder { + ctor public CredentialProviderInfo.Builder(@NonNull android.content.pm.ServiceInfo); + method @NonNull public android.credentials.CredentialProviderInfo.Builder addCapabilities(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.credentials.CredentialProviderInfo build(); + method @NonNull public android.credentials.CredentialProviderInfo.Builder setEnabled(boolean); + method @NonNull public android.credentials.CredentialProviderInfo.Builder setSystemProvider(boolean); + } + +} + package android.credentials.ui { public final class AuthenticationEntry implements android.os.Parcelable { diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index f0230e7f4593..0806f1db2bb7 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -26,12 +26,12 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; +import android.annotation.TestApi; import android.app.Activity; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.IntentSender; -import android.content.pm.ServiceInfo; import android.os.CancellationSignal; import android.os.ICancellationSignal; import android.os.OutcomeReceiver; @@ -75,21 +75,21 @@ public final class CredentialManager { * * @hide */ - public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0; + @TestApi public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0; /** * Returns system credential providers only. * * @hide */ - public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1; + @TestApi public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1; /** * Returns user credential providers only. * * @hide */ - public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2; + @TestApi public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2; private final Context mContext; private final ICredentialManager mService; @@ -263,44 +263,6 @@ public final class CredentialManager { } /** - * Gets a list of all user configurable credential providers registered on the system. This API - * is intended for browsers and settings apps. - * - * @param cancellationSignal an optional signal that allows for cancelling this call - * @param executor the callback will take place on this {@link Executor} - * @param callback the callback invoked when the request succeeds or fails - * @hide - */ - @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) - public void listEnabledProviders( - @Nullable CancellationSignal cancellationSignal, - @CallbackExecutor @NonNull Executor executor, - @NonNull - OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> - callback) { - requireNonNull(executor, "executor must not be null"); - requireNonNull(callback, "callback must not be null"); - - if (cancellationSignal != null && cancellationSignal.isCanceled()) { - Log.w(TAG, "listEnabledProviders already canceled"); - return; - } - - ICancellationSignal cancelRemote = null; - try { - cancelRemote = - mService.listEnabledProviders( - new ListEnabledProvidersTransport(executor, callback)); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - - if (cancellationSignal != null && cancelRemote != null) { - cancellationSignal.setRemote(cancelRemote); - } - } - - /** * Sets a list of all user configurable credential providers registered on the system. This API * is intended for settings apps. * @@ -348,36 +310,43 @@ public final class CredentialManager { } /** - * Returns the list of ServiceInfo for all discovered credential providers on this device. + * Returns the list of CredentialProviderInfo for all discovered credential providers on this + * device but will include test system providers as well. * * @hide */ @NonNull - @RequiresPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS) - public List<ServiceInfo> getCredentialProviderServicesForTesting( - @ProviderFilter int providerFilter) { + @TestApi + @RequiresPermission( + anyOf = { + android.Manifest.permission.QUERY_ALL_PACKAGES, + android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS + }) + public List<CredentialProviderInfo> getCredentialProviderServicesForTesting( + @ProviderFilter int providerFilter) { try { - return mService.getCredentialProviderServices( - mContext.getUserId(), - /* disableSystemAppVerificationForTests= */ true, - providerFilter); + return mService.getCredentialProviderServicesForTesting(providerFilter); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Returns the list of ServiceInfo for all discovered credential providers on this device. + * Returns the list of CredentialProviderInfo for all discovered credential providers on this + * device. * * @hide */ @NonNull - @RequiresPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS) - public List<ServiceInfo> getCredentialProviderServices( + @RequiresPermission( + anyOf = { + android.Manifest.permission.QUERY_ALL_PACKAGES, + android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS + }) + public List<CredentialProviderInfo> getCredentialProviderServices( int userId, @ProviderFilter int providerFilter) { try { - return mService.getCredentialProviderServices( - userId, /* disableSystemAppVerificationForTests= */ false, providerFilter); + return mService.getCredentialProviderServices(userId, providerFilter); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -388,7 +357,9 @@ public final class CredentialManager { * * @hide */ - public static boolean isServiceEnabled(Context context) { + @TestApi + public static boolean isServiceEnabled(@NonNull Context context) { + requireNonNull(context, "context must not be null"); if (context == null) { return false; } @@ -578,33 +549,6 @@ public final class CredentialManager { } } - private static class ListEnabledProvidersTransport extends IListEnabledProvidersCallback.Stub { - // TODO: listen for cancellation to release callback. - - private final Executor mExecutor; - private final OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> - mCallback; - - private ListEnabledProvidersTransport( - Executor executor, - OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> - callback) { - mExecutor = executor; - mCallback = callback; - } - - @Override - public void onResponse(ListEnabledProvidersResponse response) { - mExecutor.execute(() -> mCallback.onResult(response)); - } - - @Override - public void onError(String errorType, String message) { - mExecutor.execute( - () -> mCallback.onError(new ListEnabledProvidersException(errorType, message))); - } - } - private static class SetEnabledProvidersTransport extends ISetEnabledProvidersCallback.Stub { // TODO: listen for cancellation to release callback. diff --git a/core/java/android/credentials/IListEnabledProvidersCallback.aidl b/core/java/android/credentials/CredentialProviderInfo.aidl index 3a8e25ed954a..30b7742d17f8 100644 --- a/core/java/android/credentials/IListEnabledProvidersCallback.aidl +++ b/core/java/android/credentials/CredentialProviderInfo.aidl @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Android Open Source Project + * Copyright 2023 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. @@ -16,14 +16,4 @@ package android.credentials; -import android.credentials.ListEnabledProvidersResponse; - -/** - * Listener for an listEnabledProviders request. - * - * @hide - */ -interface IListEnabledProvidersCallback { - oneway void onResponse(in ListEnabledProvidersResponse response); - oneway void onError(String errorType, String message); -}
\ No newline at end of file +parcelable CredentialProviderInfo;
\ No newline at end of file diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java new file mode 100644 index 000000000000..7276770d281e --- /dev/null +++ b/core/java/android/credentials/CredentialProviderInfo.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2023 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.credentials; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ServiceInfo; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * {@link ServiceInfo} and meta-data about a credential provider. + * + * @hide + */ +@TestApi +public final class CredentialProviderInfo implements Parcelable { + @NonNull private final ServiceInfo mServiceInfo; + @NonNull private final List<String> mCapabilities = new ArrayList<>(); + @Nullable private final CharSequence mOverrideLabel; + private final boolean mIsSystemProvider; + private final boolean mIsEnabled; + + /** + * Constructs an information instance of the credential provider. + * + * @param builder the builder object. + */ + private CredentialProviderInfo(@NonNull Builder builder) { + mServiceInfo = builder.mServiceInfo; + mCapabilities.addAll(builder.mCapabilities); + mIsSystemProvider = builder.mIsSystemProvider; + mIsEnabled = builder.mIsEnabled; + mOverrideLabel = builder.mOverrideLabel; + } + + /** Returns true if the service supports the given {@code credentialType}, false otherwise. */ + @NonNull + public boolean hasCapability(@NonNull String credentialType) { + return mCapabilities.contains(credentialType); + } + + /** Returns the service info. */ + @NonNull + public ServiceInfo getServiceInfo() { + return mServiceInfo; + } + + /** Returns whether it is a system provider. */ + public boolean isSystemProvider() { + return mIsSystemProvider; + } + + /** Returns the service icon. */ + @Nullable + public Drawable getServiceIcon(@NonNull Context context) { + return mServiceInfo.loadIcon(context.getPackageManager()); + } + + /** Returns the service label. */ + @Nullable + public CharSequence getLabel(@NonNull Context context) { + if (mOverrideLabel != null) { + return mOverrideLabel; + } + return mServiceInfo.loadSafeLabel(context.getPackageManager()); + } + + /** Returns a list of capabilities this provider service can support. */ + @NonNull + public List<String> getCapabilities() { + return Collections.unmodifiableList(mCapabilities); + } + + /** Returns whether the provider is enabled by the user. */ + public boolean isEnabled() { + return mIsEnabled; + } + + /** Returns the component name for the service. */ + @NonNull + public ComponentName getComponentName() { + return mServiceInfo.getComponentName(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedObject(mServiceInfo, flags); + dest.writeBoolean(mIsSystemProvider); + dest.writeStringList(mCapabilities); + dest.writeBoolean(mIsEnabled); + TextUtils.writeToParcel(mOverrideLabel, dest, flags); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "CredentialProviderInfo {" + + "serviceInfo=" + + mServiceInfo + + ", " + + "isSystemProvider=" + + mIsSystemProvider + + ", " + + "isEnabled=" + + mIsEnabled + + ", " + + "overrideLabel=" + + mOverrideLabel + + ", " + + "capabilities=" + + String.join(",", mCapabilities) + + "}"; + } + + private CredentialProviderInfo(@NonNull Parcel in) { + mServiceInfo = in.readTypedObject(ServiceInfo.CREATOR); + mIsSystemProvider = in.readBoolean(); + in.readStringList(mCapabilities); + mIsEnabled = in.readBoolean(); + mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + } + + public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR = + new Parcelable.Creator<CredentialProviderInfo>() { + @Override + public CredentialProviderInfo[] newArray(int size) { + return new CredentialProviderInfo[size]; + } + + @Override + public CredentialProviderInfo createFromParcel(@NonNull Parcel in) { + return new CredentialProviderInfo(in); + } + }; + + /** A builder for {@link CredentialProviderInfo} objects. */ + public static final class Builder { + + @NonNull private ServiceInfo mServiceInfo; + @NonNull private List<String> mCapabilities = new ArrayList<>(); + private boolean mIsSystemProvider = false; + private boolean mIsEnabled = false; + @Nullable private CharSequence mOverrideLabel = null; + + /** + * Creates a new builder. + * + * @param serviceInfo the service info of the credential provider service. + */ + public Builder(@NonNull ServiceInfo serviceInfo) { + mServiceInfo = serviceInfo; + } + + /** Sets whether it is a system provider. */ + public @NonNull Builder setSystemProvider(boolean isSystemProvider) { + mIsSystemProvider = isSystemProvider; + return this; + } + + /** + * Sets the label to be used instead of getting from the system (for unit tests). + * + * @hide + */ + public @NonNull Builder setOverrideLabel(@NonNull CharSequence overrideLabel) { + mOverrideLabel = overrideLabel; + return this; + } + + /** Sets a list of capabilities this provider service can support. */ + public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) { + mCapabilities.addAll(capabilities); + return this; + } + + /** Sets whether it is enabled by the user. */ + public @NonNull Builder setEnabled(boolean isEnabled) { + mIsEnabled = isEnabled; + return this; + } + + /** Builds a new {@link CredentialProviderInfo} instance. */ + public @NonNull CredentialProviderInfo build() { + return new CredentialProviderInfo(this); + } + } +} diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl index 625fc8ab5dad..8c2cb5aa0d77 100644 --- a/core/java/android/credentials/ICredentialManager.aidl +++ b/core/java/android/credentials/ICredentialManager.aidl @@ -18,7 +18,7 @@ package android.credentials; import java.util.List; -import android.content.pm.ServiceInfo; +import android.credentials.CredentialProviderInfo; import android.credentials.ClearCredentialStateRequest; import android.credentials.CreateCredentialRequest; import android.credentials.GetCredentialRequest; @@ -27,7 +27,6 @@ import android.credentials.UnregisterCredentialDescriptionRequest; import android.credentials.IClearCredentialStateCallback; import android.credentials.ICreateCredentialCallback; import android.credentials.IGetCredentialCallback; -import android.credentials.IListEnabledProvidersCallback; import android.credentials.ISetEnabledProvidersCallback; import android.content.ComponentName; import android.os.ICancellationSignal; @@ -45,8 +44,6 @@ interface ICredentialManager { @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage); - @nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback); - void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback); void registerCredentialDescription(in RegisterCredentialDescriptionRequest request, String callingPackage); @@ -55,6 +52,8 @@ interface ICredentialManager { boolean isEnabledCredentialProviderService(in ComponentName componentName, String callingPackage); - List<ServiceInfo> getCredentialProviderServices(in int userId, in boolean disableSystemAppVerificationForTests, in int providerFilter); + List<CredentialProviderInfo> getCredentialProviderServices(in int userId, in int providerFilter); + + List<CredentialProviderInfo> getCredentialProviderServicesForTesting(in int providerFilter); } diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java index 067ae4d438cc..490e55ba260f 100644 --- a/core/java/android/hardware/display/VirtualDisplayConfig.java +++ b/core/java/android/hardware/display/VirtualDisplayConfig.java @@ -28,6 +28,7 @@ import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; +import android.view.ContentRecordingSession; import android.view.Display; import android.view.Surface; @@ -53,6 +54,8 @@ public final class VirtualDisplayConfig implements Parcelable { private final int mDisplayIdToMirror; private final boolean mWindowManagerMirroringEnabled; private ArraySet<String> mDisplayCategories = null; + @Nullable + private ContentRecordingSession mContentRecordingSession; private final float mRequestedRefreshRate; private VirtualDisplayConfig( @@ -65,6 +68,7 @@ public final class VirtualDisplayConfig implements Parcelable { @Nullable String uniqueId, int displayIdToMirror, boolean windowManagerMirroringEnabled, + ContentRecordingSession session, @NonNull ArraySet<String> displayCategories, float requestedRefreshRate) { mName = name; @@ -76,6 +80,7 @@ public final class VirtualDisplayConfig implements Parcelable { mUniqueId = uniqueId; mDisplayIdToMirror = displayIdToMirror; mWindowManagerMirroringEnabled = windowManagerMirroringEnabled; + mContentRecordingSession = session; mDisplayCategories = displayCategories; mRequestedRefreshRate = requestedRefreshRate; } @@ -156,6 +161,17 @@ public final class VirtualDisplayConfig implements Parcelable { } /** + * Returns the recording session associated with this VirtualDisplay. Only used for + * recording via {@link MediaProjection}. + * + * @hide + */ + @Nullable + public ContentRecordingSession getContentRecordingSession() { + return mContentRecordingSession; + } + + /** * Returns the display categories. * * @see Builder#setDisplayCategories @@ -186,6 +202,7 @@ public final class VirtualDisplayConfig implements Parcelable { dest.writeString8(mUniqueId); dest.writeInt(mDisplayIdToMirror); dest.writeBoolean(mWindowManagerMirroringEnabled); + dest.writeTypedObject(mContentRecordingSession, flags); dest.writeArraySet(mDisplayCategories); dest.writeFloat(mRequestedRefreshRate); } @@ -211,6 +228,7 @@ public final class VirtualDisplayConfig implements Parcelable { && Objects.equals(mUniqueId, that.mUniqueId) && mDisplayIdToMirror == that.mDisplayIdToMirror && mWindowManagerMirroringEnabled == that.mWindowManagerMirroringEnabled + && Objects.equals(mContentRecordingSession, that.mContentRecordingSession) && Objects.equals(mDisplayCategories, that.mDisplayCategories) && mRequestedRefreshRate == that.mRequestedRefreshRate; } @@ -219,8 +237,8 @@ public final class VirtualDisplayConfig implements Parcelable { public int hashCode() { int hashCode = Objects.hash( mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId, - mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories, - mRequestedRefreshRate); + mDisplayIdToMirror, mWindowManagerMirroringEnabled, mContentRecordingSession, + mDisplayCategories, mRequestedRefreshRate); return hashCode; } @@ -237,6 +255,7 @@ public final class VirtualDisplayConfig implements Parcelable { + " mUniqueId=" + mUniqueId + " mDisplayIdToMirror=" + mDisplayIdToMirror + " mWindowManagerMirroringEnabled=" + mWindowManagerMirroringEnabled + + " mContentRecordingSession=" + mContentRecordingSession + " mDisplayCategories=" + mDisplayCategories + " mRequestedRefreshRate=" + mRequestedRefreshRate + ")"; @@ -252,6 +271,7 @@ public final class VirtualDisplayConfig implements Parcelable { mUniqueId = in.readString8(); mDisplayIdToMirror = in.readInt(); mWindowManagerMirroringEnabled = in.readBoolean(); + mContentRecordingSession = in.readTypedObject(ContentRecordingSession.CREATOR); mDisplayCategories = (ArraySet<String>) in.readArraySet(null); mRequestedRefreshRate = in.readFloat(); } @@ -283,6 +303,8 @@ public final class VirtualDisplayConfig implements Parcelable { private String mUniqueId = null; private int mDisplayIdToMirror = DEFAULT_DISPLAY; private boolean mWindowManagerMirroringEnabled = false; + @Nullable + private ContentRecordingSession mContentRecordingSession; private ArraySet<String> mDisplayCategories = new ArraySet<>(); private float mRequestedRefreshRate = 0.0f; @@ -375,6 +397,18 @@ public final class VirtualDisplayConfig implements Parcelable { } /** + * Sets the recording session associated with this {@link VirtualDisplay}. Only used for + * recording via {@link MediaProjection}. + * + * @hide + */ + @NonNull + public Builder setContentRecordingSession(@Nullable ContentRecordingSession session) { + mContentRecordingSession = session; + return this; + } + + /** * Sets the display categories. * * <p>The categories of the display indicate the type of activities allowed to run on that @@ -435,6 +469,7 @@ public final class VirtualDisplayConfig implements Parcelable { mUniqueId, mDisplayIdToMirror, mWindowManagerMirroringEnabled, + mContentRecordingSession, mDisplayCategories, mRequestedRefreshRate); } diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java index 85eac6edb6d2..e9cebd2e6af7 100644 --- a/core/java/android/service/credentials/CredentialEntry.java +++ b/core/java/android/service/credentials/CredentialEntry.java @@ -131,9 +131,7 @@ public class CredentialEntry implements Parcelable { * @param slice the slice containing the metadata to be shown on the UI. Must be * constructed through the androidx.credentials jetpack library. * - * @hide */ - // TODO: Unhide this constructor when the registry APIs are stable public CredentialEntry(@NonNull String type, @NonNull Slice slice) { mBeginGetCredentialOptionId = null; mType = requireNonNull(type, "type must not be null"); diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index b5464db98d0e..fd9360f00d4a 100644 --- a/core/java/android/service/credentials/CredentialProviderInfo.java +++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java @@ -34,40 +34,27 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.credentials.CredentialManager; -import android.graphics.drawable.Drawable; +import android.credentials.CredentialProviderInfo; import android.os.Bundle; import android.os.RemoteException; -import android.text.TextUtils; +import android.os.UserHandle; import android.util.Log; import android.util.Slog; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** - * {@link ServiceInfo} and meta-data about a credential provider. + * {@link CredentialProviderInfo} generator. * * @hide */ -public final class CredentialProviderInfo { - private static final String TAG = "CredentialProviderInfo"; - - @NonNull - private final ServiceInfo mServiceInfo; - @NonNull - private final List<String> mCapabilities; - - @NonNull - private final Context mContext; - @Nullable - private final Drawable mIcon; - @Nullable - private final CharSequence mLabel; - private final boolean mIsSystemProvider; +public final class CredentialProviderInfoFactory { + private static final String TAG = "CredentialProviderInfoFactory"; /** * Constructs an information instance of the credential provider. @@ -79,14 +66,18 @@ public final class CredentialProviderInfo { * @throws PackageManager.NameNotFoundException If provider service is not found * @throws SecurityException If provider does not require the relevant permission */ - public CredentialProviderInfo(@NonNull Context context, - @NonNull ComponentName serviceComponent, int userId, boolean isSystemProvider) + public static CredentialProviderInfo create( + @NonNull Context context, + @NonNull ComponentName serviceComponent, + int userId, + boolean isSystemProvider) throws PackageManager.NameNotFoundException { - this( + return create( context, getServiceInfoOrThrow(serviceComponent, userId), isSystemProvider, - /* disableSystemAppVerificationForTests= */ false); + /* disableSystemAppVerificationForTests= */ false, + /* isEnabled= */ false); } /** @@ -98,13 +89,16 @@ public final class CredentialProviderInfo { * @param isSystemProvider whether the provider app is a system provider * @param disableSystemAppVerificationForTests whether to disable system app permission * verification so that tests can install system providers + * @param isEnabled whether the user enabled this provider * @throws SecurityException If provider does not require the relevant permission */ - public CredentialProviderInfo( + public static CredentialProviderInfo create( @NonNull Context context, @NonNull ServiceInfo serviceInfo, boolean isSystemProvider, - boolean disableSystemAppVerificationForTests) { + boolean disableSystemAppVerificationForTests, + boolean isEnabled) + throws SecurityException { verifyProviderPermission(serviceInfo); if (isSystemProvider) { if (!isValidSystemProvider( @@ -114,23 +108,11 @@ public final class CredentialProviderInfo { "Provider is not a valid system provider: " + serviceInfo); } } - mIsSystemProvider = isSystemProvider; - mContext = requireNonNull(context, "context must not be null"); - mServiceInfo = requireNonNull(serviceInfo, "serviceInfo must not be null"); - mCapabilities = new ArrayList<>(); - mIcon = mServiceInfo.loadIcon(mContext.getPackageManager()); - mLabel = - mServiceInfo.loadSafeLabel( - mContext.getPackageManager(), - 0 /* do not ellipsize */, - TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM); - Log.i( - TAG, - "mLabel is : " - + mLabel - + ", for: " - + mServiceInfo.getComponentName().flattenToString()); - populateProviderCapabilities(context, serviceInfo); + + return populateMetadata(context, serviceInfo) + .setSystemProvider(isSystemProvider) + .setEnabled(isEnabled) + .build(); } private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException { @@ -138,19 +120,14 @@ public final class CredentialProviderInfo { if (permission.equals(serviceInfo.permission)) { return; } - - Slog.e( - TAG, - "Credential Provider Service from : " - + serviceInfo.packageName - + "does not require permission" - + permission); throw new SecurityException( "Service does not require the expected permission : " + permission); } private static boolean isSystemProviderWithValidPermission( ServiceInfo serviceInfo, Context context) { + requireNonNull(context, "context must not be null"); + final String permission = Manifest.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE; try { ApplicationInfo appInfo = @@ -177,67 +154,88 @@ public final class CredentialProviderInfo { Context context, ServiceInfo serviceInfo, boolean disableSystemAppVerificationForTests) { - boolean isValidSystemTestProvider = - isTestSystemProvider(serviceInfo, disableSystemAppVerificationForTests); - if (isValidSystemTestProvider) { - return true; + requireNonNull(context, "context must not be null"); + + if (disableSystemAppVerificationForTests) { + Bundle metadata = serviceInfo.metaData; + if (metadata == null) { + Slog.e(TAG, "isValidSystemProvider - metadata is null: " + serviceInfo); + return false; + } + return metadata.getBoolean( + CredentialProviderService.TEST_SYSTEM_PROVIDER_META_DATA_KEY); } + return isSystemProviderWithValidPermission(serviceInfo, context); } - private static boolean isTestSystemProvider( - ServiceInfo serviceInfo, boolean disableSystemAppVerificationForTests) { - if (!disableSystemAppVerificationForTests) { - return false; - } + private static CredentialProviderInfo.Builder populateMetadata( + @NonNull Context context, ServiceInfo serviceInfo) { + requireNonNull(context, "context must not be null"); - Bundle metadata = serviceInfo.metaData; + final CredentialProviderInfo.Builder builder = + new CredentialProviderInfo.Builder(serviceInfo); + final PackageManager pm = context.getPackageManager(); + + // 1. Get the metadata for the service. + final Bundle metadata = serviceInfo.metaData; if (metadata == null) { - Slog.e(TAG, "metadata is null: " + serviceInfo); - return false; + Log.i(TAG, "populateMetadata - metadata is null"); + return builder; } - return metadata.getBoolean(CredentialProviderService.TEST_SYSTEM_PROVIDER_META_DATA_KEY); - } - private void populateProviderCapabilities(@NonNull Context context, ServiceInfo serviceInfo) { - final PackageManager pm = context.getPackageManager(); + // 2. Extract the capabilities from the bundle. try { - Bundle metadata = serviceInfo.metaData; Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo); if (metadata == null || resources == null) { - Log.i(TAG, "populateProviderCapabilities - metadata or resources is null"); - return; - } - - String[] capabilities = resources.getStringArray(metadata.getInt( - CredentialProviderService.CAPABILITY_META_DATA_KEY)); - if (capabilities == null || capabilities.length == 0) { - Slog.i(TAG, "No capabilities found for provider:" + serviceInfo.packageName); - return; + Log.i(TAG, "populateMetadata - resources is null"); + return builder; } - for (String capability : capabilities) { - if (capability.isEmpty()) { - Slog.i(TAG, "Skipping empty capability"); - continue; - } - Slog.i(TAG, "Capabilities found for provider: " + capability); - mCapabilities.add(capability); - } + builder.addCapabilities(populateProviderCapabilities(resources, metadata, serviceInfo)); } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, e.getMessage()); + } + + return builder; + } + + private static List<String> populateProviderCapabilities( + Resources resources, Bundle metadata, ServiceInfo serviceInfo) { + List<String> output = new ArrayList<>(); + String[] capabilities = new String[0]; + + try { + capabilities = + resources.getStringArray( + metadata.getInt(CredentialProviderService.CAPABILITY_META_DATA_KEY)); } catch (Resources.NotFoundException e) { - Slog.e(TAG, e.getMessage()); + Slog.e(TAG, "Failed to get capabilities: " + e.getMessage()); + } + + if (capabilities == null || capabilities.length == 0) { + Slog.e(TAG, "No capabilities found for provider:" + serviceInfo.packageName); + return output; } + + for (String capability : capabilities) { + if (capability.isEmpty()) { + Slog.e(TAG, "Skipping empty capability"); + continue; + } + Slog.e(TAG, "Capabilities found for provider: " + capability); + output.add(capability); + } + return output; } - private static ServiceInfo getServiceInfoOrThrow(@NonNull ComponentName serviceComponent, - int userId) throws PackageManager.NameNotFoundException { + private static ServiceInfo getServiceInfoOrThrow( + @NonNull ComponentName serviceComponent, int userId) + throws PackageManager.NameNotFoundException { try { - ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo( - serviceComponent, - PackageManager.GET_META_DATA, - userId); + ServiceInfo si = + AppGlobals.getPackageManager() + .getServiceInfo(serviceComponent, PackageManager.GET_META_DATA, userId); if (si != null) { return si; } @@ -256,6 +254,8 @@ public final class CredentialProviderInfo { @NonNull Context context, @UserIdInt int userId, boolean disableSystemAppVerificationForTests) { + requireNonNull(context, "context must not be null"); + final List<ServiceInfo> services = new ArrayList<>(); final List<ResolveInfo> resolveInfos = new ArrayList<>(); @@ -268,15 +268,20 @@ public final class CredentialProviderInfo { for (ResolveInfo resolveInfo : resolveInfos) { final ServiceInfo serviceInfo = resolveInfo.serviceInfo; + if (disableSystemAppVerificationForTests) { + if (serviceInfo != null) { + services.add(serviceInfo); + } + continue; + } + try { - PackageManager.ApplicationInfoFlags appInfoFlags = - disableSystemAppVerificationForTests - ? PackageManager.ApplicationInfoFlags.of(0) - : PackageManager.ApplicationInfoFlags.of( - PackageManager.MATCH_SYSTEM_ONLY); ApplicationInfo appInfo = context.getPackageManager() - .getApplicationInfo(serviceInfo.packageName, appInfoFlags); + .getApplicationInfo( + serviceInfo.packageName, + PackageManager.ApplicationInfoFlags.of( + PackageManager.MATCH_SYSTEM_ONLY)); if (appInfo == null || serviceInfo == null) { continue; @@ -300,19 +305,22 @@ public final class CredentialProviderInfo { public static List<CredentialProviderInfo> getAvailableSystemServices( @NonNull Context context, @UserIdInt int userId, - boolean disableSystemAppVerificationForTests) { + boolean disableSystemAppVerificationForTests, + Set<ServiceInfo> enabledServices) { requireNonNull(context, "context must not be null"); + final List<CredentialProviderInfo> providerInfos = new ArrayList<>(); for (ServiceInfo si : getAvailableSystemServiceInfos( context, userId, disableSystemAppVerificationForTests)) { try { CredentialProviderInfo cpi = - new CredentialProviderInfo( + CredentialProviderInfoFactory.create( context, si, /* isSystemProvider= */ true, - disableSystemAppVerificationForTests); + disableSystemAppVerificationForTests, + enabledServices.contains(si)); if (cpi.isSystemProvider()) { providerInfos.add(cpi); } else { @@ -325,45 +333,12 @@ public final class CredentialProviderInfo { return providerInfos; } - /** - * Returns true if the service supports the given {@code credentialType}, false otherwise. - */ - @NonNull - public boolean hasCapability(@NonNull String credentialType) { - return mCapabilities.contains(credentialType); - } - - /** Returns the service info. */ - @NonNull - public ServiceInfo getServiceInfo() { - return mServiceInfo; - } - - public boolean isSystemProvider() { - return mIsSystemProvider; - } - - /** Returns the service icon. */ - @Nullable - public Drawable getServiceIcon() { - return mIcon; - } - - /** Returns the service label. */ - @Nullable - public CharSequence getServiceLabel() { - return mLabel; - } - - /** Returns an immutable list of capabilities this provider service can support. */ - @NonNull - public List<String> getCapabilities() { - return Collections.unmodifiableList(mCapabilities); - } + private static @Nullable PackagePolicy getDeviceManagerPolicy( + @NonNull Context context, int userId) { + Context newContext = context.createContextAsUser(UserHandle.of(userId), 0); - private static @Nullable PackagePolicy getDeviceManagerPolicy(@NonNull Context context) { try { - DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); + DevicePolicyManager dpm = newContext.getSystemService(DevicePolicyManager.class); return dpm.getCredentialManagerPolicy(); } catch (SecurityException e) { // If the current user is not enrolled in DPM then this can throw a security error. @@ -381,21 +356,53 @@ public final class CredentialProviderInfo { public static List<CredentialProviderInfo> getCredentialProviderServices( @NonNull Context context, int userId, - boolean disableSystemAppVerificationForTests, - int providerFilter) { + int providerFilter, + Set<ServiceInfo> enabledServices) { + requireNonNull(context, "context must not be null"); + + // Get the device policy. + PackagePolicy pp = getDeviceManagerPolicy(context, userId); + + // Generate the provider list. + final boolean disableSystemAppVerificationForTests = false; + ProviderGenerator generator = + new ProviderGenerator( + context, pp, disableSystemAppVerificationForTests, providerFilter); + generator.addUserProviders( + getUserProviders( + context, userId, disableSystemAppVerificationForTests, enabledServices)); + generator.addSystemProviders( + getAvailableSystemServices( + context, userId, disableSystemAppVerificationForTests, enabledServices)); + return generator.getProviders(); + } + + /** + * Returns the valid credential provider services available for the user with the given {@code + * userId}. Includes test providers. + */ + @NonNull + public static List<CredentialProviderInfo> getCredentialProviderServicesForTesting( + @NonNull Context context, + int userId, + int providerFilter, + Set<ServiceInfo> enabledServices) { requireNonNull(context, "context must not be null"); // Get the device policy. - PackagePolicy pp = getDeviceManagerPolicy(context); + PackagePolicy pp = getDeviceManagerPolicy(context, userId); // Generate the provider list. + final boolean disableSystemAppVerificationForTests = true; ProviderGenerator generator = new ProviderGenerator( context, pp, disableSystemAppVerificationForTests, providerFilter); generator.addUserProviders( - getUserProviders(context, userId, disableSystemAppVerificationForTests)); + getUserProviders( + context, userId, disableSystemAppVerificationForTests, enabledServices)); generator.addSystemProviders( - getAvailableSystemServices(context, userId, disableSystemAppVerificationForTests)); + getAvailableSystemServices( + context, userId, disableSystemAppVerificationForTests, enabledServices)); return generator.getProviders(); } @@ -484,7 +491,8 @@ public final class CredentialProviderInfo { private static List<CredentialProviderInfo> getUserProviders( @NonNull Context context, @UserIdInt int userId, - boolean disableSystemAppVerificationForTests) { + boolean disableSystemAppVerificationForTests, + Set<ServiceInfo> enabledServices) { final List<CredentialProviderInfo> services = new ArrayList<>(); final List<ResolveInfo> resolveInfos = context.getPackageManager() @@ -496,11 +504,12 @@ public final class CredentialProviderInfo { final ServiceInfo serviceInfo = resolveInfo.serviceInfo; try { CredentialProviderInfo cpi = - new CredentialProviderInfo( + CredentialProviderInfoFactory.create( context, serviceInfo, /* isSystemProvider= */ false, - disableSystemAppVerificationForTests); + disableSystemAppVerificationForTests, + enabledServices.contains(serviceInfo)); if (!cpi.isSystemProvider()) { services.add(cpi); } diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index 85aea85907b5..4a7ed644f9e2 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -32,9 +32,10 @@ import java.util.Map; * * Use {@link #obtain} to retrieve a new instance of the class when you are going * to begin tracking. Put the motion events you receive into it with - * {@link #addMovement(MotionEvent)}. When you want to determine the velocity call - * {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)} - * and {@link #getYVelocity(int)} to retrieve the velocity for each pointer id. + * {@link #addMovement(MotionEvent)}. When you want to determine the velocity, call + * {@link #computeCurrentVelocity(int)} and then call the velocity-getter methods like + * {@link #getXVelocity(int)}, {@link #getYVelocity(int)}, or {@link #getAxisVelocity(int, int)} + * to retrieve velocity for different axes and/or pointer IDs. */ public final class VelocityTracker { private static final SynchronizedPool<VelocityTracker> sPool = diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp index 4bc567abf27a..ad54004ec81c 100644 --- a/core/jni/android_view_InputEventSender.cpp +++ b/core/jni/android_view_InputEventSender.cpp @@ -20,20 +20,21 @@ #include <android_runtime/AndroidRuntime.h> #include <input/InputTransport.h> +#include <inttypes.h> #include <log/log.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedLocalRef.h> #include <utils/Looper.h> + +#include <optional> +#include <unordered_map> + #include "android_os_MessageQueue.h" #include "android_view_InputChannel.h" #include "android_view_KeyEvent.h" #include "android_view_MotionEvent.h" #include "core_jni_helpers.h" -#include <inttypes.h> -#include <unordered_map> - - using android::base::Result; namespace android { @@ -67,7 +68,7 @@ private: jobject mSenderWeakGlobal; InputPublisher mInputPublisher; sp<MessageQueue> mMessageQueue; - std::unordered_map<uint32_t, uint32_t> mPublishedSeqMap; + std::unordered_map<uint32_t, std::optional<uint32_t>> mPublishedSeqMap; uint32_t mNextPublishedSeq; @@ -165,8 +166,14 @@ status_t NativeInputEventSender::sendMotionEvent(uint32_t seq, const MotionEvent getInputChannelName().c_str(), status); return status; } + // mPublishedSeqMap tracks all sequences published from this sender. Only the last + // sequence number is used to signal this motion event is finished. + if (i == event->getHistorySize()) { + mPublishedSeqMap.emplace(publishedSeq, seq); + } else { + mPublishedSeqMap.emplace(publishedSeq, std::nullopt); + } } - mPublishedSeqMap.emplace(publishedSeq, seq); return OK; } @@ -277,8 +284,16 @@ bool NativeInputEventSender::notifyConsumerResponse( // does something wrong and sends bad data. Just ignore and process other events. return true; } - const uint32_t seq = it->second; + + const std::optional<uint32_t> seqOptional = it->second; mPublishedSeqMap.erase(it); + // If this optional does not have a value, it means we are processing an event that had history + // and was split. There are more events coming, so we can't call 'dispatchInputEventFinished' + // yet. The final split event will have a valid sequence number. + if (!seqOptional.has_value()) { + return true; + } + const uint32_t seq = seqOptional.value(); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.", diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java index 43334ab08b2f..e31d5aef9b69 100644 --- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java +++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.when; import android.app.Activity; import android.app.slice.Slice; import android.content.Context; +import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; @@ -47,6 +48,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -59,6 +61,17 @@ public class CredentialManagerTest { @Mock private Activity mMockActivity; + private static final int TEST_USER_ID = 1; + private static final CredentialProviderInfo TEST_CREDENTIAL_PROVIDER_INFO = + new CredentialProviderInfo.Builder(new ServiceInfo()) + .setSystemProvider(true) + .setOverrideLabel("test") + .addCapabilities(Arrays.asList("passkey")) + .setEnabled(true) + .build(); + private static final List<CredentialProviderInfo> TEST_CREDENTIAL_PROVIDER_INFO_LIST = + Arrays.asList(TEST_CREDENTIAL_PROVIDER_INFO); + private GetCredentialRequest mGetRequest; private CreateCredentialRequest mCreateRequest; @@ -438,95 +451,53 @@ public class CredentialManagerTest { } @Test - public void testListEnabledProviders_nullExecutor() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.listEnabledProviders(null, null, result -> { - })); - + public void testGetCredentialProviderServices_allProviders() throws RemoteException { + verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS); } @Test - public void testListEnabledProviders_nullCallback() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.listEnabledProviders(null, mExecutor, null)); - + public void testGetCredentialProviderServices_userProviders() throws RemoteException { + verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY); } @Test - public void testListEnabledProviders_alreadyCancelled() throws RemoteException { - final CancellationSignal cancellation = new CancellationSignal(); - cancellation.cancel(); - - mCredentialManager.listEnabledProviders(cancellation, mExecutor, result -> { - }); - - verify(mMockCredentialManagerService, never()).listEnabledProviders(any()); + public void testGetCredentialProviderServices_systemProviders() throws RemoteException { + verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY); } @Test - public void testListEnabledProviders_cancel() throws RemoteException { - final ICancellationSignal serviceSignal = mock(ICancellationSignal.class); - final CancellationSignal cancellation = new CancellationSignal(); - - OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> callback = - mock(OutcomeReceiver.class); - - when(mMockCredentialManagerService.listEnabledProviders(any())).thenReturn(serviceSignal); - - mCredentialManager.listEnabledProviders(cancellation, mExecutor, callback); - - verify(mMockCredentialManagerService).listEnabledProviders(any()); - - cancellation.cancel(); - verify(serviceSignal).cancel(); + public void testGetCredentialProviderServicesForTesting_allProviders() throws RemoteException { + verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS); } @Test - public void testListEnabledProviders_failed() throws RemoteException { - ArgumentCaptor<IListEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass( - IListEnabledProvidersCallback.class); - ArgumentCaptor<ListEnabledProvidersException> errorCaptor = ArgumentCaptor.forClass( - ListEnabledProvidersException.class); - - OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> callback = - mock(OutcomeReceiver.class); - - when(mMockCredentialManagerService.listEnabledProviders( - callbackCaptor.capture())).thenReturn(mock(ICancellationSignal.class)); - mCredentialManager.listEnabledProviders(null, mExecutor, callback); - verify(mMockCredentialManagerService).listEnabledProviders(any()); - - final String errorType = "type"; - callbackCaptor.getValue().onError("type", "unknown error"); - verify(callback).onError(errorCaptor.capture()); - - assertThat(errorCaptor.getValue().getType()).isEqualTo(errorType); + public void testGetCredentialProviderServicesForTesting_userProviders() throws RemoteException { + verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY); } @Test - public void testListEnabledProviders_success() throws RemoteException { - ListEnabledProvidersResponse response = ListEnabledProvidersResponse.create( - List.of("foo", "bar", "baz")); + public void testGetCredentialProviderServicesForTesting_systemProviders() throws RemoteException { + verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY); + } - OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> callback = - mock(OutcomeReceiver.class); + private void verifyGetCredentialProviderServices(int testFilter) throws RemoteException { + when(mMockCredentialManagerService.getCredentialProviderServices( + TEST_USER_ID, testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST); - ArgumentCaptor<IListEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass( - IListEnabledProvidersCallback.class); - ArgumentCaptor<ListEnabledProvidersResponse> responseCaptor = ArgumentCaptor.forClass( - ListEnabledProvidersResponse.class); + List<CredentialProviderInfo> output = + mCredentialManager.getCredentialProviderServices(TEST_USER_ID, testFilter); - when(mMockCredentialManagerService.listEnabledProviders( - callbackCaptor.capture())).thenReturn(mock(ICancellationSignal.class)); - mCredentialManager.listEnabledProviders(null, mExecutor, callback); + assertThat(output).containsExactlyElementsIn(TEST_CREDENTIAL_PROVIDER_INFO_LIST); + } - verify(mMockCredentialManagerService).listEnabledProviders(any()); + private void verifyGetCredentialProviderServicesForTesting(int testFilter) throws RemoteException { + when(mMockCredentialManagerService.getCredentialProviderServicesForTesting( + testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST); - callbackCaptor.getValue().onResponse(response); + List<CredentialProviderInfo> output = + mCredentialManager.getCredentialProviderServicesForTesting(testFilter); - verify(callback).onResult(responseCaptor.capture()); - assertThat(responseCaptor.getValue().getProviderComponentNames()).containsExactlyElementsIn( - response.getProviderComponentNames()); + assertThat(output).containsExactlyElementsIn(TEST_CREDENTIAL_PROVIDER_INFO_LIST); } @Test diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 70d3b3509b2f..146abea2bc31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -764,17 +764,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); if (pendingIntent2 == null) { - // Launching a solo task. - ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); - activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); - options1 = activityOptions.toBundle(); - addActivityOptions(options1, null /* launchTarget */); - if (shortcutInfo1 != null) { - wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1); - } else { - wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1); - } - mSyncQueue.queue(wct); + // Launching a solo intent or shortcut as fullscreen. + launchAsFullscreenWithRemoteAnimation(pendingIntent1, fillInIntent1, shortcutInfo1, + options1, adapter, wct); return; } @@ -797,13 +789,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); if (taskId == INVALID_TASK_ID) { - // Launching a solo task. - ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); - activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); - options1 = activityOptions.toBundle(); - addActivityOptions(options1, null /* launchTarget */); - wct.sendPendingIntent(pendingIntent, fillInIntent, options1); - mSyncQueue.queue(wct); + // Launching a solo intent as fullscreen. + launchAsFullscreenWithRemoteAnimation(pendingIntent, fillInIntent, null, options1, + adapter, wct); return; } @@ -822,13 +810,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); if (taskId == INVALID_TASK_ID) { - // Launching a solo task. - ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); - activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); - options1 = activityOptions.toBundle(); - addActivityOptions(options1, null /* launchTarget */); - wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1); - mSyncQueue.queue(wct); + // Launching a solo shortcut as fullscreen. + launchAsFullscreenWithRemoteAnimation(null, null, shortcutInfo, options1, adapter, wct); return; } @@ -838,6 +821,49 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, instanceId); } + private void launchAsFullscreenWithRemoteAnimation(@Nullable PendingIntent pendingIntent, + @Nullable Intent fillInIntent, @Nullable ShortcutInfo shortcutInfo, + @Nullable Bundle options, RemoteAnimationAdapter adapter, + WindowContainerTransaction wct) { + LegacyTransitions.ILegacyTransition transition = + (transit, apps, wallpapers, nonApps, finishedCallback, t) -> { + if (apps == null || apps.length == 0) { + onRemoteAnimationFinished(apps); + t.apply(); + try { + adapter.getRunner().onAnimationCancelled(mKeyguardShowing); + } catch (RemoteException e) { + Slog.e(TAG, "Error starting remote animation", e); + } + return; + } + + for (int i = 0; i < apps.length; ++i) { + if (apps[i].mode == MODE_OPENING) { + t.show(apps[i].leash); + } + } + t.apply(); + + try { + adapter.getRunner().onAnimationStart( + transit, apps, wallpapers, nonApps, finishedCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Error starting remote animation", e); + } + }; + + addActivityOptions(options, null /* launchTarget */); + if (shortcutInfo != null) { + wct.startShortcut(mContext.getPackageName(), shortcutInfo, options); + } else if (pendingIntent != null) { + wct.sendPendingIntent(pendingIntent, fillInIntent, options); + } else { + Slog.e(TAG, "Pending intent and shortcut are null is invalid case."); + } + mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); + } + private void startWithLegacyTransition(WindowContainerTransaction wct, @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent, @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions, @@ -894,23 +920,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (options == null) options = new Bundle(); addActivityOptions(options, mMainStage); - options = wrapAsSplitRemoteAnimation(adapter, options); updateWindowBounds(mSplitLayout, wct); + wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); // TODO(b/268008375): Merge APIs to start a split pair into one. if (mainTaskId != INVALID_TASK_ID) { + options = wrapAsSplitRemoteAnimation(adapter, options); wct.startTask(mainTaskId, options); - } else if (mainShortcutInfo != null) { - wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options); + mSyncQueue.queue(wct); } else { - wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options); + if (mainShortcutInfo != null) { + wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options); + } else { + wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options); + } + mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct); } - wct.reorder(mRootTaskInfo.token, true); - wct.setForceTranslucent(mRootTaskInfo.token, false); - - mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { setDividerVisibility(true, t); }); @@ -967,6 +995,54 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return activityOptions.toBundle(); } + private LegacyTransitions.ILegacyTransition wrapAsSplitRemoteAnimation( + RemoteAnimationAdapter adapter) { + LegacyTransitions.ILegacyTransition transition = + (transit, apps, wallpapers, nonApps, finishedCallback, t) -> { + if (apps == null || apps.length == 0) { + onRemoteAnimationFinished(apps); + t.apply(); + try { + adapter.getRunner().onAnimationCancelled(mKeyguardShowing); + } catch (RemoteException e) { + Slog.e(TAG, "Error starting remote animation", e); + } + return; + } + + // Wrap the divider bar into non-apps target to animate together. + nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps, + getDividerBarLegacyTarget()); + + for (int i = 0; i < apps.length; ++i) { + if (apps[i].mode == MODE_OPENING) { + t.show(apps[i].leash); + // Reset the surface position of the opening app to prevent offset. + t.setPosition(apps[i].leash, 0, 0); + } + } + t.apply(); + + IRemoteAnimationFinishedCallback wrapCallback = + new IRemoteAnimationFinishedCallback.Stub() { + @Override + public void onAnimationFinished() throws RemoteException { + onRemoteAnimationFinished(apps); + finishedCallback.onAnimationFinished(); + } + }; + Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication()); + try { + adapter.getRunner().onAnimationStart( + transit, apps, wallpapers, nonApps, wrapCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Error starting remote animation", e); + } + }; + + return transition; + } + private void setEnterInstanceId(InstanceId instanceId) { if (instanceId != null) { mLogger.enterRequested(instanceId, ENTER_REASON_LAUNCHER); @@ -993,6 +1069,27 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) { + mIsDividerRemoteAnimating = false; + mShouldUpdateRecents = true; + mSplitRequest = null; + // If any stage has no child after finished animation, that side of the split will display + // nothing. This might happen if starting the same app on the both sides while not + // supporting multi-instance. Exit the split screen and expand that app to full screen. + if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { + mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0 + ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); + mSplitUnsupportedToast.show(); + return; + } + + final WindowContainerTransaction evictWct = new WindowContainerTransaction(); + prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct); + prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct); + mSyncQueue.queue(evictWct); + } + + /** * Collects all the current child tasks of a specific split and prepares transaction to evict * them to display. diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java index 5ecec4ddd1ad..3125f088c72b 100644 --- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java +++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java @@ -72,6 +72,7 @@ public final class LowLightDreamManager { public static final int AMBIENT_LIGHT_MODE_LOW_LIGHT = 2; private final DreamManager mDreamManager; + private final LowLightTransitionCoordinator mLowLightTransitionCoordinator; @Nullable private final ComponentName mLowLightDreamComponent; @@ -81,8 +82,10 @@ public final class LowLightDreamManager { @Inject public LowLightDreamManager( DreamManager dreamManager, + LowLightTransitionCoordinator lowLightTransitionCoordinator, @Named(LOW_LIGHT_DREAM_COMPONENT) @Nullable ComponentName lowLightDreamComponent) { mDreamManager = dreamManager; + mLowLightTransitionCoordinator = lowLightTransitionCoordinator; mLowLightDreamComponent = lowLightDreamComponent; } @@ -111,7 +114,9 @@ public final class LowLightDreamManager { mAmbientLightMode = ambientLightMode; - mDreamManager.setSystemDreamComponent(mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT - ? mLowLightDreamComponent : null); + boolean shouldEnterLowLight = mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT; + mLowLightTransitionCoordinator.notifyBeforeLowLightTransition(shouldEnterLowLight, + () -> mDreamManager.setSystemDreamComponent( + shouldEnterLowLight ? mLowLightDreamComponent : null)); } } diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java new file mode 100644 index 000000000000..874a2d5af75e --- /dev/null +++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 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.dream.lowlight; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.Nullable; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Helper class that allows listening and running animations before entering or exiting low light. + */ +@Singleton +public class LowLightTransitionCoordinator { + /** + * Listener that is notified before low light entry. + */ + public interface LowLightEnterListener { + /** + * Callback that is notified before the device enters low light. + * + * @return an optional animator that will be waited upon before entering low light. + */ + Animator onBeforeEnterLowLight(); + } + + /** + * Listener that is notified before low light exit. + */ + public interface LowLightExitListener { + /** + * Callback that is notified before the device exits low light. + * + * @return an optional animator that will be waited upon before exiting low light. + */ + Animator onBeforeExitLowLight(); + } + + private LowLightEnterListener mLowLightEnterListener; + private LowLightExitListener mLowLightExitListener; + + @Inject + public LowLightTransitionCoordinator() { + } + + /** + * Sets the listener for the low light enter event. + * + * Only one listener can be set at a time. This method will overwrite any previously set + * listener. Null can be used to unset the listener. + */ + public void setLowLightEnterListener(@Nullable LowLightEnterListener lowLightEnterListener) { + mLowLightEnterListener = lowLightEnterListener; + } + + /** + * Sets the listener for the low light exit event. + * + * Only one listener can be set at a time. This method will overwrite any previously set + * listener. Null can be used to unset the listener. + */ + public void setLowLightExitListener(@Nullable LowLightExitListener lowLightExitListener) { + mLowLightExitListener = lowLightExitListener; + } + + /** + * Notifies listeners that the device is about to enter or exit low light. + * + * @param entering true if listeners should be notified before entering low light, false if this + * is notifying before exiting. + * @param callback callback that will be run after listeners complete. + */ + void notifyBeforeLowLightTransition(boolean entering, Runnable callback) { + Animator animator = null; + + if (entering && mLowLightEnterListener != null) { + animator = mLowLightEnterListener.onBeforeEnterLowLight(); + } else if (!entering && mLowLightExitListener != null) { + animator = mLowLightExitListener.onBeforeExitLowLight(); + } + + // If the listener returned an animator to indicate it was running an animation, run the + // callback after the animation completes, otherwise call the callback directly. + if (animator != null) { + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + callback.run(); + } + }); + } else { + callback.run(); + } + } +} diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java index 91a170f7ae14..4b95d8c84bac 100644 --- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java +++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java @@ -21,7 +21,10 @@ import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_UNKNOWN; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -44,44 +47,52 @@ public class LowLightDreamManagerTest { private DreamManager mDreamManager; @Mock + private LowLightTransitionCoordinator mTransitionCoordinator; + + @Mock private ComponentName mDreamComponent; + LowLightDreamManager mLowLightDreamManager; + @Before public void setUp() { MockitoAnnotations.initMocks(this); + + // Automatically run any provided Runnable to mTransitionCoordinator to simplify testing. + doAnswer(invocation -> { + ((Runnable) invocation.getArgument(1)).run(); + return null; + }).when(mTransitionCoordinator).notifyBeforeLowLightTransition(anyBoolean(), + any(Runnable.class)); + + mLowLightDreamManager = new LowLightDreamManager(mDreamManager, mTransitionCoordinator, + mDreamComponent); } @Test public void setAmbientLightMode_lowLight_setSystemDream() { - final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager, - mDreamComponent); - - lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT); + mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT); + verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(true), any()); verify(mDreamManager).setSystemDreamComponent(mDreamComponent); } @Test public void setAmbientLightMode_regularLight_clearSystemDream() { - final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager, - mDreamComponent); - - lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR); + mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR); + verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(false), any()); verify(mDreamManager).setSystemDreamComponent(null); } @Test public void setAmbientLightMode_defaultUnknownMode_clearSystemDream() { - final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager, - mDreamComponent); - // Set to low light first. - lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT); + mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT); clearInvocations(mDreamManager); // Return to default unknown mode. - lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN); + mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN); verify(mDreamManager).setSystemDreamComponent(null); } @@ -89,7 +100,7 @@ public class LowLightDreamManagerTest { @Test public void setAmbientLightMode_dreamComponentNotSet_doNothing() { final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager, - null /*dream component*/); + mTransitionCoordinator, null /*dream component*/); lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT); diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java new file mode 100644 index 000000000000..81e1e33d6220 --- /dev/null +++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2023 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.dream.lowlight; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.animation.Animator; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class LowLightTransitionCoordinatorTest { + @Mock + private LowLightTransitionCoordinator.LowLightEnterListener mEnterListener; + + @Mock + private LowLightTransitionCoordinator.LowLightExitListener mExitListener; + + @Mock + private Animator mAnimator; + + @Captor + private ArgumentCaptor<Animator.AnimatorListener> mAnimatorListenerCaptor; + + @Mock + private Runnable mRunnable; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void onEnterCalledOnListeners() { + LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator(); + + coordinator.setLowLightEnterListener(mEnterListener); + + coordinator.notifyBeforeLowLightTransition(true, mRunnable); + + verify(mEnterListener).onBeforeEnterLowLight(); + verify(mRunnable).run(); + } + + @Test + public void onExitCalledOnListeners() { + LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator(); + + coordinator.setLowLightExitListener(mExitListener); + + coordinator.notifyBeforeLowLightTransition(false, mRunnable); + + verify(mExitListener).onBeforeExitLowLight(); + verify(mRunnable).run(); + } + + @Test + public void listenerNotCalledAfterRemoval() { + LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator(); + + coordinator.setLowLightEnterListener(mEnterListener); + coordinator.setLowLightEnterListener(null); + + coordinator.notifyBeforeLowLightTransition(true, mRunnable); + + verifyZeroInteractions(mEnterListener); + verify(mRunnable).run(); + } + + @Test + public void runnableCalledAfterAnimationEnds() { + when(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator); + + LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator(); + coordinator.setLowLightEnterListener(mEnterListener); + + coordinator.notifyBeforeLowLightTransition(true, mRunnable); + + // Animator listener is added and the runnable is not run yet. + verify(mAnimator).addListener(mAnimatorListenerCaptor.capture()); + verifyZeroInteractions(mRunnable); + + // Runnable is run once the animation ends. + mAnimatorListenerCaptor.getValue().onAnimationEnd(null); + verify(mRunnable).run(); + } +} diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java index f85bdee18967..5f5e214357ea 100644 --- a/media/java/android/media/audiopolicy/AudioMix.java +++ b/media/java/android/media/audiopolicy/AudioMix.java @@ -252,10 +252,10 @@ public class AudioMix { if (o == null || getClass() != o.getClass()) return false; final AudioMix that = (AudioMix) o; - return (this.mRouteFlags == that.mRouteFlags) - && (this.mRule == that.mRule) - && (this.mMixType == that.mMixType) - && (this.mFormat == that.mFormat); + return (mRouteFlags == that.mRouteFlags) + && (mMixType == that.mMixType) + && Objects.equals(mRule, that.mRule) + && Objects.equals(mFormat, that.mFormat); } /** @hide */ diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java index 440447e5ec1d..ce9773312a10 100644 --- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java +++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java @@ -24,6 +24,7 @@ import android.os.Parcelable; import android.util.Log; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Objects; @@ -50,7 +51,8 @@ public class AudioPolicyConfig implements Parcelable { mMixes = conf.mMixes; } - AudioPolicyConfig(ArrayList<AudioMix> mixes) { + @VisibleForTesting + public AudioPolicyConfig(ArrayList<AudioMix> mixes) { mMixes = mixes; } diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java index d70e8b36afdb..178a6d97dff8 100644 --- a/media/java/android/media/projection/MediaProjection.java +++ b/media/java/android/media/projection/MediaProjection.java @@ -191,20 +191,13 @@ public final class MediaProjection { } else { session = ContentRecordingSession.createTaskSession(launchCookie); } + // Pass in the current session details, so they are guaranteed to only be set in WMS + // AFTER a VirtualDisplay is constructed (assuming there are no errors during set-up). + virtualDisplayConfig.setContentRecordingSession(session); virtualDisplayConfig.setWindowManagerMirroringEnabled(true); final DisplayManager dm = mContext.getSystemService(DisplayManager.class); final VirtualDisplay virtualDisplay = dm.createVirtualDisplay(this, virtualDisplayConfig.build(), callback, handler, windowContext); - if (virtualDisplay == null) { - // Since WM handling a new display and DM creating a new VirtualDisplay is async, - // WM may have tried to start task recording and encountered an error that required - // stopping recording entirely. The VirtualDisplay would then be null when the - // MediaProjection is no longer active. - return null; - } - session.setDisplayId(virtualDisplay.getDisplay().getDisplayId()); - // Successfully set up, so save the current session details. - getProjectionService().setContentRecordingSession(session, mImpl); return virtualDisplay; } catch (RemoteException e) { // Can not capture if WMS is not accessible, so bail out. diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp index 63292ce766f8..4624dfe70756 100644 --- a/media/tests/AudioPolicyTest/Android.bp +++ b/media/tests/AudioPolicyTest/Android.bp @@ -14,6 +14,7 @@ android_test { "androidx.test.ext.junit", "androidx.test.rules", "guava", + "guava-android-testlib", "hamcrest-library", "platform-test-annotations", ], diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java new file mode 100644 index 000000000000..bbca8823dde4 --- /dev/null +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2023 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.audiopolicytest; + +import static android.media.AudioFormat.CHANNEL_OUT_MONO; +import static android.media.AudioFormat.CHANNEL_OUT_STEREO; +import static android.media.AudioFormat.ENCODING_PCM_16BIT; +import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR; +import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS; +import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID; +import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID; + +import android.media.AudioFormat; +import android.media.audiopolicy.AudioMix; +import android.media.audiopolicy.AudioMixingRule; +import android.media.audiopolicy.AudioPolicyConfig; +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.google.common.testing.EqualsTester; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit tests for AudioMix. + * + * Run with "atest AudioMixUnitTests". + */ +@Presubmit +@RunWith(AndroidJUnit4.class) +public class AudioMixUnitTests { + private static final AudioFormat OUTPUT_FORMAT_STEREO_44KHZ_PCM = + new AudioFormat.Builder() + .setSampleRate(44000) + .setChannelMask(CHANNEL_OUT_STEREO) + .setEncoding(ENCODING_PCM_16BIT).build(); + private static final AudioFormat OUTPUT_FORMAT_MONO_16KHZ_PCM = + new AudioFormat.Builder() + .setSampleRate(16000) + .setChannelMask(CHANNEL_OUT_MONO) + .setEncoding(ENCODING_PCM_16BIT).build(); + private static final AudioFormat INPUT_FORMAT_MONO_16KHZ_PCM = + new AudioFormat.Builder() + .setSampleRate(16000) + .setChannelMask(AudioFormat.CHANNEL_IN_MONO) + .setEncoding(ENCODING_PCM_16BIT).build(); + + @Test + public void testEquals() { + final EqualsTester equalsTester = new EqualsTester(); + + // --- Equality group 1 + final AudioMix playbackAudioMixWithSessionId42AndUid123 = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_PLAYERS) + .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) + .addMixRule(RULE_MATCH_UID, 123).build()) + .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); + final AudioMix playbackAudioMixWithUid123AndSessionId42 = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_PLAYERS) + .addMixRule(RULE_MATCH_UID, 123) + .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42).build()) + .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); + equalsTester.addEqualityGroup( + playbackAudioMixWithSessionId42AndUid123, + playbackAudioMixWithUid123AndSessionId42, + writeToAndFromParcel(playbackAudioMixWithSessionId42AndUid123), + writeToAndFromParcel(playbackAudioMixWithUid123AndSessionId42)); + + // --- Equality group 2 + final AudioMix recordingAudioMixWithSessionId42AndUid123 = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_INJECTOR) + .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) + .addMixRule(RULE_MATCH_UID, 123).build()) + .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); + final AudioMix recordingAudioMixWithUid123AndSessionId42 = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_INJECTOR) + .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) + .addMixRule(RULE_MATCH_UID, 123).build()) + .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); + equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123, + recordingAudioMixWithUid123AndSessionId42, + writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123), + writeToAndFromParcel(recordingAudioMixWithUid123AndSessionId42)); + + // --- Equality group 3 + final AudioMix recordingAudioMixWithSessionId42AndUid123Render = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_INJECTOR) + .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) + .addMixRule(RULE_MATCH_UID, 123).build()) + .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM) + .setRouteFlags( + AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER).build(); + equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123Render, + writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123Render)); + + // --- Equality group 4 + final AudioMix playbackAudioMixWithUid123 = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_PLAYERS) + .addMixRule(RULE_MATCH_UID, 123).build()) + .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); + equalsTester.addEqualityGroup(playbackAudioMixWithUid123, + writeToAndFromParcel(playbackAudioMixWithUid123)); + + // --- Equality group 5 + final AudioMix playbackAudioMixWithUid42 = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_PLAYERS) + .addMixRule(RULE_MATCH_UID, 42).build()) + .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); + equalsTester.addEqualityGroup(playbackAudioMixWithUid42, + writeToAndFromParcel(playbackAudioMixWithUid42)); + + equalsTester.testEquals(); + } + + private static AudioMix writeToAndFromParcel(AudioMix audioMix) { + AudioPolicyConfig apc = new AudioPolicyConfig(new ArrayList<>(List.of(audioMix))); + Parcel parcel = Parcel.obtain(); + apc.writeToParcel(parcel, /*flags=*/0); + parcel.setDataPosition(0); + AudioMix unmarshalledMix = + AudioPolicyConfig.CREATOR.createFromParcel(parcel).getMixes().get(0); + parcel.recycle(); + return unmarshalledMix; + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 418011acf6f3..27c8cdfe98f3 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3056,11 +3056,11 @@ public class SettingsProvider extends ContentProvider { final int key = makeKey(type, userId); boolean success = false; - boolean isNewSetting = false; + boolean wasUnsetNonPredefinedSetting = false; SettingsState settingsState = peekSettingsStateLocked(key); if (settingsState != null) { - if (!settingsState.hasSetting(name)) { - isNewSetting = true; + if (!isSettingPreDefined(name, type) && !settingsState.hasSetting(name)) { + wasUnsetNonPredefinedSetting = true; } success = settingsState.insertSettingLocked(name, value, tag, makeDefault, forceNonSystemPackage, packageName, @@ -3073,9 +3073,9 @@ public class SettingsProvider extends ContentProvider { if (forceNotify || success) { notifyForSettingsChange(key, name); - if (isNewSetting && !isSettingPreDefined(name, type)) { - // Increment the generation number for all null settings because a new - // non-predefined setting has been inserted + if (wasUnsetNonPredefinedSetting) { + // Increment the generation number for all non-predefined, unset settings, + // because a new non-predefined setting has been inserted mGenerationRegistry.incrementGenerationForUnsetSettings(key); } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 17a94b8639d0..296c2ae5cf99 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -419,7 +419,7 @@ class ActivityLaunchAnimator( internal val delegate: AnimationDelegate init { - delegate = AnimationDelegate(controller, callback, launchAnimator, listener) + delegate = AnimationDelegate(controller, callback, listener, launchAnimator) } @BinderThread @@ -446,10 +446,10 @@ class ActivityLaunchAnimator( constructor( private val controller: Controller, private val callback: Callback, - /** The animator to use to animate the window launch. */ - private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR, /** Listener for animation lifecycle events. */ - private val listener: Listener? = null + private val listener: Listener? = null, + /** The animator to use to animate the window launch. */ + private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR ) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> { private val launchContainer = controller.launchContainer private val context = launchContainer.context diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index e65c327736e1..8f90724c09b9 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -782,7 +782,7 @@ <!-- Duration in milliseconds of the dream in complications fade-in animation. --> <integer name="config_dreamOverlayInComplicationsDurationMs">250</integer> <!-- Duration in milliseconds of the y-translation animation when entering a dream --> - <integer name="config_dreamOverlayInTranslationYDurationMs">917</integer> + <integer name="config_dreamOverlayInTranslationYDurationMs">1167</integer> <!-- Delay in milliseconds before switching to the dock user and dreaming if a secondary user is active when the device is locked and docked. 0 indicates disabled. Default is 1 minute. --> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index aba3fc4615c9..0f2ce444f225 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -196,9 +196,6 @@ <!-- Increased height of a small notification in the status bar --> <dimen name="notification_min_height_increased">146dp</dimen> - <!-- Increased height of a collapsed media notification in the status bar --> - <dimen name="notification_min_height_media">160dp</dimen> - <!-- Height of a small notification in the status bar which was used before android N --> <dimen name="notification_min_height_legacy">64dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 2501be93d189..e049ae09b1de 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -351,9 +351,20 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv } private void animateFromMinimized() { - mIsMinimized = false; - setExpandedView(); - animateIn(); + if (mEnterAnimator != null && mEnterAnimator.isRunning()) { + mEnterAnimator.cancel(); + } + mEnterAnimator = mView.getMinimizedFadeoutAnimation(); + mEnterAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mIsMinimized = false; + setExpandedView(); + animateIn(); + } + }); + mEnterAnimator.start(); } private String getAccessibilityAnnouncement(ClipboardModel.Type type) { diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java index f372bb4bc7f2..28c57d31a4f3 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java @@ -21,6 +21,7 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.Nullable; @@ -286,6 +287,20 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { mActionChips.clear(); } + Animator getMinimizedFadeoutAnimation() { + ObjectAnimator anim = ObjectAnimator.ofFloat(mMinimizedPreview, "alpha", 1, 0); + anim.setDuration(66); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mMinimizedPreview.setVisibility(View.GONE); + mMinimizedPreview.setAlpha(1); + } + }); + return anim; + } + Animator getEnterAnimation() { if (mAccessibilityManager.isEnabled()) { mDismissButton.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt index ca1cef385755..d0a92f0846d0 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt @@ -43,7 +43,6 @@ import com.android.systemui.util.concurrency.DelayableExecutor import javax.inject.Inject import javax.inject.Named import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch @@ -131,9 +130,17 @@ constructor( } } - /** Starts the dream content and dream overlay entry animations. */ + /** + * Starts the dream content and dream overlay entry animations. + * + * @param downwards if true, the entry animation translations downwards into position rather + * than upwards. + */ @JvmOverloads - fun startEntryAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) { + fun startEntryAnimations( + downwards: Boolean, + animatorBuilder: () -> AnimatorSet = { AnimatorSet() } + ) { cancelAnimations() mAnimator = @@ -153,7 +160,7 @@ constructor( interpolator = Interpolators.LINEAR ), translationYAnimator( - from = mDreamInTranslationYDistance.toFloat(), + from = mDreamInTranslationYDistance.toFloat() * (if (downwards) -1 else 1), to = 0f, durationMs = mDreamInTranslationYDurationMs, interpolator = Interpolators.EMPHASIZED_DECELERATE @@ -167,6 +174,71 @@ constructor( } } + /** + * Starts the dream content and dream overlay exit animations. + * + * This should only be used when the low light dream is entering, animations to/from other SysUI + * views is controlled by `transitionViewModel`. + */ + // TODO(b/256916668): integrate with the keyguard transition model once dream surfaces work is + // done. + @JvmOverloads + fun startExitAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }): Animator { + cancelAnimations() + + mAnimator = + animatorBuilder().apply { + playTogether( + translationYAnimator( + from = 0f, + to = -mDreamInTranslationYDistance.toFloat(), + durationMs = mDreamInTranslationYDurationMs, + delayMs = 0, + interpolator = Interpolators.EMPHASIZED + ), + alphaAnimator( + from = + mCurrentAlphaAtPosition.getOrDefault( + key = POSITION_BOTTOM, + defaultValue = 1f + ), + to = 0f, + durationMs = mDreamInComplicationsAnimDurationMs, + delayMs = 0, + positions = POSITION_BOTTOM + ) + .apply { + doOnEnd { + // The logical end of the animation is once the alpha and blur + // animations finish, end the animation so that any listeners are + // notified. The Y translation animation is much longer than all of + // the other animations due to how the spec is defined, but is not + // expected to run to completion. + mAnimator?.end() + } + }, + alphaAnimator( + from = + mCurrentAlphaAtPosition.getOrDefault( + key = POSITION_TOP, + defaultValue = 1f + ), + to = 0f, + durationMs = mDreamInComplicationsAnimDurationMs, + delayMs = 0, + positions = POSITION_TOP + ) + ) + doOnEnd { + mAnimator = null + mOverlayStateController.setExitAnimationsRunning(false) + } + start() + } + mOverlayStateController.setExitAnimationsRunning(true) + return mAnimator as AnimatorSet + } + /** Starts the dream content and dream overlay exit animations. */ fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) { cancelAnimations() @@ -182,19 +254,6 @@ constructor( } } - /** - * Ends the dream content and dream overlay animations, if they're currently running. - * - * @see [AnimatorSet.end] - */ - fun endAnimations() { - mAnimator = - mAnimator?.let { - it.end() - null - } - } - private fun blurAnimator( view: View, fromBlurRadius: Float, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 50cfb6a905c9..4b478cdca9f9 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -23,6 +23,7 @@ import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_BOTTOM; import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP; +import android.animation.Animator; import android.content.res.Resources; import android.os.Handler; import android.util.MathUtils; @@ -31,6 +32,7 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; +import com.android.dream.lowlight.LowLightTransitionCoordinator; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.dagger.qualifiers.Main; @@ -54,11 +56,14 @@ import javax.inject.Named; * View controller for {@link DreamOverlayContainerView}. */ @DreamOverlayComponent.DreamOverlayScope -public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> { +public class DreamOverlayContainerViewController extends + ViewController<DreamOverlayContainerView> implements + LowLightTransitionCoordinator.LowLightEnterListener { private final DreamOverlayStatusBarViewController mStatusBarViewController; private final BlurUtils mBlurUtils; private final DreamOverlayAnimationsController mDreamOverlayAnimationsController; private final DreamOverlayStateController mStateController; + private final LowLightTransitionCoordinator mLowLightTransitionCoordinator; private final ComplicationHostViewController mComplicationHostViewController; @@ -143,19 +148,18 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve }; /** - * If true, overlay entry animations should be skipped once. - * - * This is turned on when exiting low light and should be turned off once the entry animations - * are skipped once. + * If {@code true}, the dream has just transitioned from the low light dream back to the user + * dream and we should play an entry animation where the overlay slides in downwards from the + * top instead of the typicla slide in upwards from the bottom. */ - private boolean mSkipEntryAnimations; + private boolean mExitingLowLight; private final DreamOverlayStateController.Callback mDreamOverlayStateCallback = new DreamOverlayStateController.Callback() { @Override public void onExitLowLight() { - mSkipEntryAnimations = true; + mExitingLowLight = true; } }; @@ -165,6 +169,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve ComplicationHostViewController complicationHostViewController, @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView, DreamOverlayStatusBarViewController statusBarViewController, + LowLightTransitionCoordinator lowLightTransitionCoordinator, BlurUtils blurUtils, @Main Handler handler, @Main Resources resources, @@ -182,6 +187,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve mBlurUtils = blurUtils; mDreamOverlayAnimationsController = animationsController; mStateController = stateController; + mLowLightTransitionCoordinator = lowLightTransitionCoordinator; mBouncerlessScrimController = bouncerlessScrimController; mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback); @@ -208,6 +214,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve mStatusBarViewController.init(); mComplicationHostViewController.init(); mDreamOverlayAnimationsController.init(mView); + mLowLightTransitionCoordinator.setLowLightEnterListener(this); } @Override @@ -219,14 +226,10 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve // Start dream entry animations. Skip animations for low light clock. if (!mStateController.isLowLightActive()) { - mDreamOverlayAnimationsController.startEntryAnimations(); - - if (mSkipEntryAnimations) { - // If we're transitioning from the low light dream back to the user dream, skip the - // overlay animations and show immediately. - mDreamOverlayAnimationsController.endAnimations(); - mSkipEntryAnimations = false; - } + // If this is transitioning from the low light dream to the user dream, the overlay + // should translate in downwards instead of upwards. + mDreamOverlayAnimationsController.startEntryAnimations(mExitingLowLight); + mExitingLowLight = false; } } @@ -310,4 +313,12 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor); } + + @Override + public Animator onBeforeEnterLowLight() { + // Return the animator so that the transition coordinator waits for the overlay exit + // animations to finish before entering low light, as otherwise the default DreamActivity + // animation plays immediately and there's no time for this animation to play. + return mDreamOverlayAnimationsController.startExitAnimations(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java index a2e11b21ea59..24e90f066622 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java @@ -22,14 +22,18 @@ import static com.android.systemui.dreams.complication.dagger.ComplicationModule import android.graphics.Rect; import android.graphics.Region; import android.os.Debug; +import android.os.UserHandle; +import android.provider.Settings; import android.util.Log; import android.view.View; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.lifecycle.LifecycleOwner; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.util.ViewController; +import com.android.systemui.util.settings.SecureSettings; import java.util.Collection; import java.util.HashMap; @@ -54,6 +58,8 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay private final LifecycleOwner mLifecycleOwner; private final ComplicationCollectionViewModel mComplicationCollectionViewModel; private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>(); + @VisibleForTesting + boolean mIsAnimationEnabled; // Whether dream entry animations are finished. private boolean mEntryAnimationsFinished = false; @@ -64,7 +70,8 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay ComplicationLayoutEngine layoutEngine, DreamOverlayStateController dreamOverlayStateController, LifecycleOwner lifecycleOwner, - @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) { + @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel, + SecureSettings secureSettings) { super(view); mLayoutEngine = layoutEngine; mLifecycleOwner = lifecycleOwner; @@ -78,6 +85,10 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay mDreamOverlayStateController.areEntryAnimationsFinished(); } }); + + // Whether animations are enabled. + mIsAnimationEnabled = secureSettings.getFloatForUser( + Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, UserHandle.USER_CURRENT) != 0.0f; } @Override @@ -148,7 +159,7 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay // Complications to be added before dream entry animations are finished are set // to invisible and are animated in. - if (!mEntryAnimationsFinished) { + if (!mEntryAnimationsFinished && mIsAnimationEnabled) { view.setVisibility(View.INVISIBLE); } mComplications.put(id, viewHolder); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java index ead3b7b1de53..0b4b7c691cfd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java @@ -45,6 +45,7 @@ public class DraggableConstraintLayout extends ConstraintLayout implements ViewTreeObserver.OnComputeInternalInsetsListener { private static final float VELOCITY_DP_PER_MS = 1; + private static final int MAXIMUM_DISMISS_DISTANCE_DP = 400; private final SwipeDismissHandler mSwipeDismissHandler; private final GestureDetector mSwipeDetector; @@ -347,14 +348,18 @@ public class DraggableConstraintLayout extends ConstraintLayout } else { finalX = -1 * getBackgroundRight(); } - float distance = Math.abs(finalX - startX); + float distance = Math.min(Math.abs(finalX - startX), + FloatingWindowUtil.dpToPx(mDisplayMetrics, MAXIMUM_DISMISS_DISTANCE_DP)); + // ensure that view dismisses in the right direction (right in LTR, left in RTL) + float distanceVector = Math.copySign(distance, finalX - startX); anim.addUpdateListener(animation -> { - float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction()); + float translation = MathUtils.lerp( + startX, startX + distanceVector, animation.getAnimatedFraction()); mView.setTranslationX(translation); mView.setAlpha(1 - animation.getAnimatedFraction()); }); - anim.setDuration((long) (distance / Math.abs(velocity))); + anim.setDuration((long) (Math.abs(distance / velocity))); return anim; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index b1987c151e5f..ee9e54a22ddf 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -4720,8 +4720,13 @@ public final class NotificationPanelViewController implements Dumpable { gesture possible. */ int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { - pointerIndex = 0; - mTrackingPointer = event.getPointerId(pointerIndex); + if (mTrackingPointer < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } else { + mShadeLog.logMotionEvent(event, "Skipping intercept of multitouch pointer"); + return false; + } } final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index c35c5c522798..77550038c94a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -93,6 +93,16 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi @IntDef({STATE_ICON, STATE_DOT, STATE_HIDDEN}) public @interface VisibleState { } + /** Returns a human-readable string of {@link VisibleState}. */ + public static String getVisibleStateString(@VisibleState int state) { + switch(state) { + case STATE_ICON: return "ICON"; + case STATE_DOT: return "DOT"; + case STATE_HIDDEN: return "HIDDEN"; + default: return "UNKNOWN"; + } + } + private static final String TAG = "StatusBarIconView"; private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT = new FloatProperty<StatusBarIconView>("iconAppearAmount") { @@ -561,7 +571,8 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi @Override public String toString() { return "StatusBarIconView(" - + "slot='" + mSlot + " alpha=" + getAlpha() + " icon=" + mIcon + + "slot='" + mSlot + "' alpha=" + getAlpha() + " icon=" + mIcon + + " visibleState=" + getVisibleStateString(getVisibleState()) + " iconColor=#" + Integer.toHexString(mIconColor) + " notification=" + mNotification + ')'; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt new file mode 100644 index 000000000000..5ce1db2b6acd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2023 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.statusbar.notification.collection.coordinator + +import android.util.ArrayMap +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator +import com.android.systemui.statusbar.notification.collection.render.NotifGroupController +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.time.SystemClock +import javax.inject.Inject +import kotlin.math.max +import kotlin.math.min + +/** A small coordinator which finds, stores, and applies the closest notification time. */ +@CoordinatorScope +class GroupWhenCoordinator +@Inject +constructor( + @Main private val delayableExecutor: DelayableExecutor, + private val systemClock: SystemClock +) : Coordinator { + + private val invalidator = object : Invalidator("GroupWhenCoordinator") {} + private val notificationGroupTimes = ArrayMap<GroupEntry, Long>() + private var cancelInvalidateListRunnable: Runnable? = null + + private val invalidateListRunnable: Runnable = Runnable { + invalidator.invalidateList("future notification invalidation") + } + + override fun attach(pipeline: NotifPipeline) { + pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilterListener) + pipeline.addOnAfterRenderGroupListener(::onAfterRenderGroupListener) + pipeline.addPreRenderInvalidator(invalidator) + } + + private fun onBeforeFinalizeFilterListener(entries: List<ListEntry>) { + cancelListInvalidation() + notificationGroupTimes.clear() + + val now = systemClock.currentTimeMillis() + var closestFutureTime = Long.MAX_VALUE + entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry -> + val whenMillis = calculateGroupNotificationTime(groupEntry, now) + notificationGroupTimes[groupEntry] = whenMillis + if (whenMillis > now) { + closestFutureTime = min(closestFutureTime, whenMillis) + } + } + + if (closestFutureTime != Long.MAX_VALUE) { + cancelInvalidateListRunnable = + delayableExecutor.executeDelayed(invalidateListRunnable, closestFutureTime - now) + } + } + + private fun cancelListInvalidation() { + cancelInvalidateListRunnable?.run() + cancelInvalidateListRunnable = null + } + + private fun onAfterRenderGroupListener(group: GroupEntry, controller: NotifGroupController) { + notificationGroupTimes[group]?.let(controller::setNotificationGroupWhen) + } + + private fun calculateGroupNotificationTime( + groupEntry: GroupEntry, + currentTimeMillis: Long + ): Long { + var pastTime = Long.MIN_VALUE + var futureTime = Long.MAX_VALUE + groupEntry.children + .asSequence() + .mapNotNull { child -> child.sbn.notification.`when`.takeIf { it > 0 } } + .forEach { time -> + val isInThePast = currentTimeMillis - time > 0 + if (isInThePast) { + pastTime = max(pastTime, time) + } else { + futureTime = min(futureTime, time) + } + } + + if (pastTime == Long.MIN_VALUE && futureTime == Long.MAX_VALUE) { + return checkNotNull(groupEntry.summary).creationTime + } + + return if (futureTime != Long.MAX_VALUE) futureTime else pastTime + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index 8a82bcad44e4..6bb5b9218ed7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -31,31 +31,32 @@ interface NotifCoordinators : Coordinator, PipelineDumpable @CoordinatorScope class NotifCoordinatorsImpl @Inject constructor( - notifPipelineFlags: NotifPipelineFlags, - dataStoreCoordinator: DataStoreCoordinator, - hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, - hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, - keyguardCoordinator: KeyguardCoordinator, - rankingCoordinator: RankingCoordinator, - appOpsCoordinator: AppOpsCoordinator, - deviceProvisionedCoordinator: DeviceProvisionedCoordinator, - bubbleCoordinator: BubbleCoordinator, - headsUpCoordinator: HeadsUpCoordinator, - gutsCoordinator: GutsCoordinator, - conversationCoordinator: ConversationCoordinator, - debugModeCoordinator: DebugModeCoordinator, - groupCountCoordinator: GroupCountCoordinator, - mediaCoordinator: MediaCoordinator, - preparationCoordinator: PreparationCoordinator, - remoteInputCoordinator: RemoteInputCoordinator, - rowAppearanceCoordinator: RowAppearanceCoordinator, - stackCoordinator: StackCoordinator, - shadeEventCoordinator: ShadeEventCoordinator, - smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator, - viewConfigCoordinator: ViewConfigCoordinator, - visualStabilityCoordinator: VisualStabilityCoordinator, - sensitiveContentCoordinator: SensitiveContentCoordinator, - dismissibilityCoordinator: DismissibilityCoordinator + notifPipelineFlags: NotifPipelineFlags, + dataStoreCoordinator: DataStoreCoordinator, + hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, + hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, + keyguardCoordinator: KeyguardCoordinator, + rankingCoordinator: RankingCoordinator, + appOpsCoordinator: AppOpsCoordinator, + deviceProvisionedCoordinator: DeviceProvisionedCoordinator, + bubbleCoordinator: BubbleCoordinator, + headsUpCoordinator: HeadsUpCoordinator, + gutsCoordinator: GutsCoordinator, + conversationCoordinator: ConversationCoordinator, + debugModeCoordinator: DebugModeCoordinator, + groupCountCoordinator: GroupCountCoordinator, + groupWhenCoordinator: GroupWhenCoordinator, + mediaCoordinator: MediaCoordinator, + preparationCoordinator: PreparationCoordinator, + remoteInputCoordinator: RemoteInputCoordinator, + rowAppearanceCoordinator: RowAppearanceCoordinator, + stackCoordinator: StackCoordinator, + shadeEventCoordinator: ShadeEventCoordinator, + smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator, + viewConfigCoordinator: ViewConfigCoordinator, + visualStabilityCoordinator: VisualStabilityCoordinator, + sensitiveContentCoordinator: SensitiveContentCoordinator, + dismissibilityCoordinator: DismissibilityCoordinator ) : NotifCoordinators { private val mCoordinators: MutableList<Coordinator> = ArrayList() @@ -82,6 +83,7 @@ class NotifCoordinatorsImpl @Inject constructor( mCoordinators.add(debugModeCoordinator) mCoordinators.add(conversationCoordinator) mCoordinators.add(groupCountCoordinator) + mCoordinators.add(groupWhenCoordinator) mCoordinators.add(mediaCoordinator) mCoordinators.add(rowAppearanceCoordinator) mCoordinators.add(stackCoordinator) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt index e2edc01f0d7c..061ef9e9341c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt @@ -20,4 +20,7 @@ package com.android.systemui.statusbar.notification.collection.render interface NotifGroupController { /** Set the number of children that this group would have if not for the 8-child max */ fun setUntruncatedChildCount(untruncatedChildCount: Int) + + /** Set the when value of notification group that reflects most important closest notification time */ + fun setNotificationGroupWhen(whenMillis: Long) }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 2affa77eee04..6deaa23ca20e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -192,7 +192,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private int mMaxSmallHeightBeforeS; private int mMaxSmallHeight; private int mMaxSmallHeightLarge; - private int mMaxSmallHeightMedia; private int mMaxExpandedHeight; private int mIncreasedPaddingBetweenElements; private int mNotificationLaunchHeight; @@ -853,6 +852,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** + * @see NotificationChildrenContainer#setNotificationGroupWhen(long) + */ + public void setNotificationGroupWhen(long whenMillis) { + if (mIsSummaryWithChildren) { + mChildrenContainer.setNotificationGroupWhen(whenMillis); + } else { + Log.w(TAG, "setNotificationGroupWhen( whenMillis: " + whenMillis + ")" + + " mIsSummaryWithChildren: false" + + " mChildrenContainer has not been inflated yet."); + } + } + + /** * Called after children have been attached to set the expansion states */ public void resetChildSystemExpandedStates() { @@ -1774,8 +1786,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView R.dimen.notification_min_height); mMaxSmallHeightLarge = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_min_height_increased); - mMaxSmallHeightMedia = NotificationUtils.getFontScaledHeight(mContext, - R.dimen.notification_min_height_media); mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_height); mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 2dda6fd802e8..dfc80fde3cd2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -349,6 +349,15 @@ public class ExpandableNotificationRowController implements NotifViewController } @Override + public void setNotificationGroupWhen(long whenMillis) { + if (mView.isSummaryWithChildren()) { + mView.setNotificationGroupWhen(whenMillis); + } else { + Log.w(TAG, "Called setNotificationTime(" + whenMillis + ") on a leaf row"); + } + } + + @Override public void setSystemExpanded(boolean systemExpanded) { mView.setSystemExpanded(systemExpanded); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 1f664cb16179..9a777ea6230b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -27,6 +27,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import android.widget.DateTimeView; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; @@ -344,6 +345,21 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple mTransformationHelper.setVisible(visible); } + /*** + * Set Notification when value + * @param whenMillis + */ + public void setNotificationWhen(long whenMillis) { + if (mNotificationHeader == null) { + return; + } + + final View timeView = mNotificationHeader.findViewById(com.android.internal.R.id.time); + + if (timeView instanceof DateTimeView) { + ((DateTimeView) timeView).setTime(whenMillis); + } + } protected void addTransformedViews(View... views) { for (View view : views) { if (view != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 9b93d7b9e1d0..40f55bd3726c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -296,6 +296,19 @@ public class NotificationChildrenContainer extends ViewGroup } /** + * Set the notification time in the group so that the view can show the latest event in the UI + * appropriately. + */ + public void setNotificationGroupWhen(long whenMillis) { + if (mNotificationHeaderWrapper != null) { + mNotificationHeaderWrapper.setNotificationWhen(whenMillis); + } + if (mNotificationHeaderWrapperLowPriority != null) { + mNotificationHeaderWrapperLowPriority.setNotificationWhen(whenMillis); + } + } + + /** * Add a child notification to this view. * * @param row the row to add diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java index c72eb054c62c..39b5b5a4cef8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java @@ -40,6 +40,7 @@ import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger; import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView; import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel; import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView; @@ -288,10 +289,14 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da * @param mobileContext possibly mcc/mnc overridden mobile context * @param subId the subscriptionId for this mobile view */ - public void addModernMobileView(Context mobileContext, int subId) { + public void addModernMobileView( + Context mobileContext, + MobileViewLogger mobileViewLogger, + int subId) { Log.d(TAG, "addModernMobileView (subId=" + subId + ")"); ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind( mobileContext, + mobileViewLogger, "mobile", mMobileIconsViewModel.viewModelForSub(subId, mLocation) ); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 11863627218e..04cc8ce792d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -569,7 +569,10 @@ public interface StatusBarIconController { mGroup.addView(view, index, onCreateLayoutParams()); if (mIsInDemoMode) { - mDemoStatusIcons.addModernMobileView(mContext, subId); + mDemoStatusIcons.addModernMobileView( + mContext, + mMobileIconsViewModel.getLogger(), + subId); } return view; @@ -601,6 +604,7 @@ public interface StatusBarIconController { return ModernStatusBarMobileView .constructAndBind( mobileContext, + mMobileIconsViewModel.getLogger(), slot, mMobileIconsViewModel.viewModelForSub(subId, mLocation) ); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java index f6c0da8da8c0..833cb93f62e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java @@ -79,6 +79,18 @@ public class StatusBarIconHolder { private @IconType int mType = TYPE_ICON; private int mTag = 0; + /** Returns a human-readable string representing the given type. */ + public static String getTypeString(@IconType int type) { + switch(type) { + case TYPE_ICON: return "ICON"; + case TYPE_WIFI: return "WIFI_OLD"; + case TYPE_MOBILE: return "MOBILE_OLD"; + case TYPE_MOBILE_NEW: return "MOBILE_NEW"; + case TYPE_WIFI_NEW: return "WIFI_NEW"; + default: return "UNKNOWN"; + } + } + private StatusBarIconHolder() { } @@ -230,4 +242,11 @@ public class StatusBarIconHolder { public int getTag() { return mTag; } + + @Override + public String toString() { + return "StatusBarIconHolder(type=" + getTypeString(mType) + + " tag=" + getTag() + + " visible=" + isVisible() + ")"; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java index 8800b05fadb7..565481a20d95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java @@ -27,6 +27,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; /** A class holding the list of all the system icons that could be shown in the status bar. */ public class StatusBarIconList { @@ -302,7 +303,7 @@ public class StatusBarIconList { @Override public String toString() { - return String.format("(%s) %s", mName, subSlotsString()); + return String.format("(%s) holder=%s %s", mName, mHolder, subSlotsString()); } private String subSlotsString() { @@ -310,7 +311,10 @@ public class StatusBarIconList { return ""; } - return "" + mSubSlots.size() + " subSlots"; + return "| " + mSubSlots.size() + " subSlots: " + + mSubSlots.stream() + .map(StatusBarIconHolder::toString) + .collect(Collectors.joining("|")); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt new file mode 100644 index 000000000000..e594a8a5efd9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 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.statusbar.pipeline.dagger + +import javax.inject.Qualifier + +/** Logs for changes with the new mobile views. */ +@Qualifier +@MustBeDocumented +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +annotation class MobileViewLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 44647515a6e5..adfea80715a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -148,5 +148,19 @@ abstract class StatusBarPipelineModule { fun provideMobileInputLogBuffer(factory: LogBufferFactory): LogBuffer { return factory.create("MobileInputLog", 100) } + + @Provides + @SysUISingleton + @MobileViewLog + fun provideMobileViewLogBuffer(factory: LogBufferFactory): LogBuffer { + return factory.create("MobileViewLog", 100) + } + + @Provides + @SysUISingleton + @VerboseMobileViewLog + fun provideVerboseMobileViewLogBuffer(factory: LogBufferFactory): LogBuffer { + return factory.create("VerboseMobileViewLog", 100) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt new file mode 100644 index 000000000000..b98789807dd3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 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.statusbar.pipeline.dagger + +import javax.inject.Qualifier + +/** Logs for **verbose** changes with the new mobile views. */ +@Qualifier +@MustBeDocumented +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +annotation class VerboseMobileViewLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index 3cbd2b76c248..159f689de370 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline.mobile.shared +package com.android.systemui.statusbar.pipeline.mobile.data import android.net.Network import android.net.NetworkCapabilities @@ -133,24 +133,6 @@ constructor( ) } - fun logUiAdapterSubIdsUpdated(subs: List<Int>) { - buffer.log( - TAG, - LogLevel.INFO, - { str1 = subs.toString() }, - { "Sub IDs in MobileUiAdapter updated internally: $str1" }, - ) - } - - fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) { - buffer.log( - TAG, - LogLevel.INFO, - { str1 = subs.toString() }, - { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" }, - ) - } - fun logCarrierConfigChanged(subId: Int) { buffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt index bb3b9b2166c3..efdce062bb37 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt @@ -30,8 +30,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index 96b96f14d6aa..e182bc66081a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -36,6 +36,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType @@ -47,7 +48,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameMo import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 53a208cd171e..f97e41c018f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -45,11 +45,11 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt index da63ab10f733..075e6ec11ae7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt @@ -22,7 +22,6 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import java.io.PrintWriter import javax.inject.Inject @@ -55,17 +54,14 @@ constructor( interactor: MobileIconsInteractor, private val iconController: StatusBarIconController, private val iconsViewModelFactory: MobileIconsViewModel.Factory, - private val logger: MobileInputLogger, + private val logger: MobileViewLogger, @Application private val scope: CoroutineScope, private val statusBarPipelineFlags: StatusBarPipelineFlags, ) : CoreStartable { private val mobileSubIds: Flow<List<Int>> = - interactor.filteredSubscriptions - .mapLatest { subscriptions -> - subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } - } - .distinctUntilChanged() - .onEach { logger.logUiAdapterSubIdsUpdated(it) } + interactor.filteredSubscriptions.mapLatest { subscriptions -> + subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } + } /** * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is @@ -75,7 +71,10 @@ constructor( * NOTE: this should go away as the view presenter learns more about this data pipeline */ private val mobileSubIdsState: StateFlow<List<Int>> = - mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) + mobileSubIds + .distinctUntilChanged() + .onEach { logger.logUiAdapterSubIdsUpdated(it) } + .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */ val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt new file mode 100644 index 000000000000..90dff23c637c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2023 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.statusbar.pipeline.mobile.ui + +import android.view.View +import com.android.systemui.Dumpable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.statusbar.pipeline.dagger.MobileViewLog +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel +import java.io.PrintWriter +import javax.inject.Inject + +/** Logs for changes with the new mobile views. */ +@SysUISingleton +class MobileViewLogger +@Inject +constructor( + @MobileViewLog private val buffer: LogBuffer, + dumpManager: DumpManager, +) : Dumpable { + init { + dumpManager.registerNormalDumpable(this) + } + + private val collectionStatuses = mutableMapOf<String, Boolean>() + + fun logUiAdapterSubIdsUpdated(subs: List<Int>) { + buffer.log( + TAG, + LogLevel.INFO, + { str1 = subs.toString() }, + { "Sub IDs in MobileUiAdapter updated internally: $str1" }, + ) + } + + fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) { + buffer.log( + TAG, + LogLevel.INFO, + { str1 = subs.toString() }, + { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" }, + ) + } + + fun logNewViewBinding(view: View, viewModel: LocationBasedMobileViewModel) { + buffer.log( + TAG, + LogLevel.INFO, + { + str1 = view.getIdForLogging() + str2 = viewModel.getIdForLogging() + str3 = viewModel.locationName + }, + { "New view binding. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" }, + ) + } + + fun logCollectionStarted(view: View, viewModel: LocationBasedMobileViewModel) { + collectionStatuses[view.getIdForLogging()] = true + buffer.log( + TAG, + LogLevel.INFO, + { + str1 = view.getIdForLogging() + str2 = viewModel.getIdForLogging() + str3 = viewModel.locationName + }, + { "Collection started. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" }, + ) + } + + fun logCollectionStopped(view: View, viewModel: LocationBasedMobileViewModel) { + collectionStatuses[view.getIdForLogging()] = false + buffer.log( + TAG, + LogLevel.INFO, + { + str1 = view.getIdForLogging() + str2 = viewModel.getIdForLogging() + str3 = viewModel.locationName + }, + { "Collection stopped. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" }, + ) + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("Collection statuses per view:---") + collectionStatuses.forEach { viewId, isCollecting -> + pw.println("viewId=$viewId, isCollecting=$isCollecting") + } + } + + companion object { + fun Any.getIdForLogging(): String { + // The identityHashCode is guaranteed to be constant for the lifetime of the object. + return Integer.toHexString(System.identityHashCode(this)) + } + } +} + +private const val TAG = "MobileViewLogger" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt new file mode 100644 index 000000000000..f67bc8f14447 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 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.statusbar.pipeline.mobile.ui + +import android.view.View +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging +import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel +import javax.inject.Inject + +/** + * Logs for **verbose** changes with the new mobile views. + * + * This is a hopefully temporary log until we resolve some open bugs (b/267236367, b/269565345, + * b/270300839). + */ +@SysUISingleton +class VerboseMobileViewLogger +@Inject +constructor( + @VerboseMobileViewLog private val buffer: LogBuffer, +) { + fun logBinderReceivedSignalIcon(parentView: View, subId: Int, icon: SignalIconModel) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = parentView.getIdForLogging() + int1 = subId + int2 = icon.level + bool1 = icon.showExclamationMark + }, + { + "Binder[subId=$int1, viewId=$str1] received new signal icon: " + + "level=$int2 showExclamation=$bool1" + }, + ) + } + + fun logBinderReceivedNetworkTypeIcon(parentView: View, subId: Int, icon: Icon.Resource?) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = parentView.getIdForLogging() + int1 = subId + bool1 = icon != null + int2 = icon?.res ?: -1 + }, + { + "Binder[subId=$int1, viewId=$str1] received new network type icon: " + + if (bool1) "resId=$int2" else "null" + }, + ) + } +} + +private const val TAG = "VerboseMobileViewLogger" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt index db585e68d185..5b7d45b55c5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt @@ -36,8 +36,10 @@ import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch @@ -48,6 +50,7 @@ object MobileIconBinder { fun bind( view: ViewGroup, viewModel: LocationBasedMobileViewModel, + logger: MobileViewLogger, ): ModernStatusBarViewBinding { val mobileGroupView = view.requireViewById<ViewGroup>(R.id.mobile_group) val activityContainer = view.requireViewById<View>(R.id.inout_container) @@ -70,8 +73,13 @@ object MobileIconBinder { val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) + var isCollecting: Boolean = false + view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { + logger.logCollectionStarted(view, viewModel) + isCollecting = true + launch { visibilityState.collect { state -> when (state) { @@ -96,6 +104,11 @@ object MobileIconBinder { // Set the icon for the triangle launch { viewModel.icon.distinctUntilChanged().collect { icon -> + viewModel.verboseLogger?.logBinderReceivedSignalIcon( + view, + viewModel.subscriptionId, + icon, + ) mobileDrawable.level = SignalDrawable.getState( icon.level, @@ -114,6 +127,11 @@ object MobileIconBinder { // Set the network type icon launch { viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId -> + viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon( + view, + viewModel.subscriptionId, + dataTypeId, + ) dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) } networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE } @@ -150,6 +168,13 @@ object MobileIconBinder { } launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } } + + try { + awaitCancellation() + } finally { + isCollecting = false + logger.logCollectionStopped(view, viewModel) + } } } @@ -175,6 +200,10 @@ object MobileIconBinder { } decorTint.value = newTint } + + override fun isCollecting(): Boolean { + return isCollecting + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt index ed9a1884a7b4..4144293d5ccd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt @@ -20,6 +20,8 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import com.android.systemui.R +import com.android.systemui.statusbar.StatusBarIconView.getVisibleStateString +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView @@ -31,6 +33,15 @@ class ModernStatusBarMobileView( var subId: Int = -1 + override fun toString(): String { + return "ModernStatusBarMobileView(" + + "slot='$slot', " + + "subId=$subId, " + + "isCollecting=${binding.isCollecting()}, " + + "visibleState=${getVisibleStateString(visibleState)}); " + + "viewString=${super.toString()}" + } + companion object { /** @@ -40,6 +51,7 @@ class ModernStatusBarMobileView( @JvmStatic fun constructAndBind( context: Context, + logger: MobileViewLogger, slot: String, viewModel: LocationBasedMobileViewModel, ): ModernStatusBarMobileView { @@ -48,7 +60,8 @@ class ModernStatusBarMobileView( as ModernStatusBarMobileView) .also { it.subId = viewModel.subscriptionId - it.initView(slot) { MobileIconBinder.bind(it, viewModel) } + it.initView(slot) { MobileIconBinder.bind(it, viewModel, logger) } + logger.logNewViewBinding(it, viewModel) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt index 8e103f7bee2f..f775940140cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import android.graphics.Color import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags +import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger /** * A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This @@ -26,11 +27,15 @@ import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags * * @param commonImpl for convenience, this class wraps a base interface that can provides all of the * common implementations between locations. See [MobileIconViewModel] + * @property locationName the name of the location of this VM, used for logging. + * @property verboseLogger an optional logger to log extremely verbose view updates. */ abstract class LocationBasedMobileViewModel( val commonImpl: MobileIconViewModelCommon, statusBarPipelineFlags: StatusBarPipelineFlags, debugTint: Int, + val locationName: String, + val verboseLogger: VerboseMobileViewLogger?, ) : MobileIconViewModelCommon by commonImpl { val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring() @@ -45,11 +50,16 @@ abstract class LocationBasedMobileViewModel( fun viewModelForLocation( commonImpl: MobileIconViewModelCommon, statusBarPipelineFlags: StatusBarPipelineFlags, + verboseMobileViewLogger: VerboseMobileViewLogger, loc: StatusBarLocation, ): LocationBasedMobileViewModel = when (loc) { StatusBarLocation.HOME -> - HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags) + HomeMobileIconViewModel( + commonImpl, + statusBarPipelineFlags, + verboseMobileViewLogger, + ) StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags) StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, statusBarPipelineFlags) @@ -60,20 +70,41 @@ abstract class LocationBasedMobileViewModel( class HomeMobileIconViewModel( commonImpl: MobileIconViewModelCommon, statusBarPipelineFlags: StatusBarPipelineFlags, + verboseMobileViewLogger: VerboseMobileViewLogger, ) : MobileIconViewModelCommon, - LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.CYAN) + LocationBasedMobileViewModel( + commonImpl, + statusBarPipelineFlags, + debugTint = Color.CYAN, + locationName = "Home", + verboseMobileViewLogger, + ) class QsMobileIconViewModel( commonImpl: MobileIconViewModelCommon, statusBarPipelineFlags: StatusBarPipelineFlags, ) : MobileIconViewModelCommon, - LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.GREEN) + LocationBasedMobileViewModel( + commonImpl, + statusBarPipelineFlags, + debugTint = Color.GREEN, + locationName = "QS", + // Only do verbose logging for the Home location. + verboseLogger = null, + ) class KeyguardMobileIconViewModel( commonImpl: MobileIconViewModelCommon, statusBarPipelineFlags: StatusBarPipelineFlags, ) : MobileIconViewModelCommon, - LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.MAGENTA) + LocationBasedMobileViewModel( + commonImpl, + statusBarPipelineFlags, + debugTint = Color.MAGENTA, + locationName = "Keyguard", + // Only do verbose logging for the Home location. + verboseLogger = null, + ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index 049627899eff..dbb534b24471 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -49,7 +49,7 @@ interface MobileIconViewModelCommon { val contentDescription: Flow<ContentDescription> val roaming: Flow<Boolean> /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ - val networkTypeIcon: Flow<Icon?> + val networkTypeIcon: Flow<Icon.Resource?> val activityInVisible: Flow<Boolean> val activityOutVisible: Flow<Boolean> val activityContainerVisible: Flow<Boolean> @@ -161,7 +161,7 @@ constructor( ) .stateIn(scope, SharingStarted.WhileSubscribed(), false) - override val networkTypeIcon: Flow<Icon?> = + override val networkTypeIcon: Flow<Icon.Resource?> = combine( iconInteractor.networkTypeIconGroup, showNetworkTypeIcon, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index 8cb52af336da..2b90065284d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -23,6 +23,8 @@ import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger +import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import javax.inject.Inject @@ -39,6 +41,8 @@ class MobileIconsViewModel @Inject constructor( val subscriptionIdsFlow: StateFlow<List<Int>>, + val logger: MobileViewLogger, + private val verboseLogger: VerboseMobileViewLogger, private val interactor: MobileIconsInteractor, private val airplaneModeInteractor: AirplaneModeInteractor, private val constants: ConnectivityConstants, @@ -66,6 +70,7 @@ constructor( return LocationBasedMobileViewModel.viewModelForLocation( common, statusBarPipelineFlags, + verboseLogger, location, ) } @@ -79,6 +84,8 @@ constructor( class Factory @Inject constructor( + private val logger: MobileViewLogger, + private val verboseLogger: VerboseMobileViewLogger, private val interactor: MobileIconsInteractor, private val airplaneModeInteractor: AirplaneModeInteractor, private val constants: ConnectivityConstants, @@ -88,6 +95,8 @@ constructor( fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel { return MobileIconsViewModel( subscriptionIdsFlow, + logger, + verboseLogger, interactor, airplaneModeInteractor, constants, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt index f67876b50233..81f8683411ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt @@ -37,4 +37,7 @@ interface ModernStatusBarViewBinding { /** Notifies that the decor tint has been updated (used only for the dot). */ fun onDecorTintChanged(newTint: Int) + + /** Returns true if the binding between the view and view-model is currently collecting. */ + fun isCollecting(): Boolean } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt index b1e28129a690..1a1340484bfc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt @@ -36,7 +36,7 @@ open class ModernStatusBarView(context: Context, attrs: AttributeSet?) : BaseStatusBarFrameLayout(context, attrs) { private lateinit var slot: String - private lateinit var binding: ModernStatusBarViewBinding + internal lateinit var binding: ModernStatusBarViewBinding @StatusBarIconView.VisibleState private var iconVisibleState: Int = STATE_HIDDEN diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt index 2aff12c8721d..9e8c814ca2a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt @@ -34,6 +34,7 @@ import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarV import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged @@ -74,8 +75,12 @@ object WifiViewBinder { val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) + var isCollecting: Boolean = false + view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { + isCollecting = true + launch { visibilityState.collect { visibilityState -> groupView.isVisible = visibilityState == STATE_ICON @@ -127,6 +132,12 @@ object WifiViewBinder { airplaneSpacer.isVisible = visible } } + + try { + awaitCancellation() + } finally { + isCollecting = false + } } } @@ -152,6 +163,10 @@ object WifiViewBinder { } decorTint.value = newTint } + + override fun isCollecting(): Boolean { + return isCollecting + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt index 7a734862fe1b..f23e10287164 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt @@ -21,6 +21,7 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import com.android.systemui.R +import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel @@ -33,6 +34,15 @@ class ModernStatusBarWifiView( context: Context, attrs: AttributeSet?, ) : ModernStatusBarView(context, attrs) { + + override fun toString(): String { + return "ModernStatusBarWifiView(" + + "slot='$slot', " + + "isCollecting=${binding.isCollecting()}, " + + "visibleState=${StatusBarIconView.getVisibleStateString(visibleState)}); " + + "viewString=${super.toString()}" + } + companion object { /** * Inflates a new instance of [ModernStatusBarWifiView], binds it to a view model, and @@ -45,12 +55,9 @@ class ModernStatusBarWifiView( slot: String, wifiViewModel: LocationBasedWifiViewModel, ): ModernStatusBarWifiView { - return ( - LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null) - as ModernStatusBarWifiView - ).also { - it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) } - } + return (LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null) + as ModernStatusBarWifiView) + .also { it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) } } } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index c2fb904f64ad..ffd75fb9bbc6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.app.RemoteAction; import android.content.ClipData; import android.content.ClipDescription; @@ -101,6 +102,9 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor; private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks; + @Captor + private ArgumentCaptor<AnimatorListenerAdapter> mAnimatorArgumentCaptor; + private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); @Before @@ -478,12 +482,16 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { when(mClipboardOverlayWindow.getWindowInsets()).thenReturn( getImeInsets(new Rect(0, 0, 0, 1))); mOverlayController.setClipData(mSampleClipData, ""); + Animator mockFadeoutAnimator = Mockito.mock(Animator.class); + when(mClipboardOverlayView.getMinimizedFadeoutAnimation()).thenReturn(mockFadeoutAnimator); verify(mClipboardOverlayView).setMinimized(true); verify(mClipboardOverlayView, never()).setMinimized(false); verify(mClipboardOverlayView, never()).showTextPreview(any(), anyBoolean()); mCallbacks.onMinimizedViewTapped(); + verify(mockFadeoutAnimator).addListener(mAnimatorArgumentCaptor.capture()); + mAnimatorArgumentCaptor.getValue().onAnimationEnd(mockFadeoutAnimator); verify(mClipboardOverlayView).setMinimized(false); verify(mClipboardOverlayView).showTextPreview("Test Item", false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt index 6c23254941a8..0a9470617a5f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt @@ -1,6 +1,8 @@ package com.android.systemui.dreams +import android.animation.Animator import android.animation.AnimatorSet +import android.animation.ValueAnimator import android.testing.AndroidTestingRunner import android.view.View import androidx.test.filters.SmallTest @@ -10,13 +12,16 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransition import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.anyLong import org.mockito.Mockito.eq @@ -71,6 +76,19 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { } @Test + fun testExitAnimationUpdatesState() { + controller.startExitAnimations(animatorBuilder = { mockAnimator }) + + verify(stateController).setExitAnimationsRunning(true) + + val captor = argumentCaptor<Animator.AnimatorListener>() + verify(mockAnimator).addListener(captor.capture()) + + captor.value.onAnimationEnd(mockAnimator) + verify(stateController).setExitAnimationsRunning(false) + } + + @Test fun testWakeUpCallsExecutor() { val mockExecutor: DelayableExecutor = mock() val mockCallback: Runnable = mock() @@ -87,7 +105,7 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { fun testWakeUpAfterStartWillCancel() { val mockStartAnimator: AnimatorSet = mock() - controller.startEntryAnimations(animatorBuilder = { mockStartAnimator }) + controller.startEntryAnimations(false, animatorBuilder = { mockStartAnimator }) verify(mockStartAnimator, never()).cancel() @@ -100,4 +118,50 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { // animator. verify(mockStartAnimator, times(1)).cancel() } + + @Test + fun testEntryAnimations_translatesUpwards() { + val mockStartAnimator: AnimatorSet = mock() + + controller.startEntryAnimations( + /* downwards= */ false, + animatorBuilder = { mockStartAnimator } + ) + + val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java) + verify(mockStartAnimator).playTogether(animatorCaptor.capture()) + + // Check if there's a ValueAnimator starting at the expected Y distance. + val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator> + assertTrue( + animators.any { + // Call setCurrentFraction so the animated value jumps to the initial value. + it.setCurrentFraction(0f) + it.animatedValue == DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat() + } + ) + } + + @Test + fun testEntryAnimations_translatesDownwards() { + val mockStartAnimator: AnimatorSet = mock() + + controller.startEntryAnimations( + /* downwards= */ true, + animatorBuilder = { mockStartAnimator } + ) + + val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java) + verify(mockStartAnimator).playTogether(animatorCaptor.capture()) + + // Check if there's a ValueAnimator starting at the expected Y distance. + val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator> + assertTrue( + animators.any { + // Call setCurrentFraction so the animated value jumps to the initial value. + it.setCurrentFraction(0f) + it.animatedValue == -DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat() + } + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index 6b095ffd3977..2a72e7d85d3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.dreams; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -33,6 +34,7 @@ import android.view.ViewTreeObserver; import androidx.test.filters.SmallTest; +import com.android.dream.lowlight.LowLightTransitionCoordinator; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.complication.ComplicationHostViewController; @@ -65,6 +67,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController; @Mock + LowLightTransitionCoordinator mLowLightTransitionCoordinator; + + @Mock DreamOverlayContainerView mDreamOverlayContainerView; @Mock @@ -109,6 +114,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mComplicationHostViewController, mDreamOverlayContentView, mDreamOverlayStatusBarViewController, + mLowLightTransitionCoordinator, mBlurUtils, mHandler, mResources, @@ -200,7 +206,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mController.onViewAttached(); - verify(mAnimationsController).startEntryAnimations(); + verify(mAnimationsController).startEntryAnimations(false); verify(mAnimationsController, never()).cancelAnimations(); } @@ -210,11 +216,11 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mController.onViewAttached(); - verify(mAnimationsController, never()).startEntryAnimations(); + verify(mAnimationsController, never()).startEntryAnimations(anyBoolean()); } @Test - public void testSkipEntryAnimationsWhenExitingLowLight() { + public void testDownwardEntryAnimationsWhenExitingLowLight() { ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor = ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); when(mStateController.isLowLightActive()).thenReturn(false); @@ -230,8 +236,14 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mController.onViewAttached(); // Entry animations should be started then immediately ended to skip to the end. - verify(mAnimationsController).startEntryAnimations(); - verify(mAnimationsController).endAnimations(); + verify(mAnimationsController).startEntryAnimations(true); + } + + @Test + public void testStartsExitAnimationsBeforeEnteringLowLight() { + mController.onBeforeEnterLowLight(); + + verify(mAnimationsController).startExitAnimations(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java index dcd8736711f6..068852de7a43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java @@ -22,6 +22,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.os.UserHandle; +import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.view.View; @@ -33,6 +35,8 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.util.settings.SecureSettings; import org.junit.Before; import org.junit.Test; @@ -96,6 +100,10 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase { private ComplicationHostViewController mController; + private SecureSettings mSecureSettings; + + private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM; + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -108,12 +116,17 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase { when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams); when(mComplicationView.getParent()).thenReturn(mComplicationHostView); + mSecureSettings = new FakeSettings(); + mSecureSettings.putFloatForUser( + Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, CURRENT_USER_ID); + mController = new ComplicationHostViewController( mComplicationHostView, mLayoutEngine, mDreamOverlayStateController, mLifecycleOwner, - mViewModel); + mViewModel, + mSecureSettings); mController.init(); } @@ -188,6 +201,23 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase { verify(mComplicationView, never()).setVisibility(View.INVISIBLE); } + @Test + public void testAnimationsDisabled_ComplicationsNeverSetToInvisible() { + //Disable animations + mController.mIsAnimationEnabled = false; + + final Observer<Collection<ComplicationViewModel>> observer = + captureComplicationViewModelsObserver(); + + // Add a complication before entry animations are finished. + final HashSet<ComplicationViewModel> complications = new HashSet<>( + Collections.singletonList(mComplicationViewModel)); + observer.onChanged(complications); + + // The complication view should not be set to invisible. + verify(mComplicationView, never()).setVisibility(View.INVISIBLE); + } + private Observer<Collection<ComplicationViewModel>> captureComplicationViewModelsObserver() { verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner), mObserverCaptor.capture()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt new file mode 100644 index 000000000000..eac0e296c51f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2023 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.statusbar.notification.collection.coordinator + +import android.app.Notification +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.SbnBuilder +import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener +import com.android.systemui.statusbar.notification.collection.render.NotifGroupController +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.time.SystemClock +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations.initMocks + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class GroupWhenCoordinatorTest : SysuiTestCase() { + + private lateinit var beforeFinalizeFilterListener: OnBeforeFinalizeFilterListener + private lateinit var afterRenderGroupListener: OnAfterRenderGroupListener + + @Mock private lateinit var pipeline: NotifPipeline + + @Mock private lateinit var delayableExecutor: DelayableExecutor + + @Mock private lateinit var groupController: NotifGroupController + + @Mock private lateinit var systemClock: SystemClock + + @InjectMocks private lateinit var coordinator: GroupWhenCoordinator + + @Before + fun setUp() { + initMocks(this) + whenever(systemClock.currentTimeMillis()).thenReturn(NOW) + coordinator.attach(pipeline) + + beforeFinalizeFilterListener = withArgCaptor { + verify(pipeline).addOnBeforeFinalizeFilterListener(capture()) + } + afterRenderGroupListener = withArgCaptor { + verify(pipeline).addOnAfterRenderGroupListener(capture()) + } + } + + @Test + fun setNotificationGroupWhen_setClosestTimeByNow_whenAllNotificationsAreBeforeNow() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, NOW - 10L) + val childEntry2 = buildNotificationEntry(2, NOW - 100L) + val groupEntry = + GroupEntryBuilder() + .setSummary(summaryEntry) + .setChildren(listOf(childEntry1, childEntry2)) + .build() + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + // THEN + verify(groupController).setNotificationGroupWhen(eq(NOW - 10L)) + } + + @Test + fun setNotificationGroupWhen_setClosestTimeByNow_whenAllNotificationsAreAfterNow() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, NOW + 10L) + val childEntry2 = buildNotificationEntry(2, NOW + 100L) + + val groupEntry = + GroupEntryBuilder() + .setSummary(summaryEntry) + .setChildren(listOf(childEntry1, childEntry2)) + .build() + + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + // THEN + verify(groupController).setNotificationGroupWhen(eq(NOW + 10L)) + } + + @Test + fun setNotificationGroupWhen_setClosestFutureTimeByNow_whenThereAreBothBeforeAndAfterNow() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, NOW + 100L) + val childEntry2 = buildNotificationEntry(2, NOW + 10L) + val childEntry3 = buildNotificationEntry(3, NOW - 100L) + val childEntry4 = buildNotificationEntry(4, NOW - 9L) + + val groupEntry = + GroupEntryBuilder() + .setSummary(summaryEntry) + .setChildren(listOf(childEntry1, childEntry2, childEntry3, childEntry4)) + .build() + + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + // THEN + verify(groupController).setNotificationGroupWhen(eq(NOW + 10L)) + } + + @Test + fun setNotificationGroupWhen_filterInvalidNotificationTimes() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, NOW + 100L) + val childEntry2 = buildNotificationEntry(2, -20000L) + val childEntry3 = buildNotificationEntry(4, 0) + + val groupEntry = + GroupEntryBuilder() + .setSummary(summaryEntry) + .setChildren(listOf(childEntry1, childEntry2, childEntry3)) + .build() + + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + // THEN + verify(groupController).setNotificationGroupWhen(eq(NOW + 100)) + } + + @Test + fun setNotificationGroupWhen_setSummaryTimeWhenAllNotificationTimesAreInvalid() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, 0) + val childEntry2 = buildNotificationEntry(2, -1) + + val groupEntry = + GroupEntryBuilder() + .setSummary(summaryEntry) + .setChildren(listOf(childEntry1, childEntry2)) + .build() + + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + // THEN + verify(groupController, never()).setNotificationGroupWhen(NOW) + } + + @Test + fun setNotificationGroupWhen_schedulePipelineInvalidationWhenAnyNotificationIsInTheFuture() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, NOW + 1000L) + val childEntry2 = buildNotificationEntry(2, NOW + 2000L) + val childEntry3 = buildNotificationEntry(3, NOW - 100L) + + val groupEntry = + GroupEntryBuilder() + .setSummary(summaryEntry) + .setChildren(listOf(childEntry1, childEntry2, childEntry3)) + .build() + + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + // THEN + verify(delayableExecutor).executeDelayed(any(), eq(1000)) + } + + @Test + fun setNotificationGroupWhen_cancelPrevPipelineInvalidation() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, NOW + 1L) + val prevInvalidation = mock<Runnable>() + whenever(delayableExecutor.executeDelayed(any(), any())).thenReturn(prevInvalidation) + + val groupEntry = + GroupEntryBuilder().setSummary(summaryEntry).setChildren(listOf(childEntry1)).build() + + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + + // THEN + verify(prevInvalidation).run() + } + + private fun buildNotificationEntry(id: Int, timeMillis: Long): NotificationEntry { + val notification = Notification.Builder(mContext).setWhen(timeMillis).build() + val sbn = SbnBuilder().setNotification(notification).build() + return NotificationEntryBuilder().setId(id).setSbn(sbn).build() + } + + private companion object { + private const val NOW = 1000L + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt index 86529dce948a..35dea60b1a1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline.mobile.shared +package com.android.systemui.statusbar.pipeline.mobile.data import android.net.Network import android.net.NetworkCapabilities diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt index 0145103d55e1..dfef62e95eda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt @@ -24,8 +24,8 @@ import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index 17502f28a479..07c8cee9a3d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -27,13 +27,13 @@ import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index b2577e349da7..bd5a4d7f7385 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -53,6 +53,7 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_LTE import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel @@ -65,7 +66,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrier import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 09b7a66c925d..68b1cda62f4c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -37,12 +37,12 @@ import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt new file mode 100644 index 000000000000..4aa48d6f25f1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2023 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.statusbar.pipeline.mobile.ui + +import android.widget.TextView +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBufferFactory +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.KeyguardMobileIconViewModel +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +class MobileViewLoggerTest : SysuiTestCase() { + private val buffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10) + private val stringWriter = StringWriter() + private val printWriter = PrintWriter(stringWriter) + + private val underTest = MobileViewLogger(buffer, mock()) + + @Mock private lateinit var flags: StatusBarPipelineFlags + @Mock private lateinit var commonViewModel: MobileIconViewModel + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun collectionStarted_dumpHasInfo() { + val view = TextView(context) + val viewModel = QsMobileIconViewModel(commonViewModel, flags) + + underTest.logCollectionStarted(view, viewModel) + + val dumpString = getDumpString() + assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=true") + } + + @Test + fun collectionStarted_multipleViews_dumpHasInfo() { + val view = TextView(context) + val view2 = TextView(context) + val viewModel = QsMobileIconViewModel(commonViewModel, flags) + val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags) + + underTest.logCollectionStarted(view, viewModel) + underTest.logCollectionStarted(view2, viewModel2) + + val dumpString = getDumpString() + assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=true") + assertThat(dumpString).contains("${view2.getIdForLogging()}, isCollecting=true") + } + + @Test + fun collectionStopped_dumpHasInfo() { + val view = TextView(context) + val view2 = TextView(context) + val viewModel = QsMobileIconViewModel(commonViewModel, flags) + val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags) + + underTest.logCollectionStarted(view, viewModel) + underTest.logCollectionStarted(view2, viewModel2) + underTest.logCollectionStopped(view, viewModel) + + val dumpString = getDumpString() + assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=false") + assertThat(dumpString).contains("${view2.getIdForLogging()}, isCollecting=true") + } + + private fun getDumpString(): String { + underTest.dump(printWriter, args = arrayOf()) + return stringWriter.toString() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt index e68a3970ae93..7420db2e895e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel @@ -60,6 +61,7 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var tableLogBuffer: TableLogBuffer + @Mock private lateinit var viewLogger: MobileViewLogger @Mock private lateinit var constants: ConnectivityConstants private lateinit var interactor: FakeMobileIconInteractor private lateinit var airplaneModeRepository: FakeAirplaneModeRepository @@ -94,7 +96,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { @Test fun setVisibleState_icon_iconShownDotHidden() { - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) view.setVisibleState(StatusBarIconView.STATE_ICON, /* animate= */ false) @@ -109,8 +117,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { @Test fun setVisibleState_dot_iconHiddenDotShown() { - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) - + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) view.setVisibleState(StatusBarIconView.STATE_DOT, /* animate= */ false) ViewUtils.attachView(view) @@ -124,8 +137,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { @Test fun setVisibleState_hidden_iconAndDotHidden() { - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) - + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) view.setVisibleState(StatusBarIconView.STATE_HIDDEN, /* animate= */ false) ViewUtils.attachView(view) @@ -142,8 +160,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { whenever(constants.hasDataCapabilities).thenReturn(false) createViewModel() - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) - + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) ViewUtils.attachView(view) testableLooper.processAllMessages() @@ -157,8 +180,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { whenever(constants.hasDataCapabilities).thenReturn(true) createViewModel() - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) - + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) ViewUtils.attachView(view) testableLooper.processAllMessages() @@ -171,8 +199,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { fun isIconVisible_notAirplaneMode_outputsTrue() { airplaneModeRepository.setIsAirplaneMode(false) - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) - + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) ViewUtils.attachView(view) testableLooper.processAllMessages() @@ -185,8 +218,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { fun isIconVisible_airplaneMode_outputsTrue() { airplaneModeRepository.setIsAirplaneMode(true) - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) - + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) ViewUtils.attachView(view) testableLooper.processAllMessages() @@ -198,7 +236,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { @Test fun onDarkChanged_iconHasNewColor() { whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false) - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) ViewUtils.attachView(view) testableLooper.processAllMessages() @@ -214,7 +258,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { @Test fun setStaticDrawableColor_iconHasNewColor() { whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false) - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) ViewUtils.attachView(view) testableLooper.processAllMessages() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt index f9830309252d..a6d915243f60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn @@ -84,7 +85,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { testScope.backgroundScope, ) - homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags) + homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags, mock()) qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags) keyguardIcon = KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt index 4628f8410245..ddb7f4d88d30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt @@ -24,6 +24,8 @@ import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirp import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger +import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository @@ -51,6 +53,8 @@ class MobileIconsViewModelTest : SysuiTestCase() { private lateinit var airplaneModeInteractor: AirplaneModeInteractor @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var constants: ConnectivityConstants + @Mock private lateinit var logger: MobileViewLogger + @Mock private lateinit var verboseLogger: VerboseMobileViewLogger private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -73,6 +77,8 @@ class MobileIconsViewModelTest : SysuiTestCase() { underTest = MobileIconsViewModel( subscriptionIdsFlow, + logger, + verboseLogger, interactor, airplaneModeInteractor, constants, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt index e4c8fd0cd8a1..b4039d906810 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt @@ -164,6 +164,10 @@ class ModernStatusBarViewTest : SysuiTestCase() { override fun getShouldIconBeVisible(): Boolean { return shouldIconBeVisibleInternal } + + override fun isCollecting(): Boolean { + return true + } } } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 0ee883f745ca..f236a961fcb6 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -402,6 +402,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub public void systemServicesReady() { mStats.systemServicesReady(mContext); + mCpuWakeupStats.systemServicesReady(); mWorker.systemServicesReady(); final INetworkManagementService nms = INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 55d2921b2878..ea157c89f675 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -128,6 +128,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.Spline; +import android.view.ContentRecordingSession; import android.view.Display; import android.view.DisplayEventReceiver; import android.view.DisplayInfo; @@ -250,6 +251,7 @@ public final class DisplayManagerService extends SystemService { private ActivityManagerInternal mActivityManagerInternal; private ActivityManager mActivityManager; private UidImportanceListener mUidImportanceListener = new UidImportanceListener(); + @Nullable private IMediaProjectionManager mProjectionService; private DeviceStateManagerInternal mDeviceStateManager; @GuardedBy("mSyncRoot") @@ -1494,8 +1496,9 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { + final int displayId; synchronized (mSyncRoot) { - final int displayId = + displayId = createVirtualDisplayLocked( callback, projection, @@ -1509,8 +1512,39 @@ public final class DisplayManagerService extends SystemService { mDisplayWindowPolicyControllers.put( displayId, Pair.create(virtualDevice, dwpc)); } - return displayId; } + + // When calling setContentRecordingSession into the WindowManagerService, the WMS + // attempts to acquire a lock before executing its main body. Due to this, we need + // to be sure that it isn't called while the DisplayManagerService is also holding + // a lock, to avoid a deadlock scenario. + final ContentRecordingSession session = + virtualDisplayConfig.getContentRecordingSession(); + + if (displayId != Display.INVALID_DISPLAY && session != null) { + // Only attempt to set content recording session if there are details to set and a + // VirtualDisplay has been successfully constructed. + session.setDisplayId(displayId); + + // We set the content recording session here on the server side instead of using + // a second AIDL call in MediaProjection. By ensuring that a virtual display has + // been constructed before calling setContentRecordingSession, we avoid a race + // condition between the DMS & WMS which could lead to the MediaProjection + // being pre-emptively torn down. + if (!mWindowManagerInternal.setContentRecordingSession(session)) { + // Unable to start mirroring, so tear down projection & release VirtualDisplay. + try { + getProjectionService().stopActiveProjection(); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to tell MediaProjectionManagerService to stop the " + + "active projection", e); + } + releaseVirtualDisplayInternal(callback.asBinder()); + return Display.INVALID_DISPLAY; + } + } + + return displayId; } finally { Binder.restoreCallingIdentity(token); } @@ -2804,8 +2838,7 @@ public final class DisplayManagerService extends SystemService { private IMediaProjectionManager getProjectionService() { if (mProjectionService == null) { - IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE); - mProjectionService = IMediaProjectionManager.Stub.asInterface(b); + mProjectionService = mInjector.getProjectionService(); } return mProjectionService; } @@ -2964,6 +2997,11 @@ public final class DisplayManagerService extends SystemService { boolean getHdrOutputConversionSupport() { return DisplayControl.getHdrOutputConversionSupport(); } + + IMediaProjectionManager getProjectionService() { + IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE); + return IMediaProjectionManager.Stub.asInterface(b); + } } @VisibleForTesting diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index eda15ae32c8b..4f7a2ba58570 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -88,7 +88,17 @@ public class VirtualDisplayAdapter extends DisplayAdapter { // Called with SyncRoot lock held. public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener) { - this(syncRoot, context, handler, listener, DisplayControl::createDisplay); + this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() { + @Override + public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) { + return DisplayControl.createDisplay(name, secure, requestedRefreshRate); + } + + @Override + public void destroyDisplay(IBinder displayToken) { + DisplayControl.destroyDisplay(displayToken); + } + }); } @VisibleForTesting @@ -311,7 +321,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mSurface.release(); mSurface = null; } - DisplayControl.destroyDisplay(getDisplayTokenLocked()); + mSurfaceControlDisplayFactory.destroyDisplay(getDisplayTokenLocked()); if (mProjection != null && mMediaProjectionCallback != null) { try { mProjection.unregisterCallback(mMediaProjectionCallback); @@ -653,5 +663,12 @@ public class VirtualDisplayAdapter extends DisplayAdapter { * @return The token reference for the display in SurfaceFlinger. */ IBinder createDisplay(String name, boolean secure, float requestedRefreshRate); + + /** + * Destroy a display in SurfaceFlinger. + * + * @param displayToken The display token for the display to be destroyed. + */ + void destroyDisplay(IBinder displayToken); } } diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java index 706aedcbba7b..b05b662dc1e8 100644 --- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java +++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java @@ -22,7 +22,9 @@ import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI; import android.content.Context; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.util.IndentingPrintWriter; import android.util.IntArray; import android.util.LongSparseArray; @@ -40,6 +42,8 @@ import com.android.internal.util.IntPair; import java.util.Arrays; import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -52,13 +56,13 @@ public class CpuWakeupStats { private static final String SUBSYSTEM_ALARM_STRING = "Alarm"; private static final String SUBSYSTEM_ALARM_WIFI = "Wifi"; @VisibleForTesting - static final long WAKEUP_RETENTION_MS = 3 * 24 * 60 * 60_000; // 3 days. - @VisibleForTesting static final long WAKEUP_REASON_HALF_WINDOW_MS = 500; - private static final long WAKEUP_WRITE_DELAY_MS = 2 * 60 * 1000; // 2 minutes. + private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.MINUTES.toMillis(2); private final Handler mHandler; private final IrqDeviceMap mIrqDeviceMap; + @VisibleForTesting + final Config mConfig = new Config(); private final WakingActivityHistory mRecentWakingActivity = new WakingActivityHistory(); @VisibleForTesting @@ -72,6 +76,14 @@ public class CpuWakeupStats { mHandler = handler; } + /** + * Called on the boot phase SYSTEM_SERVICES_READY. + * This ensures that DeviceConfig is ready for calls to read properties. + */ + public synchronized void systemServicesReady() { + mConfig.register(new HandlerExecutor(mHandler)); + } + private static int subsystemToStatsReason(int subsystem) { switch (subsystem) { case CPU_WAKEUP_SUBSYSTEM_ALARM: @@ -136,14 +148,15 @@ public class CpuWakeupStats { // we can delete all history that will not be useful in attributing future wakeups. mRecentWakingActivity.clearAllBefore(elapsedRealtime - WAKEUP_REASON_HALF_WINDOW_MS); - // Limit history of wakeups and their attribution to the last WAKEUP_RETENTION_MS. Note that + // Limit history of wakeups and their attribution to the last retentionDuration. Note that // the last wakeup and its attribution (if computed) is always stored, even if that wakeup - // had occurred before WAKEUP_RETENTION_MS. - int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS); + // had occurred before retentionDuration. + final long retentionDuration = mConfig.WAKEUP_STATS_RETENTION_MS; + int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - retentionDuration); for (int i = lastIdx; i >= 0; i--) { mWakeupEvents.removeAt(i); } - lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS); + lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - retentionDuration); for (int i = lastIdx; i >= 0; i--) { mWakeupAttribution.removeAt(i); } @@ -226,6 +239,9 @@ public class CpuWakeupStats { pw.println("CPU wakeup stats:"); pw.increaseIndent(); + mConfig.dump(pw); + pw.println(); + mIrqDeviceMap.dump(pw); pw.println(); @@ -296,7 +312,8 @@ public class CpuWakeupStats { } private static final class WakingActivityHistory { - private static final long WAKING_ACTIVITY_RETENTION_MS = 3 * 60 * 60_000; // 3 hours. + private static final long WAKING_ACTIVITY_RETENTION_MS = TimeUnit.MINUTES.toMillis(10); + private SparseArray<TimeSparseArray<SparseBooleanArray>> mWakingActivity = new SparseArray<>(); @@ -521,4 +538,52 @@ public class CpuWakeupStats { } } } + + static final class Config implements DeviceConfig.OnPropertiesChangedListener { + static final String KEY_WAKEUP_STATS_RETENTION_MS = "wakeup_stats_retention_ms"; + + private static final String[] PROPERTY_NAMES = { + KEY_WAKEUP_STATS_RETENTION_MS, + }; + + static final long DEFAULT_WAKEUP_STATS_RETENTION_MS = TimeUnit.DAYS.toMillis(3); + + /** + * Wakeup stats are retained only for this duration. + */ + public volatile long WAKEUP_STATS_RETENTION_MS = DEFAULT_WAKEUP_STATS_RETENTION_MS; + + void register(Executor executor) { + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BATTERY_STATS, + executor, this); + onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BATTERY_STATS, + PROPERTY_NAMES)); + } + + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + for (String name : properties.getKeyset()) { + if (name == null) { + continue; + } + switch (name) { + case KEY_WAKEUP_STATS_RETENTION_MS: + WAKEUP_STATS_RETENTION_MS = properties.getLong( + KEY_WAKEUP_STATS_RETENTION_MS, DEFAULT_WAKEUP_STATS_RETENTION_MS); + break; + } + } + } + + void dump(IndentingPrintWriter pw) { + pw.println("Config:"); + + pw.increaseIndent(); + + pw.print(KEY_WAKEUP_STATS_RETENTION_MS, WAKEUP_STATS_RETENTION_MS); + pw.println(); + + pw.decreaseIndent(); + } + } } diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java index c8ec7c20650b..e84f0cc17d05 100644 --- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java @@ -20,17 +20,18 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.credentials.ClearCredentialStateRequest; +import android.credentials.CredentialProviderInfo; import android.credentials.IClearCredentialStateCallback; import android.credentials.ui.ProviderData; import android.credentials.ui.RequestInfo; import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; -import android.service.credentials.CredentialProviderInfo; import android.util.Log; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; +import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; @@ -120,22 +121,22 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta Log.i(TAG, "respondToClientWithResponseAndFinish"); if (isSessionCancelled()) { mChosenProviderMetric.setChosenProviderStatus( - MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS); + ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED); + ApiStatus.CLIENT_CANCELED); finishSession(/*propagateCancellation=*/true); return; } try { mClientCallback.onSuccess(); logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_SUCCESS); + ApiStatus.SUCCESS); } catch (RemoteException e) { mChosenProviderMetric.setChosenProviderStatus( - MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE); + ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); Log.i(TAG, "Issue while propagating the response to the client"); logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_FAILURE); + ApiStatus.FAILURE); } finishSession(/*propagateCancellation=*/false); } @@ -144,7 +145,7 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta Log.i(TAG, "respondToClientWithErrorAndFinish"); if (isSessionCancelled()) { logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED); + ApiStatus.CLIENT_CANCELED); finishSession(/*propagateCancellation=*/true); return; } @@ -154,7 +155,7 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta e.printStackTrace(); } logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_FAILURE); + ApiStatus.FAILURE); finishSession(/*propagateCancellation=*/false); } diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index 0c1133ce8793..7e1780d05d75 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -16,9 +16,6 @@ package com.android.server.credentials; -import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE; -import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS; - import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -27,17 +24,18 @@ import android.credentials.CreateCredentialException; import android.credentials.CreateCredentialRequest; import android.credentials.CreateCredentialResponse; import android.credentials.CredentialManager; +import android.credentials.CredentialProviderInfo; import android.credentials.ICreateCredentialCallback; import android.credentials.ui.ProviderData; import android.credentials.ui.RequestInfo; import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; -import android.service.credentials.CredentialProviderInfo; import android.util.Log; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; +import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; @@ -103,11 +101,11 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR setChosenMetric(componentName); if (response != null) { mChosenProviderMetric.setChosenProviderStatus( - METRICS_PROVIDER_STATUS_FINAL_SUCCESS); + ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); respondToClientWithResponseAndFinish(response); } else { mChosenProviderMetric.setChosenProviderStatus( - METRICS_PROVIDER_STATUS_FINAL_FAILURE); + ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS, "Invalid response"); } @@ -144,18 +142,18 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR } if (isSessionCancelled()) { logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED); + ApiStatus.CLIENT_CANCELED); finishSession(/*propagateCancellation=*/true); return; } try { mClientCallback.onResponse(response); logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_SUCCESS); + ApiStatus.SUCCESS); } catch (RemoteException e) { Log.i(TAG, "Issue while responding to client: " + e.getMessage()); logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_FAILURE); + ApiStatus.FAILURE); } finishSession(/*propagateCancellation=*/false); } @@ -168,7 +166,7 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR } if (isSessionCancelled()) { logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED); + ApiStatus.CLIENT_CANCELED); finishSession(/*propagateCancellation=*/true); return; } @@ -184,10 +182,10 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR private void logFailureOrUserCancel(String errorType) { if (CreateCredentialException.TYPE_USER_CANCELED.equals(errorType)) { logApiCall(ApiName.CREATE_CREDENTIAL, - /* apiStatus */ ApiStatus.METRICS_API_STATUS_USER_CANCELED); + /* apiStatus */ ApiStatus.USER_CANCELED); } else { logApiCall(ApiName.CREATE_CREDENTIAL, - /* apiStatus */ ApiStatus.METRICS_API_STATUS_FAILURE); + /* apiStatus */ ApiStatus.FAILURE); } } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 41ae9118d965..9c870050449c 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -36,15 +36,14 @@ import android.credentials.CreateCredentialRequest; import android.credentials.CredentialDescription; import android.credentials.CredentialManager; import android.credentials.CredentialOption; +import android.credentials.CredentialProviderInfo; import android.credentials.GetCredentialException; import android.credentials.GetCredentialRequest; import android.credentials.IClearCredentialStateCallback; import android.credentials.ICreateCredentialCallback; import android.credentials.ICredentialManager; import android.credentials.IGetCredentialCallback; -import android.credentials.IListEnabledProvidersCallback; import android.credentials.ISetEnabledProvidersCallback; -import android.credentials.ListEnabledProvidersResponse; import android.credentials.RegisterCredentialDescriptionRequest; import android.credentials.UnregisterCredentialDescriptionRequest; import android.credentials.ui.IntentFactory; @@ -56,7 +55,7 @@ import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; import android.service.credentials.CallingAppInfo; -import android.service.credentials.CredentialProviderInfo; +import android.service.credentials.CredentialProviderInfoFactory; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -118,10 +117,11 @@ public final class CredentialManagerService int resolvedUserId) { List<CredentialManagerServiceImpl> services = new ArrayList<>(); List<CredentialProviderInfo> serviceInfos = - CredentialProviderInfo.getAvailableSystemServices( + CredentialProviderInfoFactory.getAvailableSystemServices( mContext, resolvedUserId, - /* disableSystemAppVerificationForTests= */ false); + /* disableSystemAppVerificationForTests= */ false, + new HashSet<>()); serviceInfos.forEach( info -> { services.add( @@ -222,10 +222,16 @@ public final class CredentialManagerService return hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS); } - private void verifyPermission(String permission) throws SecurityException { - if (!hasPermission(permission)) { - throw new SecurityException("Caller is missing permission: " + permission); + private void verifyGetProvidersPermission() throws SecurityException { + if (hasPermission(android.Manifest.permission.QUERY_ALL_PACKAGES)) { + return; } + + if (hasPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS)) { + return; + } + + throw new SecurityException("Caller is missing permission: QUERY_ALL_PACKAGES or LIST_ENABLED_CREDENTIAL_PROVIDERS"); } private boolean hasPermission(String permission) { @@ -550,40 +556,6 @@ public final class CredentialManagerService providerSessions.forEach(ProviderSession::invokeSession); } - @SuppressWarnings("GuardedBy") // ErrorProne requires listEnabledProviders - // to be guarded by 'service.mLock', which is the same as mLock. - @Override - public ICancellationSignal listEnabledProviders(IListEnabledProvidersCallback callback) { - Log.i(TAG, "listEnabledProviders"); - ICancellationSignal cancelTransport = CancellationSignal.createTransport(); - - if (!hasWriteSecureSettingsPermission()) { - try { - callback.onError( - PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR); - } catch (RemoteException e) { - Log.e(TAG, "Issue with invoking response: " + e.getMessage()); - } - return cancelTransport; - } - - List<String> enabledProviders = new ArrayList<>(); - runForUser( - (service) -> { - enabledProviders.add(service.getComponentName().flattenToString()); - }); - - // Call the callback. - try { - callback.onResponse(ListEnabledProvidersResponse.create(enabledProviders)); - } catch (RemoteException e) { - Log.i(TAG, "Issue with invoking response: " + e.getMessage()); - // TODO: Propagate failure - } - - return cancelTransport; - } - @Override public void setEnabledProviders( List<String> providers, int userId, ISetEnabledProvidersCallback callback) { @@ -659,7 +631,7 @@ public final class CredentialManagerService // The component name and the package name do not match. MetricUtilities.logApiCalled( ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE, - ApiStatus.METRICS_API_STATUS_FAILURE, callingUid); + ApiStatus.FAILURE, callingUid); Log.w( TAG, "isEnabledCredentialProviderService: Component name does not" @@ -667,7 +639,7 @@ public final class CredentialManagerService return false; } MetricUtilities.logApiCalled(ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE, - ApiStatus.METRICS_API_STATUS_SUCCESS, callingUid); + ApiStatus.SUCCESS, callingUid); return true; } } @@ -677,20 +649,35 @@ public final class CredentialManagerService } @Override - public List<ServiceInfo> getCredentialProviderServices( - int userId, boolean disableSystemAppVerificationForTests, int providerFilter) { + public List<CredentialProviderInfo> getCredentialProviderServices( + int userId, int providerFilter) { Log.i(TAG, "getCredentialProviderServices"); - verifyPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS); - - List<ServiceInfo> services = new ArrayList<>(); - List<CredentialProviderInfo> providers = - CredentialProviderInfo.getCredentialProviderServices( - mContext, userId, disableSystemAppVerificationForTests, providerFilter); - for (CredentialProviderInfo p : providers) { - services.add(p.getServiceInfo()); - } + verifyGetProvidersPermission(); - return services; + return CredentialProviderInfoFactory.getCredentialProviderServices( + mContext, userId, providerFilter, getEnabledProviders()); + } + + @Override + public List<CredentialProviderInfo> getCredentialProviderServicesForTesting( + int providerFilter) { + Log.i(TAG, "getCredentialProviderServicesForTesting"); + verifyGetProvidersPermission(); + + final int userId = UserHandle.getCallingUserId(); + return CredentialProviderInfoFactory.getCredentialProviderServicesForTesting( + mContext, userId, providerFilter, getEnabledProviders()); + } + + private Set<ServiceInfo> getEnabledProviders() { + Set<ServiceInfo> enabledProviders = new HashSet<>(); + synchronized (mLock) { + runForUser( + (service) -> { + enabledProviders.add(service.getCredentialProviderInfo().getServiceInfo()); + }); + } + return enabledProviders; } @Override @@ -828,11 +815,11 @@ public final class CredentialManagerService } private List<CredentialProviderInfo> getServicesForCredentialDescription(int userId) { - return CredentialProviderInfo.getCredentialProviderServices( + return CredentialProviderInfoFactory.getCredentialProviderServices( mContext, userId, - /* disableSystemAppVerificationForTests= */ false, - CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS); + CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS, + new HashSet<>()); } } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java index 546c48fe05f4..ee55a1ccc357 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java @@ -21,7 +21,8 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; -import android.service.credentials.CredentialProviderInfo; +import android.credentials.CredentialProviderInfo; +import android.service.credentials.CredentialProviderInfoFactory; import android.util.Log; import android.util.Slog; @@ -82,7 +83,7 @@ public final class CredentialManagerServiceImpl extends Log.i(TAG, "newServiceInfoLocked with null mInfo , " + serviceComponent.getPackageName()); } - mInfo = new CredentialProviderInfo( + mInfo = CredentialProviderInfoFactory.create( getContext(), serviceComponent, mUserId, /*isSystemProvider=*/false); return mInfo.getServiceInfo(); diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java index 2c6c0d8b4018..546c37ff95af 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ServiceInfo; import android.credentials.CredentialManager; +import android.credentials.CredentialProviderInfo; import android.credentials.ui.DisabledProviderData; import android.credentials.ui.IntentFactory; import android.credentials.ui.ProviderData; @@ -31,11 +32,12 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.ResultReceiver; -import android.service.credentials.CredentialProviderInfo; +import android.service.credentials.CredentialProviderInfoFactory; import android.util.Log; import android.util.Slog; import java.util.ArrayList; +import java.util.HashSet; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -118,11 +120,11 @@ public class CredentialManagerUi { .map(ProviderData::getProviderFlattenedComponentName) .collect(Collectors.toUnmodifiableSet()); Set<String> allProviders = - CredentialProviderInfo.getCredentialProviderServices( + CredentialProviderInfoFactory.getCredentialProviderServices( mContext, mUserId, - /* disableSystemAppVerificationForTests= */ false, - CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY) + CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY, + new HashSet<>()) .stream() .map(CredentialProviderInfo::getServiceInfo) .map(ServiceInfo::getComponentName) diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java index 13f4b542a83e..8c6e5cea3bba 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -16,12 +16,10 @@ package com.android.server.credentials; -import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE; -import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS; - import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.credentials.CredentialProviderInfo; import android.credentials.GetCredentialException; import android.credentials.GetCredentialRequest; import android.credentials.GetCredentialResponse; @@ -31,11 +29,11 @@ import android.credentials.ui.RequestInfo; import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; -import android.service.credentials.CredentialProviderInfo; import android.util.Log; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; +import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; @@ -95,11 +93,11 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest setChosenMetric(componentName); if (response != null) { mChosenProviderMetric.setChosenProviderStatus( - METRICS_PROVIDER_STATUS_FINAL_SUCCESS); + ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); respondToClientWithResponseAndFinish(response); } else { mChosenProviderMetric.setChosenProviderStatus( - METRICS_PROVIDER_STATUS_FINAL_FAILURE); + ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, "Invalid response from provider"); } @@ -120,18 +118,18 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest } if (isSessionCancelled()) { logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED); + ApiStatus.CLIENT_CANCELED); finishSession(/*propagateCancellation=*/true); return; } try { mClientCallback.onResponse(response); logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_SUCCESS); + ApiStatus.SUCCESS); } catch (RemoteException e) { Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage()); logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_FAILURE); + ApiStatus.FAILURE); } finishSession(/*propagateCancellation=*/false); } @@ -143,7 +141,7 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest } if (isSessionCancelled()) { logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED); + ApiStatus.CLIENT_CANCELED); finishSession(/*propagateCancellation=*/true); return; } @@ -160,10 +158,10 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest private void logFailureOrUserCancel(String errorType) { if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) { logApiCall(ApiName.GET_CREDENTIAL, - /* apiStatus */ ApiStatus.METRICS_API_STATUS_USER_CANCELED); + /* apiStatus */ ApiStatus.USER_CANCELED); } else { logApiCall(ApiName.GET_CREDENTIAL, - /* apiStatus */ ApiStatus.METRICS_API_STATUS_FAILURE); + /* apiStatus */ ApiStatus.FAILURE); } } diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index e7b0a2d9f731..880ae6d4201e 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -16,12 +16,6 @@ package com.android.server.credentials; -import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE; -import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS; -import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE; -import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS; -import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN; - import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; @@ -46,18 +40,6 @@ public class MetricUtilities { public static final int DEFAULT_INT_32 = -1; public static final int[] DEFAULT_REPEATED_INT_32 = new int[0]; - // Metrics constants TODO(b/269290341) migrate to enums eventually to improve - protected static final int METRICS_PROVIDER_STATUS_FINAL_FAILURE = - CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE; - protected static final int METRICS_PROVIDER_STATUS_QUERY_FAILURE = - CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE; - protected static final int METRICS_PROVIDER_STATUS_FINAL_SUCCESS = - CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS; - protected static final int METRICS_PROVIDER_STATUS_QUERY_SUCCESS = - CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS; - protected static final int METRICS_PROVIDER_STATUS_UNKNOWN = - CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN; - /** * This retrieves the uid of any package name, given a context and a component name for the diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java index 941d9ad26dca..b7a4cd581a1a 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java @@ -20,11 +20,11 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; import android.credentials.ClearCredentialStateException; +import android.credentials.CredentialProviderInfo; import android.credentials.ui.ProviderData; import android.credentials.ui.ProviderPendingIntentResponse; import android.service.credentials.CallingAppInfo; import android.service.credentials.ClearCredentialStateRequest; -import android.service.credentials.CredentialProviderInfo; import android.util.Log; import android.util.Slog; diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java index 17caba5831e8..640cc3331b1a 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.credentials.CreateCredentialException; import android.credentials.CreateCredentialResponse; +import android.credentials.CredentialProviderInfo; import android.credentials.ui.CreateCredentialProviderData; import android.credentials.ui.Entry; import android.credentials.ui.ProviderPendingIntentResponse; @@ -33,7 +34,6 @@ import android.service.credentials.BeginCreateCredentialResponse; import android.service.credentials.CallingAppInfo; import android.service.credentials.CreateCredentialRequest; import android.service.credentials.CreateEntry; -import android.service.credentials.CredentialProviderInfo; import android.service.credentials.CredentialProviderService; import android.service.credentials.RemoteEntry; import android.util.Log; diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index b8b11ebd13f0..07e2f877bfc0 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -23,6 +23,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.credentials.CredentialOption; +import android.credentials.CredentialProviderInfo; import android.credentials.GetCredentialException; import android.credentials.GetCredentialResponse; import android.credentials.ui.AuthenticationEntry; @@ -35,7 +36,6 @@ import android.service.credentials.BeginGetCredentialRequest; import android.service.credentials.BeginGetCredentialResponse; import android.service.credentials.CallingAppInfo; import android.service.credentials.CredentialEntry; -import android.service.credentials.CredentialProviderInfo; import android.service.credentials.CredentialProviderService; import android.service.credentials.GetCredentialRequest; import android.service.credentials.RemoteEntry; diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index 53ed070e3e49..a85769572972 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -16,9 +16,6 @@ package com.android.server.credentials; -import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_QUERY_FAILURE; -import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_QUERY_SUCCESS; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,14 +24,15 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.credentials.Credential; +import android.credentials.CredentialProviderInfo; import android.credentials.ui.ProviderData; import android.credentials.ui.ProviderPendingIntentResponse; import android.os.ICancellationSignal; import android.os.RemoteException; -import android.service.credentials.CredentialProviderInfo; import android.util.Log; import com.android.server.credentials.metrics.CandidateProviderMetric; +import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.UUID; @@ -205,9 +203,11 @@ public abstract class ProviderSession<T, R> mCandidateProviderMetric .setQueryFinishTimeNanoseconds(System.nanoTime()); if (isTerminatingStatus(status)) { - mCandidateProviderMetric.setProviderQueryStatus(METRICS_PROVIDER_STATUS_QUERY_FAILURE); + mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_FAILURE + .getMetricCode()); } else if (isCompletionStatus(status)) { - mCandidateProviderMetric.setProviderQueryStatus(METRICS_PROVIDER_STATUS_QUERY_SUCCESS); + mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_SUCCESS + .getMetricCode()); } } diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index abfb2107c4a3..c1f35d0f8195 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; +import android.credentials.CredentialProviderInfo; import android.credentials.ui.ProviderData; import android.credentials.ui.UserSelectionDialogResult; import android.os.Binder; @@ -30,7 +31,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.service.credentials.CallingAppInfo; -import android.service.credentials.CredentialProviderInfo; import android.util.Log; import com.android.internal.R; diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java index 36a1f2df6d24..22cab707387d 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java @@ -22,11 +22,11 @@ import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_USER_CANCELED; public enum ApiStatus { - METRICS_API_STATUS_SUCCESS(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS), - METRICS_API_STATUS_FAILURE(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE), - METRICS_API_STATUS_CLIENT_CANCELED( + SUCCESS(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS), + FAILURE(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE), + CLIENT_CANCELED( CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_CLIENT_CANCELED), - METRICS_API_STATUS_USER_CANCELED( + USER_CANCELED( CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_USER_CANCELED); private final int mInnerMetricCode; diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java index f49995d041aa..9f438ecc1146 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java @@ -18,6 +18,9 @@ package com.android.server.credentials.metrics; /** * The central candidate provider metric object that mimics our defined metric setup. + * Some types are redundant across these metric collectors, but that has debug use-cases as + * these data-types are available at different moments of the flow (and typically, one can feed + * into the next). * TODO(b/270403549) - iterate on this in V3+ */ public class CandidateProviderMetric { diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java index 75fdc567013c..03102558d21b 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java @@ -22,6 +22,9 @@ import com.android.server.credentials.MetricUtilities; /** * The central chosen provider metric object that mimics our defined metric setup. + * Some types are redundant across these metric collectors, but that has debug use-cases as + * these data-types are available at different moments of the flow (and typically, one can feed + * into the next). * TODO(b/270403549) - iterate on this in V3+ */ public class ChosenProviderMetric { @@ -65,12 +68,12 @@ public class ChosenProviderMetric { /** * In order for a chosen provider to be selected, the call must have successfully begun. - * Thus, the {@link PreCandidateMetric} can directly pass this initial latency figure into + * Thus, the {@link InitialPhaseMetric} can directly pass this initial latency figure into * this chosen provider metric. * * @param preQueryPhaseLatencyMicroseconds the millisecond latency for the service start, * typically passed in through the - * {@link PreCandidateMetric} + * {@link InitialPhaseMetric} */ public void setPreQueryPhaseLatencyMicroseconds(int preQueryPhaseLatencyMicroseconds) { mPreQueryPhaseLatencyMicroseconds = preQueryPhaseLatencyMicroseconds; @@ -112,7 +115,7 @@ public class ChosenProviderMetric { /** * Returns the full (platform invoked to response) latency in microseconds. Expects the - * start time to be provided, such as from {@link PreCandidateMetric}. + * start time to be provided, such as from {@link InitialPhaseMetric}. */ public int getEntireLatencyMicroseconds() { return (int) ((this.mFinalFinishTimeNanoseconds @@ -123,11 +126,11 @@ public class ChosenProviderMetric { /** * In order for a chosen provider to be selected, the call must have successfully begun. - * Thus, the {@link PreCandidateMetric} can directly pass this initial timestamp into this + * Thus, the {@link InitialPhaseMetric} can directly pass this initial timestamp into this * chosen provider metric. * * @param serviceBeganTimeNanoseconds the timestamp moment when the platform was called, - * typically passed in through the {@link PreCandidateMetric} + * typically passed in through the {@link InitialPhaseMetric} */ public void setServiceBeganTimeNanoseconds(long serviceBeganTimeNanoseconds) { mServiceBeganTimeNanoseconds = serviceBeganTimeNanoseconds; @@ -188,8 +191,6 @@ public class ChosenProviderMetric { - this.mServiceBeganTimeNanoseconds) / 1000); } - - /* ----------- Provider Status -------------- */ public int getChosenProviderStatus() { diff --git a/services/credentials/java/com/android/server/credentials/metrics/PreCandidateMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java index 952328f6ba3a..5f062b05df67 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/PreCandidateMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java @@ -18,19 +18,34 @@ package com.android.server.credentials.metrics; /** * This handles metrics collected prior to any remote calls to providers. + * Some types are redundant across these metric collectors, but that has debug use-cases as + * these data-types are available at different moments of the flow (and typically, one can feed + * into the next). * TODO(b/270403549) - iterate on this in V3+ */ -public class PreCandidateMetric { - +public class InitialPhaseMetric { private static final String TAG = "PreCandidateMetric"; + // The api being called, default set to unknown + private int mApiName = ApiName.UNKNOWN.getMetricCode(); + // The caller uid of the calling application, default to -1 + private int mCallerUid = -1; + // The session id to unite multiple atom emits, default to -1 + private long mSessionId = -1; + // A sequence id to order united emits, default to -1 + private int mSequenceId = -1; + private int mCountRequestClassType = -1; + // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a // reference point. - private long mCredentialServiceStartedTimeNanoseconds = -1; + + // A reference point to give this object utility to capture latency. Can be directly handed + // over to the next latency object. private long mCredentialServiceBeginQueryTimeNanoseconds = -1; - public PreCandidateMetric() { + + public InitialPhaseMetric() { } /* ---------- Latencies ---------- */ @@ -62,4 +77,54 @@ public class PreCandidateMetric { public long getCredentialServiceBeginQueryTimeNanoseconds() { return mCredentialServiceBeginQueryTimeNanoseconds; } + + /* ------ ApiName ------ */ + + public void setApiName(int apiName) { + mApiName = apiName; + } + + public int getApiName() { + return mApiName; + } + + /* ------ CallerUid ------ */ + + public void setCallerUid(int callerUid) { + mCallerUid = callerUid; + } + + public int getCallerUid() { + return mCallerUid; + } + + /* ------ SessionId ------ */ + + public void setSessionId(long sessionId) { + mSessionId = sessionId; + } + + public long getSessionId() { + return mSessionId; + } + + /* ------ SequenceId ------ */ + + public void setSequenceId(int sequenceId) { + mSequenceId = sequenceId; + } + + public int getSequenceId() { + return mSequenceId; + } + + /* ------ Count Request Class Types ------ */ + + public void setCountRequestClassType(int countRequestClassType) { + mCountRequestClassType = countRequestClassType; + } + + public int getCountRequestClassType() { + return mCountRequestClassType; + } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java new file mode 100644 index 000000000000..08f1afa2f438 --- /dev/null +++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 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.credentials.metrics; + +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE; +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS; +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE; +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS; +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN; + +public enum ProviderStatusForMetrics { + + UNKNOWN( + CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN), + FINAL_FAILURE( + CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE), + QUERY_FAILURE( + CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE), + FINAL_SUCCESS( + CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS), + QUERY_SUCCESS( + CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS); + + private final int mInnerMetricCode; + + ProviderStatusForMetrics(int innerMetricCode) { + this.mInnerMetricCode = innerMetricCode; + } + + /** + * Gives the West-world version of the metric name. + * + * @return a code corresponding to the west world metric name + */ + public int getMetricCode() { + return this.mInnerMetricCode; + } +} diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 71492656913b..6861c2f049fb 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -108,6 +108,7 @@ <uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" /> <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" /> <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" /> + <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" /> <queries> <package android:name="com.android.servicestests.apps.suspendtestapp" /> diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index de1c2195fcdb..94d30bb4440b 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -24,6 +24,8 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_D import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -32,8 +34,10 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -61,10 +65,13 @@ import android.hardware.display.HdrConversionMode; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; +import android.media.projection.IMediaProjectionManager; +import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.MessageQueue; import android.os.Process; +import android.view.ContentRecordingSession; import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayEventReceiver; @@ -158,25 +165,40 @@ public class DisplayManagerServiceTest { } }; - class BasicInjector extends DisplayManagerService.Injector { - @Override - VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context, - Handler handler, DisplayAdapter.Listener displayAdapterListener) { - return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener, - (String name, boolean secure, float refreshRate) -> mMockDisplayToken); - } - - @Override - LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, - Handler handler, DisplayAdapter.Listener displayAdapterListener) { - return new LocalDisplayAdapter(syncRoot, context, handler, - displayAdapterListener, new LocalDisplayAdapter.Injector() { - @Override - public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { - return mSurfaceControlProxy; - } - }); - } + class BasicInjector extends DisplayManagerService.Injector { + @Override + IMediaProjectionManager getProjectionService() { + return mMockProjectionService; + } + + @Override + VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context, + Handler handler, DisplayAdapter.Listener displayAdapterListener) { + return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener, + new VirtualDisplayAdapter.SurfaceControlDisplayFactory() { + @Override + public IBinder createDisplay(String name, boolean secure, + float requestedRefreshRate) { + return mMockDisplayToken; + } + + @Override + public void destroyDisplay(IBinder displayToken) { + } + }); + } + + @Override + LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, + Handler handler, DisplayAdapter.Listener displayAdapterListener) { + return new LocalDisplayAdapter(syncRoot, context, handler, + displayAdapterListener, new LocalDisplayAdapter.Injector() { + @Override + public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { + return mSurfaceControlProxy; + } + }); + } @Override int setHdrConversionMode(int conversionMode, int preferredHdrOutputType, @@ -198,6 +220,7 @@ public class DisplayManagerServiceTest { private final DisplayManagerService.Injector mBasicInjector = new BasicInjector(); + @Mock IMediaProjectionManager mMockProjectionService; @Mock IVirtualDeviceManager mIVirtualDeviceManager; @Mock InputManagerInternal mMockInputManagerInternal; @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal; @@ -285,6 +308,7 @@ public class DisplayManagerServiceTest { builder.setFlags(flags); int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); @@ -410,6 +434,7 @@ public class DisplayManagerServiceTest { builder.setUniqueId(uniqueId); int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); @@ -446,6 +471,7 @@ public class DisplayManagerServiceTest { builder.setUniqueId(uniqueId); int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken, /* projection= */ null, PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); @@ -479,6 +505,7 @@ public class DisplayManagerServiceTest { builder.setUniqueId(uniqueId); int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken, /* projection= */ null, PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); @@ -720,6 +747,7 @@ public class DisplayManagerServiceTest { builder.setUniqueId(uniqueId); final int firstDisplayId = binderService.createVirtualDisplay(builder.build(), mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); // The second virtual display requests to mirror the first virtual display. final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2"; @@ -731,6 +759,7 @@ public class DisplayManagerServiceTest { final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(), mMockAppToken2 /* callback */, null /* projection */, PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); // flush the handler @@ -768,6 +797,7 @@ public class DisplayManagerServiceTest { virtualDevice /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId; // Create a second virtual display. This should be added to the previously created display @@ -783,6 +813,7 @@ public class DisplayManagerServiceTest { virtualDevice /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId; assertEquals( @@ -820,6 +851,7 @@ public class DisplayManagerServiceTest { virtualDevice /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId; // Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP, @@ -838,6 +870,7 @@ public class DisplayManagerServiceTest { virtualDevice /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId; assertNotEquals( @@ -881,6 +914,7 @@ public class DisplayManagerServiceTest { virtualDevice /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); // Check that FLAG_ALWAYS_UNLOCKED is set. assertNotEquals( @@ -906,6 +940,7 @@ public class DisplayManagerServiceTest { virtualDevice /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); // Check that FLAG_ALWAYS_UNLOCKED is set. assertNotEquals( @@ -929,6 +964,7 @@ public class DisplayManagerServiceTest { null /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); // Check that FLAG_ALWAYS_UNLOCKED is not set. assertEquals( @@ -960,6 +996,7 @@ public class DisplayManagerServiceTest { .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); final int firstDisplayId = binderService.createVirtualDisplay(builder.build(), mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); // The second virtual display requests to mirror the first virtual display. final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2"; @@ -971,6 +1008,7 @@ public class DisplayManagerServiceTest { final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(), mMockAppToken2 /* callback */, null /* projection */, PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); // flush the handler @@ -985,6 +1023,54 @@ public class DisplayManagerServiceTest { Display.INVALID_DISPLAY); } + @Test + public void testCreateVirtualDisplay_setContentRecordingSessionSuccess() throws Exception { + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + when(mMockWindowManagerInternal + .setContentRecordingSession(any(ContentRecordingSession.class))) + .thenReturn(true); + + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, 600, 800, 320); + builder.setUniqueId("uniqueId --- setContentRecordingSession true"); + builder.setContentRecordingSession( + ContentRecordingSession.createDisplaySession(new Binder(""))); + + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + displayManager.windowManagerAndInputReady(); + + DisplayManagerService.BinderService binderService = displayManager.new BinderService(); + final int displayId = binderService.createVirtualDisplay(builder.build(), + mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + + assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY); + } + + @Test + public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws Exception { + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + when(mMockWindowManagerInternal + .setContentRecordingSession(any(ContentRecordingSession.class))) + .thenReturn(false); + + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, 600, 800, 320); + builder.setUniqueId("uniqueId --- setContentRecordingSession false"); + builder.setContentRecordingSession( + ContentRecordingSession.createDisplaySession(new Binder(""))); + + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + displayManager.windowManagerAndInputReady(); + + DisplayManagerService.BinderService binderService = displayManager.new BinderService(); + final int displayId = binderService.createVirtualDisplay(builder.build(), + mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + + assertThat(displayId).isEqualTo(Display.INVALID_DISPLAY); + } + /** * Tests that the virtual display is created with * {@link VirtualDisplayConfig.Builder#setSurface(Surface)} @@ -1011,6 +1097,7 @@ public class DisplayManagerServiceTest { builder.setUniqueId(uniqueId); final int displayId = binderService.createVirtualDisplay(builder.build(), mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); @@ -1043,6 +1130,7 @@ public class DisplayManagerServiceTest { int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */); DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId); @@ -1093,7 +1181,6 @@ public class DisplayManagerServiceTest { registerDefaultDisplays(displayManager); - DisplayManagerService.BinderService bs = displayManager.new BinderService(); when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn( @@ -1111,6 +1198,7 @@ public class DisplayManagerServiceTest { int displayId = localService.createVirtualDisplay(builder.build(), mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any()); displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */); DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId); diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java index 178670e678f0..397d7b5f2a3e 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java @@ -21,7 +21,6 @@ import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI; import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS; -import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_RETENTION_MS; import static com.google.common.truth.Truth.assertThat; @@ -66,6 +65,7 @@ public class CpuWakeupStatsTest { public void removesOldWakeups() { // The xml resource doesn't matter for this test. final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1, mHandler); + final long retention = obj.mConfig.WAKEUP_STATS_RETENTION_MS; final Set<Long> timestamps = new HashSet<>(); final long firstWakeup = 453192; @@ -73,22 +73,21 @@ public class CpuWakeupStatsTest { obj.noteWakeupTimeAndReason(firstWakeup, 32, KERNEL_REASON_UNKNOWN_IRQ); timestamps.add(firstWakeup); for (int i = 1; i < 1000; i++) { - final long delta = mRandom.nextLong(WAKEUP_RETENTION_MS); + final long delta = mRandom.nextLong(retention); if (timestamps.add(firstWakeup + delta)) { obj.noteWakeupTimeAndReason(firstWakeup + delta, i, KERNEL_REASON_UNKNOWN_IRQ); } } assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size()); - obj.noteWakeupTimeAndReason(firstWakeup + WAKEUP_RETENTION_MS + 1242, 231, + obj.noteWakeupTimeAndReason(firstWakeup + retention + 1242, 231, KERNEL_REASON_UNKNOWN_IRQ); assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size()); for (int i = 0; i < 100; i++) { - final long now = mRandom.nextLong(WAKEUP_RETENTION_MS + 1, 100 * WAKEUP_RETENTION_MS); + final long now = mRandom.nextLong(retention + 1, 100 * retention); obj.noteWakeupTimeAndReason(now, i, KERNEL_REASON_UNKNOWN_IRQ); - assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - WAKEUP_RETENTION_MS)) - .isLessThan(0); + assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - retention)).isLessThan(0); } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt index 19ecf6ab8799..05abf9fd1a8e 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt @@ -53,7 +53,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { +open class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt new file mode 100644 index 000000000000..46899f373fcf --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 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.wm.flicker.launch + +import android.tools.common.NavBar +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** Some assertions will fail because of b/264415996 */ +@FlickerServiceCompatible +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenAppColdFromIconCfArm(flicker: FlickerTest) : OpenAppColdFromIcon(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + // TAPL fails on landscape mode b/240916028 + return FlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_3BUTTON) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt index da985232a1e3..b848e63c9c87 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt @@ -93,8 +93,7 @@ class OverrideTaskTransitionTest(val flicker: FlickerTest) { .then() // Animation starts, but the app may not be drawn yet which means the Splash // may be visible. - .isInvisible(testApp, isOptional = true) - .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true) + .isSplashScreenVisibleFor(testApp, isOptional = true) .then() // App shows up with the custom animation starting at alpha=1. .isVisible(testApp) |