diff options
390 files changed, 9930 insertions, 3895 deletions
diff --git a/apct-tests/perftests/core/src/android/libcore/XmlSerializerPerfTest.java b/apct-tests/perftests/core/src/android/libcore/XmlSerializerPerfTest.java new file mode 100644 index 000000000000..412cb5acbf8b --- /dev/null +++ b/apct-tests/perftests/core/src/android/libcore/XmlSerializerPerfTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.libcore; + +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Xml; + +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.XmlObjectFactory; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +/** + * Compares various kinds of method invocation. + */ +@RunWith(AndroidJUnit4.class) +@LargeTest +public class XmlSerializerPerfTest { + +    @Rule +    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + +    @Test +    public void timeFastSerializer_nonIndent_depth100() throws IOException { +        BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); +        while (state.keepRunning()) { +            XmlSerializer serializer = Xml.newFastSerializer(); +            runTest(serializer, 100); +        } +    } + +    @Test +    public void timeFastSerializer_indent_depth100() throws IOException { +        BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); +        while (state.keepRunning()) { +            XmlSerializer serializer = Xml.newFastSerializer(); +            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); +            runTest(serializer, 100); +        } +    } + +    @Test +    public void timeKXmlSerializer_nonIndent_depth100() throws IOException { +        BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); +        while (state.keepRunning()) { +            XmlSerializer serializer = XmlObjectFactory.newXmlSerializer(); +            runTest(serializer, 100); +        } +    } + +    @Test +    public void timeKXmlSerializer_indent_depth100() throws IOException { +        BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); +        while (state.keepRunning()) { +            XmlSerializer serializer = XmlObjectFactory.newXmlSerializer(); +            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); +            runTest(serializer, 100); +        } +    } + +    private void runTest(XmlSerializer serializer, int depth) throws IOException { +        File file = File.createTempFile(XmlSerializerPerfTest.class.getSimpleName(), "tmp"); +        try (OutputStream out = new FileOutputStream(file)) { +            serializer.setOutput(out, StandardCharsets.UTF_8.name()); +            serializer.startDocument(null, true); +            writeContent(serializer, depth); +            serializer.endDocument(); +        } +    } + +    private void writeContent(XmlSerializer serializer, int depth) throws IOException { +        serializer.startTag(null, "tag"); +        serializer.attribute(null, "attribute", "value1"); +        if (depth > 0) { +            writeContent(serializer, depth - 1); +        } +        serializer.endTag(null, "tag"); +    } + +} diff --git a/core/api/current.txt b/core/api/current.txt index 74ff6726cad2..1bfd1e7b50cd 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -23192,6 +23192,7 @@ package android.media {      method public int describeContents();      method @Nullable public String getClientPackageName();      method public int getConnectionState(); +    method @NonNull public java.util.Set<java.lang.String> getDeduplicationIds();      method @Nullable public CharSequence getDescription();      method @Nullable public android.os.Bundle getExtras();      method @NonNull public java.util.List<java.lang.String> getFeatures(); @@ -23225,6 +23226,7 @@ package android.media {      method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures();      method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String);      method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int); +    method @NonNull public android.media.MediaRoute2Info.Builder setDeduplicationIds(@NonNull java.util.Set<java.lang.String>);      method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);      method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);      method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri); @@ -41791,6 +41793,8 @@ package android.telephony {      field public static final String KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL = "voicemail_notification_persistent_bool";      field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";      field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int"; +    field public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool"; +    field public static final String KEY_VONR_SETTING_VISIBILITY_BOOL = "vonr_setting_visibility_bool";      field public static final String KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL = "vt_upgrade_supported_for_downgraded_rtt_call";      field public static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";      field public static final String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string"; @@ -50935,6 +50939,7 @@ package android.view {      method public int getScaledDoubleTapSlop();      method public int getScaledEdgeSlop();      method public int getScaledFadingEdgeLength(); +    method public int getScaledHandwritingGestureLineMargin();      method public int getScaledHandwritingSlop();      method public float getScaledHorizontalScrollFactor();      method public int getScaledHoverSlop(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 7c1c8ba7410c..c170f74b41c3 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -401,6 +401,7 @@ package android {    public static final class R.dimen {      field public static final int config_restrictedIconSize = 17104903; // 0x1050007 +    field public static final int config_viewConfigurationHandwritingGestureLineMargin;    }    public static final class R.drawable { @@ -7076,6 +7077,7 @@ package android.media.tv.tuner {      method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_SHARED_FILTER) public static android.media.tv.tuner.filter.SharedFilter openSharedFilter(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.filter.SharedFilterCallback);      method @Nullable public android.media.tv.tuner.filter.TimeFilter openTimeFilter();      method public int removeOutputPid(@IntRange(from=0) int); +    method public int requestFrontendById(int);      method public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback);      method public int setLnaEnabled(boolean);      method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int); @@ -11979,7 +11981,7 @@ package android.service.voice {    public final class HotwordAudioStream implements android.os.Parcelable {      method public int describeContents();      method @NonNull public android.media.AudioFormat getAudioFormat(); -    method @NonNull public android.os.ParcelFileDescriptor getAudioStream(); +    method @NonNull public android.os.ParcelFileDescriptor getAudioStreamParcelFileDescriptor();      method @NonNull public android.os.PersistableBundle getMetadata();      method @Nullable public android.media.AudioTimestamp getTimestamp();      method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -11990,7 +11992,7 @@ package android.service.voice {      ctor public HotwordAudioStream.Builder(@NonNull android.media.AudioFormat, @NonNull android.os.ParcelFileDescriptor);      method @NonNull public android.service.voice.HotwordAudioStream build();      method @NonNull public android.service.voice.HotwordAudioStream.Builder setAudioFormat(@NonNull android.media.AudioFormat); -    method @NonNull public android.service.voice.HotwordAudioStream.Builder setAudioStream(@NonNull android.os.ParcelFileDescriptor); +    method @NonNull public android.service.voice.HotwordAudioStream.Builder setAudioStreamParcelFileDescriptor(@NonNull android.os.ParcelFileDescriptor);      method @NonNull public android.service.voice.HotwordAudioStream.Builder setMetadata(@NonNull android.os.PersistableBundle);      method @NonNull public android.service.voice.HotwordAudioStream.Builder setTimestamp(@NonNull android.media.AudioTimestamp);    } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 1e4023eeee18..186e5be92524 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -134,7 +134,7 @@ package android.app {      method public static void resumeAppSwitches() throws android.os.RemoteException;      method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);      method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int); -    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean startUserInBackgroundOnSecondaryDisplay(int, int); +    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean startUserInBackgroundOnSecondaryDisplay(int, int);      method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);      method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);      method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle(); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 576b572dcc9a..cb7b478d73b4 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4401,7 +4401,7 @@ public class ActivityManager {       */      @TestApi      @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, -            android.Manifest.permission.CREATE_USERS}) +            android.Manifest.permission.INTERACT_ACROSS_USERS})      public boolean startUserInBackgroundOnSecondaryDisplay(@UserIdInt int userId,              int displayId) {          if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 7a9f3c1c4c76..67d441611e63 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -658,7 +658,8 @@ public final class ActivityThread extends ClientTransactionHandler                                  "Received config update for non-existing activity");                      }                      activity.mMainThread.handleActivityConfigurationChanged( -                            ActivityClientRecord.this, overrideConfig, newDisplayId); +                            ActivityClientRecord.this, overrideConfig, newDisplayId, +                            false /* alwaysReportChange */);                  }                  @Override @@ -5859,13 +5860,13 @@ public final class ActivityThread extends ClientTransactionHandler       * @return {@link Configuration} instance sent to client, null if not sent.       */      private Configuration performConfigurationChangedForActivity(ActivityClientRecord r, -            Configuration newBaseConfig, int displayId) { +            Configuration newBaseConfig, int displayId, boolean alwaysReportChange) {          r.tmpConfig.setTo(newBaseConfig);          if (r.overrideConfig != null) {              r.tmpConfig.updateFrom(r.overrideConfig);          }          final Configuration reportedConfig = performActivityConfigurationChanged(r.activity, -                r.tmpConfig, r.overrideConfig, displayId); +                r.tmpConfig, r.overrideConfig, displayId, alwaysReportChange);          freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));          return reportedConfig;      } @@ -5881,7 +5882,8 @@ public final class ActivityThread extends ClientTransactionHandler       * @return Configuration sent to client, null if no changes and not moved to different display.       */      private Configuration performActivityConfigurationChanged(Activity activity, -            Configuration newConfig, Configuration amOverrideConfig, int displayId) { +            Configuration newConfig, Configuration amOverrideConfig, int displayId, +            boolean alwaysReportChange) {          final IBinder activityToken = activity.getActivityToken();          // WindowConfiguration differences aren't considered as public, check it separately. @@ -5900,9 +5902,9 @@ public final class ActivityThread extends ClientTransactionHandler          final boolean shouldUpdateResources = hasPublicResConfigChange                  || shouldUpdateResources(activityToken, currentResConfig, newConfig,                  amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange); -        final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig, -                r != null ? r.mSizeConfigurations : null, -                activity.mActivityInfo.getRealConfigChanged()); +        final boolean shouldReportChange = shouldReportChange( +                activity.mCurrentConfig, newConfig, r != null ? r.mSizeConfigurations : null, +                activity.mActivityInfo.getRealConfigChanged(), alwaysReportChange);          // Nothing significant, don't proceed with updating and reporting.          if (!shouldUpdateResources && !shouldReportChange) {              return null; @@ -5962,12 +5964,18 @@ public final class ActivityThread extends ClientTransactionHandler      @VisibleForTesting      public static boolean shouldReportChange(@Nullable Configuration currentConfig,              @NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets sizeBuckets, -            int handledConfigChanges) { +            int handledConfigChanges, boolean alwaysReportChange) {          final int publicDiff = currentConfig.diffPublicOnly(newConfig);          // Don't report the change if there's no public diff between current and new config.          if (publicDiff == 0) {              return false;          } + +        // Report the change regardless if the changes across size-config-buckets. +        if (alwaysReportChange) { +            return true; +        } +          final int diffWithBucket = SizeConfigurationBuckets.filterDiff(publicDiff, currentConfig,                  newConfig, sizeBuckets);          // Compare to the diff which filter the change without crossing size buckets with @@ -6094,6 +6102,18 @@ public final class ActivityThread extends ClientTransactionHandler          }      } +    @Override +    public void handleActivityConfigurationChanged(ActivityClientRecord r, +            @NonNull Configuration overrideConfig, int displayId) { +        handleActivityConfigurationChanged(r, overrideConfig, displayId, +                // This is the only place that uses alwaysReportChange=true. The entry point should +                // be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side +                // has confirmed the activity should handle the configuration instead of relaunch. +                // If Activity#onConfigurationChanged is called unexpectedly, then we can know it is +                // something wrong from server side. +                true /* alwaysReportChange */); +    } +      /**       * Handle new activity configuration and/or move to a different display. This method is a noop       * if {@link #updatePendingActivityConfiguration(IBinder, Configuration)} has been @@ -6104,9 +6124,8 @@ public final class ActivityThread extends ClientTransactionHandler       * @param displayId Id of the display where activity was moved to, -1 if there was no move and       *                  value didn't change.       */ -    @Override -    public void handleActivityConfigurationChanged(ActivityClientRecord r, -            @NonNull Configuration overrideConfig, int displayId) { +    void handleActivityConfigurationChanged(ActivityClientRecord r, +            @NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) {          synchronized (mPendingOverrideConfigs) {              final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(r.token);              if (overrideConfig.isOtherSeqNewer(pendingOverrideConfig)) { @@ -6150,7 +6169,8 @@ public final class ActivityThread extends ClientTransactionHandler          }          final Configuration reportedConfig = performConfigurationChangedForActivity(r,                  mConfigurationController.getCompatConfiguration(), -                movedToDifferentDisplay ? displayId : r.activity.getDisplayId()); +                movedToDifferentDisplay ? displayId : r.activity.getDisplayId(), +                alwaysReportChange);          // Notify the ViewRootImpl instance about configuration changes. It may have initiated this          // update to make sure that resources are updated before updating itself.          if (viewRoot != null) { diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java index a8d8c75601aa..5517c57d1f1e 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -1196,7 +1196,8 @@ public final class ApplicationExitInfo implements Parcelable {          return sb.toString();      } -    private static String reasonCodeToString(@Reason int reason) { +    /** @hide */ +    public static String reasonCodeToString(@Reason int reason) {          switch (reason) {              case REASON_EXIT_SELF:                  return "EXIT_SELF"; diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index c2df8022af0f..13da1901e559 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -16,6 +16,7 @@  package android.app; +import android.annotation.IntDef;  import android.annotation.IntRange;  import android.annotation.NonNull;  import android.annotation.Nullable; @@ -34,6 +35,10 @@ import android.os.PowerExemptionManager;  import android.os.PowerExemptionManager.ReasonCode;  import android.os.PowerExemptionManager.TempAllowListType; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy;  import java.util.Objects;  /** @@ -59,6 +64,8 @@ public class BroadcastOptions extends ComponentOptions {      private boolean mIsAlarmBroadcast = false;      private long mIdForResponseEvent;      private @Nullable IntentFilter mRemoveMatchingFilter; +    private @DeliveryGroupPolicy int mDeliveryGroupPolicy; +    private @Nullable String mDeliveryGroupKey;      /**       * Change ID which is invalid. @@ -190,6 +197,46 @@ public class BroadcastOptions extends ComponentOptions {      private static final String KEY_REMOVE_MATCHING_FILTER =              "android:broadcast.removeMatchingFilter"; +    /** +     * Corresponds to {@link #setDeliveryGroupPolicy(int)}. +     */ +    private static final String KEY_DELIVERY_GROUP_POLICY = +            "android:broadcast.deliveryGroupPolicy"; + +    /** +     * Corresponds to {@link #setDeliveryGroupKey(String, String)}. +     */ +    private static final String KEY_DELIVERY_GROUP_KEY = +            "android:broadcast.deliveryGroupKey"; + +    /** +     * The list of delivery group policies which specify how multiple broadcasts belonging to +     * the same delivery group has to be handled. +     * @hide +     */ +    @IntDef(flag = true, prefix = { "DELIVERY_GROUP_POLICY_" }, value = { +            DELIVERY_GROUP_POLICY_ALL, +            DELIVERY_GROUP_POLICY_MOST_RECENT, +    }) +    @Retention(RetentionPolicy.SOURCE) +    public @interface DeliveryGroupPolicy {} + +    /** +     * Delivery group policy that indicates that all the broadcasts in the delivery group +     * need to be delivered as is. +     * +     * @hide +     */ +    public static final int DELIVERY_GROUP_POLICY_ALL = 0; + +    /** +     * Delivery group policy that indicates that only the most recent broadcast in the delivery +     * group need to be delivered and the rest can be dropped. +     * +     * @hide +     */ +    public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; +      public static BroadcastOptions makeBasic() {          BroadcastOptions opts = new BroadcastOptions();          return opts; @@ -236,6 +283,9 @@ public class BroadcastOptions extends ComponentOptions {          mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);          mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,                  IntentFilter.class); +        mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY, +                DELIVERY_GROUP_POLICY_ALL); +        mDeliveryGroupKey = opts.getString(KEY_DELIVERY_GROUP_KEY);      }      /** @@ -639,6 +689,41 @@ public class BroadcastOptions extends ComponentOptions {      }      /** +     * Set delivery group policy for this broadcast to specify how multiple broadcasts belonging to +     * the same delivery group has to be handled. +     * +     * @hide +     */ +    public void setDeliveryGroupPolicy(@DeliveryGroupPolicy int policy) { +        mDeliveryGroupPolicy = policy; +    } + +    /** @hide */ +    public @DeliveryGroupPolicy int getDeliveryGroupPolicy() { +        return mDeliveryGroupPolicy; +    } + +    /** +     * Set namespace and key to identify the delivery group that this broadcast belongs to. +     * If no namespace and key is set, then by default {@link Intent#filterEquals(Intent)} will be +     * used to identify the delivery group. +     * +     * @hide +     */ +    public void setDeliveryGroupKey(@NonNull String namespace, @NonNull String key) { +        Preconditions.checkArgument(!namespace.contains("/"), +                "namespace should not contain '/'"); +        Preconditions.checkArgument(!key.contains("/"), +                "key should not contain '/'"); +        mDeliveryGroupKey = namespace + "/" + key; +    } + +    /** @hide */ +    public String getDeliveryGroupKey() { +        return mDeliveryGroupKey; +    } + +    /**       * Returns the created options as a Bundle, which can be passed to       * {@link android.content.Context#sendBroadcast(android.content.Intent)       * Context.sendBroadcast(Intent)} and related methods. @@ -686,6 +771,12 @@ public class BroadcastOptions extends ComponentOptions {          if (mRemoveMatchingFilter != null) {              b.putParcelable(KEY_REMOVE_MATCHING_FILTER, mRemoveMatchingFilter);          } +        if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) { +            b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy); +        } +        if (mDeliveryGroupKey != null) { +            b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupKey); +        }          return b.isEmpty() ? null : b;      }  } diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java index 794c6946f7a8..be53a6249788 100644 --- a/core/java/android/app/LocaleManager.java +++ b/core/java/android/app/LocaleManager.java @@ -127,6 +127,7 @@ public class LocaleManager {       * <p>This API can be used by an app's installer       * (per {@link android.content.pm.InstallSourceInfo#getInstallingPackageName}) to retrieve       * the app's locales. +     * <p>This API can be used by the current input method to retrieve locales of another packages.       * All other cases require {@code android.Manifest.permission#READ_APP_SPECIFIC_LOCALES}.       * Apps should generally retrieve their own locales via their in-process LocaleLists,       * or by calling {@link #getApplicationLocales()}. diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 74eb1c526777..f9ef3cc7e1f6 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -8467,8 +8467,8 @@ public class Notification implements Parcelable              }              int maxAvatarSize = resources.getDimensionPixelSize( -                    isLowRam ? R.dimen.notification_person_icon_max_size -                            : R.dimen.notification_person_icon_max_size_low_ram); +                    isLowRam ? R.dimen.notification_person_icon_max_size_low_ram +                            : R.dimen.notification_person_icon_max_size);              if (mUser != null && mUser.getIcon() != null) {                  mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize);              } diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java index 88a7c0f910d3..d2c797255b0d 100644 --- a/core/java/android/app/backup/BackupManager.java +++ b/core/java/android/app/backup/BackupManager.java @@ -29,7 +29,6 @@ import android.content.ComponentName;  import android.content.Context;  import android.content.Intent;  import android.os.Build; -import android.os.Bundle;  import android.os.Handler;  import android.os.Message;  import android.os.RemoteException; @@ -1123,18 +1122,4 @@ public class BackupManager {              });          }      } - -    private class BackupManagerMonitorWrapper extends IBackupManagerMonitor.Stub { -        final BackupManagerMonitor mMonitor; - -        BackupManagerMonitorWrapper(BackupManagerMonitor monitor) { -            mMonitor = monitor; -        } - -        @Override -        public void onEvent(final Bundle event) throws RemoteException { -            mMonitor.onEvent(event); -        } -    } -  } diff --git a/core/java/android/app/backup/BackupManagerMonitorWrapper.java b/core/java/android/app/backup/BackupManagerMonitorWrapper.java new file mode 100644 index 000000000000..0b189958ef0a --- /dev/null +++ b/core/java/android/app/backup/BackupManagerMonitorWrapper.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.backup; + +import android.os.Bundle; +import android.os.RemoteException; + +/** + * Wrapper around {@link BackupManagerMonitor} that helps with IPC between the caller of backup + * APIs and the backup service. + * + * The caller implements {@link BackupManagerMonitor} and passes it into framework APIs that run on + * the caller's process. Those framework APIs will then wrap it around this class when doing the + * actual IPC. + */ +class BackupManagerMonitorWrapper extends IBackupManagerMonitor.Stub { +    private final BackupManagerMonitor mMonitor; + +    BackupManagerMonitorWrapper(BackupManagerMonitor monitor) { +        mMonitor = monitor; +    } + +    @Override +    public void onEvent(final Bundle event) throws RemoteException { +        mMonitor.onEvent(event); +    } +} diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java new file mode 100644 index 000000000000..b789b38c966e --- /dev/null +++ b/core/java/android/app/backup/BackupRestoreEventLogger.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.backup; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +// TODO(b/244436184): Make this @SystemApi +/** + * Class to log B&R stats for each data type that is backed up and restored by the calling app. + * + * The logger instance is designed to accept a limited number of unique + * {link @BackupRestoreDataType} values, as determined by the underlying implementation. Apps are + * expected to have a small pre-defined set of data type values they use. Attempts to log too many + * unique values will be rejected. + * + * @hide + */ +public class BackupRestoreEventLogger { +    /** +     * Max number of unique data types for which an instance of this logger can store info. Attempts +     * to use more distinct data type values will be rejected. +     */ +    public static final int DATA_TYPES_ALLOWED = 15; + +    /** +     * Operation types for which this logger can be used. +     */ +    @Retention(RetentionPolicy.SOURCE) +    @IntDef({ +            OperationType.BACKUP, +            OperationType.RESTORE +    }) +    @interface OperationType { +        int BACKUP = 1; +        int RESTORE = 2; +    } + +    /** +     * Denotes that the annotated element identifies a data type as required by the logging methods +     * of {@code BackupRestoreEventLogger} +     */ +    @Retention(RetentionPolicy.SOURCE) +    public @interface BackupRestoreDataType {} + +    /** +     * Denotes that the annotated element identifies an error type as required by the logging +     * methods of {@code BackupRestoreEventLogger} +     */ +    @Retention(RetentionPolicy.SOURCE) +    public @interface BackupRestoreError {} + +    private final int mOperationType; + +    /** +     * @param operationType type of the operation for which logging will be performed. See +     *                      {@link OperationType}. Attempts to use logging methods that don't match +     *                      the specified operation type will be rejected (e.g. use backup methods +     *                      for a restore logger and vice versa). +     */ +    public BackupRestoreEventLogger(@OperationType int operationType) { +        mOperationType = operationType; +    } + +    /** +     * Report progress during a backup operation. Call this method for each distinct data type that +     * your {@code BackupAgent} implementation handles for any items of that type that have been +     * successfully backed up. Repeated calls to this method with the same {@code dataType} will +     * increase the total count of items associated with this data type by {@code count}. +     * +     * This method should be called from a {@link BackupAgent} implementation during an ongoing +     * backup operation. +     * +     * @param dataType the type of data being backed. +     * @param count number of items of the given type that have been successfully backed up. +     * +     * @return boolean, indicating whether the log has been accepted. +     */ +    public boolean logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) { +        return true; +    } + +    /** +     * Report errors during a backup operation. Call this method whenever items of a certain data +     * type failed to back up. Repeated calls to this method with the same {@code dataType} / +     * {@code error} will increase the total count of items associated with this data type / error +     * by {@code count}. +     * +     * This method should be called from a {@link BackupAgent} implementation during an ongoing +     * backup operation. +     * +     * @param dataType the type of data being backed. +     * @param count number of items of the given type that have failed to back up. +     * @param error optional, the error that has caused the failure. +     * +     * @return boolean, indicating whether the log has been accepted. +     */ +    public boolean logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count, +            @Nullable @BackupRestoreError String error) { +        return true; +    } + +    /** +     * Report metadata associated with a data type that is currently being backed up, e.g. name of +     * the selected wallpaper file / package. Repeated calls to this method with the same {@code +     * dataType} will overwrite the previously supplied {@code metaData} value. +     * +     * The logger does not store or transmit the provided metadata value. Instead, it’s replaced +     * with the SHA-256 hash of the provided string. +     * +     * This method should be called from a {@link BackupAgent} implementation during an ongoing +     * backup operation. +     * +     * @param dataType the type of data being backed up. +     * @param metaData the metadata associated with the data type. +     * +     * @return boolean, indicating whether the log has been accepted. +     */ +    public boolean logBackupMetaData(@NonNull @BackupRestoreDataType String dataType, +            @NonNull String metaData) { +        return true; +    } + +    /** +     * Report progress during a restore operation. Call this method for each distinct data type that +     * your {@code BackupAgent} implementation handles if any items of that type have been +     * successfully restored. Repeated calls to this method with the same {@code dataType} will +     * increase the total count of items associated with this data type by {@code count}. +     * +     * This method should either be called from a {@link BackupAgent} implementation during an +     * ongoing restore operation or during any delayed restore actions the package had scheduled +     * earlier (e.g. complete the restore once a certain dependency becomes available on the +     * device). +     * +     * @param dataType the type of data being restored. +     * @param count number of items of the given type that have been successfully restored. +     * +     * @return boolean, indicating whether the log has been accepted. +     */ +    public boolean logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) { +        return true; +    } + +    /** +     * Report errors during a restore operation. Call this method whenever items of a certain data +     * type failed to restore. Repeated calls to this method with the same {@code dataType} / +     * {@code error} will increase the total count of items associated with this data type / error +     * by {@code count}. +     * +     * This method should either be called from a {@link BackupAgent} implementation during an +     * ongoing restore operation or during any delayed restore actions the package had scheduled +     * earlier (e.g. complete the restore once a certain dependency becomes available on the +     * device). +     * +     * @param dataType the type of data being restored. +     * @param count number of items of the given type that have failed to restore. +     * @param error optional, the error that has caused the failure. +     * +     * @return boolean, indicating whether the log has been accepted. +     */ +    public boolean logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count, +            @Nullable @BackupRestoreError String error) { +        return true; +    } + +    /** +     * Report metadata associated with a data type that is currently being restored, e.g. name of +     * the selected wallpaper file / package. Repeated calls to this method with the same +     * {@code dataType} will overwrite the previously supplied {@code metaData} value. +     * +     * The logger does not store or transmit the provided metadata value. Instead, it’s replaced +     * with the SHA-256 hash of the provided string. +     * +     * This method should either be called from a {@link BackupAgent} implementation during an +     * ongoing restore operation or during any delayed restore actions the package had scheduled +     * earlier (e.g. complete the restore once a certain dependency becomes available on the +     * device). +     * +     * @param dataType the type of data being restored. +     * @param metadata the metadata associated with the data type. +     * +     * @return boolean, indicating whether the log has been accepted. +     */ +    public boolean logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType, +            @NonNull  String metadata) { +        return true; +    } + +    /** +     * Get the contents of this logger. This method should only be used by B&R code in Android +     * Framework. +     * +     * @hide +     */ +    public List<DataTypeResult> getLoggingResults() { +        return Collections.emptyList(); +    } + +    /** +     * Get the operation type for which this logger was created. This method should only be used +     * by B&R code in Android Framework. +     * +     * @hide +     */ +    public @OperationType int getOperationType() { +        return mOperationType; +    } + +    /** +     * Encapsulate logging results for a single data type. +     */ +    public static class DataTypeResult { +        @BackupRestoreDataType +        private final String mDataType; +        private final int mSuccessCount; +        private final Map<String, Integer> mErrors; +        private final byte[] mMetadataHash; + +        public DataTypeResult(String dataType, int successCount, +                Map<String, Integer> errors, byte[] metadataHash) { +            mDataType = dataType; +            mSuccessCount = successCount; +            mErrors = errors; +            mMetadataHash = metadataHash; +        } + +        @NonNull +        @BackupRestoreDataType +        public String getDataType() { +            return mDataType; +        } + +        /** +         * @return number of items of the given data type that have been successfully backed up or +         *         restored. +         */ +        public int getSuccessCount() { +            return mSuccessCount; +        } + +        /** +         * @return mapping of {@link BackupRestoreError} to the count of items that are affected by +         *         the error. +         */ +        @NonNull +        public Map<String, Integer> getErrors() { +            return mErrors; +        } + +        /** +         * @return SHA-256 hash of the metadata or {@code null} of no metadata has been logged for +         *         this data type. +         */ +        @Nullable +        public byte[] getMetadataHash() { +            return mMetadataHash; +        } +    } +} diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index f6de72b43de6..90e9df47bc4b 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -656,6 +656,20 @@ public class BackupTransport {      }      /** +     * Ask the transport for a {@link IBackupManagerMonitor} instance which will be used by the +     * framework to report logging events back to the transport. +     * +     * <p>Backups requested from outside the framework may pass in a monitor with the request, +     * however backups initiated by the framework will call this method to retrieve one. +     * +     * @hide +     */ +    @Nullable +    public BackupManagerMonitor getBackupManagerMonitor() { +        return null; +    } + +    /**       * Bridge between the actual IBackupTransport implementation and the stable API.  If the       * binder interface needs to change, we use this layer to translate so that we can       * (if appropriate) decouple those framework-side changes from the BackupTransport @@ -952,5 +966,15 @@ public class BackupTransport {                  callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR);              }          } + +        @Override +        public void getBackupManagerMonitor(AndroidFuture<IBackupManagerMonitor> resultFuture) { +            try { +                BackupManagerMonitor result = BackupTransport.this.getBackupManagerMonitor(); +                resultFuture.complete(new BackupManagerMonitorWrapper(result)); +            } catch (RuntimeException e) { +                resultFuture.cancel(/* mayInterruptIfRunning */ true); +            } +        }      }  } diff --git a/core/java/android/app/backup/RestoreSession.java b/core/java/android/app/backup/RestoreSession.java index 933670415f2e..fe68ec193642 100644 --- a/core/java/android/app/backup/RestoreSession.java +++ b/core/java/android/app/backup/RestoreSession.java @@ -20,7 +20,6 @@ import android.annotation.NonNull;  import android.annotation.Nullable;  import android.annotation.SystemApi;  import android.content.Context; -import android.os.Bundle;  import android.os.Handler;  import android.os.Message;  import android.os.RemoteException; @@ -393,17 +392,4 @@ public class RestoreSession {                      mHandler.obtainMessage(MSG_RESTORE_FINISHED, error, 0));          }      } - -    private class BackupManagerMonitorWrapper extends IBackupManagerMonitor.Stub { -        final BackupManagerMonitor mMonitor; - -        BackupManagerMonitorWrapper(BackupManagerMonitor monitor) { -            mMonitor = monitor; -        } - -        @Override -        public void onEvent(final Bundle event) throws RemoteException { -            mMonitor.onEvent(event); -        } -    }  } diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java index cc303fb1f413..24e47bf9e47c 100644 --- a/core/java/android/appwidget/AppWidgetHost.java +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -418,14 +418,7 @@ public class AppWidgetHost {          AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);          view.setInteractionHandler(mInteractionHandler);          view.setAppWidget(appWidgetId, appWidget); -        addListener(appWidgetId, view); -        RemoteViews views; -        try { -            views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId); -        } catch (RemoteException e) { -            throw new RuntimeException("system server dead?", e); -        } -        view.updateAppWidget(views); +        setListener(appWidgetId, view);          return view;      } @@ -513,13 +506,19 @@ public class AppWidgetHost {       * The AppWidgetHost retains a pointer to the newly-created listener.       * @param appWidgetId The ID of the app widget for which to add the listener       * @param listener The listener interface that deals with actions towards the widget view -     *       * @hide       */ -    public void addListener(int appWidgetId, @NonNull AppWidgetHostListener listener) { +    public void setListener(int appWidgetId, @NonNull AppWidgetHostListener listener) {          synchronized (mListeners) {              mListeners.put(appWidgetId, listener);          } +        RemoteViews views = null; +        try { +            views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId); +        } catch (RemoteException e) { +            throw new RuntimeException("system server dead?", e); +        } +        listener.updateAppWidget(views);      }      /** diff --git a/core/java/android/credentials/ui/Constants.java b/core/java/android/credentials/ui/Constants.java new file mode 100644 index 000000000000..aeeede744188 --- /dev/null +++ b/core/java/android/credentials/ui/Constants.java @@ -0,0 +1,33 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.credentials.ui; + +/** + * Constants for the ui protocol that doesn't fit into other individual data structures. + * + * @hide + */ +public class Constants { + +    /** +    * The intent extra key for the {@code ResultReceiver} object when launching the UX +    * activities. +    */ +    public static final String EXTRA_RESULT_RECEIVER = +            "android.credentials.ui.extra.RESULT_RECEIVER"; + +} diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java new file mode 100644 index 000000000000..9a038d137434 --- /dev/null +++ b/core/java/android/credentials/ui/IntentFactory.java @@ -0,0 +1,68 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.credentials.ui; + +import android.content.ComponentName; +import android.content.Intent; +import android.os.Parcel; +import android.os.ResultReceiver; + +import java.util.ArrayList; + +/** + * Helpers for generating the intents and related extras parameters to launch the UI activities. + * + * @hide + */ +public class IntentFactory { +    /** Generate a new launch intent to the . */ +    public static Intent newIntent(RequestInfo requestInfo, +            ArrayList<ProviderData> providerDataList, ResultReceiver resultReceiver) { +        Intent intent = new Intent(); +        // TODO: define these as proper config strings. +        String activityName = "com.androidauth.tatiaccountselector/.CredentialSelectorActivity"; +        // String activityName = "com.android.credentialmanager/.CredentialSelectorActivity"; +        intent.setComponent(ComponentName.unflattenFromString(activityName)); + +        intent.putParcelableArrayListExtra( +                ProviderData.EXTRA_PROVIDER_DATA_LIST, providerDataList); +        intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo); +        intent.putExtra(Constants.EXTRA_RESULT_RECEIVER, +                toIpcFriendlyResultReceiver(resultReceiver)); + +        return intent; +    } + +    /** +    * Convert an instance of a "locally-defined" ResultReceiver to an instance of +    * {@link android.os.ResultReceiver} itself, which the receiving process will be able to +    * unmarshall. +    */ +    private static <T extends ResultReceiver> ResultReceiver toIpcFriendlyResultReceiver( +            T resultReceiver) { +        final Parcel parcel = Parcel.obtain(); +        resultReceiver.writeToParcel(parcel, 0); +        parcel.setDataPosition(0); + +        final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel); +        parcel.recycle(); + +        return ipcFriendly; +    } + +    private IntentFactory() {} +} diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java index 18e6ba430589..35e12fa43b28 100644 --- a/core/java/android/credentials/ui/ProviderData.java +++ b/core/java/android/credentials/ui/ProviderData.java @@ -16,8 +16,10 @@  package android.credentials.ui; +import android.annotation.CurrentTimeMillisLong;  import android.annotation.NonNull;  import android.annotation.Nullable; +import android.graphics.drawable.Icon;  import android.os.Parcel;  import android.os.Parcelable; @@ -41,7 +43,11 @@ public class ProviderData implements Parcelable {              "android.credentials.ui.extra.PROVIDER_DATA_LIST";      @NonNull -    private final String mPackageName; +    private final String mProviderId; +    @NonNull +    private final String mProviderDisplayName; +    @NonNull +    private final Icon mIcon;      @NonNull      private final List<Entry> mCredentialEntries;      @NonNull @@ -49,21 +55,36 @@ public class ProviderData implements Parcelable {      @Nullable      private final Entry mAuthenticationEntry; +    private final @CurrentTimeMillisLong long mLastUsedTimeMillis; +      public ProviderData( -            @NonNull String packageName, -            @NonNull List<Entry> credentialEntries, -            @NonNull List<Entry> actionChips, -            @Nullable Entry authenticationEntry) { -        mPackageName = packageName; +            @NonNull String providerId, @NonNull String providerDisplayName, +            @NonNull Icon icon, @NonNull List<Entry> credentialEntries, +            @NonNull List<Entry> actionChips, @Nullable Entry authenticationEntry, +            @CurrentTimeMillisLong long lastUsedTimeMillis) { +        mProviderId = providerId; +        mProviderDisplayName = providerDisplayName; +        mIcon = icon;          mCredentialEntries = credentialEntries;          mActionChips = actionChips;          mAuthenticationEntry = authenticationEntry; +        mLastUsedTimeMillis = lastUsedTimeMillis; +    } + +    /** Returns the unique provider id. */ +    @NonNull +    public String getProviderId() { +        return mProviderId; +    } + +    @NonNull +    public String getProviderDisplayName() { +        return mProviderDisplayName;      } -    /** Returns the provider package name. */      @NonNull -    public String getPackageName() { -        return mPackageName; +    public Icon getIcon() { +        return mIcon;      }      @NonNull @@ -81,10 +102,23 @@ public class ProviderData implements Parcelable {          return mAuthenticationEntry;      } +    /** Returns the time when the provider was last used. */ +    public @CurrentTimeMillisLong long getLastUsedTimeMillis() { +        return mLastUsedTimeMillis; +    } +      protected ProviderData(@NonNull Parcel in) { -        String packageName = in.readString8(); -        mPackageName = packageName; -        AnnotationValidations.validate(NonNull.class, null, mPackageName); +        String providerId = in.readString8(); +        mProviderId = providerId; +        AnnotationValidations.validate(NonNull.class, null, mProviderId); + +        String providerDisplayName = in.readString8(); +        mProviderDisplayName = providerDisplayName; +        AnnotationValidations.validate(NonNull.class, null, mProviderDisplayName); + +        Icon icon = in.readTypedObject(Icon.CREATOR); +        mIcon = icon; +        AnnotationValidations.validate(NonNull.class, null, mIcon);          List<Entry> credentialEntries = new ArrayList<>();          in.readTypedList(credentialEntries, Entry.CREATOR); @@ -98,14 +132,20 @@ public class ProviderData implements Parcelable {          Entry authenticationEntry = in.readTypedObject(Entry.CREATOR);          mAuthenticationEntry = authenticationEntry; + +        long lastUsedTimeMillis = in.readLong(); +        mLastUsedTimeMillis = lastUsedTimeMillis;      }      @Override      public void writeToParcel(@NonNull Parcel dest, int flags) { -        dest.writeString8(mPackageName); +        dest.writeString8(mProviderId); +        dest.writeString8(mProviderDisplayName); +        dest.writeTypedObject(mIcon, flags);          dest.writeTypedList(mCredentialEntries);          dest.writeTypedList(mActionChips);          dest.writeTypedObject(mAuthenticationEntry, flags); +        dest.writeLong(mLastUsedTimeMillis);      }      @Override @@ -124,4 +164,83 @@ public class ProviderData implements Parcelable {              return new ProviderData[size];          }      }; + +    /** +     * Builder for {@link ProviderData}. +     * +     * @hide +     */ +    public static class Builder { +        private @NonNull String mProviderId; +        private @NonNull String mProviderDisplayName; +        private @NonNull Icon mIcon; +        private @NonNull List<Entry> mCredentialEntries = new ArrayList<>(); +        private @NonNull List<Entry> mActionChips = new ArrayList<>(); +        private @Nullable Entry mAuthenticationEntry = null; +        private @CurrentTimeMillisLong long mLastUsedTimeMillis = 0L; + +        /** Constructor with required properties. */ +        public Builder(@NonNull String providerId, @NonNull String providerDisplayName, +                @NonNull Icon icon) { +            mProviderId = providerId; +            mProviderDisplayName = providerDisplayName; +            mIcon = icon; +        } + +        /** Sets the unique provider id. */ +        @NonNull +        public Builder setProviderId(@NonNull String providerId) { +            mProviderId = providerId; +            return this; +        } + +        /** Sets the provider display name to be displayed to the user. */ +        @NonNull +        public Builder setProviderDisplayName(@NonNull String providerDisplayName) { +            mProviderDisplayName = providerDisplayName; +            return this; +        } + +        /** Sets the provider icon to be displayed to the user. */ +        @NonNull +        public Builder setIcon(@NonNull Icon icon) { +            mIcon = icon; +            return this; +        } + +        /** Sets the list of save / get credential entries to be displayed to the user. */ +        @NonNull +        public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) { +            mCredentialEntries = credentialEntries; +            return this; +        } + +        /** Sets the list of action chips to be displayed to the user. */ +        @NonNull +        public Builder setActionChips(@NonNull List<Entry> actionChips) { +            mActionChips = actionChips; +            return this; +        } + +        /** Sets the authentication entry to be displayed to the user. */ +        @NonNull +        public Builder setAuthenticationEntry(@Nullable Entry authenticationEntry) { +            mAuthenticationEntry = authenticationEntry; +            return this; +        } + +        /** Sets the time when the provider was last used. */ +        @NonNull +        public Builder setLastUsedTimeMillis(@CurrentTimeMillisLong long lastUsedTimeMillis) { +            mLastUsedTimeMillis = lastUsedTimeMillis; +            return this; +        } + +        /** Builds a {@link ProviderData}. */ +        @NonNull +        public ProviderData build() { +            return new ProviderData(mProviderId, mProviderDisplayName, mIcon, mCredentialEntries, +                mActionChips, mAuthenticationEntry, mLastUsedTimeMillis); +        } +    }  } diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java index 5de6d73945eb..eddb519051a9 100644 --- a/core/java/android/credentials/ui/RequestInfo.java +++ b/core/java/android/credentials/ui/RequestInfo.java @@ -36,12 +36,6 @@ public class RequestInfo implements Parcelable {       */      public static final @NonNull String EXTRA_REQUEST_INFO =              "android.credentials.ui.extra.REQUEST_INFO"; -    /** -     * The intent extra key for the {@code ResultReceiver} object when launching the UX -     * activities. -     */ -    public static final @NonNull String EXTRA_RESULT_RECEIVER = -            "android.credentials.ui.extra.RESULT_RECEIVER";      /** Type value for an executeGetCredential request. */      public static final @NonNull String TYPE_GET = "android.credentials.ui.TYPE_GET"; diff --git a/core/java/android/credentials/ui/UserSelectionResult.java b/core/java/android/credentials/ui/UserSelectionResult.java index 0927fb830a0b..2ac559381c6e 100644 --- a/core/java/android/credentials/ui/UserSelectionResult.java +++ b/core/java/android/credentials/ui/UserSelectionResult.java @@ -43,11 +43,16 @@ public class UserSelectionResult implements Parcelable {      @NonNull      private final IBinder mRequestToken; +    @NonNull +    private final String mProviderId; +      // TODO: consider switching to string or other types, depending on the service implementation.      private final int mEntryId; -    public UserSelectionResult(@NonNull IBinder requestToken, int entryId) { +    public UserSelectionResult(@NonNull IBinder requestToken, @NonNull String providerId, +            int entryId) {          mRequestToken = requestToken; +        mProviderId = providerId;          mEntryId = entryId;      } @@ -57,23 +62,33 @@ public class UserSelectionResult implements Parcelable {          return mRequestToken;      } +    /** Returns provider package name whose entry was selected by the user. */ +    @NonNull +    public String getProviderId() { +        return mProviderId; +    } +      /** Returns the id of the visual entry that the user selected. */ -    public int geEntryId() { +    public int getEntryId() {          return mEntryId;      }      protected UserSelectionResult(@NonNull Parcel in) {          IBinder requestToken = in.readStrongBinder(); +        String providerId = in.readString8();          int entryId = in.readInt();          mRequestToken = requestToken;          AnnotationValidations.validate(NonNull.class, null, mRequestToken); +        mProviderId = providerId; +        AnnotationValidations.validate(NonNull.class, null, mProviderId);          mEntryId = entryId;      }      @Override      public void writeToParcel(@NonNull Parcel dest, int flags) {          dest.writeStrongBinder(mRequestToken); +        dest.writeString8(mProviderId);          dest.writeInt(mEntryId);      } diff --git a/core/java/android/hardware/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java index d7cda9ec33cf..4d61553ccb52 100644 --- a/core/java/android/hardware/input/VirtualDpad.java +++ b/core/java/android/hardware/input/VirtualDpad.java @@ -24,7 +24,6 @@ import android.os.IBinder;  import android.os.RemoteException;  import android.view.KeyEvent; -import java.io.Closeable;  import java.util.Arrays;  import java.util.Collections;  import java.util.HashSet; @@ -39,7 +38,7 @@ import java.util.Set;   * @hide   */  @SystemApi -public class VirtualDpad implements Closeable { +public class VirtualDpad extends VirtualInputDevice {      private final Set<Integer> mSupportedKeyCodes =              Collections.unmodifiableSet( @@ -50,23 +49,10 @@ public class VirtualDpad implements Closeable {                                      KeyEvent.KEYCODE_DPAD_LEFT,                                      KeyEvent.KEYCODE_DPAD_RIGHT,                                      KeyEvent.KEYCODE_DPAD_CENTER))); -    private final IVirtualDevice mVirtualDevice; -    private final IBinder mToken;      /** @hide */      public VirtualDpad(IVirtualDevice virtualDevice, IBinder token) { -        mVirtualDevice = virtualDevice; -        mToken = token; -    } - -    @Override -    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) -    public void close() { -        try { -            mVirtualDevice.unregisterInputDevice(mToken); -        } catch (RemoteException e) { -            throw e.rethrowFromSystemServer(); -        } +        super(virtualDevice, token);      }      /** diff --git a/core/java/android/hardware/input/VirtualInputDevice.java b/core/java/android/hardware/input/VirtualInputDevice.java new file mode 100644 index 000000000000..2a79ef0e0afd --- /dev/null +++ b/core/java/android/hardware/input/VirtualInputDevice.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.annotation.RequiresPermission; +import android.companion.virtual.IVirtualDevice; +import android.os.IBinder; +import android.os.RemoteException; + +import java.io.Closeable; + +/** + * The base class for all virtual input devices such as VirtualKeyboard, VirtualMouse. + * This implements the shared functionality such as closing the device and keeping track of + * identifiers. + * + * @hide + */ +abstract class VirtualInputDevice implements Closeable { + +    /** +     * The virtual device to which this VirtualInputDevice belongs to. +     */ +    protected final IVirtualDevice mVirtualDevice; + +    /** +     * The token used to uniquely identify the virtual input device. +     */ +    protected final IBinder mToken; + +    /** @hide */ +    VirtualInputDevice( +            IVirtualDevice virtualDevice, IBinder token) { +        mVirtualDevice = virtualDevice; +        mToken = token; +    } + + +    @Override +    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) +    public void close() { +        try { +            mVirtualDevice.unregisterInputDevice(mToken); +        } catch (RemoteException e) { +            throw e.rethrowFromSystemServer(); +        } +    } +} diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java index 901401fea32c..e569dbf6b6b6 100644 --- a/core/java/android/hardware/input/VirtualKeyboard.java +++ b/core/java/android/hardware/input/VirtualKeyboard.java @@ -24,8 +24,6 @@ import android.os.IBinder;  import android.os.RemoteException;  import android.view.KeyEvent; -import java.io.Closeable; -  /**   * A virtual keyboard representing a key input mechanism on a remote device, such as a built-in   * keyboard on a laptop, a software keyboard on a tablet, or a keypad on a TV remote control. @@ -36,26 +34,13 @@ import java.io.Closeable;   * @hide   */  @SystemApi -public class VirtualKeyboard implements Closeable { +public class VirtualKeyboard extends VirtualInputDevice {      private final int mUnsupportedKeyCode = KeyEvent.KEYCODE_DPAD_CENTER; -    private final IVirtualDevice mVirtualDevice; -    private final IBinder mToken;      /** @hide */      public VirtualKeyboard(IVirtualDevice virtualDevice, IBinder token) { -        mVirtualDevice = virtualDevice; -        mToken = token; -    } - -    @Override -    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) -    public void close() { -        try { -            mVirtualDevice.unregisterInputDevice(mToken); -        } catch (RemoteException e) { -            throw e.rethrowFromSystemServer(); -        } +        super(virtualDevice, token);      }      /** diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java index 6e2b56a2b5bc..7eba2b8bfdf0 100644 --- a/core/java/android/hardware/input/VirtualMouse.java +++ b/core/java/android/hardware/input/VirtualMouse.java @@ -25,8 +25,6 @@ import android.os.IBinder;  import android.os.RemoteException;  import android.view.MotionEvent; -import java.io.Closeable; -  /**   * A virtual mouse representing a relative input mechanism on a remote device, such as a mouse or   * trackpad. @@ -37,25 +35,11 @@ import java.io.Closeable;   * @hide   */  @SystemApi -public class VirtualMouse implements Closeable { - -    private final IVirtualDevice mVirtualDevice; -    private final IBinder mToken; +public class VirtualMouse extends VirtualInputDevice {      /** @hide */      public VirtualMouse(IVirtualDevice virtualDevice, IBinder token) { -        mVirtualDevice = virtualDevice; -        mToken = token; -    } - -    @Override -    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) -    public void close() { -        try { -            mVirtualDevice.unregisterInputDevice(mToken); -        } catch (RemoteException e) { -            throw e.rethrowFromSystemServer(); -        } +        super(virtualDevice, token);      }      /** diff --git a/core/java/android/hardware/input/VirtualTouchscreen.java b/core/java/android/hardware/input/VirtualTouchscreen.java index c8d602acaff6..0d07753b9b60 100644 --- a/core/java/android/hardware/input/VirtualTouchscreen.java +++ b/core/java/android/hardware/input/VirtualTouchscreen.java @@ -23,8 +23,6 @@ import android.companion.virtual.IVirtualDevice;  import android.os.IBinder;  import android.os.RemoteException; -import java.io.Closeable; -  /**   * A virtual touchscreen representing a touch-based display input mechanism on a remote device.   * @@ -34,25 +32,10 @@ import java.io.Closeable;   * @hide   */  @SystemApi -public class VirtualTouchscreen implements Closeable { - -    private final IVirtualDevice mVirtualDevice; -    private final IBinder mToken; - +public class VirtualTouchscreen extends VirtualInputDevice {      /** @hide */      public VirtualTouchscreen(IVirtualDevice virtualDevice, IBinder token) { -        mVirtualDevice = virtualDevice; -        mToken = token; -    } - -    @Override -    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) -    public void close() { -        try { -            mVirtualDevice.unregisterInputDevice(mToken); -        } catch (RemoteException e) { -            throw e.rethrowFromSystemServer(); -        } +        super(virtualDevice, token);      }      /** diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java index 891da2466bff..4f09beec81dd 100644 --- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java +++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java @@ -777,7 +777,7 @@ final class IRemoteInputConnectionInvoker {      }      /** -     * Invokes {@link IRemoteInputConnection#replaceText(InputConnectionCommandHeader, int, int, +     * Invokes {@code IRemoteInputConnection#replaceText(InputConnectionCommandHeader, int, int,       * CharSequence, TextAttribute)}.       *       * @param start the character index where the replacement should start. @@ -788,6 +788,8 @@ final class IRemoteInputConnectionInvoker {       *     that this means you can't position the cursor within the text.       * @param text the text to replace. This may include styles.       * @param textAttribute The extra information about the text. This value may be null. +     * @return {@code true} if the invocation is completed without {@link RemoteException}, {@code +     *     false} otherwise.       */      @AnyThread      public boolean replaceText( diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 4df013949de5..d3a6323230a5 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -1246,8 +1246,21 @@ public class Binder implements IBinder {          // If the call was {@link IBinder#FLAG_ONEWAY} then these exceptions          // disappear into the ether.          final boolean tagEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_AIDL); +        final boolean hasFullyQualifiedName = getMaxTransactionId() > 0;          final String transactionTraceName; -        if (tagEnabled) { + +        if (tagEnabled && hasFullyQualifiedName) { +            // If tracing enabled and we have a fully qualified name, fetch the name +            transactionTraceName = getTransactionTraceName(code); +        } else if (tagEnabled && isStackTrackingEnabled()) { +            // If tracing is enabled and we *don't* have a fully qualified name, fetch the +            // 'best effort' name only for stack tracking. This works around noticeable perf impact +            // on low latency binder calls (<100us). The tracing call itself is between (1-10us) and +            // the perf impact can be quite noticeable while benchmarking such binder calls. +            // The primary culprits are ContentProviders and Cursors which convenienty don't +            // autogenerate their AIDL and hence will not have a fully qualified name. +            // +            // TODO(b/253426478): Relax this constraint after a more robust fix              transactionTraceName = getTransactionTraceName(code);          } else {              transactionTraceName = null; diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index f62cc879cce3..8afd6de235a0 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -341,4 +341,10 @@ public abstract class PowerManagerInternal {       * device is not awake.       */      public abstract void nap(long eventTime, boolean allowWake); + +    /** +     * Returns true if ambient display is suppressed by any app with any token. This method will +     * return false if ambient display is not available. +     */ +    public abstract boolean isAmbientDisplaySuppressed();  } diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java index f6a7c8eb8c4b..a2fa1392b079 100644 --- a/core/java/android/service/dreams/DreamActivity.java +++ b/core/java/android/service/dreams/DreamActivity.java @@ -44,6 +44,8 @@ import android.text.TextUtils;  public class DreamActivity extends Activity {      static final String EXTRA_CALLBACK = "binder";      static final String EXTRA_DREAM_TITLE = "title"; +    @Nullable +    private DreamService.DreamActivityCallbacks mCallback;      public DreamActivity() {} @@ -57,11 +59,19 @@ public class DreamActivity extends Activity {          }          final Bundle extras = getIntent().getExtras(); -        final DreamService.DreamActivityCallback callback = -                (DreamService.DreamActivityCallback) extras.getBinder(EXTRA_CALLBACK); +        mCallback = (DreamService.DreamActivityCallbacks) extras.getBinder(EXTRA_CALLBACK); -        if (callback != null) { -            callback.onActivityCreated(this); +        if (mCallback != null) { +            mCallback.onActivityCreated(this);          }      } + +    @Override +    public void onDestroy() { +        if (mCallback != null) { +            mCallback.onActivityDestroyed(); +        } + +        super.onDestroy(); +    }  } diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 3c1fef02f9ba..cb0dce91589e 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -1295,7 +1295,7 @@ public class DreamService extends Service implements Window.Callback {              Intent i = new Intent(this, DreamActivity.class);              i.setPackage(getApplicationContext().getPackageName());              i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); -            i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallback(mDreamToken)); +            i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken));              final ServiceInfo serviceInfo = fetchServiceInfo(this,                      new ComponentName(this, getClass()));              i.putExtra(DreamActivity.EXTRA_DREAM_TITLE, fetchDreamLabel(this, serviceInfo)); @@ -1488,10 +1488,10 @@ public class DreamService extends Service implements Window.Callback {      }      /** @hide */ -    final class DreamActivityCallback extends Binder { +    final class DreamActivityCallbacks extends Binder {          private final IBinder mActivityDreamToken; -        DreamActivityCallback(IBinder token) { +        DreamActivityCallbacks(IBinder token) {              mActivityDreamToken = token;          } @@ -1516,6 +1516,12 @@ public class DreamService extends Service implements Window.Callback {              mActivity = activity;              onWindowCreated(activity.getWindow());          } + +        // If DreamActivity is destroyed, wake up from Dream. +        void onActivityDestroyed() { +            mActivity = null; +            onDestroy(); +        }      }      /** diff --git a/core/java/android/service/voice/HotwordAudioStream.java b/core/java/android/service/voice/HotwordAudioStream.java index 18375add95a7..1c4d14c919a8 100644 --- a/core/java/android/service/voice/HotwordAudioStream.java +++ b/core/java/android/service/voice/HotwordAudioStream.java @@ -57,10 +57,10 @@ public final class HotwordAudioStream implements Parcelable {       * the audio until the stream is shutdown by the {@link HotwordDetectionService}.       */      @NonNull -    private final ParcelFileDescriptor mAudioStream; +    private final ParcelFileDescriptor mAudioStreamParcelFileDescriptor;      /** -     * The timestamp when the {@link #getAudioStream()} was captured by the Audio platform. +     * The timestamp when the audio stream was captured by the Audio platform.       *       * <p>       * The {@link HotwordDetectionService} egressing the audio is the owner of the underlying @@ -74,6 +74,8 @@ public final class HotwordAudioStream implements Parcelable {       * {@link HotwordDetectedResult#getHotwordDurationMillis()} to translate these durations to       * timestamps.       * </p> +     * +     * @see #getAudioStreamParcelFileDescriptor()       */      @Nullable      private final AudioTimestamp mTimestamp; @@ -143,15 +145,15 @@ public final class HotwordAudioStream implements Parcelable {      @DataClass.Generated.Member      /* package-private */ HotwordAudioStream(              @NonNull AudioFormat audioFormat, -            @NonNull ParcelFileDescriptor audioStream, +            @NonNull ParcelFileDescriptor audioStreamParcelFileDescriptor,              @Nullable AudioTimestamp timestamp,              @NonNull PersistableBundle metadata) {          this.mAudioFormat = audioFormat;          com.android.internal.util.AnnotationValidations.validate(                  NonNull.class, null, mAudioFormat); -        this.mAudioStream = audioStream; +        this.mAudioStreamParcelFileDescriptor = audioStreamParcelFileDescriptor;          com.android.internal.util.AnnotationValidations.validate( -                NonNull.class, null, mAudioStream); +                NonNull.class, null, mAudioStreamParcelFileDescriptor);          this.mTimestamp = timestamp;          this.mMetadata = metadata;          com.android.internal.util.AnnotationValidations.validate( @@ -173,12 +175,12 @@ public final class HotwordAudioStream implements Parcelable {       * the audio until the stream is shutdown by the {@link HotwordDetectionService}.       */      @DataClass.Generated.Member -    public @NonNull ParcelFileDescriptor getAudioStream() { -        return mAudioStream; +    public @NonNull ParcelFileDescriptor getAudioStreamParcelFileDescriptor() { +        return mAudioStreamParcelFileDescriptor;      }      /** -     * The timestamp when the {@link #getAudioStream()} was captured by the Audio platform. +     * The timestamp when the audio stream was captured by the Audio platform.       *       * <p>       * The {@link HotwordDetectionService} egressing the audio is the owner of the underlying @@ -192,6 +194,8 @@ public final class HotwordAudioStream implements Parcelable {       * {@link HotwordDetectedResult#getHotwordDurationMillis()} to translate these durations to       * timestamps.       * </p> +     * +     * @see #getAudioStreamParcelFileDescriptor()       */      @DataClass.Generated.Member      public @Nullable AudioTimestamp getTimestamp() { @@ -214,7 +218,7 @@ public final class HotwordAudioStream implements Parcelable {          return "HotwordAudioStream { " +                  "audioFormat = " + mAudioFormat + ", " + -                "audioStream = " + mAudioStream + ", " + +                "audioStreamParcelFileDescriptor = " + mAudioStreamParcelFileDescriptor + ", " +                  "timestamp = " + timestampToString() + ", " +                  "metadata = " + mMetadata +          " }"; @@ -234,7 +238,7 @@ public final class HotwordAudioStream implements Parcelable {          //noinspection PointlessBooleanExpression          return true                  && Objects.equals(mAudioFormat, that.mAudioFormat) -                && Objects.equals(mAudioStream, that.mAudioStream) +                && Objects.equals(mAudioStreamParcelFileDescriptor, that.mAudioStreamParcelFileDescriptor)                  && Objects.equals(mTimestamp, that.mTimestamp)                  && Objects.equals(mMetadata, that.mMetadata);      } @@ -247,7 +251,7 @@ public final class HotwordAudioStream implements Parcelable {          int _hash = 1;          _hash = 31 * _hash + Objects.hashCode(mAudioFormat); -        _hash = 31 * _hash + Objects.hashCode(mAudioStream); +        _hash = 31 * _hash + Objects.hashCode(mAudioStreamParcelFileDescriptor);          _hash = 31 * _hash + Objects.hashCode(mTimestamp);          _hash = 31 * _hash + Objects.hashCode(mMetadata);          return _hash; @@ -263,7 +267,7 @@ public final class HotwordAudioStream implements Parcelable {          if (mTimestamp != null) flg |= 0x4;          dest.writeByte(flg);          dest.writeTypedObject(mAudioFormat, flags); -        dest.writeTypedObject(mAudioStream, flags); +        dest.writeTypedObject(mAudioStreamParcelFileDescriptor, flags);          parcelTimestamp(dest, flags);          dest.writeTypedObject(mMetadata, flags);      } @@ -281,16 +285,16 @@ public final class HotwordAudioStream implements Parcelable {          byte flg = in.readByte();          AudioFormat audioFormat = (AudioFormat) in.readTypedObject(AudioFormat.CREATOR); -        ParcelFileDescriptor audioStream = (ParcelFileDescriptor) in.readTypedObject(ParcelFileDescriptor.CREATOR); +        ParcelFileDescriptor audioStreamParcelFileDescriptor = (ParcelFileDescriptor) in.readTypedObject(ParcelFileDescriptor.CREATOR);          AudioTimestamp timestamp = unparcelTimestamp(in);          PersistableBundle metadata = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);          this.mAudioFormat = audioFormat;          com.android.internal.util.AnnotationValidations.validate(                  NonNull.class, null, mAudioFormat); -        this.mAudioStream = audioStream; +        this.mAudioStreamParcelFileDescriptor = audioStreamParcelFileDescriptor;          com.android.internal.util.AnnotationValidations.validate( -                NonNull.class, null, mAudioStream); +                NonNull.class, null, mAudioStreamParcelFileDescriptor);          this.mTimestamp = timestamp;          this.mMetadata = metadata;          com.android.internal.util.AnnotationValidations.validate( @@ -321,7 +325,7 @@ public final class HotwordAudioStream implements Parcelable {      public static final class Builder {          private @NonNull AudioFormat mAudioFormat; -        private @NonNull ParcelFileDescriptor mAudioStream; +        private @NonNull ParcelFileDescriptor mAudioStreamParcelFileDescriptor;          private @Nullable AudioTimestamp mTimestamp;          private @NonNull PersistableBundle mMetadata; @@ -332,19 +336,19 @@ public final class HotwordAudioStream implements Parcelable {           *           * @param audioFormat           *   The {@link AudioFormat} of the audio stream. -         * @param audioStream +         * @param audioStreamParcelFileDescriptor           *   This stream starts with the audio bytes used for hotword detection, but continues streaming           *   the audio until the stream is shutdown by the {@link HotwordDetectionService}.           */          public Builder(                  @NonNull AudioFormat audioFormat, -                @NonNull ParcelFileDescriptor audioStream) { +                @NonNull ParcelFileDescriptor audioStreamParcelFileDescriptor) {              mAudioFormat = audioFormat;              com.android.internal.util.AnnotationValidations.validate(                      NonNull.class, null, mAudioFormat); -            mAudioStream = audioStream; +            mAudioStreamParcelFileDescriptor = audioStreamParcelFileDescriptor;              com.android.internal.util.AnnotationValidations.validate( -                    NonNull.class, null, mAudioStream); +                    NonNull.class, null, mAudioStreamParcelFileDescriptor);          }          /** @@ -363,15 +367,15 @@ public final class HotwordAudioStream implements Parcelable {           * the audio until the stream is shutdown by the {@link HotwordDetectionService}.           */          @DataClass.Generated.Member -        public @NonNull Builder setAudioStream(@NonNull ParcelFileDescriptor value) { +        public @NonNull Builder setAudioStreamParcelFileDescriptor(@NonNull ParcelFileDescriptor value) {              checkNotUsed();              mBuilderFieldsSet |= 0x2; -            mAudioStream = value; +            mAudioStreamParcelFileDescriptor = value;              return this;          }          /** -         * The timestamp when the {@link #getAudioStream()} was captured by the Audio platform. +         * The timestamp when the audio stream was captured by the Audio platform.           *           * <p>           * The {@link HotwordDetectionService} egressing the audio is the owner of the underlying @@ -385,6 +389,8 @@ public final class HotwordAudioStream implements Parcelable {           * {@link HotwordDetectedResult#getHotwordDurationMillis()} to translate these durations to           * timestamps.           * </p> +         * +         * @see #getAudioStreamParcelFileDescriptor()           */          @DataClass.Generated.Member          public @NonNull Builder setTimestamp(@NonNull AudioTimestamp value) { @@ -418,7 +424,7 @@ public final class HotwordAudioStream implements Parcelable {              }              HotwordAudioStream o = new HotwordAudioStream(                      mAudioFormat, -                    mAudioStream, +                    mAudioStreamParcelFileDescriptor,                      mTimestamp,                      mMetadata);              return o; @@ -433,10 +439,10 @@ public final class HotwordAudioStream implements Parcelable {      }      @DataClass.Generated( -            time = 1665463434564L, +            time = 1665976240224L,              codegenVersion = "1.0.23",              sourceFile = "frameworks/base/core/java/android/service/voice/HotwordAudioStream.java", -            inputSignatures = "private final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStream\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static  android.media.AudioTimestamp defaultTimestamp()\nprivate static  android.os.PersistableBundle defaultMetadata()\nprivate  java.lang.String timestampToString()\nprivate  void parcelTimestamp(android.os.Parcel,int)\nprivate static @android.annotation.Nullable android.media.AudioTimestamp unparcelTimestamp(android.os.Parcel)\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)") +            inputSignatures = "private final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStreamParcelFileDescriptor\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static  android.media.AudioTimestamp defaultTimestamp()\nprivate static  android.os.PersistableBundle defaultMetadata()\nprivate  java.lang.String timestampToString()\nprivate  void parcelTimestamp(android.os.Parcel,int)\nprivate static @android.annotation.Nullable android.media.AudioTimestamp unparcelTimestamp(android.os.Parcel)\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)")      @Deprecated      private void __metadata() {} diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java index 6255d0060e3c..e22bbd820e2b 100644 --- a/core/java/android/service/voice/HotwordDetectedResult.java +++ b/core/java/android/service/voice/HotwordDetectedResult.java @@ -32,6 +32,7 @@ import com.android.internal.util.DataClass;  import com.android.internal.util.Preconditions;  import java.util.ArrayList; +import java.util.Collections;  import java.util.List;  import java.util.Objects; @@ -203,7 +204,7 @@ public final class HotwordDetectedResult implements Parcelable {      @NonNull      private final List<HotwordAudioStream> mAudioStreams;      private static List<HotwordAudioStream> defaultAudioStreams() { -        return new ArrayList<>(); +        return Collections.emptyList();      }      /** @@ -364,9 +365,27 @@ public final class HotwordDetectedResult implements Parcelable {          }      } +    /** +     * The list of the audio streams containing audio bytes that were used for hotword detection. +     */ +    public @NonNull List<HotwordAudioStream> getAudioStreams() { +        return List.copyOf(mAudioStreams); +    } +      @DataClass.Suppress("addAudioStreams")      abstract static class BaseBuilder { - +        /** +         * The list of the audio streams containing audio bytes that were used for hotword +         * detection. +         */ +        public @NonNull Builder setAudioStreams(@NonNull List<HotwordAudioStream> value) { +            Objects.requireNonNull(value, "value should not be null"); +            final Builder builder = (Builder) this; +            // If the code gen flag in build() is changed, we must update the flag e.g. 0x200 here. +            builder.mBuilderFieldsSet |= 0x200; +            builder.mAudioStreams = List.copyOf(value); +            return builder; +        }      } @@ -555,14 +574,6 @@ public final class HotwordDetectedResult implements Parcelable {      }      /** -     * The list of the audio streams containing audio bytes that were used for hotword detection. -     */ -    @DataClass.Generated.Member -    public @NonNull List<HotwordAudioStream> getAudioStreams() { -        return mAudioStreams; -    } - -    /**       * App-specific extras to support trigger.       *       * <p>The size of this bundle will be limited to {@link #getMaxBundleSize}. Results will larger @@ -881,17 +892,6 @@ public final class HotwordDetectedResult implements Parcelable {          }          /** -         * The list of the audio streams containing audio bytes that were used for hotword detection. -         */ -        @DataClass.Generated.Member -        public @NonNull Builder setAudioStreams(@NonNull List<HotwordAudioStream> value) { -            checkNotUsed(); -            mBuilderFieldsSet |= 0x200; -            mAudioStreams = value; -            return this; -        } - -        /**           * App-specific extras to support trigger.           *           * <p>The size of this bundle will be limited to {@link #getMaxBundleSize}. Results will larger @@ -984,10 +984,10 @@ public final class HotwordDetectedResult implements Parcelable {      }      @DataClass.Generated( -            time = 1664876310951L, +            time = 1665995595979L,              codegenVersion = "1.0.23",              sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java", -            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final  java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\nclass BaseBuilder extends java.lang.Object implements []") +            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final  java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")      @Deprecated      private void __metadata() {} diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index b559161ae0ad..a59d429b24e6 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -284,7 +284,6 @@ public abstract class WallpaperService extends Service {          private Display mDisplay;          private Context mDisplayContext;          private int mDisplayState; -        private @Surface.Rotation int mDisplayInstallOrientation;          private float mWallpaperDimAmount = 0.05f;          private float mPreviousWallpaperDimAmount = mWallpaperDimAmount;          private float mDefaultDimAmount = mWallpaperDimAmount; @@ -1159,7 +1158,7 @@ public abstract class WallpaperService extends Service {                              mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle);                      final int transformHint = SurfaceControl.rotationToBufferTransform( -                            (mDisplayInstallOrientation + mDisplay.getRotation()) % 4); +                            (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4);                      mSurfaceControl.setTransformHint(transformHint);                      WindowLayout.computeSurfaceSize(mLayout, maxBounds, mWidth, mHeight,                              mWinFrames.frame, false /* dragResizing */, mSurfaceSize); @@ -1420,7 +1419,6 @@ public abstract class WallpaperService extends Service {              mWallpaperDimAmount = mDefaultDimAmount;              mPreviousWallpaperDimAmount = mWallpaperDimAmount;              mDisplayState = mDisplay.getState(); -            mDisplayInstallOrientation = mDisplay.getInstallOrientation();              if (DEBUG) Log.v(TAG, "onCreate(): " + this);              onCreate(mSurfaceHolder); diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index a1ece9298e7f..a0a07b30662f 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -162,7 +162,13 @@ public class HandwritingInitiator {                          if (candidateView == getConnectedView()) {                              startHandwriting(candidateView);                          } else { -                            candidateView.requestFocus(); +                            if (candidateView.getRevealOnFocusHint()) { +                                candidateView.setRevealOnFocusHint(false); +                                candidateView.requestFocus(); +                                candidateView.setRevealOnFocusHint(true); +                            } else { +                                candidateView.requestFocus(); +                            }                          }                      }                  } diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index 4de7c4fdd513..43828d58afc4 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -108,10 +108,11 @@ public final class ImeFocusController {      }      /** -     * @see InputMethodManager#checkFocus() +     * @see ViewRootImpl#dispatchCheckFocus()       */ -    public boolean checkFocus(boolean forceNewFocus, boolean startInput) { -        return getImmDelegate().checkFocus(forceNewFocus, startInput, mViewRootImpl); +    @UiThread +    void onScheduledCheckFocus() { +        getImmDelegate().onScheduledCheckFocus(mViewRootImpl);      }      @UiThread @@ -163,7 +164,7 @@ public final class ImeFocusController {          void onPostWindowGainedFocus(View viewForWindowFocus,                  @NonNull WindowManager.LayoutParams windowAttribute);          void onViewFocusChanged(@NonNull View view, boolean hasFocus); -        boolean checkFocus(boolean forceNewFocus, boolean startInput, ViewRootImpl viewRootImpl); +        void onScheduledCheckFocus(@NonNull ViewRootImpl viewRootImpl);          void onViewDetachedFromWindow(View view, ViewRootImpl viewRootImpl);          void onWindowDismissed(ViewRootImpl viewRootImpl);      } diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index e5a535b59eb6..f51d9bacc0a5 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -218,6 +218,11 @@ public class ViewConfiguration {      private static final int WINDOW_TOUCH_SLOP = 16;      /** +     * Margin in dips around text line bounds where stylus handwriting gestures should be supported. +     */ +    private static final int HANDWRITING_GESTURE_LINE_MARGIN = 16; + +    /**       * Minimum velocity to initiate a fling, as measured in dips per second       */      private static final int MINIMUM_FLING_VELOCITY = 50; @@ -338,6 +343,7 @@ public class ViewConfiguration {      private final int mPagingTouchSlop;      private final int mDoubleTapSlop;      private final int mWindowTouchSlop; +    private final int mHandwritingGestureLineMargin;      private final float mAmbiguousGestureMultiplier;      private final int mMaximumDrawingCacheSize;      private final int mOverscrollDistance; @@ -381,6 +387,7 @@ public class ViewConfiguration {          mPagingTouchSlop = PAGING_TOUCH_SLOP;          mDoubleTapSlop = DOUBLE_TAP_SLOP;          mWindowTouchSlop = WINDOW_TOUCH_SLOP; +        mHandwritingGestureLineMargin = HANDWRITING_GESTURE_LINE_MARGIN;          mAmbiguousGestureMultiplier = AMBIGUOUS_GESTURE_MULTIPLIER;          //noinspection deprecation          mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE; @@ -490,6 +497,9 @@ public class ViewConfiguration {          mDoubleTapTouchSlop = mTouchSlop; +        mHandwritingGestureLineMargin = res.getDimensionPixelSize( +                com.android.internal.R.dimen.config_viewConfigurationHandwritingGestureLineMargin); +          mMinimumFlingVelocity = res.getDimensionPixelSize(                  com.android.internal.R.dimen.config_viewMinFlingVelocity);          mMaximumFlingVelocity = res.getDimensionPixelSize( @@ -796,6 +806,14 @@ public class ViewConfiguration {      }      /** +     * @return margin in pixels around text line bounds where stylus handwriting gestures should be +     *     supported. +     */ +    public int getScaledHandwritingGestureLineMargin() { +        return mHandwritingGestureLineMargin; +    } + +    /**       * Interval for dispatching a recurring accessibility event in milliseconds.       * This interval guarantees that a recurring event will be send at most once       * during the {@link #getSendRecurringAccessibilityEventsInterval()} time frame. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 58c81260076b..bfa13507ed50 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -428,8 +428,6 @@ public final class ViewRootImpl implements ViewParent,      final DisplayManager mDisplayManager;      final String mBasePackageName; -    private @Surface.Rotation int mDisplayInstallOrientation; -      final int[] mTmpLocation = new int[2];      final TypedValue mTmpValue = new TypedValue(); @@ -1134,7 +1132,6 @@ public final class ViewRootImpl implements ViewParent,              if (mView == null) {                  mView = view; -                mDisplayInstallOrientation = mDisplay.getInstallOrientation();                  mViewLayoutDirectionInitial = mView.getRawLayoutDirection();                  mFallbackEventHandler.setView(view);                  mWindowAttributes.copyFrom(attrs); @@ -1905,7 +1902,6 @@ public final class ViewRootImpl implements ViewParent,          updateInternalDisplay(displayId, mView.getResources());          mImeFocusController.onMovedToDisplay();          mAttachInfo.mDisplayState = mDisplay.getState(); -        mDisplayInstallOrientation = mDisplay.getInstallOrientation();          // Internal state updated, now notify the view hierarchy.          mView.dispatchMovedToDisplay(mDisplay, config);      } @@ -5718,7 +5714,7 @@ public final class ViewRootImpl implements ViewParent,                      enqueueInputEvent(event, null, 0, true);                  } break;                  case MSG_CHECK_FOCUS: { -                    getImeFocusController().checkFocus(false, true); +                    getImeFocusController().onScheduledCheckFocus();                  } break;                  case MSG_CLOSE_SYSTEM_DIALOGS: {                      if (mView != null) { @@ -8235,7 +8231,7 @@ public final class ViewRootImpl implements ViewParent,          }          final int transformHint = SurfaceControl.rotationToBufferTransform( -                (mDisplayInstallOrientation + mDisplay.getRotation()) % 4); +                (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4);          WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,                  requestedHeight, mWinFrameInScreen, mPendingDragResizing, mSurfaceSize); @@ -8260,7 +8256,7 @@ public final class ViewRootImpl implements ViewParent,          }          mLastTransformHint = transformHint; -       +          mSurfaceControl.setTransformHint(transformHint);          if (mAttachInfo.mContentCaptureManager != null) { diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index 4a79ba62de69..febdac26a0f2 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -595,6 +595,10 @@ public class EditorInfo implements InputType, Parcelable {                  == HandwritingGesture.GESTURE_TYPE_SELECT) {              list.add(SelectGesture.class);          } +        if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_SELECT_RANGE) +                == HandwritingGesture.GESTURE_TYPE_SELECT_RANGE) { +            list.add(SelectRangeGesture.class); +        }          if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_INSERT)                  == HandwritingGesture.GESTURE_TYPE_INSERT) {              list.add(InsertGesture.class); @@ -603,6 +607,10 @@ public class EditorInfo implements InputType, Parcelable {                  == HandwritingGesture.GESTURE_TYPE_DELETE) {              list.add(DeleteGesture.class);          } +        if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_DELETE_RANGE) +                == HandwritingGesture.GESTURE_TYPE_DELETE_RANGE) { +            list.add(DeleteRangeGesture.class); +        }          if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_REMOVE_SPACE)                  == HandwritingGesture.GESTURE_TYPE_REMOVE_SPACE) {              list.add(RemoveSpaceGesture.class); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 18b3e212d6f4..201efe81c102 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -776,20 +776,20 @@ public final class InputMethodManager {                      "InputMethodManager.DelegateImpl#startInputAsyncOnWindowFocusGain",                      InputMethodManager.this, null /* icProto */); -            final ImeFocusController controller = getFocusController(); -            if (controller == null) { -                return; -            } - +            boolean checkFocusResult;              synchronized (mH) { +                if (mCurRootView == null) { +                    return; +                }                  if (mRestartOnNextWindowFocus) {                      if (DEBUG) Log.v(TAG, "Restarting due to mRestartOnNextWindowFocus as true");                      mRestartOnNextWindowFocus = false;                      forceNewFocus = true;                  } +                checkFocusResult = checkFocusInternalLocked(forceNewFocus, mCurRootView);              } -            if (controller.checkFocus(forceNewFocus, false)) { +            if (checkFocusResult) {                  // We need to restart input on the current focus view.  This                  // should be done in conjunction with telling the system service                  // about the window gaining focus, to help make the transition @@ -825,9 +825,15 @@ public final class InputMethodManager {          }          @Override -        public boolean checkFocus(boolean forceNewFocus, boolean startInput, -                ViewRootImpl viewRootImpl) { -            return checkFocusInternal(forceNewFocus, startInput, viewRootImpl); +        public void onScheduledCheckFocus(ViewRootImpl viewRootImpl) { +            synchronized (mH) { +                if (!checkFocusInternalLocked(false, viewRootImpl)) { +                    return; +                } +            } +            startInputOnWindowFocusGainInternal(StartInputReason.SCHEDULED_CHECK_FOCUS, +                    null /* focusedView */, 0 /* startInputFlags */, 0 /* softInputMode */, +                    0 /* windowFlags */);          }          @Override @@ -937,15 +943,6 @@ public final class InputMethodManager {          return mCurRootView != null ? mNextServedView : null;      } -    private ImeFocusController getFocusController() { -        synchronized (mH) { -            if (mCurRootView != null) { -                return mCurRootView.getImeFocusController(); -            } -            return null; -        } -    } -      /**       * Returns {@code true} when the given view has been served by Input Method.       */ @@ -1128,8 +1125,7 @@ public final class InputMethodManager {                          if (mCurRootView == null) {                              return;                          } -                        if (!mCurRootView.getImeFocusController().checkFocus( -                                mRestartOnNextWindowFocus, false)) { +                        if (!checkFocusInternalLocked(mRestartOnNextWindowFocus, mCurRootView)) {                              return;                          }                          final int reason = active ? StartInputReason.ACTIVATED_BY_IMMS @@ -2349,8 +2345,7 @@ public final class InputMethodManager {      }      /** -     * Called from {@link #checkFocusInternal(boolean, boolean, ViewRootImpl)}, -     * {@link #restartInput(View)}, {@link #MSG_BIND} or {@link #MSG_UNBIND}. +     * Starts an input connection from the served view that gains the window focus.       * Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input       * background thread may blocked by other methods which already inside {@code mH} lock.       */ @@ -2658,52 +2653,53 @@ public final class InputMethodManager {      }      /** -     * Check the next served view from {@link ImeFocusController} if needs to start input.       * Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input       * background thread may blocked by other methods which already inside {@code mH} lock.       * @hide       */      @UnsupportedAppUsage      public void checkFocus() { -        final ImeFocusController controller = getFocusController(); -        if (controller != null) { -            controller.checkFocus(false /* forceNewFocus */, true /* startInput */); -        } -    } - -    private boolean checkFocusInternal(boolean forceNewFocus, boolean startInput, -            ViewRootImpl viewRootImpl) {          synchronized (mH) { -            if (mCurRootView != viewRootImpl) { -                return false; -            } -            if (mServedView == mNextServedView && !forceNewFocus) { -                return false; -            } -            if (DEBUG) { -                Log.v(TAG, "checkFocus: view=" + mServedView -                        + " next=" + mNextServedView -                        + " force=" + forceNewFocus -                        + " package=" -                        + (mServedView != null ? mServedView.getContext().getPackageName() -                        : "<none>")); -            } -            // Close the connection when no next served view coming. -            if (mNextServedView == null) { -                finishInputLocked(); -                closeCurrentInput(); -                return false; +            if (mCurRootView == null) { +                return;              } -            mServedView = mNextServedView; -            if (mServedInputConnection != null) { -                mServedInputConnection.finishComposingTextFromImm(); +            if (!checkFocusInternalLocked(false /* forceNewFocus */, mCurRootView)) { +                return;              }          } +        startInputOnWindowFocusGainInternal(StartInputReason.CHECK_FOCUS, +                null /* focusedView */, +                0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */); +    } -        if (startInput) { -            startInputOnWindowFocusGainInternal(StartInputReason.CHECK_FOCUS, -                    null /* focusedView */, -                    0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */); +    /** +     * Check the next served view if needs to start input. +     */ +    @GuardedBy("mH") +    private boolean checkFocusInternalLocked(boolean forceNewFocus, ViewRootImpl viewRootImpl) { +        if (mCurRootView != viewRootImpl) { +            return false; +        } +        if (mServedView == mNextServedView && !forceNewFocus) { +            return false; +        } +        if (DEBUG) { +            Log.v(TAG, "checkFocus: view=" + mServedView +                    + " next=" + mNextServedView +                    + " force=" + forceNewFocus +                    + " package=" +                    + (mServedView != null ? mServedView.getContext().getPackageName() +                    : "<none>")); +        } +        // Close the connection when no next served view coming. +        if (mNextServedView == null) { +            finishInputLocked(); +            closeCurrentInput(); +            return false; +        } +        mServedView = mNextServedView; +        if (mServedInputConnection != null) { +            mServedInputConnection.finishComposingTextFromImm();          }          return true;      } diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 84b6f65ffea6..a5e708642c9f 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -547,6 +547,18 @@ public class ScrollView extends FrameLayout {                          handled = fullScroll(View.FOCUS_DOWN);                      }                      break; +                case KeyEvent.KEYCODE_MOVE_HOME: +                    handled = fullScroll(View.FOCUS_UP); +                    break; +                case KeyEvent.KEYCODE_MOVE_END: +                    handled = fullScroll(View.FOCUS_DOWN); +                    break; +                case KeyEvent.KEYCODE_PAGE_UP: +                    handled = pageScroll(View.FOCUS_UP); +                    break; +                case KeyEvent.KEYCODE_PAGE_DOWN: +                    handled = pageScroll(View.FOCUS_DOWN); +                    break;                  case KeyEvent.KEYCODE_SPACE:                      pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);                      break; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index ce5365acf1f4..57103e4955ca 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -189,6 +189,7 @@ import android.view.inputmethod.CompletionInfo;  import android.view.inputmethod.CorrectionInfo;  import android.view.inputmethod.CursorAnchorInfo;  import android.view.inputmethod.DeleteGesture; +import android.view.inputmethod.DeleteRangeGesture;  import android.view.inputmethod.EditorInfo;  import android.view.inputmethod.ExtractedText;  import android.view.inputmethod.ExtractedTextRequest; @@ -199,6 +200,7 @@ import android.view.inputmethod.InsertGesture;  import android.view.inputmethod.JoinOrSplitGesture;  import android.view.inputmethod.RemoveSpaceGesture;  import android.view.inputmethod.SelectGesture; +import android.view.inputmethod.SelectRangeGesture;  import android.view.inspector.InspectableProperty;  import android.view.inspector.InspectableProperty.EnumEntry;  import android.view.inspector.InspectableProperty.FlagEntry; @@ -9096,7 +9098,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener                  ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();                  gestures.add(SelectGesture.class); +                gestures.add(SelectRangeGesture.class);                  gestures.add(DeleteGesture.class); +                gestures.add(DeleteRangeGesture.class);                  gestures.add(InsertGesture.class);                  gestures.add(RemoveSpaceGesture.class);                  gestures.add(JoinOrSplitGesture.class); @@ -9325,6 +9329,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener      }      /** @hide */ +    public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) { +        Range<Integer> startRange = getRangeForRect( +                convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()), +                gesture.getGranularity()); +        if (startRange == null) { +            return handleGestureFailure(gesture); +        } +        Range<Integer> endRange = getRangeForRect( +                convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()), +                gesture.getGranularity()); +        if (endRange == null || endRange.getUpper() <= startRange.getLower()) { +            return handleGestureFailure(gesture); +        } +        Range<Integer> range = startRange.extend(endRange); +        Selection.setSelection(getEditableText(), range.getLower(), range.getUpper()); +        mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false); +        return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; +    } + +    /** @hide */      public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) {          Range<Integer> range = getRangeForRect(                  convertFromScreenToContentCoordinates(gesture.getDeletionArea()), @@ -9332,71 +9356,106 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener          if (range == null) {              return handleGestureFailure(gesture);          } -        int start = range.getLower(); -        int end = range.getUpper(); -        // For word granularity, adjust the start and end offsets to remove extra whitespace around -        // the deleted text.          if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) { -            // If the deleted text is at the start of the text, the behavior is the same as the case -            // where the deleted text follows a new line character. -            int codePointBeforeStart = start > 0 -                    ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT; -            // If the deleted text is at the end of the text, the behavior is the same as the case -            // where the deleted text precedes a new line character. -            int codePointAtEnd = end < mText.length() -                    ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT; -            if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart) -                    && (TextUtils.isWhitespace(codePointAtEnd) -                            || TextUtils.isPunctuation(codePointAtEnd))) { -                // Remove whitespace (except new lines) before the deleted text, in these cases: -                // - There is whitespace following the deleted text -                //     e.g. "one [deleted] three" -> "one | three" -> "one| three" -                // - There is punctuation following the deleted text -                //     e.g. "one [deleted]!" -> "one |!" -> "one|!" -                // - There is a new line following the deleted text -                //     e.g. "one [deleted]\n" -> "one |\n" -> "one|\n" -                // - The deleted text is at the end of the text -                //     e.g. "one [deleted]" -> "one |" -> "one|" -                // (The pipe | indicates the cursor position.) -                do { -                    start -= Character.charCount(codePointBeforeStart); -                    if (start == 0) break; -                    codePointBeforeStart = Character.codePointBefore(mText, start); -                } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)); -            } else if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd) -                    && (TextUtils.isWhitespace(codePointBeforeStart) -                            || TextUtils.isPunctuation(codePointBeforeStart))) { -                // Remove whitespace (except new lines) after the deleted text, in these cases: -                // - There is punctuation preceding the deleted text -                //     e.g. "([deleted] two)" -> "(| two)" -> "(|two)" -                // - There is a new line preceding the deleted text -                //     e.g. "\n[deleted] two" -> "\n| two" -> "\n|two" -                // - The deleted text is at the start of the text -                //     e.g. "[deleted] two" -> "| two" -> "|two" -                // (The pipe | indicates the cursor position.) -                do { -                    end += Character.charCount(codePointAtEnd); -                    if (end == mText.length()) break; -                    codePointAtEnd = Character.codePointAt(mText, end); -                } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)); -            } -        } - -        getEditableText().delete(start, end); -        Selection.setSelection(getEditableText(), start); +            range = adjustHandwritingDeleteGestureRange(range); +        } + +        getEditableText().delete(range.getLower(), range.getUpper()); +        Selection.setSelection(getEditableText(), range.getLower());          return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;      }      /** @hide */ -    public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) { -        PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint()); -        int line = mLayout.getLineForVertical((int) point.y); -        if (point.y < mLayout.getLineTop(line) -                || point.y > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) { +    public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) { +        Range<Integer> startRange = getRangeForRect( +                convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()), +                gesture.getGranularity()); +        if (startRange == null) {              return handleGestureFailure(gesture);          } -        if (point.x < mLayout.getLineLeft(line) || point.x > mLayout.getLineRight(line)) { +        Range<Integer> endRange = getRangeForRect( +                convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()), +                gesture.getGranularity()); +        if (endRange == null) { +            return handleGestureFailure(gesture); +        } +        Range<Integer> range = startRange.extend(endRange); + +        if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) { +            range = adjustHandwritingDeleteGestureRange(range); +        } + +        getEditableText().delete(range.getLower(), range.getUpper()); +        Selection.setSelection(getEditableText(), range.getLower()); +        return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; +    } + +    private Range<Integer> adjustHandwritingDeleteGestureRange(Range<Integer> range) { +        // For handwriting delete gestures with word granularity, adjust the start and end offsets +        // to remove extra whitespace around the deleted text. + +        int start = range.getLower(); +        int end = range.getUpper(); + +        // If the deleted text is at the start of the text, the behavior is the same as the case +        // where the deleted text follows a new line character. +        int codePointBeforeStart = start > 0 +                ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT; +        // If the deleted text is at the end of the text, the behavior is the same as the case where +        // the deleted text precedes a new line character. +        int codePointAtEnd = end < mText.length() +                ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT; + +        if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart) +                && (TextUtils.isWhitespace(codePointAtEnd) +                        || TextUtils.isPunctuation(codePointAtEnd))) { +            // Remove whitespace (except new lines) before the deleted text, in these cases: +            // - There is whitespace following the deleted text +            //     e.g. "one [deleted] three" -> "one | three" -> "one| three" +            // - There is punctuation following the deleted text +            //     e.g. "one [deleted]!" -> "one |!" -> "one|!" +            // - There is a new line following the deleted text +            //     e.g. "one [deleted]\n" -> "one |\n" -> "one|\n" +            // - The deleted text is at the end of the text +            //     e.g. "one [deleted]" -> "one |" -> "one|" +            // (The pipe | indicates the cursor position.) +            do { +                start -= Character.charCount(codePointBeforeStart); +                if (start == 0) break; +                codePointBeforeStart = Character.codePointBefore(mText, start); +            } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)); +            return new Range(start, end); +        } + +        if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd) +                && (TextUtils.isWhitespace(codePointBeforeStart) +                        || TextUtils.isPunctuation(codePointBeforeStart))) { +            // Remove whitespace (except new lines) after the deleted text, in these cases: +            // - There is punctuation preceding the deleted text +            //     e.g. "([deleted] two)" -> "(| two)" -> "(|two)" +            // - There is a new line preceding the deleted text +            //     e.g. "\n[deleted] two" -> "\n| two" -> "\n|two" +            // - The deleted text is at the start of the text +            //     e.g. "[deleted] two" -> "| two" -> "|two" +            // (The pipe | indicates the cursor position.) +            do { +                end += Character.charCount(codePointAtEnd); +                if (end == mText.length()) break; +                codePointAtEnd = Character.codePointAt(mText, end); +            } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)); +            return new Range(start, end); +        } + +        // Return the original range. +        return range; +    } + +    /** @hide */ +    public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) { +        PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint()); +        int line = getLineForHandwritingGesture(point); +        if (line == -1) {              return handleGestureFailure(gesture);          }          int offset = mLayout.getOffsetForHorizontal(line, point.x); @@ -9412,27 +9471,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener          PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint());          PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint()); -        // The operation should be applied to the first line of text touched by the line joining -        // the points. -        int yMin = (int) Math.min(startPoint.y, endPoint.y); -        int yMax = (int) Math.max(startPoint.y, endPoint.y); -        int line = mLayout.getLineForVertical(yMin); -        if (yMax < mLayout.getLineTop(line)) { -            // Both points are above the top of the first line. -            return handleGestureFailure(gesture); -        } -        if (yMin > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) { -            if (line == mLayout.getLineCount() - 1 || yMax < mLayout.getLineTop(line + 1)) { -                // The points are below the last line, or they are between two lines. +        // The operation should be applied to the first line of text containing one of the points. +        int startPointLine = getLineForHandwritingGesture(startPoint); +        int endPointLine = getLineForHandwritingGesture(endPoint); +        int line; +        if (startPointLine == -1) { +            if (endPointLine == -1) {                  return handleGestureFailure(gesture); -            } else { -                // Apply the operation to the next line. -                line++;              } -        } -        if (Math.max(startPoint.x, endPoint.x) < mLayout.getLineLeft(line) -                || Math.min(startPoint.x, endPoint.x) > mLayout.getLineRight(line)) { -            return handleGestureFailure(gesture); +            line = endPointLine; +        } else { +            line = (endPointLine == -1) ? startPointLine : Math.min(startPointLine, endPointLine);          }          // The operation should be applied to all characters touched by the line joining the points. @@ -9479,12 +9528,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener      public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) {          PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint()); -        int line = mLayout.getLineForVertical((int) point.y); -        if (point.y < mLayout.getLineTop(line) -                || point.y > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) { -            return handleGestureFailure(gesture); -        } -        if (point.x < mLayout.getLineLeft(line) || point.x > mLayout.getLineRight(line)) { +        int line = getLineForHandwritingGesture(point); +        if (line == -1) {              return handleGestureFailure(gesture);          } @@ -9529,6 +9574,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener          return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;      } +    /** +     * Returns the closest line such that the point is either inside the line bounds or within +     * {@link ViewConfiguration#getScaledHandwritingGestureLineMargin} of the line bounds. Returns +     * -1 if the point is not within the margin of any line bounds. +     */ +    private int getLineForHandwritingGesture(PointF point) { +        int line = mLayout.getLineForVertical((int) point.y); +        int lineMargin = ViewConfiguration.get(mContext).getScaledHandwritingGestureLineMargin(); +        if (line < mLayout.getLineCount() - 1 +                && point.y > mLayout.getLineBottom(line) - lineMargin +                && point.y +                        > (mLayout.getLineBottom(line, false) + mLayout.getLineBottom(line)) / 2f) { +            // If a point is in the space between line i and line (i + 1), Layout#getLineForVertical +            // returns i. If the point is within lineMargin of line (i + 1), and closer to line +            // (i + 1) than line i, then the gesture operation should be applied to line (i + 1). +            line++; +        } else if (point.y < mLayout.getLineTop(line) - lineMargin +                || point.y +                        > mLayout.getLineBottom(line, /* includeLineSpacing= */ false) +                                + lineMargin) { +            // The point is not within lineMargin of a line. +            return -1; +        } +        if (point.x < mLayout.getLineLeft(line) - lineMargin +                || point.x > mLayout.getLineRight(line) + lineMargin) { +            // The point is not within lineMargin of a line. +            return -1; +        } +        return line; +    } +      @Nullable      private Range<Integer> getRangeForRect(@NonNull RectF area, int granularity) {          SegmentFinder segmentFinder; diff --git a/core/java/com/android/internal/app/AppLocaleCollector.java b/core/java/com/android/internal/app/AppLocaleCollector.java new file mode 100644 index 000000000000..65e8c646e17d --- /dev/null +++ b/core/java/com/android/internal/app/AppLocaleCollector.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + +import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET; +import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG; + +import android.content.Context; +import android.os.Build; +import android.os.LocaleList; +import android.util.Log; + +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +/** The Locale data collector for per-app language. */ +class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { +    private static final String TAG = AppLocaleCollector.class.getSimpleName(); +    private final Context mContext; +    private final String mAppPackageName; +    private final LocaleStore.LocaleInfo mAppCurrentLocale; + +    AppLocaleCollector(Context context, String appPackageName) { +        mContext = context; +        mAppPackageName = appPackageName; +        mAppCurrentLocale = LocaleStore.getAppCurrentLocaleInfo( +                mContext, mAppPackageName); +    } + +    @Override +    public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) { +        HashSet<String> langTagsToIgnore = new HashSet<>(); + +        LocaleList systemLangList = LocaleList.getDefault(); +        for(int i = 0; i < systemLangList.size(); i++) { +            langTagsToIgnore.add(systemLangList.get(i).toLanguageTag()); +        } + +        if (mAppCurrentLocale != null) { +            langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag()); +        } +        return langTagsToIgnore; +    } + +    @Override +    public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent, +            boolean translatedOnly, boolean isForCountryMode) { +        AppLocaleStore.AppLocaleResult result = +                AppLocaleStore.getAppSupportedLocales(mContext, mAppPackageName); +        Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly); +        Set<LocaleStore.LocaleInfo> appLocaleList = new HashSet<>(); +        Set<LocaleStore.LocaleInfo> systemLocaleList; +        boolean shouldShowList = +                result.mLocaleStatus == GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG +                        || result.mLocaleStatus == GET_SUPPORTED_LANGUAGE_FROM_ASSET; + +        // Get system supported locale list +        if (isForCountryMode) { +            systemLocaleList = LocaleStore.getLevelLocales(mContext, +                    langTagsToIgnore, parent, translatedOnly); +        } else { +            systemLocaleList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore, +                    null /* no parent */, translatedOnly); +        } + +        // Add current app locale +        if (mAppCurrentLocale != null && !isForCountryMode) { +            appLocaleList.add(mAppCurrentLocale); +        } + +        // Add current system language into suggestion list +        for(LocaleStore.LocaleInfo localeInfo: +                LocaleStore.getSystemCurrentLocaleInfo()) { +            boolean isNotCurrentLocale = mAppCurrentLocale == null +                    || !localeInfo.getLocale().equals(mAppCurrentLocale.getLocale()); +            if (!isForCountryMode && isNotCurrentLocale) { +                appLocaleList.add(localeInfo); +            } +        } + +        // Add the languages that included in system supported locale +        if (shouldShowList) { +            appLocaleList.addAll(filterTheLanguagesNotIncludedInSystemLocale( +                    systemLocaleList, result.mAppSupportedLocales)); +        } + +        // Add "system language" option +        if (!isForCountryMode && shouldShowList) { +            appLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo( +                    mAppCurrentLocale == null)); +        } + +        if (Build.isDebuggable()) { +            Log.d(TAG, "App locale list: " + appLocaleList); +        } + +        return appLocaleList; +    } + +    @Override +    public boolean hasSpecificPackageName() { +        return true; +    } + +    private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotIncludedInSystemLocale( +            Set<LocaleStore.LocaleInfo> systemLocaleList, +            HashSet<Locale> appSupportedLocales) { +        Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>(); + +        for(LocaleStore.LocaleInfo li: systemLocaleList) { +            if (appSupportedLocales.contains(li.getLocale())) { +                filteredList.add(li); +            } else { +                for(Locale l: appSupportedLocales) { +                    if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) { +                        filteredList.add(li); +                        break; +                    } +                } +            } +        } +        return filteredList; +    } +} diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index 965895f08d6e..3efd279c2639 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -16,16 +16,12 @@  package com.android.internal.app; -import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus; -  import android.app.FragmentManager;  import android.app.FragmentTransaction;  import android.app.ListFragment;  import android.content.Context;  import android.os.Bundle; -import android.os.LocaleList;  import android.text.TextUtils; -import android.util.Log;  import android.view.Menu;  import android.view.MenuInflater;  import android.view.MenuItem; @@ -36,7 +32,6 @@ import android.widget.SearchView;  import com.android.internal.R; -import java.util.Collections;  import java.util.HashSet;  import java.util.Locale;  import java.util.Set; @@ -54,6 +49,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O      private SuggestedLocaleAdapter mAdapter;      private LocaleSelectedListener mListener; +    private LocaleCollectorBase mLocalePickerCollector;      private Set<LocaleStore.LocaleInfo> mLocaleList;      private LocaleStore.LocaleInfo mParentLocale;      private boolean mTranslatedOnly = false; @@ -62,7 +58,6 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O      private boolean mPreviousSearchHadFocus = false;      private int mFirstVisiblePosition = 0;      private int mTopDistance = 0; -    private String mAppPackageName;      private CharSequence mTitle = null;      private OnActionExpandListener mOnActionExpandListener; @@ -79,31 +74,50 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O          void onLocaleSelected(LocaleStore.LocaleInfo locale);      } -    private static LocalePickerWithRegion createCountryPicker(Context context, +    /** +     * The interface which provides the locale list. +     */ +    interface LocaleCollectorBase { +        /** Gets the ignored locale list. */ +        HashSet<String> getIgnoredLocaleList(boolean translatedOnly); + +        /** Gets the supported locale list. */ +        Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent, +                boolean translatedOnly, boolean isForCountryMode); + +        /** Indicates if the class work for specific package. */ +        boolean hasSpecificPackageName(); +    } + +    private static LocalePickerWithRegion createCountryPicker(              LocaleSelectedListener listener, LocaleStore.LocaleInfo parent, -            boolean translatedOnly, String appPackageName, -            OnActionExpandListener onActionExpandListener) { +            boolean translatedOnly, OnActionExpandListener onActionExpandListener, +            LocaleCollectorBase localePickerCollector) {          LocalePickerWithRegion localePicker = new LocalePickerWithRegion();          localePicker.setOnActionExpandListener(onActionExpandListener); -        boolean shouldShowTheList = localePicker.setListener(context, listener, parent, -                translatedOnly, appPackageName); +        boolean shouldShowTheList = localePicker.setListener(listener, parent, +                translatedOnly, localePickerCollector);          return shouldShowTheList ? localePicker : null;      }      public static LocalePickerWithRegion createLanguagePicker(Context context,              LocaleSelectedListener listener, boolean translatedOnly) { -        LocalePickerWithRegion localePicker = new LocalePickerWithRegion(); -        localePicker.setListener(context, listener, /* parent */ null, translatedOnly, null); -        return localePicker; +        return createLanguagePicker(context, listener, translatedOnly, null, null);      }      public static LocalePickerWithRegion createLanguagePicker(Context context,              LocaleSelectedListener listener, boolean translatedOnly, String appPackageName,              OnActionExpandListener onActionExpandListener) { +        LocaleCollectorBase localePickerController; +        if (TextUtils.isEmpty(appPackageName)) { +            localePickerController = new SystemLocaleCollector(context); +        } else { +            localePickerController = new AppLocaleCollector(context, appPackageName); +        }          LocalePickerWithRegion localePicker = new LocalePickerWithRegion();          localePicker.setOnActionExpandListener(onActionExpandListener); -        localePicker.setListener( -                context, listener, /* parent */ null, translatedOnly, appPackageName); +        localePicker.setListener(listener, /* parent */ null, translatedOnly, +                localePickerController);          return localePicker;      } @@ -120,109 +134,23 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O       * In this case we don't even show the list, we call the listener with that locale,       * "pretending" it was selected, and return false.</p>       */ -    private boolean setListener(Context context, LocaleSelectedListener listener, -            LocaleStore.LocaleInfo parent, boolean translatedOnly, String appPackageName) { +    private boolean setListener(LocaleSelectedListener listener, LocaleStore.LocaleInfo parent, +            boolean translatedOnly, LocaleCollectorBase localePickerController) {          this.mParentLocale = parent;          this.mListener = listener;          this.mTranslatedOnly = translatedOnly; -        this.mAppPackageName = appPackageName; +        this.mLocalePickerCollector = localePickerController;          setRetainInstance(true); -        final HashSet<String> langTagsToIgnore = new HashSet<>(); -        LocaleStore.LocaleInfo appCurrentLocale = -                LocaleStore.getAppCurrentLocaleInfo(context, appPackageName); -        boolean isForCountryMode = parent != null; - -        if (!TextUtils.isEmpty(appPackageName) && !isForCountryMode) { -            // Filter current system locale to add them into suggestion -            LocaleList systemLangList = LocaleList.getDefault(); -            for(int i = 0; i < systemLangList.size(); i++) { -                langTagsToIgnore.add(systemLangList.get(i).toLanguageTag()); -            } - -            if (appCurrentLocale != null) { -                Log.d(TAG, "appCurrentLocale: " + appCurrentLocale.getLocale().toLanguageTag()); -                langTagsToIgnore.add(appCurrentLocale.getLocale().toLanguageTag()); -            } else { -                Log.d(TAG, "appCurrentLocale is null"); -            } -        } else if (!translatedOnly) { -            final LocaleList userLocales = LocalePicker.getLocales(); -            final String[] langTags = userLocales.toLanguageTags().split(","); -            Collections.addAll(langTagsToIgnore, langTags); -        } +        mLocaleList = localePickerController.getSupportedLocaleList( +                parent, translatedOnly, parent != null); -        if (isForCountryMode) { -            mLocaleList = LocaleStore.getLevelLocales(context, -                    langTagsToIgnore, parent, translatedOnly); -            if (mLocaleList.size() <= 1) { -                if (listener != null && (mLocaleList.size() == 1)) { -                    listener.onLocaleSelected(mLocaleList.iterator().next()); -                } -                return false; -            } +        if (parent != null && listener != null && mLocaleList.size() == 1) { +            listener.onLocaleSelected(mLocaleList.iterator().next()); +            return false;          } else { -            mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore, -                    null /* no parent */, translatedOnly); +            return true;          } -        Log.d(TAG, "mLocaleList size:  " + mLocaleList.size()); - -        // Adding current locale and system default option into suggestion list -        if(!TextUtils.isEmpty(appPackageName)) { -            if (appCurrentLocale != null && !isForCountryMode) { -                mLocaleList.add(appCurrentLocale); -            } - -            AppLocaleStore.AppLocaleResult result = -                    AppLocaleStore.getAppSupportedLocales(context, appPackageName); -            boolean shouldShowList = -                    result.mLocaleStatus == LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG -                    || result.mLocaleStatus == LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET; - -            // Add current system language into suggestion list -            for(LocaleStore.LocaleInfo localeInfo: LocaleStore.getSystemCurrentLocaleInfo()) { -                boolean isNotCurrentLocale = appCurrentLocale == null -                        || !localeInfo.getLocale().equals(appCurrentLocale.getLocale()); -                if (!isForCountryMode && isNotCurrentLocale) { -                    mLocaleList.add(localeInfo); -                } -            } - -            // Filter the language not support in app -            mLocaleList = filterTheLanguagesNotSupportedInApp( -                    shouldShowList, result.mAppSupportedLocales); - -            Log.d(TAG, "mLocaleList after app-supported filter:  " + mLocaleList.size()); - -            // Add "system language" -            if (!isForCountryMode && shouldShowList) { -                mLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo(appCurrentLocale == null)); -            } -        } -        return true; -    } - -    private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotSupportedInApp( -            boolean shouldShowList, HashSet<Locale> supportedLocales) { -        Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>(); -        if (!shouldShowList) { -            return filteredList; -        } - -        for(LocaleStore.LocaleInfo li: mLocaleList) { -            if (supportedLocales.contains(li.getLocale())) { -                filteredList.add(li); -            } else { -                for(Locale l: supportedLocales) { -                    if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) { -                        filteredList.add(li); -                        break; -                    } -                } -            } -        } - -        return filteredList;      }      private void returnToParentFrame() { @@ -246,7 +174,9 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O          mTitle = getActivity().getTitle();          final boolean countryMode = mParentLocale != null;          final Locale sortingLocale = countryMode ? mParentLocale.getLocale() : Locale.getDefault(); -        mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, mAppPackageName); +        final boolean hasSpecificPackageName = +                mLocalePickerCollector != null && mLocalePickerCollector.hasSpecificPackageName(); +        mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, hasSpecificPackageName);          final LocaleHelper.LocaleInfoComparator comp =                  new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode);          mAdapter.sort(comp); @@ -321,8 +251,8 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O              returnToParentFrame();          } else {              LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker( -                    getContext(), mListener, locale, mTranslatedOnly /* translate only */, -                    mAppPackageName, mOnActionExpandListener); +                    mListener, locale, mTranslatedOnly /* translate only */, +                    mOnActionExpandListener, this.mLocalePickerCollector);              if (selector != null) {                  getFragmentManager().beginTransaction()                          .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) @@ -340,7 +270,8 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O              inflater.inflate(R.menu.language_selection_list, menu);              final MenuItem searchMenuItem = menu.findItem(R.id.locale_search_menu); -            if (!TextUtils.isEmpty(mAppPackageName) && mOnActionExpandListener != null) { +            if (mLocalePickerCollector.hasSpecificPackageName() +                    && mOnActionExpandListener != null) {                  searchMenuItem.setOnActionExpandListener(mOnActionExpandListener);              } diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java index 8f6bc438ed9f..a61a6d7d241b 100644 --- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java +++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java @@ -69,17 +69,17 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable {      protected Locale mDisplayLocale = null;      // used to potentially cache a modified Context that uses mDisplayLocale      protected Context mContextOverride = null; -    private String mAppPackageName; +    private boolean mHasSpecificAppPackageName;      public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode) { -        this(localeOptions, countryMode, null); +        this(localeOptions, countryMode, false);      }      public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode, -            String appPackageName) { +            boolean hasSpecificAppPackageName) {          mCountryMode = countryMode;          mLocaleOptions = new ArrayList<>(localeOptions.size()); -        mAppPackageName = appPackageName; +        mHasSpecificAppPackageName = hasSpecificAppPackageName;          for (LocaleStore.LocaleInfo li : localeOptions) {              if (li.isSuggested()) { @@ -136,7 +136,7 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable {      @Override      public int getViewTypeCount() { -        if (!TextUtils.isEmpty(mAppPackageName) && showHeaders()) { +        if (mHasSpecificAppPackageName && showHeaders()) {              // Two headers, 1 "System language", 1 current locale              return APP_LANGUAGE_PICKER_TYPE_COUNT;          } else if (showHeaders()) { diff --git a/core/java/com/android/internal/app/SystemLocaleCollector.java b/core/java/com/android/internal/app/SystemLocaleCollector.java new file mode 100644 index 000000000000..9a6d4c192fdc --- /dev/null +++ b/core/java/com/android/internal/app/SystemLocaleCollector.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + +import android.content.Context; +import android.os.LocaleList; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** The Locale data collector for System language. */ +class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { +    private final Context mContext; + +    SystemLocaleCollector(Context context) { +        mContext = context; +    } + +    @Override +    public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) { +        HashSet<String> ignoreList = new HashSet<>(); +        if (!translatedOnly) { +            final LocaleList userLocales = LocalePicker.getLocales(); +            final String[] langTags = userLocales.toLanguageTags().split(","); +            Collections.addAll(ignoreList, langTags); +        } +        return ignoreList; +    } + +    @Override +    public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent, +            boolean translatedOnly, boolean isForCountryMode) { +        Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly); +        Set<LocaleStore.LocaleInfo> localeList; + +        if (isForCountryMode) { +            localeList = LocaleStore.getLevelLocales(mContext, +                    langTagsToIgnore, parent, translatedOnly); +        } else { +            localeList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore, +                    null /* no parent */, translatedOnly); +        } +        return localeList; +    } + + +    @Override +    public boolean hasSpecificPackageName() { +        return false; +    } +}
\ No newline at end of file diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index f09e176beea0..21c7baab4e83 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -16,6 +16,7 @@  package com.android.internal.backup; +import android.app.backup.IBackupManagerMonitor;  import android.app.backup.RestoreDescription;  import android.app.backup.RestoreSet;  import android.content.Intent; @@ -400,4 +401,13 @@ oneway interface IBackupTransport {       * <p>For supported flags see {@link android.app.backup.BackupAgent}.       */      void getTransportFlags(in AndroidFuture<int> resultFuture); + +    /** +     * Ask the transport for a {@link IBackupManagerMonitor} instance which will be used by the +     * framework to report logging events back to the transport. +     * +     * Backups requested from outside the framework may pass in a monitor with the request, +     * however backups initiated by the framework will call this method to retrieve one. +     */ +    void getBackupManagerMonitor(in AndroidFuture<IBackupManagerMonitor> resultFuture);  } diff --git a/core/java/com/android/internal/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java index f725b37acde7..d5f7ba57a694 100644 --- a/core/java/com/android/internal/infra/AbstractRemoteService.java +++ b/core/java/com/android/internal/infra/AbstractRemoteService.java @@ -20,10 +20,14 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa  import android.annotation.NonNull;  import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ApplicationExitInfo; +import android.app.IActivityManager;  import android.content.ComponentName;  import android.content.Context;  import android.content.Intent;  import android.content.ServiceConnection; +import android.content.pm.ParceledListSlice;  import android.os.Handler;  import android.os.IBinder;  import android.os.IBinder.DeathRecipient; @@ -39,6 +43,7 @@ import com.android.internal.annotations.GuardedBy;  import java.io.PrintWriter;  import java.lang.ref.WeakReference;  import java.util.ArrayList; +import java.util.List;  /**   * Base class representing a remote service. @@ -66,6 +71,7 @@ import java.util.ArrayList;  @Deprecated  public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I>,          I extends IInterface> implements DeathRecipient { +    private static final int SERVICE_NOT_EXIST = -1;      private static final int MSG_BIND = 1;      private static final int MSG_UNBIND = 2; @@ -96,6 +102,9 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I      // Used just for debugging purposes (on dump)      private long mNextUnbind; +    // Used just for debugging purposes (on dump) +    private int mServiceExitReason; +    private int mServiceExitSubReason;      /** Requests that have been scheduled, but that are not finished yet */      private final ArrayList<BasePendingRequest<S, I>> mUnfinishedRequests = new ArrayList<>(); @@ -126,6 +135,8 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I          mUserId = userId;          mHandler = new Handler(handler.getLooper());          mBindingFlags = bindingFlags; +        mServiceExitReason = SERVICE_NOT_EXIST; +        mServiceExitSubReason = SERVICE_NOT_EXIST;      }      /** @@ -229,6 +240,7 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I          if (mService != null) {              mService.asBinder().unlinkToDeath(this, 0);          } +        updateServicelicationExitInfo(mComponentName, mUserId);          mConnecting = true;          mService = null;          mServiceDied = true; @@ -239,6 +251,33 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I          handleBindFailure();      } +    private void updateServicelicationExitInfo(ComponentName componentName, int userId) { +        IActivityManager am = ActivityManager.getService(); +        String packageName = componentName.getPackageName(); +        ParceledListSlice<ApplicationExitInfo> plistSlice = null; +        try { +            plistSlice = am.getHistoricalProcessExitReasons(packageName, 0, 1, userId); +        } catch (RemoteException e) { +            // do nothing. The local binder so it can not throw it. +        } +        if (plistSlice == null) { +            return; +        } +        List<ApplicationExitInfo> list = plistSlice.getList(); +        if (list.isEmpty()) { +            return; +        } +        ApplicationExitInfo info = list.get(0); +        mServiceExitReason = info.getReason(); +        mServiceExitSubReason = info.getSubReason(); +        if (mVerbose) { +            Slog.v(mTag, "updateServicelicationExitInfo: exitReason=" +                    + ApplicationExitInfo.reasonCodeToString(mServiceExitReason) +                    + " exitSubReason= " + ApplicationExitInfo.subreasonToString( +                    mServiceExitSubReason)); +        } +    } +      // Note: we are dumping without a lock held so this is a bit racy but      // adding a lock to a class that offloads to a handler thread would      // mean adding a lock adding overhead to normal runtime operation. @@ -272,6 +311,16 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I              }          }          pw.println(); +        if (mServiceExitReason != SERVICE_NOT_EXIST) { +            pw.append(prefix).append(tab).append("serviceExistReason=") +                    .append(ApplicationExitInfo.reasonCodeToString(mServiceExitReason)); +            pw.println(); +        } +        if (mServiceExitSubReason != SERVICE_NOT_EXIST) { +            pw.append(prefix).append(tab).append("serviceExistSubReason=") +                    .append(ApplicationExitInfo.subreasonToString(mServiceExitSubReason)); +            pw.println(); +        }          pw.append(prefix).append("mBindingFlags=").println(mBindingFlags);          pw.append(prefix).append("idleTimeout=")              .append(Long.toString(idleTimeout / 1000)).append("s\n"); @@ -498,6 +547,8 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I                  return;              }              mService = getServiceInterface(service); +            mServiceExitReason = SERVICE_NOT_EXIST; +            mServiceExitSubReason = SERVICE_NOT_EXIST;              handleOnConnectedStateChangedInternal(true);              mServiceDied = false;          } diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java index f260d7dfc6a6..f600c36cd8c9 100644 --- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java +++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java @@ -35,6 +35,7 @@ import android.view.inputmethod.BaseInputConnection;  import android.view.inputmethod.CompletionInfo;  import android.view.inputmethod.CorrectionInfo;  import android.view.inputmethod.DeleteGesture; +import android.view.inputmethod.DeleteRangeGesture;  import android.view.inputmethod.DumpableInputConnection;  import android.view.inputmethod.ExtractedText;  import android.view.inputmethod.ExtractedTextRequest; @@ -44,6 +45,7 @@ import android.view.inputmethod.InsertGesture;  import android.view.inputmethod.JoinOrSplitGesture;  import android.view.inputmethod.RemoveSpaceGesture;  import android.view.inputmethod.SelectGesture; +import android.view.inputmethod.SelectRangeGesture;  import android.widget.TextView;  import java.util.concurrent.Executor; @@ -275,8 +277,12 @@ public final class EditableInputConnection extends BaseInputConnection          int result;          if (gesture instanceof SelectGesture) {              result = mTextView.performHandwritingSelectGesture((SelectGesture) gesture); +        } else if (gesture instanceof SelectRangeGesture) { +            result = mTextView.performHandwritingSelectRangeGesture((SelectRangeGesture) gesture);          } else if (gesture instanceof DeleteGesture) {              result = mTextView.performHandwritingDeleteGesture((DeleteGesture) gesture); +        } else if (gesture instanceof DeleteRangeGesture) { +            result = mTextView.performHandwritingDeleteRangeGesture((DeleteRangeGesture) gesture);          } else if (gesture instanceof InsertGesture) {              result = mTextView.performHandwritingInsertGesture((InsertGesture) gesture);          } else if (gesture instanceof RemoveSpaceGesture) { diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index 09c97b39e260..1b4afd6dd39f 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -49,6 +49,8 @@ public final class InputMethodDebug {                  return "WINDOW_FOCUS_GAIN";              case StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY:                  return "WINDOW_FOCUS_GAIN_REPORT_ONLY"; +            case StartInputReason.SCHEDULED_CHECK_FOCUS: +                return "SCHEDULED_CHECK_FOCUS";              case StartInputReason.APP_CALLED_RESTART_INPUT_API:                  return "APP_CALLED_RESTART_INPUT_API";              case StartInputReason.CHECK_FOCUS: diff --git a/core/java/com/android/internal/inputmethod/StartInputReason.java b/core/java/com/android/internal/inputmethod/StartInputReason.java index 51ed841410d7..733d9751c810 100644 --- a/core/java/com/android/internal/inputmethod/StartInputReason.java +++ b/core/java/com/android/internal/inputmethod/StartInputReason.java @@ -31,6 +31,7 @@ import java.lang.annotation.Retention;          StartInputReason.UNSPECIFIED,          StartInputReason.WINDOW_FOCUS_GAIN,          StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, +        StartInputReason.SCHEDULED_CHECK_FOCUS,          StartInputReason.APP_CALLED_RESTART_INPUT_API,          StartInputReason.CHECK_FOCUS,          StartInputReason.BOUND_TO_IMMS, @@ -58,6 +59,11 @@ public @interface StartInputReason {       */      int WINDOW_FOCUS_GAIN_REPORT_ONLY = 2;      /** +     * Similar to {@link #CHECK_FOCUS}, but the one scheduled with +     * {@link android.view.ViewRootImpl#dispatchCheckFocus()}. +     */ +    int SCHEDULED_CHECK_FOCUS = 3; +    /**       * {@link android.view.inputmethod.InputMethodManager#restartInput(android.view.View)} is       * either explicitly called by the application or indirectly called by some Framework class       * (e.g. {@link android.widget.EditText}). diff --git a/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java b/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java index a03bac45d14f..90ad34d6924f 100644 --- a/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java +++ b/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java @@ -87,6 +87,10 @@ public final class SystemServerClassLoaderFactory {          if (isTestOnly) {              return true;          } +        // If system server is being profiled, it's OK to create class loaders anytime. +        if (ZygoteInit.shouldProfileSystemServer()) { +            return true; +        }          return false;      } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 73fb7fe0be49..076e4e118e66 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -238,6 +238,21 @@ public class ZygoteInit {          Trace.traceEnd(Trace.TRACE_TAG_DALVIK);      } +    private static boolean isExperimentEnabled(String experiment) { +        boolean defaultValue = SystemProperties.getBoolean( +                "dalvik.vm." + experiment, +                /*def=*/false); +        // Can't use device_config since we are the zygote, and it's not initialized at this point. +        return SystemProperties.getBoolean( +                "persist.device_config." + DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT +                        + "." + experiment, +                defaultValue); +    } + +    /* package-private */ static boolean shouldProfileSystemServer() { +        return isExperimentEnabled("profilesystemserver"); +    } +      /**       * Performs Zygote process initialization. Loads and initializes commonly used classes.       * @@ -341,14 +356,7 @@ public class ZygoteInit {              // If we are profiling the boot image, reset the Jit counters after preloading the              // classes. We want to preload for performance, and we can use method counters to              // infer what clases are used after calling resetJitCounters, for profile purposes. -            // Can't use device_config since we are the zygote. -            String prop = SystemProperties.get( -                    "persist.device_config.runtime_native_boot.profilebootclasspath", ""); -            // Might be empty if the property is unset since the default is "". -            if (prop.length() == 0) { -                prop = SystemProperties.get("dalvik.vm.profilebootclasspath", ""); -            } -            if ("true".equals(prop)) { +            if (isExperimentEnabled("profilebootclasspath")) {                  Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ResetJitCounters");                  VMRuntime.resetJitCounters();                  Trace.traceEnd(Trace.TRACE_TAG_DALVIK); @@ -489,16 +497,6 @@ public class ZygoteInit {          ZygoteHooks.gcAndFinalize();      } -    private static boolean shouldProfileSystemServer() { -        boolean defaultValue = SystemProperties.getBoolean("dalvik.vm.profilesystemserver", -                /*default=*/ false); -        // Can't use DeviceConfig since it's not initialized at this point. -        return SystemProperties.getBoolean( -                "persist.device_config." + DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT -                        + ".profilesystemserver", -                defaultValue); -    } -      /**       * Finish remaining work for the newly forked system server process.       */ @@ -585,6 +583,13 @@ public class ZygoteInit {       * in the forked system server process in the zygote SELinux domain.       */      private static void prefetchStandaloneSystemServerJars() { +        if (shouldProfileSystemServer()) { +            // We don't prefetch AOT artifacts if we are profiling system server, as we are going to +            // JIT it. +            // This method only gets called from native and should already be skipped if we profile +            // system server. Still, be robust and check it again. +            return; +        }          String envStr = Os.getenv("STANDALONE_SYSTEMSERVER_JARS");          if (TextUtils.isEmpty(envStr)) {              return; diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 550259fa7083..664e964f09ef 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -343,6 +343,7 @@ enum MountExternalKind {  // Must match values in com.android.internal.os.Zygote.  enum RuntimeFlags : uint32_t {      DEBUG_ENABLE_JDWP = 1, +    PROFILE_SYSTEM_SERVER = 1 << 14,      PROFILE_FROM_SHELL = 1 << 15,      MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20),      MEMORY_TAG_LEVEL_TBI = 1 << 19, @@ -1821,9 +1822,11 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,                                             instruction_set.value().c_str());      } -    if (is_system_server) { +    if (is_system_server && !(runtime_flags & RuntimeFlags::PROFILE_SYSTEM_SERVER)) {          // Prefetch the classloader for the system server. This is done early to          // allow a tie-down of the proper system server selinux domain. +        // We don't prefetch when the system server is being profiled to avoid +        // loading AOT code.          env->CallStaticObjectMethod(gZygoteInitClass, gGetOrCreateSystemServerClassLoader);          if (env->ExceptionCheck()) {              // Be robust here. The Java code will attempt to create the classloader diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index a7f2aa7cba69..be1c939f0ff8 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -24,6 +24,7 @@      android:gravity="center_vertical"      android:orientation="horizontal"      android:theme="@style/Theme.DeviceDefault.Notification" +    android:importantForAccessibility="no"      >      <ImageView diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 8fde9d5c16c1..caa67de1fe61 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2612,6 +2612,9 @@           movement threshold under which hover is considered "stationary". -->      <dimen name="config_viewConfigurationHoverSlop">4dp</dimen> +    <!-- Margin around text line bounds where stylus handwriting gestures should be supported. --> +    <dimen name="config_viewConfigurationHandwritingGestureLineMargin">16dp</dimen> +      <!-- Multiplier for gesture thresholds when a MotionEvent classification is ambiguous. -->      <item name="config_ambiguousGestureMultiplier" format="float" type="dimen">2.0</item> @@ -3556,9 +3559,9 @@           config_sidefpsSkipWaitForPowerVendorAcquireMessage -->      <integer name="config_sidefpsSkipWaitForPowerAcquireMessage">6</integer> -    <!-- This vendor acquired message that will cause the sidefpsKgPowerPress window to be skipped. -         config_sidefpsSkipWaitForPowerOnFingerUp must be true and -         config_sidefpsSkipWaitForPowerAcquireMessage must be BIOMETRIC_ACQUIRED_VENDOR == 6. --> +    <!-- This vendor acquired message will cause the sidefpsKgPowerPress window to be skipped +         when config_sidefpsSkipWaitForPowerAcquireMessage == 6 (VENDOR) and the vendor acquire +         message equals this constant -->      <integer name="config_sidefpsSkipWaitForPowerVendorAcquireMessage">2</integer>      <!-- This config is used to force VoiceInteractionService to start on certain low ram devices. diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index a1d73ff25cb8..71b2f00ff2b1 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -117,4 +117,17 @@      <!-- Whether using the new SubscriptionManagerService or the old SubscriptionController -->      <bool name="config_using_subscription_manager_service">false</bool>      <java-symbol type="bool" name="config_using_subscription_manager_service" /> + +    <!-- Boolean indicating whether the emergency numbers for a country, sourced from modem/config, +         should be ignored if that country is 'locked' (i.e. ignore_modem_config set to true) in +         Android Emergency DB. If this value is true, emergency numbers for a country, sourced from +         modem/config, will be ignored if that country is 'locked' in Android Emergency DB. --> +    <bool name="ignore_modem_config_emergency_numbers">false</bool> +    <java-symbol type="bool" name="ignore_modem_config_emergency_numbers" /> + +    <!-- Boolean indicating whether emergency numbers routing from the android emergency number +         database should be ignored (i.e. routing will always be set to UNKNOWN). If this value is +         true, routing from the android emergency number database will be ignored. --> +    <bool name="ignore_emergency_number_routing_from_db">false</bool> +    <java-symbol type="bool" name="ignore_emergency_number_routing_from_db" />  </resources> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 89741effb945..d03d20697788 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -131,6 +131,8 @@    </staging-public-group>    <staging-public-group type="dimen" first-id="0x01ca0000"> +    <!-- @hide @SystemApi --> +    <public name="config_viewConfigurationHandwritingGestureLineMargin" />    </staging-public-group>    <staging-public-group type="color" first-id="0x01c90000"> diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp index 113f45dfef55..7cb64c8b4f0c 100644 --- a/core/tests/BroadcastRadioTests/Android.bp +++ b/core/tests/BroadcastRadioTests/Android.bp @@ -23,23 +23,32 @@ package {  android_test {      name: "BroadcastRadioTests", +    srcs: ["src/**/*.java"],      privileged: true,      certificate: "platform",      // TODO(b/13282254): uncomment when b/13282254 is fixed      // sdk_version: "current"      platform_apis: true, -    static_libs: [ -        "compatibility-device-util-axt", -        "androidx.test.rules", -        "testng", -        "services.core", -    ], -    libs: ["android.test.base"], -    srcs: ["src/**/*.java"],      dex_preopt: {          enabled: false,      },      optimize: {          enabled: false,      }, +    static_libs: [ +        "services.core", +        "androidx.test.rules", +        "truth-prebuilt", +        "testng", +        "mockito-target-extended", +    ], +    libs: ["android.test.base"], +    test_suites: [ +        "general-tests", +    ], +    // mockito-target-inline dependency +    jni_libs: [ +        "libcarservicejni", +        "libdexmakerjvmtiagent", +    ],  } diff --git a/core/tests/BroadcastRadioTests/AndroidManifest.xml b/core/tests/BroadcastRadioTests/AndroidManifest.xml index ce12cc99946f..869b4844e529 100644 --- a/core/tests/BroadcastRadioTests/AndroidManifest.xml +++ b/core/tests/BroadcastRadioTests/AndroidManifest.xml @@ -19,7 +19,7 @@      <uses-permission android:name="android.permission.ACCESS_BROADCAST_RADIO" /> -    <application> +    <application android:debuggable="true">          <uses-library android:name="android.test.runner" />      </application> diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java index 11eb158317e9..3f35e998e025 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java @@ -33,11 +33,9 @@ import android.content.pm.PackageManager;  import android.hardware.radio.ProgramSelector;  import android.hardware.radio.RadioManager;  import android.hardware.radio.RadioTuner; -import android.test.suitebuilder.annotation.MediumTest;  import android.util.Log;  import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4;  import org.junit.After;  import org.junit.Before; @@ -47,6 +45,7 @@ import org.mockito.ArgumentCaptor;  import org.mockito.Mock;  import org.mockito.Mockito;  import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnitRunner;  import java.util.ArrayList;  import java.util.HashMap; @@ -56,8 +55,7 @@ import java.util.Map;  /**   * A test for broadcast radio API.   */ -@RunWith(AndroidJUnit4.class) -@MediumTest +@RunWith(MockitoJUnitRunner.class)  public class RadioTunerTest {      private static final String TAG = "BroadcastRadioTests.RadioTuner"; diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java new file mode 100644 index 000000000000..259a11852784 --- /dev/null +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.radio.tests.unittests; + +import static com.google.common.truth.Truth.assertWithMessage; + +import android.hardware.radio.ProgramSelector; +import android.hardware.radio.RadioManager; + +import org.junit.Test; + +public final class RadioManagerTest { + +    private static final int REGION = RadioManager.REGION_ITU_2; +    private static final int FM_LOWER_LIMIT = 87500; +    private static final int FM_UPPER_LIMIT = 108000; +    private static final int FM_SPACING = 200; +    private static final int AM_LOWER_LIMIT = 540; +    private static final int AM_UPPER_LIMIT = 1700; +    private static final int AM_SPACING = 10; +    private static final boolean STEREO_SUPPORTED = true; +    private static final boolean RDS_SUPPORTED = true; +    private static final boolean TA_SUPPORTED = false; +    private static final boolean AF_SUPPORTED = false; +    private static final boolean EA_SUPPORTED = false; + +    private static final int PROPERTIES_ID = 10; +    private static final String SERVICE_NAME = "ServiceNameMock"; +    private static final int CLASS_ID = RadioManager.CLASS_AM_FM; +    private static final String IMPLEMENTOR = "ImplementorMock"; +    private static final String PRODUCT = "ProductMock"; +    private static final String VERSION = "VersionMock"; +    private static final String SERIAL = "SerialMock"; +    private static final int NUM_TUNERS = 1; +    private static final int NUM_AUDIO_SOURCES = 1; +    private static final boolean IS_INITIALIZATION_REQUIRED = false; +    private static final boolean IS_CAPTURE_SUPPORTED = false; +    private static final boolean IS_BG_SCAN_SUPPORTED = true; +    private static final int[] SUPPORTED_PROGRAM_TYPES = new int[]{ +            ProgramSelector.PROGRAM_TYPE_AM, ProgramSelector.PROGRAM_TYPE_FM}; +    private static final int[] SUPPORTED_IDENTIFIERS_TYPES = new int[]{ +            ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, ProgramSelector.IDENTIFIER_TYPE_RDS_PI}; + +    private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR = +            createFmBandDescriptor(); +    private static final RadioManager.AmBandDescriptor AM_BAND_DESCRIPTOR = +            createAmBandDescriptor(); +    private static final RadioManager.FmBandConfig FM_BAND_CONFIG = createFmBandConfig(); +    private static final RadioManager.AmBandConfig AM_BAND_CONFIG = createAmBandConfig(); +    private static final RadioManager.ModuleProperties AMFM_PROPERTIES = createAmFmProperties(); + +    @Test +    public void getType_forBandDescriptor() { +        RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor(); + +        assertWithMessage("AM Band Descriptor type") +                .that(bandDescriptor.getType()).isEqualTo(RadioManager.BAND_AM); +    } + +    @Test +    public void getRegion_forBandDescriptor() { +        RadioManager.BandDescriptor bandDescriptor = createFmBandDescriptor(); + +        assertWithMessage("FM Band Descriptor region") +                .that(bandDescriptor.getRegion()).isEqualTo(REGION); +    } + +    @Test +    public void getLowerLimit_forBandDescriptor() { +        RadioManager.BandDescriptor bandDescriptor = createFmBandDescriptor(); + +        assertWithMessage("FM Band Descriptor lower limit") +                .that(bandDescriptor.getLowerLimit()).isEqualTo(FM_LOWER_LIMIT); +    } + +    @Test +    public void getUpperLimit_forBandDescriptor() { +        RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor(); + +        assertWithMessage("AM Band Descriptor upper limit") +                .that(bandDescriptor.getUpperLimit()).isEqualTo(AM_UPPER_LIMIT); +    } + +    @Test +    public void getSpacing_forBandDescriptor() { +        RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor(); + +        assertWithMessage("AM Band Descriptor spacing") +                .that(bandDescriptor.getSpacing()).isEqualTo(AM_SPACING); +    } + +    @Test +    public void isAmBand_forAmBandDescriptor_returnsTrue() { +        RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor(); + +        assertWithMessage("Is AM Band Descriptor an AM band") +                .that(bandDescriptor.isAmBand()).isTrue(); +    } + +    @Test +    public void isFmBand_forAmBandDescriptor_returnsFalse() { +        RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor(); + +        assertWithMessage("Is AM Band Descriptor an FM band") +                .that(bandDescriptor.isFmBand()).isFalse(); +    } + +    @Test +    public void isStereoSupported_forFmBandDescriptor() { +        assertWithMessage("FM Band Descriptor stereo") +                .that(FM_BAND_DESCRIPTOR.isStereoSupported()).isEqualTo(STEREO_SUPPORTED); +    } + +    @Test +    public void isRdsSupported_forFmBandDescriptor() { +        assertWithMessage("FM Band Descriptor RDS or RBDS") +                .that(FM_BAND_DESCRIPTOR.isRdsSupported()).isEqualTo(RDS_SUPPORTED); +    } + +    @Test +    public void isTaSupported_forFmBandDescriptor() { +        assertWithMessage("FM Band Descriptor traffic announcement") +                .that(FM_BAND_DESCRIPTOR.isTaSupported()).isEqualTo(TA_SUPPORTED); +    } + +    @Test +    public void isAfSupported_forFmBandDescriptor() { +        assertWithMessage("FM Band Descriptor alternate frequency") +                .that(FM_BAND_DESCRIPTOR.isAfSupported()).isEqualTo(AF_SUPPORTED); +    } + +    @Test +    public void isEaSupported_forFmBandDescriptor() { +        assertWithMessage("FM Band Descriptor emergency announcement") +                .that(FM_BAND_DESCRIPTOR.isEaSupported()).isEqualTo(EA_SUPPORTED); +    } + +    @Test +    public void isStereoSupported_forAmBandDescriptor() { +        assertWithMessage("AM Band Descriptor stereo") +                .that(AM_BAND_DESCRIPTOR.isStereoSupported()).isEqualTo(STEREO_SUPPORTED); +    } + +    @Test +    public void equals_withSameFmBandDescriptors_returnsTrue() { +        RadioManager.FmBandDescriptor fmBandDescriptor1 = createFmBandDescriptor(); +        RadioManager.FmBandDescriptor fmBandDescriptor2 = createFmBandDescriptor(); + +        assertWithMessage("The same FM Band Descriptor") +                .that(fmBandDescriptor1).isEqualTo(fmBandDescriptor2); +    } + +    @Test +    public void equals_withSameAmBandDescriptors_returnsTrue() { +        RadioManager.AmBandDescriptor amBandDescriptorCompared = createAmBandDescriptor(); + +        assertWithMessage("The same AM Band Descriptor") +                .that(AM_BAND_DESCRIPTOR).isEqualTo(amBandDescriptorCompared); +    } + +    @Test +    public void equals_withAmBandDescriptorsOfDifferentUpperLimits_returnsFalse() { +        RadioManager.AmBandDescriptor amBandDescriptorCompared = +                new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT, +                        AM_UPPER_LIMIT + AM_SPACING, AM_SPACING, STEREO_SUPPORTED); + +        assertWithMessage("AM Band Descriptor of different upper limit") +                .that(AM_BAND_DESCRIPTOR).isNotEqualTo(amBandDescriptorCompared); +    } + +    @Test +    public void equals_withAmAndFmBandDescriptors_returnsFalse() { +        assertWithMessage("AM Band Descriptor") +                .that(AM_BAND_DESCRIPTOR).isNotEqualTo(FM_BAND_DESCRIPTOR); +    } + +    @Test +    public void getType_forBandConfig() { +        RadioManager.BandConfig fmBandConfig = createFmBandConfig(); + +        assertWithMessage("FM Band Config type") +                .that(fmBandConfig.getType()).isEqualTo(RadioManager.BAND_FM); +    } + +    @Test +    public void getRegion_forBandConfig() { +        RadioManager.BandConfig amBandConfig = createAmBandConfig(); + +        assertWithMessage("AM Band Config region") +                .that(amBandConfig.getRegion()).isEqualTo(REGION); +    } + +    @Test +    public void getLowerLimit_forBandConfig() { +        RadioManager.BandConfig amBandConfig = createAmBandConfig(); + +        assertWithMessage("AM Band Config lower limit") +                .that(amBandConfig.getLowerLimit()).isEqualTo(AM_LOWER_LIMIT); +    } + +    @Test +    public void getUpperLimit_forBandConfig() { +        RadioManager.BandConfig fmBandConfig = createFmBandConfig(); + +        assertWithMessage("FM Band Config upper limit") +                .that(fmBandConfig.getUpperLimit()).isEqualTo(FM_UPPER_LIMIT); +    } + +    @Test +    public void getSpacing_forBandConfig() { +        RadioManager.BandConfig fmBandConfig = createFmBandConfig(); + +        assertWithMessage("FM Band Config spacing") +                .that(fmBandConfig.getSpacing()).isEqualTo(FM_SPACING); +    } + +    @Test +    public void getStereo_forFmBandConfig() { +        assertWithMessage("FM Band Config stereo ") +                .that(FM_BAND_CONFIG.getStereo()).isEqualTo(STEREO_SUPPORTED); +    } + +    @Test +    public void getRds_forFmBandConfig() { +        assertWithMessage("FM Band Config RDS or RBDS") +                .that(FM_BAND_CONFIG.getRds()).isEqualTo(RDS_SUPPORTED); +    } + +    @Test +    public void getTa_forFmBandConfig() { +        assertWithMessage("FM Band Config traffic announcement") +                .that(FM_BAND_CONFIG.getTa()).isEqualTo(TA_SUPPORTED); +    } + +    @Test +    public void getAf_forFmBandConfig() { +        assertWithMessage("FM Band Config alternate frequency") +                .that(FM_BAND_CONFIG.getAf()).isEqualTo(AF_SUPPORTED); +    } + +    @Test +    public void getEa_forFmBandConfig() { +        assertWithMessage("FM Band Config emergency Announcement") +                .that(FM_BAND_CONFIG.getEa()).isEqualTo(EA_SUPPORTED); +    } + +    @Test +    public void getStereo_forAmBandConfig() { +        assertWithMessage("AM Band Config stereo") +                .that(AM_BAND_CONFIG.getStereo()).isEqualTo(STEREO_SUPPORTED); +    } + +    @Test +    public void equals_withSameFmBandConfigs_returnsTrue() { +        RadioManager.FmBandConfig fmBandConfigCompared = createFmBandConfig(); + +        assertWithMessage("The same FM Band Config") +                .that(FM_BAND_CONFIG).isEqualTo(fmBandConfigCompared); +    } + +    @Test +    public void equals_withFmBandConfigsOfDifferentAfs_returnsFalse() { +        RadioManager.FmBandConfig.Builder builder = new RadioManager.FmBandConfig.Builder( +                createFmBandDescriptor()).setStereo(STEREO_SUPPORTED).setRds(RDS_SUPPORTED) +                .setTa(TA_SUPPORTED).setAf(!AF_SUPPORTED).setEa(EA_SUPPORTED); +        RadioManager.FmBandConfig fmBandConfigFromBuilder = builder.build(); + +        assertWithMessage("FM Band Config of different af value") +                .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigFromBuilder); +    } + +    @Test +    public void equals_withFmAndAmBandConfigs_returnsFalse() { +        assertWithMessage("FM Band Config") +                .that(FM_BAND_CONFIG).isNotEqualTo(AM_BAND_CONFIG); +    } + +    @Test +    public void equals_withSameAmBandConfigs_returnsTrue() { +        RadioManager.AmBandConfig amBandConfigCompared = createAmBandConfig(); + +        assertWithMessage("The same AM Band Config") +                .that(AM_BAND_CONFIG).isEqualTo(amBandConfigCompared); +    } + +    @Test +    public void equals_withAmBandConfigsOfDifferentTypes_returnsFalse() { +        RadioManager.AmBandConfig amBandConfigCompared = new RadioManager.AmBandConfig( +                new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM_HD, AM_LOWER_LIMIT, +                        AM_UPPER_LIMIT, AM_SPACING, STEREO_SUPPORTED)); + +        assertWithMessage("AM Band Config of different type") +                .that(AM_BAND_CONFIG).isNotEqualTo(amBandConfigCompared); +    } + +    @Test +    public void equals_withAmBandConfigsOfDifferentStereoValues_returnsFalse() { +        RadioManager.AmBandConfig.Builder builder = new RadioManager.AmBandConfig.Builder( +                createAmBandDescriptor()).setStereo(!STEREO_SUPPORTED); +        RadioManager.AmBandConfig amBandConfigFromBuilder = builder.build(); + +        assertWithMessage("AM Band Config of different stereo value") +                .that(AM_BAND_CONFIG).isNotEqualTo(amBandConfigFromBuilder); +    } + +    @Test +    public void getId_forModuleProperties() { +        assertWithMessage("Properties id") +                .that(AMFM_PROPERTIES.getId()).isEqualTo(PROPERTIES_ID); +    } + +    @Test +    public void getServiceName_forModuleProperties() { +        assertWithMessage("Properties service name") +                .that(AMFM_PROPERTIES.getServiceName()).isEqualTo(SERVICE_NAME); +    } + +    @Test +    public void getClassId_forModuleProperties() { +        assertWithMessage("Properties class ID") +                .that(AMFM_PROPERTIES.getClassId()).isEqualTo(CLASS_ID); +    } + +    @Test +    public void getImplementor_forModuleProperties() { +        assertWithMessage("Properties implementor") +                .that(AMFM_PROPERTIES.getImplementor()).isEqualTo(IMPLEMENTOR); +    } + +    @Test +    public void getProduct_forModuleProperties() { +        assertWithMessage("Properties product") +                .that(AMFM_PROPERTIES.getProduct()).isEqualTo(PRODUCT); +    } + +    @Test +    public void getVersion_forModuleProperties() { +        assertWithMessage("Properties version") +                .that(AMFM_PROPERTIES.getVersion()).isEqualTo(VERSION); +    } + +    @Test +    public void getSerial_forModuleProperties() { +        assertWithMessage("Serial properties") +                .that(AMFM_PROPERTIES.getSerial()).isEqualTo(SERIAL); +    } + +    @Test +    public void getNumTuners_forModuleProperties() { +        assertWithMessage("Number of tuners in properties") +                .that(AMFM_PROPERTIES.getNumTuners()).isEqualTo(NUM_TUNERS); +    } + +    @Test +    public void getNumAudioSources_forModuleProperties() { +        assertWithMessage("Number of audio sources in properties") +                .that(AMFM_PROPERTIES.getNumAudioSources()).isEqualTo(NUM_AUDIO_SOURCES); +    } + +    @Test +    public void isInitializationRequired_forModuleProperties() { +        assertWithMessage("Initialization required in properties") +                .that(AMFM_PROPERTIES.isInitializationRequired()) +                .isEqualTo(IS_INITIALIZATION_REQUIRED); +    } + +    @Test +    public void isCaptureSupported_forModuleProperties() { +        assertWithMessage("Capture support in properties") +                .that(AMFM_PROPERTIES.isCaptureSupported()).isEqualTo(IS_CAPTURE_SUPPORTED); +    } + +    @Test +    public void isBackgroundScanningSupported_forModuleProperties() { +        assertWithMessage("Background scan support in properties") +                .that(AMFM_PROPERTIES.isBackgroundScanningSupported()) +                .isEqualTo(IS_BG_SCAN_SUPPORTED); +    } + +    @Test +    public void isProgramTypeSupported_withSupportedType_forModuleProperties() { +        assertWithMessage("AM/FM frequency type radio support in properties") +                .that(AMFM_PROPERTIES.isProgramTypeSupported( +                        ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)) +                .isTrue(); +    } + +    @Test +    public void isProgramTypeSupported_withNonSupportedType_forModuleProperties() { +        assertWithMessage("DAB frequency type radio support in properties") +                .that(AMFM_PROPERTIES.isProgramTypeSupported( +                        ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY)).isFalse(); +    } + +    @Test +    public void isProgramIdentifierSupported_withSupportedIdentifier_forModuleProperties() { +        assertWithMessage("AM/FM frequency identifier radio support in properties") +                .that(AMFM_PROPERTIES.isProgramIdentifierSupported( +                        ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)).isTrue(); +    } + +    @Test +    public void isProgramIdentifierSupported_withNonSupportedIdentifier_forModuleProperties() { +        assertWithMessage("DAB frequency identifier radio support in properties") +                .that(AMFM_PROPERTIES.isProgramIdentifierSupported( +                        ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY)).isFalse(); +    } + +    @Test +    public void getDabFrequencyTable_forModuleProperties() { +        assertWithMessage("Properties DAB frequency table") +                .that(AMFM_PROPERTIES.getDabFrequencyTable()).isNull(); +    } + +    @Test +    public void getVendorInfo_forModuleProperties() { +        assertWithMessage("Properties vendor info") +                .that(AMFM_PROPERTIES.getVendorInfo()).isEmpty(); +    } + +    @Test +    public void getBands_forModuleProperties() { +        assertWithMessage("Properties bands") +                .that(AMFM_PROPERTIES.getBands()).asList() +                .containsExactly(AM_BAND_DESCRIPTOR, FM_BAND_DESCRIPTOR); +    } + +    @Test +    public void equals_withSameProperties_returnsTrue() { +        RadioManager.ModuleProperties propertiesCompared = createAmFmProperties(); + +        assertWithMessage("The same module properties") +                .that(AMFM_PROPERTIES).isEqualTo(propertiesCompared); +    } + +    @Test +    public void equals_withModulePropertiesOfDifferentIds_returnsFalse() { +        RadioManager.ModuleProperties propertiesDab = new RadioManager.ModuleProperties( +                PROPERTIES_ID + 1, SERVICE_NAME, CLASS_ID, IMPLEMENTOR, PRODUCT, VERSION, +                SERIAL, NUM_TUNERS, NUM_AUDIO_SOURCES, IS_INITIALIZATION_REQUIRED, +                IS_CAPTURE_SUPPORTED, /* bands= */ null, IS_BG_SCAN_SUPPORTED, +                SUPPORTED_PROGRAM_TYPES, SUPPORTED_IDENTIFIERS_TYPES, /* dabFrequencyTable= */ null, +                /* vendorInfo= */ null); + +        assertWithMessage("Module properties of different id") +                .that(AMFM_PROPERTIES).isNotEqualTo(propertiesDab); +    } + +    private static RadioManager.ModuleProperties createAmFmProperties() { +        return new RadioManager.ModuleProperties(PROPERTIES_ID, SERVICE_NAME, CLASS_ID, +                IMPLEMENTOR, PRODUCT, VERSION, SERIAL, NUM_TUNERS, NUM_AUDIO_SOURCES, +                IS_INITIALIZATION_REQUIRED, IS_CAPTURE_SUPPORTED, +                new RadioManager.BandDescriptor[]{AM_BAND_DESCRIPTOR, FM_BAND_DESCRIPTOR}, +                IS_BG_SCAN_SUPPORTED, SUPPORTED_PROGRAM_TYPES, SUPPORTED_IDENTIFIERS_TYPES, +                /* dabFrequencyTable= */ null, /* vendorInfo= */ null); +    } + +    private static RadioManager.FmBandDescriptor createFmBandDescriptor() { +        return new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, +                FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, +                AF_SUPPORTED, EA_SUPPORTED); +    } + +    private static RadioManager.AmBandDescriptor createAmBandDescriptor() { +        return new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT, +                AM_UPPER_LIMIT, AM_SPACING, STEREO_SUPPORTED); +    } + +    private static RadioManager.FmBandConfig createFmBandConfig() { +        return new RadioManager.FmBandConfig(createFmBandDescriptor()); +    } + +    private static RadioManager.AmBandConfig createAmBandConfig() { +        return new RadioManager.AmBandConfig(createAmBandDescriptor()); +    } +} diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java new file mode 100644 index 000000000000..7f4ea1170084 --- /dev/null +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.broadcastradio; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.radio.Announcement; +import android.hardware.radio.IAnnouncementListener; +import android.hardware.radio.ICloseHandle; +import android.hardware.radio.ITuner; +import android.hardware.radio.ITunerCallback; +import android.hardware.radio.RadioManager; + +import com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Arrays; + +/** + * Tests for {@link android.hardware.radio.IRadioService} with AIDL HAL implementation + */ +@RunWith(MockitoJUnitRunner.class) +public final class IRadioServiceAidlImplTest { + +    private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC}; + +    private IRadioServiceAidlImpl mAidlImpl; + +    @Mock +    private BroadcastRadioService mServiceMock; +    @Mock +    private BroadcastRadioServiceImpl mHalMock; +    @Mock +    private RadioManager.ModuleProperties mModuleMock; +    @Mock +    private RadioManager.BandConfig mBandConfigMock; +    @Mock +    private ITunerCallback mTunerCallbackMock; +    @Mock +    private IAnnouncementListener mListenerMock; +    @Mock +    private ICloseHandle mICloseHandle; +    @Mock +    private ITuner mTunerMock; + +    @Before +    public void setUp() throws Exception { +        doNothing().when(mServiceMock).enforcePolicyAccess(); + +        when(mHalMock.listModules()).thenReturn(Arrays.asList(mModuleMock)); +        when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any())) +                .thenReturn(mTunerMock); +        when(mHalMock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle); + +        mAidlImpl = new IRadioServiceAidlImpl(mServiceMock, mHalMock); +    } + +    @Test +    public void loadModules_forAidlImpl() { +        assertWithMessage("Modules loaded in AIDL HAL") +                .that(mAidlImpl.listModules()) +                .containsExactly(mModuleMock); +    } + +    @Test +    public void openTuner_forAidlImpl() throws Exception { +        ITuner tuner = mAidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock, +                /* withAudio= */ true, mTunerCallbackMock); + +        assertWithMessage("Tuner opened in AIDL HAL") +                .that(tuner).isEqualTo(mTunerMock); +    } + +    @Test +    public void addAnnouncementListener_forAidlImpl() { +        ICloseHandle closeHandle = mAidlImpl.addAnnouncementListener(ENABLE_TYPES, mListenerMock); + +        verify(mHalMock).addAnnouncementListener(ENABLE_TYPES, mListenerMock); +        assertWithMessage("Close handle of announcement listener for HAL 2") +                .that(closeHandle).isEqualTo(mICloseHandle); +    } + +} diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java new file mode 100644 index 000000000000..f28e27d7f896 --- /dev/null +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.broadcastradio; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.radio.Announcement; +import android.hardware.radio.IAnnouncementListener; +import android.hardware.radio.ICloseHandle; +import android.hardware.radio.ITuner; +import android.hardware.radio.ITunerCallback; +import android.hardware.radio.RadioManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Arrays; + +/** + * Tests for {@link android.hardware.radio.IRadioService} with HIDL HAL implementation + */ +@RunWith(MockitoJUnitRunner.class) +public final class IRadioServiceHidlImplTest { + +    private static final int HAL1_MODULE_ID = 0; +    private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC}; + +    private IRadioServiceHidlImpl mHidlImpl; + +    @Mock +    private BroadcastRadioService mServiceMock; +    @Mock +    private com.android.server.broadcastradio.hal1.BroadcastRadioService mHal1Mock; +    @Mock +    private com.android.server.broadcastradio.hal2.BroadcastRadioService mHal2Mock; +    @Mock +    private RadioManager.ModuleProperties mHal1ModuleMock; +    @Mock +    private RadioManager.ModuleProperties mHal2ModuleMock; +    @Mock +    private RadioManager.BandConfig mBandConfigMock; +    @Mock +    private ITunerCallback mTunerCallbackMock; +    @Mock +    private IAnnouncementListener mListenerMock; +    @Mock +    private ICloseHandle mICloseHandle; +    @Mock +    private ITuner mHal1TunerMock; +    @Mock +    private ITuner mHal2TunerMock; + +    @Before +    public void setup() throws Exception { +        doNothing().when(mServiceMock).enforcePolicyAccess(); +        when(mHal1Mock.loadModules()).thenReturn(Arrays.asList(mHal1ModuleMock)); +        when(mHal1Mock.openTuner(anyInt(), any(), anyBoolean(), any())).thenReturn(mHal1TunerMock); + +        when(mHal2Mock.listModules()).thenReturn(Arrays.asList(mHal2ModuleMock)); +        doAnswer(invocation -> { +            int moduleId = (int) invocation.getArguments()[0]; +            return moduleId != HAL1_MODULE_ID; +        }).when(mHal2Mock).hasModule(anyInt()); +        when(mHal2Mock.openSession(anyInt(), any(), anyBoolean(), any())) +                .thenReturn(mHal2TunerMock); +        when(mHal2Mock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle); + +        mHidlImpl = new IRadioServiceHidlImpl(mServiceMock, mHal1Mock, mHal2Mock); +    } + +    @Test +    public void loadModules_forHidlImpl() { +        assertWithMessage("Modules loaded in HIDL HAL") +                .that(mHidlImpl.listModules()) +                .containsExactly(mHal1ModuleMock, mHal2ModuleMock); +    } + +    @Test +    public void openTuner_withHal1ModuleId_forHidlImpl() throws Exception { +        ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID, mBandConfigMock, +                /* withAudio= */ true, mTunerCallbackMock); + +        assertWithMessage("Tuner opened in HAL 1") +                .that(tuner).isEqualTo(mHal1TunerMock); +    } + +    @Test +    public void openTuner_withHal2ModuleId_forHidlImpl() throws Exception { +        ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID + 1, mBandConfigMock, +                /* withAudio= */ true, mTunerCallbackMock); + +        assertWithMessage("Tuner opened in HAL 2") +                .that(tuner).isEqualTo(mHal2TunerMock); +    } + +    @Test +    public void addAnnouncementListener_forHidlImpl() { +        when(mHal2Mock.hasAnyModules()).thenReturn(true); +        ICloseHandle closeHandle = mHidlImpl.addAnnouncementListener(ENABLE_TYPES, mListenerMock); + +        verify(mHal2Mock).addAnnouncementListener(ENABLE_TYPES, mListenerMock); +        assertWithMessage("Close handle of announcement listener for HAL 2") +                .that(closeHandle).isEqualTo(mICloseHandle); +    } + +} diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 0b8b29b9dda9..bcb13d2108b8 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -48,6 +48,7 @@ import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsWi  import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertNotNull;  import static junit.framework.Assert.fail;  import static org.junit.Assert.assertEquals; @@ -56,7 +57,9 @@ import static org.junit.Assert.assertNotSame;  import static org.junit.Assert.assertNull;  import static org.junit.Assert.assertSame;  import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when;  import android.annotation.Nullable;  import android.app.Notification.CallStyle; @@ -68,6 +71,7 @@ import android.content.res.Configuration;  import android.graphics.Bitmap;  import android.graphics.BitmapFactory;  import android.graphics.Color; +import android.graphics.Typeface;  import android.graphics.drawable.Icon;  import android.net.Uri;  import android.os.Build; @@ -79,7 +83,9 @@ import android.text.SpannableString;  import android.text.SpannableStringBuilder;  import android.text.Spanned;  import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan;  import android.text.style.TextAppearanceSpan; +import android.util.Pair;  import android.widget.RemoteViews;  import androidx.test.InstrumentationRegistry; @@ -89,6 +95,8 @@ import androidx.test.runner.AndroidJUnit4;  import com.android.internal.R;  import com.android.internal.util.ContrastColorUtil; +import junit.framework.Assert; +  import org.junit.Before;  import org.junit.Test;  import org.junit.runner.RunWith; @@ -218,8 +226,10 @@ public class NotificationTest {      @Test      public void allPendingIntents_recollectedAfterReusingBuilder() { -        PendingIntent intent1 = PendingIntent.getActivity(mContext, 0, new Intent("test1"), PendingIntent.FLAG_MUTABLE_UNAUDITED); -        PendingIntent intent2 = PendingIntent.getActivity(mContext, 0, new Intent("test2"), PendingIntent.FLAG_MUTABLE_UNAUDITED); +        PendingIntent intent1 = PendingIntent.getActivity( +                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE); +        PendingIntent intent2 = PendingIntent.getActivity( +                mContext, 0, new Intent("test2"), PendingIntent.FLAG_IMMUTABLE);          Notification.Builder builder = new Notification.Builder(mContext, "channel");          builder.setContentIntent(intent1); @@ -669,30 +679,23 @@ public class NotificationTest {          Notification notification = new Notification.Builder(mContext, "Channel").setStyle(                  style).build(); +        int targetSize = mContext.getResources().getDimensionPixelSize( +                ActivityManager.isLowRamDeviceStatic() +                        ? R.dimen.notification_person_icon_max_size_low_ram +                        : R.dimen.notification_person_icon_max_size); +          Bitmap personIcon = style.getUser().getIcon().getBitmap(); -        assertThat(personIcon.getWidth()).isEqualTo( -                mContext.getResources().getDimensionPixelSize( -                        R.dimen.notification_person_icon_max_size)); -        assertThat(personIcon.getHeight()).isEqualTo( -                mContext.getResources().getDimensionPixelSize( -                        R.dimen.notification_person_icon_max_size)); +        assertThat(personIcon.getWidth()).isEqualTo(targetSize); +        assertThat(personIcon.getHeight()).isEqualTo(targetSize);          Bitmap avatarIcon = style.getMessages().get(0).getSenderPerson().getIcon().getBitmap(); -        assertThat(avatarIcon.getWidth()).isEqualTo( -                mContext.getResources().getDimensionPixelSize( -                        R.dimen.notification_person_icon_max_size)); -        assertThat(avatarIcon.getHeight()).isEqualTo( -                mContext.getResources().getDimensionPixelSize( -                        R.dimen.notification_person_icon_max_size)); +        assertThat(avatarIcon.getWidth()).isEqualTo(targetSize); +        assertThat(avatarIcon.getHeight()).isEqualTo(targetSize);          Bitmap historicAvatarIcon = style.getHistoricMessages().get(                  0).getSenderPerson().getIcon().getBitmap(); -        assertThat(historicAvatarIcon.getWidth()).isEqualTo( -                mContext.getResources().getDimensionPixelSize( -                        R.dimen.notification_person_icon_max_size)); -        assertThat(historicAvatarIcon.getHeight()).isEqualTo( -                mContext.getResources().getDimensionPixelSize( -                        R.dimen.notification_person_icon_max_size)); +        assertThat(historicAvatarIcon.getWidth()).isEqualTo(targetSize); +        assertThat(historicAvatarIcon.getHeight()).isEqualTo(targetSize);      }      @Test @@ -780,7 +783,6 @@ public class NotificationTest {          assertFalse(notification.isMediaNotification());      } -    @Test      public void validateColorizedPaletteForColor(int rawColor) {          Notification.Colors cDay = new Notification.Colors();          Notification.Colors cNight = new Notification.Colors(); @@ -861,19 +863,22 @@ public class NotificationTest {          Bundle fakeTypes = new Bundle();          fakeTypes.putParcelable(EXTRA_LARGE_ICON_BIG, new Bundle()); -        style.restoreFromExtras(fakeTypes);          // no crash, good      }      @Test      public void testRestoreFromExtras_Messaging_invalidExtra_noCrash() { -        Notification.Style style = new Notification.MessagingStyle(); +        Notification.Style style = new Notification.MessagingStyle("test");          Bundle fakeTypes = new Bundle();          fakeTypes.putParcelable(EXTRA_MESSAGING_PERSON, new Bundle());          fakeTypes.putParcelable(EXTRA_CONVERSATION_ICON, new Bundle()); -        style.restoreFromExtras(fakeTypes); +        Notification n = new Notification.Builder(mContext, "test") +                .setStyle(style) +                .setExtras(fakeTypes) +                .build(); +        Notification.Builder.recoverBuilder(mContext, n);          // no crash, good      } @@ -885,22 +890,33 @@ public class NotificationTest {          fakeTypes.putParcelable(EXTRA_MEDIA_SESSION, new Bundle());          fakeTypes.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, new Bundle()); -        style.restoreFromExtras(fakeTypes); +        Notification n = new Notification.Builder(mContext, "test") +                .setStyle(style) +                .setExtras(fakeTypes) +                .build(); +        Notification.Builder.recoverBuilder(mContext, n);          // no crash, good      }      @Test      public void testRestoreFromExtras_Call_invalidExtra_noCrash() { -        Notification.Style style = new CallStyle(); +        PendingIntent intent1 = PendingIntent.getActivity( +                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE); +        Notification.Style style = Notification.CallStyle.forIncomingCall( +                new Person.Builder().setName("hi").build(), intent1, intent1); +          Bundle fakeTypes = new Bundle();          fakeTypes.putParcelable(EXTRA_CALL_PERSON, new Bundle());          fakeTypes.putParcelable(EXTRA_ANSWER_INTENT, new Bundle());          fakeTypes.putParcelable(EXTRA_DECLINE_INTENT, new Bundle());          fakeTypes.putParcelable(EXTRA_HANG_UP_INTENT, new Bundle()); -        style.restoreFromExtras(fakeTypes); - +        Notification n = new Notification.Builder(mContext, "test") +                .setStyle(style) +                .setExtras(fakeTypes) +                .build(); +        Notification.Builder.recoverBuilder(mContext, n);          // no crash, good      } @@ -962,7 +978,11 @@ public class NotificationTest {          fakeTypes.putParcelable(KEY_ON_READ, new Bundle());          fakeTypes.putParcelable(KEY_ON_REPLY, new Bundle());          fakeTypes.putParcelable(KEY_REMOTE_INPUT, new Bundle()); -        Notification.CarExtender.UnreadConversation.getUnreadConversationFromBundle(fakeTypes); + +        Notification n = new Notification.Builder(mContext, "test") +                .setExtras(fakeTypes) +                .build(); +        Notification.CarExtender extender = new Notification.CarExtender(n);          // no crash, good      } @@ -980,6 +1000,493 @@ public class NotificationTest {          // no crash, good      } + +    @Test +    public void testDoesNotStripsExtenders() { +        Notification.Builder nb = new Notification.Builder(mContext, "channel"); +        nb.extend(new Notification.CarExtender().setColor(Color.RED)); +        nb.extend(new Notification.TvExtender().setChannelId("different channel")); +        nb.extend(new Notification.WearableExtender().setDismissalId("dismiss")); +        Notification before = nb.build(); +        Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before); + +        assertTrue(before == after); + +        Assert.assertEquals("different channel", +                new Notification.TvExtender(before).getChannelId()); +        Assert.assertEquals(Color.RED, new Notification.CarExtender(before).getColor()); +        Assert.assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId()); +    } + +    @Test +    public void testStyleChangeVisiblyDifferent_noStyles() { +        Notification.Builder n1 = new Notification.Builder(mContext, "test"); +        Notification.Builder n2 = new Notification.Builder(mContext, "test"); + +        assertFalse(Notification.areStyledNotificationsVisiblyDifferent(n1, n2)); +    } + +    @Test +    public void testStyleChangeVisiblyDifferent_noStyleToStyle() { +        Notification.Builder n1 = new Notification.Builder(mContext, "test"); +        Notification.Builder n2 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.BigTextStyle()); + +        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2)); +    } + +    @Test +    public void testStyleChangeVisiblyDifferent_styleToNoStyle() { +        Notification.Builder n2 = new Notification.Builder(mContext, "test"); +        Notification.Builder n1 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.BigTextStyle()); + +        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2)); +    } + +    @Test +    public void testStyleChangeVisiblyDifferent_changeStyle() { +        Notification.Builder n1 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.InboxStyle()); +        Notification.Builder n2 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.BigTextStyle()); + +        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2)); +    } + +    @Test +    public void testInboxTextChange() { +        Notification.Builder nInbox1 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.InboxStyle().addLine("a").addLine("b")); +        Notification.Builder nInbox2 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.InboxStyle().addLine("b").addLine("c")); + +        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nInbox1, nInbox2)); +    } + +    @Test +    public void testBigTextTextChange() { +        Notification.Builder nBigText1 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.BigTextStyle().bigText("something")); +        Notification.Builder nBigText2 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.BigTextStyle().bigText("else")); + +        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigText1, nBigText2)); +    } + +    @Test +    public void testBigPictureChange() { +        Bitmap bitA = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888); +        Bitmap bitB = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888); + +        Notification.Builder nBigPic1 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.BigPictureStyle().bigPicture(bitA)); +        Notification.Builder nBigPic2 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.BigPictureStyle().bigPicture(bitB)); + +        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2)); +    } + +    @Test +    public void testMessagingChange_text() { +        Notification.Builder nM1 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.MessagingStyle("") +                        .addMessage(new Notification.MessagingStyle.Message( +                                "a", 100, new Person.Builder().setName("hi").build()))); +        Notification.Builder nM2 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.MessagingStyle("") +                        .addMessage(new Notification.MessagingStyle.Message( +                                "a", 100, new Person.Builder().setName("hi").build())) +                        .addMessage(new Notification.MessagingStyle.Message( +                                "b", 100, new Person.Builder().setName("hi").build())) +                ); + +        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); +    } + +    @Test +    public void testMessagingChange_data() { +        Notification.Builder nM1 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.MessagingStyle("") +                        .addMessage(new Notification.MessagingStyle.Message( +                                "a", 100, new Person.Builder().setName("hi").build()) +                                .setData("text", mock(Uri.class)))); +        Notification.Builder nM2 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.MessagingStyle("") +                        .addMessage(new Notification.MessagingStyle.Message( +                                "a", 100, new Person.Builder().setName("hi").build()))); + +        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); +    } + +    @Test +    public void testMessagingChange_sender() { +        Person a = new Person.Builder().setName("A").build(); +        Person b = new Person.Builder().setName("b").build(); +        Notification.Builder nM1 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.MessagingStyle("") +                        .addMessage(new Notification.MessagingStyle.Message("a", 100, b))); +        Notification.Builder nM2 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.MessagingStyle("") +                        .addMessage(new Notification.MessagingStyle.Message("a", 100, a))); + +        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); +    } + +    @Test +    public void testMessagingChange_key() { +        Person a = new Person.Builder().setName("hi").setKey("A").build(); +        Person b = new Person.Builder().setName("hi").setKey("b").build(); +        Notification.Builder nM1 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.MessagingStyle("") +                        .addMessage(new Notification.MessagingStyle.Message("a", 100, a))); +        Notification.Builder nM2 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.MessagingStyle("") +                        .addMessage(new Notification.MessagingStyle.Message("a", 100, b))); + +        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); +    } + +    @Test +    public void testMessagingChange_ignoreTimeChange() { +        Notification.Builder nM1 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.MessagingStyle("") +                        .addMessage(new Notification.MessagingStyle.Message( +                                "a", 100, new Person.Builder().setName("hi").build()))); +        Notification.Builder nM2 = new Notification.Builder(mContext, "test") +                .setStyle(new Notification.MessagingStyle("") +                        .addMessage(new Notification.MessagingStyle.Message( +                                "a", 1000, new Person.Builder().setName("hi").build())) +                ); + +        assertFalse(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); +    } + +    @Test +    public void testRemoteViews_nullChange() { +        Notification.Builder n1 = new Notification.Builder(mContext, "test") +                .setContent(mock(RemoteViews.class)); +        Notification.Builder n2 = new Notification.Builder(mContext, "test"); +        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); + +        n1 = new Notification.Builder(mContext, "test"); +        n2 = new Notification.Builder(mContext, "test") +                .setContent(mock(RemoteViews.class)); +        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); + +        n1 = new Notification.Builder(mContext, "test") +                .setCustomBigContentView(mock(RemoteViews.class)); +        n2 = new Notification.Builder(mContext, "test"); +        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); + +        n1 = new Notification.Builder(mContext, "test"); +        n2 = new Notification.Builder(mContext, "test") +                .setCustomBigContentView(mock(RemoteViews.class)); +        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); + +        n1 = new Notification.Builder(mContext, "test"); +        n2 = new Notification.Builder(mContext, "test"); +        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); +    } + +    @Test +    public void testRemoteViews_layoutChange() { +        RemoteViews a = mock(RemoteViews.class); +        when(a.getLayoutId()).thenReturn(234); +        RemoteViews b = mock(RemoteViews.class); +        when(b.getLayoutId()).thenReturn(189); + +        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a); +        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b); +        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); + +        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a); +        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b); +        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); + +        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a); +        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b); +        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); +    } + +    @Test +    public void testRemoteViews_layoutSame() { +        RemoteViews a = mock(RemoteViews.class); +        when(a.getLayoutId()).thenReturn(234); +        RemoteViews b = mock(RemoteViews.class); +        when(b.getLayoutId()).thenReturn(234); + +        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a); +        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b); +        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); + +        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a); +        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b); +        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); + +        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a); +        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b); +        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); +    } + +    @Test +    public void testRemoteViews_sequenceChange() { +        RemoteViews a = mock(RemoteViews.class); +        when(a.getLayoutId()).thenReturn(234); +        when(a.getSequenceNumber()).thenReturn(1); +        RemoteViews b = mock(RemoteViews.class); +        when(b.getLayoutId()).thenReturn(234); +        when(b.getSequenceNumber()).thenReturn(2); + +        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a); +        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b); +        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); + +        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a); +        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b); +        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); + +        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a); +        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b); +        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); +    } + +    @Test +    public void testRemoteViews_sequenceSame() { +        RemoteViews a = mock(RemoteViews.class); +        when(a.getLayoutId()).thenReturn(234); +        when(a.getSequenceNumber()).thenReturn(1); +        RemoteViews b = mock(RemoteViews.class); +        when(b.getLayoutId()).thenReturn(234); +        when(b.getSequenceNumber()).thenReturn(1); + +        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a); +        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b); +        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); + +        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a); +        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b); +        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); + +        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a); +        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b); +        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); +    } + +    @Test +    public void testActionsDifferent_null() { +        Notification n1 = new Notification.Builder(mContext, "test") +                .build(); +        Notification n2 = new Notification.Builder(mContext, "test") +                .build(); + +        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2)); +    } + +    @Test +    public void testActionsDifferentSame() { +        PendingIntent intent = PendingIntent.getActivity( +                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);; +        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)); + +        Notification n1 = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) +                .build(); +        Notification n2 = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) +                .build(); + +        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2)); +    } + +    @Test +    public void testActionsDifferentText() { +        PendingIntent intent = PendingIntent.getActivity( +                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);; +        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)); + +        Notification n1 = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) +                .build(); +        Notification n2 = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build()) +                .build(); + +        assertTrue(Notification.areActionsVisiblyDifferent(n1, n2)); +    } + +    @Test +    public void testActionsDifferentSpannables() { +        PendingIntent intent = PendingIntent.getActivity( +                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);; +        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)); + +        Notification n1 = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, +                        new SpannableStringBuilder().append("test1", +                                new StyleSpan(Typeface.BOLD), +                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE), +                        intent).build()) +                .build(); +        Notification n2 = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, "test1", intent).build()) +                .build(); + +        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2)); +    } + +    @Test +    public void testActionsDifferentNumber() { +        PendingIntent intent = PendingIntent.getActivity( +                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE); +        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)); + +        Notification n1 = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) +                .build(); +        Notification n2 = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) +                .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build()) +                .build(); + +        assertTrue(Notification.areActionsVisiblyDifferent(n1, n2)); +    } + +    @Test +    public void testActionsDifferentIntent() { +        PendingIntent intent1 = PendingIntent.getActivity( +                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE); +        PendingIntent intent2 = PendingIntent.getActivity( +                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE); +        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)); + +        Notification n1 = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent1).build()) +                .build(); +        Notification n2 = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent2).build()) +                .build(); + +        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2)); +    } + +    @Test +    public void testActionsIgnoresRemoteInputs() { +        PendingIntent intent = PendingIntent.getActivity( +                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);; +        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)); + +        Notification n1 = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent) +                        .addRemoteInput(new RemoteInput.Builder("a") +                                .setChoices(new CharSequence[] {"i", "m"}) +                                .build()) +                        .build()) +                .build(); +        Notification n2 = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent) +                        .addRemoteInput(new RemoteInput.Builder("a") +                                .setChoices(new CharSequence[] {"t", "m"}) +                                .build()) +                        .build()) +                .build(); + +        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2)); +    } + +    @Test +    public void testFreeformRemoteInputActionPair_noRemoteInput() { +        PendingIntent intent = PendingIntent.getActivity( +                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);; +        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)); +        Notification notification = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent) +                        .build()) +                .build(); +        Assert.assertNull(notification.findRemoteInputActionPair(false)); +    } + +    @Test +    public void testFreeformRemoteInputActionPair_hasRemoteInput() { +        PendingIntent intent = PendingIntent.getActivity( +                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);; +        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)); + +        RemoteInput remoteInput = new RemoteInput.Builder("a").build(); + +        Notification.Action actionWithRemoteInput = +                new Notification.Action.Builder(icon, "TEXT 1", intent) +                        .addRemoteInput(remoteInput) +                        .addRemoteInput(remoteInput) +                        .build(); + +        Notification.Action actionWithoutRemoteInput = +                new Notification.Action.Builder(icon, "TEXT 2", intent) +                        .build(); + +        Notification notification = new Notification.Builder(mContext, "test") +                .addAction(actionWithoutRemoteInput) +                .addAction(actionWithRemoteInput) +                .build(); + +        Pair<RemoteInput, Notification.Action> remoteInputActionPair = +                notification.findRemoteInputActionPair(false); + +        assertNotNull(remoteInputActionPair); +        Assert.assertEquals(remoteInput, remoteInputActionPair.first); +        Assert.assertEquals(actionWithRemoteInput, remoteInputActionPair.second); +    } + +    @Test +    public void testFreeformRemoteInputActionPair_requestFreeform_noFreeformRemoteInput() { +        PendingIntent intent = PendingIntent.getActivity( +                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);; +        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)); +        Notification notification = new Notification.Builder(mContext, "test") +                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent) +                        .addRemoteInput( +                                new RemoteInput.Builder("a") +                                        .setAllowFreeFormInput(false).build()) +                        .build()) +                .build(); +        Assert.assertNull(notification.findRemoteInputActionPair(true)); +    } + +    @Test +    public void testFreeformRemoteInputActionPair_requestFreeform_hasFreeformRemoteInput() { +        PendingIntent intent = PendingIntent.getActivity( +                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);; +        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)); + +        RemoteInput remoteInput = +                new RemoteInput.Builder("a").setAllowFreeFormInput(false).build(); +        RemoteInput freeformRemoteInput = +                new RemoteInput.Builder("b").setAllowFreeFormInput(true).build(); + +        Notification.Action actionWithFreeformRemoteInput = +                new Notification.Action.Builder(icon, "TEXT 1", intent) +                        .addRemoteInput(remoteInput) +                        .addRemoteInput(freeformRemoteInput) +                        .build(); + +        Notification.Action actionWithoutFreeformRemoteInput = +                new Notification.Action.Builder(icon, "TEXT 2", intent) +                        .addRemoteInput(remoteInput) +                        .build(); + +        Notification notification = new Notification.Builder(mContext, "test") +                .addAction(actionWithoutFreeformRemoteInput) +                .addAction(actionWithFreeformRemoteInput) +                .build(); + +        Pair<RemoteInput, Notification.Action> remoteInputActionPair = +                notification.findRemoteInputActionPair(true); + +        assertNotNull(remoteInputActionPair); +        Assert.assertEquals(freeformRemoteInput, remoteInputActionPair.first); +        Assert.assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second); +    } +      private void assertValid(Notification.Colors c) {          // Assert that all colors are populated          assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID); diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java index 613eddd30c8a..88b2de7137fd 100644 --- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java +++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java @@ -207,7 +207,7 @@ public class ActivityThreadClientTest {          assertFalse("Must not report change if no public diff",                  shouldReportChange(currentConfig, newConfig, null /* sizeBuckets */, -                        0 /* handledConfigChanges */)); +                        0 /* handledConfigChanges */, false /* alwaysReportChange */));          final int[] verticalThresholds = {100, 400};          final SizeConfigurationBuckets buckets = new SizeConfigurationBuckets( @@ -221,24 +221,33 @@ public class ActivityThreadClientTest {          assertFalse("Must not report changes if the diff is small and not handled",                  shouldReportChange(currentConfig, newConfig, buckets, -                        CONFIG_FONT_SCALE /* handledConfigChanges */)); +                        CONFIG_FONT_SCALE /* handledConfigChanges */, +                        false /* alwaysReportChange */));          assertTrue("Must report changes if the small diff is handled",                  shouldReportChange(currentConfig, newConfig, buckets, -                        CONFIG_SCREEN_SIZE /* handledConfigChanges */)); +                        CONFIG_SCREEN_SIZE /* handledConfigChanges */, +                        false /* alwaysReportChange */)); + +        assertTrue("Must report changes if it should, even it is small and not handled", +                shouldReportChange(currentConfig, newConfig, buckets, +                        CONFIG_FONT_SCALE /* handledConfigChanges */, +                        true /* alwaysReportChange */));          currentConfig.fontScale = 0.8f;          newConfig.fontScale = 1.2f;          assertTrue("Must report handled changes regardless of small unhandled change",                  shouldReportChange(currentConfig, newConfig, buckets, -                        CONFIG_FONT_SCALE /* handledConfigChanges */)); +                        CONFIG_FONT_SCALE /* handledConfigChanges */, +                        false /* alwaysReportChange */));          newConfig.screenHeightDp = 500;          assertFalse("Must not report changes if there's unhandled big changes",                  shouldReportChange(currentConfig, newConfig, buckets, -                        CONFIG_FONT_SCALE /* handledConfigChanges */)); +                        CONFIG_FONT_SCALE /* handledConfigChanges */, +                        false /* alwaysReportChange */));      }      private void recreateAndVerifyNoRelaunch(ActivityThread activityThread, TestActivity activity) { diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 0bc70857a113..3ee20ea95ee5 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -321,4 +321,21 @@      <!-- The smaller size of the dismiss target (shrinks when something is in the target). -->      <dimen name="floating_dismiss_circle_small">120dp</dimen> + +    <!-- The thickness of shadows of a window that has focus in DIP. --> +    <dimen name="freeform_decor_shadow_focused_thickness">20dp</dimen> + +    <!-- The thickness of shadows of a window that doesn't have focus in DIP. --> +    <dimen name="freeform_decor_shadow_unfocused_thickness">5dp</dimen> + +    <!-- Height of button (32dp)  + 2 * margin (5dp each). --> +    <dimen name="freeform_decor_caption_height">42dp</dimen> + +    <!-- Width of buttons (64dp) + handle (128dp) + padding (24dp total). --> +    <dimen name="freeform_decor_caption_width">216dp</dimen> + +    <dimen name="freeform_resize_handle">30dp</dimen> + +    <dimen name="freeform_resize_corner">44dp</dimen> +  </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 87700ee4fb50..7d1f130daaef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -21,7 +21,6 @@ import android.app.WindowConfiguration;  import android.content.Context;  import android.content.res.ColorStateList;  import android.graphics.Color; -import android.graphics.Rect;  import android.graphics.drawable.VectorDrawable;  import android.os.Handler;  import android.view.Choreographer; @@ -43,22 +42,6 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus;   * The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.   */  public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> { -    // The thickness of shadows of a window that has focus in DIP. -    private static final int DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP = 20; -    // The thickness of shadows of a window that doesn't have focus in DIP. -    private static final int DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP = 5; - -    // Height of button (32dp)  + 2 * margin (5dp each) -    private static final int DECOR_CAPTION_HEIGHT_IN_DIP = 42; -    // Width of buttons (64dp) + handle (128dp) + padding (24dp total) -    private static final int DECOR_CAPTION_WIDTH_IN_DIP = 216; -    private static final int RESIZE_HANDLE_IN_DIP = 30; -    private static final int RESIZE_CORNER_IN_DIP = 44; - -    private static final Rect EMPTY_OUTSET = new Rect(); -    private static final Rect RESIZE_HANDLE_OUTSET = new Rect( -            RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP); -      private final Handler mHandler;      private final Choreographer mChoreographer;      private final SyncTransactionQueue mSyncQueue; @@ -69,6 +52,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL      private DragResizeInputListener mDragResizeListener; +    private RelayoutParams mRelayoutParams = new RelayoutParams();      private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =              new WindowDecoration.RelayoutResult<>(); @@ -114,19 +98,31 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL      void relayout(ActivityManager.RunningTaskInfo taskInfo,              SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { -        final int shadowRadiusDp = taskInfo.isFocused -                ? DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP : DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP; +        final int shadowRadiusID = taskInfo.isFocused +                ? R.dimen.freeform_decor_shadow_focused_thickness +                : R.dimen.freeform_decor_shadow_unfocused_thickness;          final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode()                  == WindowConfiguration.WINDOWING_MODE_FREEFORM;          final boolean isDragResizeable = isFreeform && mTaskInfo.isResizeable; -        final Rect outset = isDragResizeable ? RESIZE_HANDLE_OUTSET : EMPTY_OUTSET;          WindowDecorLinearLayout oldRootView = mResult.mRootView;          final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;          final WindowContainerTransaction wct = new WindowContainerTransaction(); -        relayout(taskInfo, R.layout.caption_window_decoration, oldRootView, -                DECOR_CAPTION_HEIGHT_IN_DIP, DECOR_CAPTION_WIDTH_IN_DIP, outset, shadowRadiusDp, -                startT, finishT, wct, mResult); + +        int outsetLeftId = R.dimen.freeform_resize_handle; +        int outsetTopId = R.dimen.freeform_resize_handle; +        int outsetRightId = R.dimen.freeform_resize_handle; +        int outsetBottomId = R.dimen.freeform_resize_handle; + +        mRelayoutParams.mRunningTaskInfo = taskInfo; +        mRelayoutParams.mLayoutResId = R.layout.caption_window_decoration; +        mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; +        mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width; +        mRelayoutParams.mShadowRadiusId = shadowRadiusID; +        if (isDragResizeable) { +            mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId); +        } +        relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);          mTaskOrganizer.applyTransaction(wct); @@ -167,10 +163,12 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL          }          int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop(); - +        int resize_handle = mResult.mRootView.getResources() +                .getDimensionPixelSize(R.dimen.freeform_resize_handle); +        int resize_corner = mResult.mRootView.getResources() +                .getDimensionPixelSize(R.dimen.freeform_resize_corner);          mDragResizeListener.setGeometry( -                mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP), -                (int) (mResult.mDensity * RESIZE_CORNER_IN_DIP), touchSlop); +                mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);      }      /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index bf863ea2c7ab..01cab9aae312 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -19,11 +19,11 @@ package com.android.wm.shell.windowdecor;  import android.app.ActivityManager.RunningTaskInfo;  import android.content.Context;  import android.content.res.Configuration; +import android.content.res.Resources;  import android.graphics.Color;  import android.graphics.PixelFormat;  import android.graphics.Point;  import android.graphics.Rect; -import android.util.DisplayMetrics;  import android.view.Display;  import android.view.InsetsState;  import android.view.LayoutInflater; @@ -142,15 +142,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>       */      abstract void relayout(RunningTaskInfo taskInfo); -    void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp, -            float captionWidthDp, Rect outsetsDp, float shadowRadiusDp, -            SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, -            WindowContainerTransaction wct, RelayoutResult<T> outResult) { +    void relayout(RelayoutParams params, SurfaceControl.Transaction startT, +            SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView, +            RelayoutResult<T> outResult) {          outResult.reset();          final Configuration oldTaskConfig = mTaskInfo.getConfiguration(); -        if (taskInfo != null) { -            mTaskInfo = taskInfo; +        if (params.mRunningTaskInfo != null) { +            mTaskInfo = params.mRunningTaskInfo;          }          if (!mTaskInfo.isVisible) { @@ -159,7 +158,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>              return;          } -        if (rootView == null && layoutResId == 0) { +        if (rootView == null && params.mLayoutResId == 0) {              throw new IllegalArgumentException("layoutResId and rootView can't both be invalid.");          } @@ -176,15 +175,15 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>                  return;              }              mDecorWindowContext = mContext.createConfigurationContext(taskConfig); -            if (layoutResId != 0) { -                outResult.mRootView = -                        (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null); +            if (params.mLayoutResId != 0) { +                outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext) +                                .inflate(params.mLayoutResId, null);              }          }          if (outResult.mRootView == null) { -            outResult.mRootView = -                    (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null); +            outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext) +                            .inflate(params.mLayoutResId , null);          }          // DecorationContainerSurface @@ -200,18 +199,18 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>          }          final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); -        outResult.mDensity = taskConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; -        final int decorContainerOffsetX = -(int) (outsetsDp.left * outResult.mDensity); -        final int decorContainerOffsetY = -(int) (outsetsDp.top * outResult.mDensity); +        final int decorContainerOffsetX = -loadResource(params.mOutsetLeftId); +        final int decorContainerOffsetY = -loadResource(params.mOutsetTopId);          outResult.mWidth = taskBounds.width() -                + (int) (outsetsDp.right * outResult.mDensity) +                + loadResource(params.mOutsetRightId)                  - decorContainerOffsetX;          outResult.mHeight = taskBounds.height() -                + (int) (outsetsDp.bottom * outResult.mDensity) +                + loadResource(params.mOutsetBottomId)                  - decorContainerOffsetY;          startT.setPosition(                          mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY) -                .setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight) +                .setWindowCrop(mDecorationContainerSurface, +                        outResult.mWidth, outResult.mHeight)                  // TODO(b/244455401): Change the z-order when it's better organized                  .setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1)                  .show(mDecorationContainerSurface); @@ -226,12 +225,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>                      .build();          } -        float shadowRadius = outResult.mDensity * shadowRadiusDp; +        float shadowRadius = loadResource(params.mShadowRadiusId);          int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();          mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;          mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;          mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f; -        startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height()) +        startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), +                        taskBounds.height())                  .setShadowRadius(mTaskBackgroundSurface, shadowRadius)                  .setColor(mTaskBackgroundSurface, mTmpColor)                  // TODO(b/244455401): Change the z-order when it's better organized @@ -248,8 +248,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>                      .build();          } -        final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity); -        final int captionWidth = (int) Math.ceil(captionWidthDp * outResult.mDensity); +        final int captionHeight = loadResource(params.mCaptionHeightId); +        final int captionWidth = loadResource(params.mCaptionWidthId);          //Prevent caption from going offscreen if task is too high up          final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2; @@ -289,8 +289,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>              // Caption insets              mCaptionInsetsRect.set(taskBounds); -            mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight - captionYPos; -            wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES); +            mCaptionInsetsRect.bottom = +                    mCaptionInsetsRect.top + captionHeight - captionYPos; +            wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, +                    CAPTION_INSETS_TYPES);          } else {              startT.hide(mCaptionContainerSurface);          } @@ -307,6 +309,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>                  .setCrop(mTaskSurface, mTaskSurfaceCrop);      } +    private int loadResource(int resourceId) { +        if (resourceId == Resources.ID_NULL) { +            return 0; +        } +        return mDecorWindowContext.getResources().getDimensionPixelSize(resourceId); +    } +      /**       * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or       * registers {@link #mOnDisplaysChangedListener} if it doesn't. @@ -368,13 +377,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>      static class RelayoutResult<T extends View & TaskFocusStateConsumer> {          int mWidth;          int mHeight; -        float mDensity;          T mRootView;          void reset() {              mWidth = 0;              mHeight = 0; -            mDensity = 0;              mRootView = null;          }      } @@ -395,4 +402,37 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>              return new SurfaceControlViewHost(c, d, wmm);          }      } + +    static class RelayoutParams{ +        RunningTaskInfo mRunningTaskInfo; +        int mLayoutResId; +        int mCaptionHeightId; +        int mCaptionWidthId; +        int mShadowRadiusId; + +        int mOutsetTopId; +        int mOutsetBottomId; +        int mOutsetLeftId; +        int mOutsetRightId; + +        void setOutsets(int leftId, int topId, int rightId, int bottomId) { +            mOutsetLeftId = leftId; +            mOutsetTopId = topId; +            mOutsetRightId = rightId; +            mOutsetBottomId = bottomId; +        } + +        void reset() { +            mLayoutResId = Resources.ID_NULL; +            mCaptionHeightId = Resources.ID_NULL; +            mCaptionWidthId = Resources.ID_NULL; +            mShadowRadiusId = Resources.ID_NULL; + +            mOutsetTopId = Resources.ID_NULL; +            mOutsetBottomId = Resources.ID_NULL; +            mOutsetLeftId = Resources.ID_NULL; +            mOutsetRightId = Resources.ID_NULL; +        } + +    }  }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index fa62b9c00fc7..103c8dab17d5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -51,6 +51,7 @@ import android.window.WindowContainerTransaction;  import androidx.test.filters.SmallTest; +import com.android.wm.shell.R;  import com.android.wm.shell.ShellTaskOrganizer;  import com.android.wm.shell.ShellTestCase;  import com.android.wm.shell.TestRunningTaskInfoBuilder; @@ -76,13 +77,9 @@ import java.util.function.Supplier;  @SmallTest  @RunWith(AndroidTestingRunner.class)  public class WindowDecorationTests extends ShellTestCase { -    private static final int CAPTION_HEIGHT_DP = 32; -    private static final int CAPTION_WIDTH_DP = 216; -    private static final int SHADOW_RADIUS_DP = 5;      private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);      private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60); -    private final Rect mOutsetsDp = new Rect();      private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =              new WindowDecoration.RelayoutResult<>(); @@ -104,6 +101,7 @@ public class WindowDecorationTests extends ShellTestCase {      private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();      private SurfaceControl.Transaction mMockSurfaceControlStartT;      private SurfaceControl.Transaction mMockSurfaceControlFinishT; +    private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();      @Before      public void setUp() { @@ -147,7 +145,8 @@ public class WindowDecorationTests extends ShellTestCase {          // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is          // 64px.          taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; -        mOutsetsDp.set(10, 20, 30, 40); +        mRelayoutParams.setOutsets(R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle, +                R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle);          final SurfaceControl taskSurface = mock(SurfaceControl.class);          final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -197,8 +196,13 @@ public class WindowDecorationTests extends ShellTestCase {          // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is          // 64px.          taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; -        mOutsetsDp.set(10, 20, 30, 40); - +//        int outsetLeftId = R.dimen.split_divider_bar_width; +//        int outsetTopId = R.dimen.gestures_onehanded_drag_threshold; +//        int outsetRightId = R.dimen.freeform_resize_handle; +//        int outsetBottomId = R.dimen.bubble_dismiss_target_padding_x; +//        mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId); +        mRelayoutParams.setOutsets(R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle, +                R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle);          final SurfaceControl taskSurface = mock(SurfaceControl.class);          final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -207,8 +211,8 @@ public class WindowDecorationTests extends ShellTestCase {          verify(decorContainerSurfaceBuilder).setParent(taskSurface);          verify(decorContainerSurfaceBuilder).setContainerLayer();          verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true); -        verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40); -        verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220); +        verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -60, -60); +        verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 420, 220);          verify(taskBackgroundSurfaceBuilder).setParent(taskSurface);          verify(taskBackgroundSurfaceBuilder).setEffectLayer(); @@ -221,34 +225,36 @@ public class WindowDecorationTests extends ShellTestCase {          verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);          verify(captionContainerSurfaceBuilder).setContainerLayer(); -        verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -46, 8); -        verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); +        verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -6, -156); +        verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 432);          verify(mMockSurfaceControlStartT).show(captionContainerSurface);          verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any()); +          verify(mMockSurfaceControlViewHost)                  .setView(same(mMockView), -                        argThat(lp -> lp.height == 64 -                                && lp.width == 300 +                        argThat(lp -> lp.height == 432 +                                && lp.width == 432                                  && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));          if (ViewRootImpl.CAPTION_ON_SHELL) {              verify(mMockView).setTaskFocusState(true);              verify(mMockWindowContainerTransaction)                      .addRectInsetsProvider(taskInfo.token, -                            new Rect(100, 300, 400, 364), +                            new Rect(100, 300, 400, 516),                              new int[] { InsetsState.ITYPE_CAPTION_BAR });          }          verify(mMockSurfaceControlFinishT)                  .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);          verify(mMockSurfaceControlFinishT) -                .setCrop(taskSurface, new Rect(-20, -40, 360, 180)); +                .setCrop(taskSurface, new Rect(-60, -60, 360, 160));          verify(mMockSurfaceControlStartT)                  .show(taskSurface); -        assertEquals(380, mRelayoutResult.mWidth); +        assertEquals(420, mRelayoutResult.mWidth);          assertEquals(220, mRelayoutResult.mHeight); -        assertEquals(2, mRelayoutResult.mDensity, 0.f); + +      }      @Test @@ -287,7 +293,8 @@ public class WindowDecorationTests extends ShellTestCase {          // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is          // 64px.          taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; -        mOutsetsDp.set(10, 20, 30, 40); +        mRelayoutParams.setOutsets(R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle, +                R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle);          final SurfaceControl taskSurface = mock(SurfaceControl.class);          final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -410,9 +417,15 @@ public class WindowDecorationTests extends ShellTestCase {          @Override          void relayout(ActivityManager.RunningTaskInfo taskInfo) { -            relayout(null /* taskInfo */, 0 /* layoutResId */, mMockView, CAPTION_HEIGHT_DP, -                    CAPTION_WIDTH_DP, mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlStartT, -                    mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mRelayoutResult); + +            mRelayoutParams.mLayoutResId = 0; +            mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_width; +            mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width; +            mRelayoutParams.mShadowRadiusId = +                    R.dimen.freeform_decor_shadow_unfocused_thickness; + +            relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, +                    mMockWindowContainerTransaction, mMockView, mRelayoutResult);          }      }  } diff --git a/media/java/android/media/MediaCrypto.java b/media/java/android/media/MediaCrypto.java index 889a5f7efbb2..1930262ea253 100644 --- a/media/java/android/media/MediaCrypto.java +++ b/media/java/android/media/MediaCrypto.java @@ -75,14 +75,17 @@ public final class MediaCrypto {      public final native boolean requiresSecureDecoderComponent(@NonNull String mime);      /** -     * Associate a MediaDrm session with this MediaCrypto instance.  The -     * MediaDrm session is used to securely load decryption keys for a -     * crypto scheme.  The crypto keys loaded through the MediaDrm session +     * Associate a new MediaDrm session with this MediaCrypto instance. +     * +     * <p>The MediaDrm session is used to securely load decryption keys for a +     * crypto scheme. The crypto keys loaded through the MediaDrm session       * may be selected for use during the decryption operation performed       * by {@link android.media.MediaCodec#queueSecureInputBuffer} by specifying -     * their key ids in the {@link android.media.MediaCodec.CryptoInfo#key} field. -     * @param sessionId the MediaDrm sessionId to associate with this -     * MediaCrypto instance +     * their key IDs in the {@link android.media.MediaCodec.CryptoInfo#key} field. +     * +     * @param sessionId The MediaDrm sessionId to associate with this MediaCrypto +     *         instance. The session's scheme must match the scheme UUID used when +     *         constructing this MediaCrypto instance.       * @throws MediaCryptoException on failure to set the sessionId       */      public final native void setMediaDrmSession(@NonNull byte[] sessionId) diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 57815370e2ba..681e1124d0ab 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -539,9 +539,9 @@ public final class MediaRoute2Info implements Parcelable {      }      /** -     * Gets the Deduplication ID of the route if available. -     * @see RouteDiscoveryPreference#shouldRemoveDuplicates() -     * @hide +     * Gets the deduplication IDs associated to the route. +     * +     * <p>Two routes with a matching deduplication ID originate from the same receiver device.       */      @NonNull      public Set<String> getDeduplicationIds() { @@ -1017,13 +1017,7 @@ public final class MediaRoute2Info implements Parcelable {          }          /** -         * Sets the deduplication ID of the route. -         * Routes have the same ID could be removed even when -         * they are from different providers. -         * <p> -         * If it's {@code null}, the route will not be removed. -         * @see RouteDiscoveryPreference#shouldRemoveDuplicates() -         * @hide +         * Sets the {@link MediaRoute2Info#getDeduplicationIds() deduplication IDs} of the route.           */          @NonNull          public Builder setDeduplicationIds(@NonNull Set<String> id) { diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index b1c97300d08a..51b976bf9f69 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -289,6 +289,7 @@ public class Tuner implements AutoCloseable  {      private Integer mFrontendHandle;      private Tuner mFeOwnerTuner = null;      private int mFrontendType = FrontendSettings.TYPE_UNDEFINED; +    private Integer mDesiredFrontendId = null;      private int mUserId;      private Lnb mLnb;      private Integer mLnbHandle; @@ -1326,10 +1327,18 @@ public class Tuner implements AutoCloseable  {      private boolean requestFrontend() {          int[] feHandle = new int[1]; -        TunerFrontendRequest request = new TunerFrontendRequest(); -        request.clientId = mClientId; -        request.frontendType = mFrontendType; -        boolean granted = mTunerResourceManager.requestFrontend(request, feHandle); +        boolean granted = false; +        try { +            TunerFrontendRequest request = new TunerFrontendRequest(); +            request.clientId = mClientId; +            request.frontendType = mFrontendType; +            request.desiredId = mDesiredFrontendId == null +                    ? TunerFrontendRequest.DEFAULT_DESIRED_ID +                    : mDesiredFrontendId; +            granted = mTunerResourceManager.requestFrontend(request, feHandle); +        } finally { +            mDesiredFrontendId = null; +        }          if (granted) {              mFrontendHandle = feHandle[0];              mFrontend = nativeOpenFrontendByHandle(mFrontendHandle); @@ -2367,6 +2376,50 @@ public class Tuner implements AutoCloseable  {      }      /** +     * Request a frontend by frontend id. +     * +     * <p> This API is used if the applications want to select a desired frontend before +     * {@link tune} to use a specific satellite or sending SatCR DiSEqC command for {@link tune}. +     * +     * @param desiredId the desired fronted Id. It can be retrieved by +     * {@link getAvailableFrontendInfos} +     * +     * @return result status of open operation. +     * @throws SecurityException if the caller does not have appropriate permissions. +     */ +    @Result +    public int requestFrontendById(int desiredId) { +        mFrontendLock.lock(); +        try { +            if (mFeOwnerTuner != null) { +                Log.e(TAG, "Operation connot be done by sharee of tuner"); +                return RESULT_INVALID_STATE; +            } +            if (mFrontendHandle != null) { +                Log.e(TAG, "A frontend has been opened before"); +                return RESULT_INVALID_STATE; +            } +            FrontendInfo frontendInfo = getFrontendInfoById(desiredId); +            if (frontendInfo == null) { +                Log.e(TAG, "Failed to get a FrontendInfo by frontend id: " + desiredId); +                return RESULT_UNAVAILABLE; +            } +            int frontendType = frontendInfo.getType(); +            if (DEBUG) { +                Log.d(TAG, "Opening frontend with type " + frontendType + ", id " + desiredId); +            } +            mFrontendType = frontendType; +            mDesiredFrontendId = desiredId; +            if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) { +                return RESULT_UNAVAILABLE; +            } +            return RESULT_SUCCESS; +        } finally { +            mFrontendLock.unlock(); +        } +    } + +    /**       * Open a shared filter instance.       *       * @param context the context of the caller. @@ -2445,7 +2498,7 @@ public class Tuner implements AutoCloseable  {          return granted;      } -    private boolean checkResource(int resourceType, ReentrantLock localLock)  { +    private boolean checkResource(int resourceType, ReentrantLock localLock) {          switch (resourceType) {              case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {                  if (mFrontendHandle == null && !requestResource(resourceType, localLock)) { @@ -2483,7 +2536,7 @@ public class Tuner implements AutoCloseable  {      // 3) if no, then first release the held lock and grab the TRMS lock to avoid deadlock      // 4) grab the local lock again and release the TRMS lock      // If localLock is null, we'll assume the caller does not want the lock related operations -    private boolean requestResource(int resourceType, ReentrantLock localLock)  { +    private boolean requestResource(int resourceType, ReentrantLock localLock) {          boolean enableLockOperations = localLock != null;          // release the local lock first to avoid deadlock diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl index 144b98c36655..e0af76d27a95 100644 --- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl +++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl @@ -140,19 +140,29 @@ interface ITunerResourceManager {      void setLnbInfoList(in int[] lnbIds);      /* -     * This API is used by the Tuner framework to request an available frontend from the TunerHAL. +     * This API is used by the Tuner framework to request a frontend from the TunerHAL.       * -     * <p>There are three possible scenarios: +     * <p>There are two cases:       * <ul> -     * <li>If there is frontend available, the API would send the id back. -     * -     * <li>If no Frontend is available but the current request info can show higher priority than -     * other uses of Frontend, the API will send +     * <li>If the desiredId is not {@link TunerFrontendRequest#DEFAULT_DESIRED_ID} +     * <li><li>If the desired frontend with the given frontendType is available, the API would send +     * the id back. +     * <li><li>If the desired frontend with the given frontendType is in use but the current request +     * info can show higher priority than other uses of Frontend, the API will send       * {@link IResourcesReclaimListener#onReclaimResources()} to the {@link Tuner}. Tuner would       * handle the resource reclaim on the holder of lower priority and notify the holder of its       * resource loss. +     * <li><li>If no frontend can be granted, the API would return false. +     * <ul>       * -     * <li>If no frontend can be granted, the API would return false. +     * <li>If the desiredId is {@link TunerFrontendRequest#DEFAULT_DESIRED_ID} +     * <li><li>If there is frontend available, the API would send the id back. +     * <li><li>If no Frontend is available but the current request info can show higher priority +     * than other uses of Frontend, the API will send +     * {@link IResourcesReclaimListener#onReclaimResources()} to the {@link Tuner}. Tuner would +     * handle the resource reclaim on the holder of lower priority and notify the holder of its +     * resource loss. +     * <li><li>If no frontend can be granted, the API would return false.       * <ul>       *       * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendRequest.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendRequest.aidl index 4d9822215842..c4598a49d5dd 100644 --- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendRequest.aidl +++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendRequest.aidl @@ -22,7 +22,11 @@ package android.media.tv.tunerresourcemanager;   * @hide   */  parcelable TunerFrontendRequest { +    const int DEFAULT_DESIRED_ID = 0xFFFFFFFF; +      int clientId;      int frontendType; -}
\ No newline at end of file + +    int desiredId = DEFAULT_DESIRED_ID; +} diff --git a/packages/CarrierDefaultApp/Android.bp b/packages/CarrierDefaultApp/Android.bp index fc753da19394..1e56a9340294 100644 --- a/packages/CarrierDefaultApp/Android.bp +++ b/packages/CarrierDefaultApp/Android.bp @@ -10,6 +10,7 @@ package {  android_app {      name: "CarrierDefaultApp",      srcs: ["src/**/*.java"], +    static_libs: ["SliceStore"],      platform_apis: true,      certificate: "platform",  } diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml index 632dfb397d51..9566f22a7274 100644 --- a/packages/CarrierDefaultApp/AndroidManifest.xml +++ b/packages/CarrierDefaultApp/AndroidManifest.xml @@ -71,5 +71,22 @@                  <data android:host="*" />              </intent-filter>          </activity-alias> + +        <receiver android:name="com.android.carrierdefaultapp.SliceStoreBroadcastReceiver" +                  android:exported="true"> +            <intent-filter> +                <action android:name="com.android.phone.slicestore.action.START_SLICE_STORE" /> +                <action android:name="com.android.phone.slicestore.action.SLICE_STORE_RESPONSE_TIMEOUT" /> +                <action android:name="com.android.phone.slicestore.action.NOTIFICATION_CANCELED" /> +            </intent-filter> +        </receiver> +        <activity android:name="com.android.carrierdefaultapp.SliceStoreActivity" +                  android:label="@string/slice_store_label" +                  android:exported="true" +                  android:configChanges="keyboardHidden|orientation|screenSize"> +            <intent-filter> +                <category android:name="android.intent.category.DEFAULT"/> +            </intent-filter> +        </activity>      </application>  </manifest> diff --git a/packages/CarrierDefaultApp/res/drawable/ic_network_boost.xml b/packages/CarrierDefaultApp/res/drawable/ic_network_boost.xml new file mode 100644 index 000000000000..ad8a21c7abb6 --- /dev/null +++ b/packages/CarrierDefaultApp/res/drawable/ic_network_boost.xml @@ -0,0 +1,23 @@ +<!-- +  ~ Copyright (C) 2022 The Android Open Source Project +  ~ +  ~ Licensed under the Apache License, Version 2.0 (the "License"); +  ~ you may not use this file except in compliance with the License. +  ~ You may obtain a copy of the License at +  ~ +  ~      http://www.apache.org/licenses/LICENSE-2.0 +  ~ +  ~ Unless required by applicable law or agreed to in writing, software +  ~ distributed under the License is distributed on an "AS IS" BASIS, +  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +  ~ See the License for the specific language governing permissions and +  ~ limitations under the License. +  --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" +        android:width="24dp" +        android:height="24dp" +        android:viewportWidth="24" +        android:viewportHeight="24"> +    <path android:fillColor="@android:color/white" +          android:pathData="M3,17V15H8Q8,15 8,15Q8,15 8,15V13Q8,13 8,13Q8,13 8,13H3V7H10V9H5V11H8Q8.825,11 9.413,11.587Q10,12.175 10,13V15Q10,15.825 9.413,16.413Q8.825,17 8,17ZM21,11V15Q21,15.825 20.413,16.413Q19.825,17 19,17H14Q13.175,17 12.588,16.413Q12,15.825 12,15V9Q12,8.175 12.588,7.587Q13.175,7 14,7H19Q19.825,7 20.413,7.587Q21,8.175 21,9H14Q14,9 14,9Q14,9 14,9V15Q14,15 14,15Q14,15 14,15H19Q19,15 19,15Q19,15 19,15V13H16.5V11Z"/> +</vector>
\ No newline at end of file diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml index 65a7cecca20f..ce88a401d18e 100644 --- a/packages/CarrierDefaultApp/res/values/strings.xml +++ b/packages/CarrierDefaultApp/res/values/strings.xml @@ -13,4 +13,18 @@      <string name="ssl_error_warning">The network you’re trying to join has security issues.</string>      <string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string>      <string name="ssl_error_continue">Continue anyway via browser</string> + +    <!-- Telephony notification channel name for network boost notifications. --> +    <string name="network_boost_notification_channel">Network Boost</string> +    <!-- Notification title text for the network boost notification. --> +    <string name="network_boost_notification_title">%s recommends a data boost</string> +    <!-- Notification detail text for the network boost notification. --> +    <string name="network_boost_notification_detail">Buy a network boost for better performance</string> +    <!-- Notification button text to cancel the network boost notification. --> +    <string name="network_boost_notification_button_not_now">Not now</string> +    <!-- Notification button text to manage the network boost notification. --> +    <string name="network_boost_notification_button_manage">Manage</string> + +    <!-- Label to display when the slice store opens. --> +    <string name="slice_store_label">Purchase a network boost.</string>  </resources> diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java new file mode 100644 index 000000000000..24cb5f948474 --- /dev/null +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.carrierdefaultapp; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Activity; +import android.app.NotificationManager; +import android.content.Intent; +import android.os.Bundle; +import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.webkit.WebView; + +import com.android.phone.slicestore.SliceStore; + +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Activity that launches when the user clicks on the network boost notification. + */ +public class SliceStoreActivity extends Activity { +    private static final String TAG = "SliceStoreActivity"; + +    private URL mUrl; +    private WebView mWebView; +    private int mPhoneId; +    private int mSubId; +    private @TelephonyManager.PremiumCapability int mCapability; + +    @Override +    protected void onCreate(Bundle savedInstanceState) { +        super.onCreate(savedInstanceState); +        Intent intent = getIntent(); +        mPhoneId = intent.getIntExtra(SliceStore.EXTRA_PHONE_ID, +                SubscriptionManager.INVALID_PHONE_INDEX); +        mSubId = intent.getIntExtra(SliceStore.EXTRA_SUB_ID, +                SubscriptionManager.INVALID_SUBSCRIPTION_ID); +        mCapability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY, +                SliceStore.PREMIUM_CAPABILITY_INVALID); +        mUrl = getUrl(); +        logd("onCreate: mPhoneId=" + mPhoneId + ", mSubId=" + mSubId + ", mCapability=" +                + TelephonyManager.convertPremiumCapabilityToString(mCapability) +                + ", mUrl=" + mUrl); +        getApplicationContext().getSystemService(NotificationManager.class) +                .cancel(SliceStoreBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability); +        if (!SliceStoreBroadcastReceiver.isIntentValid(intent)) { +            loge("Not starting SliceStoreActivity with an invalid Intent: " + intent); +            SliceStoreBroadcastReceiver.sendSliceStoreResponse( +                    intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED); +            finishAndRemoveTask(); +            return; +        } +        if (mUrl == null) { +            loge("Unable to create a URL from carrier configs."); +            SliceStoreBroadcastReceiver.sendSliceStoreResponse( +                    intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR); +            finishAndRemoveTask(); +            return; +        } +        if (mSubId != SubscriptionManager.getDefaultSubscriptionId()) { +            loge("Unable to start SliceStore on the non-default data subscription: " + mSubId); +            SliceStoreBroadcastReceiver.sendSliceStoreResponse( +                    intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA); +            finishAndRemoveTask(); +            return; +        } + +        SliceStoreBroadcastReceiver.updateSliceStoreActivity(mCapability, this); + +        mWebView = new WebView(getApplicationContext()); +        setContentView(mWebView); +        mWebView.loadUrl(mUrl.toString()); +        // TODO(b/245882601): Get back response from WebView +    } + +    @Override +    protected void onDestroy() { +        logd("onDestroy: User canceled the purchase by closing the application."); +        SliceStoreBroadcastReceiver.sendSliceStoreResponse( +                getIntent(), SliceStore.EXTRA_INTENT_CANCELED); +        SliceStoreBroadcastReceiver.removeSliceStoreActivity(mCapability); +        super.onDestroy(); +    } + +    private @Nullable URL getUrl() { +        String url = getApplicationContext().getSystemService(CarrierConfigManager.class) +                .getConfigForSubId(mSubId).getString( +                        CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING); +        try { +            return new URL(url); +        } catch (MalformedURLException e) { +            loge("Invalid URL: " + url); +        } +        return null; +    } + +    private static void logd(@NonNull String s) { +        Log.d(TAG, s); +    } + +    private static void loge(@NonNull String s) { +        Log.e(TAG, s); +    } +} diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java new file mode 100644 index 000000000000..7eb851dcdd58 --- /dev/null +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.carrierdefaultapp; + +import android.annotation.NonNull; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Icon; +import android.os.UserHandle; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; +import android.webkit.WebView; + +import com.android.phone.slicestore.SliceStore; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; + +/** + * The SliceStoreBroadcastReceiver listens for {@link SliceStore#ACTION_START_SLICE_STORE} from the + * SliceStore in the phone process to start the SliceStore application. It displays the network + * boost notification to the user and will start the {@link SliceStoreActivity} to display the + * {@link WebView} to purchase network boosts from the user's carrier. + */ +public class SliceStoreBroadcastReceiver extends BroadcastReceiver{ +    private static final String TAG = "SliceStoreBroadcastReceiver"; + +    /** Weak references to {@link SliceStoreActivity} for each capability, if it exists. */ +    private static final Map<Integer, WeakReference<SliceStoreActivity>> sSliceStoreActivities = +            new HashMap<>(); + +    /** Channel ID for the network boost notification. */ +    private static final String NETWORK_BOOST_NOTIFICATION_CHANNEL_ID = "network_boost"; +    /** Tag for the network boost notification. */ +    public static final String NETWORK_BOOST_NOTIFICATION_TAG = "SliceStore.Notification"; +    /** Action for when the user clicks the "Not now" button on the network boost notification. */ +    private static final String ACTION_NOTIFICATION_CANCELED = +            "com.android.phone.slicestore.action.NOTIFICATION_CANCELED"; + +    /** +     * Create a weak reference to {@link SliceStoreActivity}. The reference will be removed when +     * {@link SliceStoreActivity#onDestroy()} is called. +     * +     * @param capability The premium capability requested. +     * @param sliceStoreActivity The instance of SliceStoreActivity. +     */ +    public static void updateSliceStoreActivity(@TelephonyManager.PremiumCapability int capability, +            @NonNull SliceStoreActivity sliceStoreActivity) { +        sSliceStoreActivities.put(capability, new WeakReference<>(sliceStoreActivity)); +    } + +    /** +     * Remove the weak reference to {@link SliceStoreActivity} when +     * {@link SliceStoreActivity#onDestroy()} is called. +     * +     * @param capability The premium capability requested. +     */ +    public static void removeSliceStoreActivity( +            @TelephonyManager.PremiumCapability int capability) { +        sSliceStoreActivities.remove(capability); +    } + +    /** +     * Send the PendingIntent containing the corresponding SliceStore response. +     * +     * @param intent The Intent containing the PendingIntent extra. +     * @param extra The extra to get the PendingIntent to send. +     */ +    public static void sendSliceStoreResponse(@NonNull Intent intent, @NonNull String extra) { +        PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class); +        if (pendingIntent == null) { +            loge("PendingIntent does not exist for extra: " + extra); +            return; +        } +        try { +            pendingIntent.send(); +        } catch (PendingIntent.CanceledException e) { +            loge("Unable to send " + getPendingIntentType(extra) + " intent: " + e); +        } +    } + +    /** +     * Check whether the Intent is valid and can be used to complete purchases in the SliceStore. +     * This checks that all necessary extras exist and that the values are valid. +     * +     * @param intent The intent to check +     * @return {@code true} if the intent is valid and {@code false} otherwise. +     */ +    public static boolean isIntentValid(@NonNull Intent intent) { +        int phoneId = intent.getIntExtra(SliceStore.EXTRA_PHONE_ID, +                SubscriptionManager.INVALID_PHONE_INDEX); +        if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) { +            loge("isIntentValid: invalid phone index: " + phoneId); +            return false; +        } + +        int subId = intent.getIntExtra(SliceStore.EXTRA_SUB_ID, +                SubscriptionManager.INVALID_SUBSCRIPTION_ID); +        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { +            loge("isIntentValid: invalid subscription ID: " + subId); +            return false; +        } + +        int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY, +                SliceStore.PREMIUM_CAPABILITY_INVALID); +        if (capability == SliceStore.PREMIUM_CAPABILITY_INVALID) { +            loge("isIntentValid: invalid premium capability: " + capability); +            return false; +        } + +        String appName = intent.getStringExtra(SliceStore.EXTRA_REQUESTING_APP_NAME); +        if (TextUtils.isEmpty(appName)) { +            loge("isIntentValid: empty requesting application name: " + appName); +            return false; +        } + +        return isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CANCELED) +                && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR) +                && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED) +                && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA); +    } + +    private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) { +        String intentType = getPendingIntentType(extra); +        PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class); +        if (pendingIntent == null) { +            loge("isPendingIntentValid: " + intentType + " intent not found."); +            return false; +        } else if (pendingIntent.getCreatorPackage().equals(TelephonyManager.PHONE_PROCESS_NAME)) { +            return true; +        } +        loge("isPendingIntentValid: " + intentType + " intent was created by " +                + pendingIntent.getCreatorPackage() + " instead of the phone process."); +        return false; +    } + +    @NonNull private static String getPendingIntentType(@NonNull String extra) { +        switch (extra) { +            case SliceStore.EXTRA_INTENT_CANCELED: return "canceled"; +            case SliceStore.EXTRA_INTENT_CARRIER_ERROR: return "carrier error"; +            case SliceStore.EXTRA_INTENT_REQUEST_FAILED: return "request failed"; +            case SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA: return "not default data"; +            default: { +                loge("Unknown pending intent extra: " + extra); +                return "unknown(" + extra + ")"; +            } +        } +    } + +    @Override +    public void onReceive(@NonNull Context context, @NonNull Intent intent) { +        logd("onReceive intent: " + intent.getAction()); +        switch (intent.getAction()) { +            case SliceStore.ACTION_START_SLICE_STORE: +                onDisplayBoosterNotification(context, intent); +                break; +            case SliceStore.ACTION_SLICE_STORE_RESPONSE_TIMEOUT: +                onTimeout(context, intent); +                break; +            case ACTION_NOTIFICATION_CANCELED: +                onUserCanceled(context, intent); +                break; +            default: +                loge("Received unknown action: " + intent.getAction()); +        } +    } + +    private void onDisplayBoosterNotification(@NonNull Context context, @NonNull Intent intent) { +        if (!isIntentValid(intent)) { +            sendSliceStoreResponse(intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED); +            return; +        } + +        context.getSystemService(NotificationManager.class).createNotificationChannel( +                new NotificationChannel(NETWORK_BOOST_NOTIFICATION_CHANNEL_ID, +                        context.getResources().getString( +                                R.string.network_boost_notification_channel), +                        NotificationManager.IMPORTANCE_DEFAULT)); + +        Notification notification = +                new Notification.Builder(context, NETWORK_BOOST_NOTIFICATION_CHANNEL_ID) +                        .setContentTitle(String.format(context.getResources().getString( +                                R.string.network_boost_notification_title), +                                intent.getStringExtra(SliceStore.EXTRA_REQUESTING_APP_NAME))) +                        .setContentText(context.getResources().getString( +                                R.string.network_boost_notification_detail)) +                        .setSmallIcon(R.drawable.ic_network_boost) +                        .setContentIntent(createContentIntent(context, intent, 1)) +                        .setDeleteIntent(intent.getParcelableExtra( +                                SliceStore.EXTRA_INTENT_CANCELED, PendingIntent.class)) +                        // Add an action for the "Not now" button, which has the same behavior as +                        // the user canceling or closing the notification. +                        .addAction(new Notification.Action.Builder( +                                Icon.createWithResource(context, R.drawable.ic_network_boost), +                                context.getResources().getString( +                                        R.string.network_boost_notification_button_not_now), +                                createCanceledIntent(context, intent)).build()) +                        // Add an action for the "Manage" button, which has the same behavior as +                        // the user clicking on the notification. +                        .addAction(new Notification.Action.Builder( +                                Icon.createWithResource(context, R.drawable.ic_network_boost), +                                context.getResources().getString( +                                        R.string.network_boost_notification_button_manage), +                                createContentIntent(context, intent, 2)).build()) +                        .build(); + +        int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY, +                SliceStore.PREMIUM_CAPABILITY_INVALID); +        logd("Display the booster notification for capability " +                + TelephonyManager.convertPremiumCapabilityToString(capability)); +        context.getSystemService(NotificationManager.class).notifyAsUser( +                NETWORK_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL); +    } + +    /** +     * Create the intent for when the user clicks on the "Manage" button on the network boost +     * notification or the notification itself. This will open {@link SliceStoreActivity}. +     * +     * @param context The Context to create the intent for. +     * @param intent The source Intent used to launch the SliceStore application. +     * @param requestCode The request code for the PendingIntent. +     * +     * @return The intent to start {@link SliceStoreActivity}. +     */ +    @NonNull private PendingIntent createContentIntent(@NonNull Context context, +            @NonNull Intent intent, int requestCode) { +        Intent i = new Intent(context, SliceStoreActivity.class); +        i.setComponent(ComponentName.unflattenFromString( +                "com.android.carrierdefaultapp/.SliceStoreActivity")); +        i.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT +                | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); +        i.putExtras(intent); +        return PendingIntent.getActivityAsUser(context, requestCode, i, +                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE, null /* options */, +                UserHandle.CURRENT); +    } + +    /** +     * Create the canceled intent for when the user clicks the "Not now" button on the network boost +     * notification. This will send {@link #ACTION_NOTIFICATION_CANCELED} and has the same function +     * as if the user had canceled or removed the notification. +     * +     * @param context The Context to create the intent for. +     * @param intent The source Intent used to launch the SliceStore application. +     * +     * @return The canceled intent. +     */ +    @NonNull private PendingIntent createCanceledIntent(@NonNull Context context, +            @NonNull Intent intent) { +        Intent i = new Intent(ACTION_NOTIFICATION_CANCELED); +        i.setComponent(ComponentName.unflattenFromString( +                "com.android.carrierdefaultapp/.SliceStoreBroadcastReceiver")); +        i.putExtras(intent); +        return PendingIntent.getBroadcast(context, 0, i, +                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE); +    } + +    private void onTimeout(@NonNull Context context, @NonNull Intent intent) { +        int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY, +                SliceStore.PREMIUM_CAPABILITY_INVALID); +        logd("Purchase capability " + TelephonyManager.convertPremiumCapabilityToString(capability) +                + " timed out."); +        if (sSliceStoreActivities.get(capability) == null) { +            // Notification is still active +            logd("Closing booster notification since the user did not respond in time."); +            context.getSystemService(NotificationManager.class).cancelAsUser( +                    NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL); +        } else { +            // Notification was dismissed but SliceStoreActivity is still active +            logd("Closing SliceStore WebView since the user did not complete the purchase " +                    + "in time."); +            sSliceStoreActivities.get(capability).get().finishAndRemoveTask(); +            // TODO: Display a toast to indicate timeout for better UX? +        } +    } + +    private void onUserCanceled(@NonNull Context context, @NonNull Intent intent) { +        int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY, +                SliceStore.PREMIUM_CAPABILITY_INVALID); +        logd("onUserCanceled: " + TelephonyManager.convertPremiumCapabilityToString(capability)); +        context.getSystemService(NotificationManager.class) +                .cancelAsUser(NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL); +        sendSliceStoreResponse(intent, SliceStore.EXTRA_INTENT_CANCELED); +    } + +    private static void logd(String s) { +        Log.d(TAG, s); +    } + +    private static void loge(String s) { +        Log.e(TAG, s); +    } +} diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 2c24bf14117a..92ce77282cb9 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -1,4 +1,5 @@ -<resources> +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">    <string name="app_name">CredentialManager</string>    <string name="string_cancel">Cancel</string>    <string name="string_continue">Continue</string> @@ -12,4 +13,7 @@    <string name="choose_create_option_title">Create a passkey at</string>    <string name="choose_sign_in_title">Use saved sign in</string>    <string name="create_passkey_at">Create passkey at</string> +  <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoName">%1$s</xliff:g> for all your sign-ins?</string> +  <string name="set_as_default">Set as default</string> +  <string name="use_once">Use once</string>  </resources>
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index c575fbc6b7f8..ec0c5b708abe 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -16,15 +16,20 @@  package com.android.credentialmanager +import android.app.Activity  import android.app.slice.Slice  import android.app.slice.SliceSpec  import android.content.Context  import android.content.Intent +import android.credentials.ui.Constants  import android.credentials.ui.Entry  import android.credentials.ui.ProviderData  import android.credentials.ui.RequestInfo +import android.credentials.ui.UserSelectionResult  import android.graphics.drawable.Icon  import android.os.Binder +import android.os.Bundle +import android.os.ResultReceiver  import com.android.credentialmanager.createflow.CreatePasskeyUiState  import com.android.credentialmanager.createflow.CreateScreenState  import com.android.credentialmanager.getflow.GetCredentialUiState @@ -37,6 +42,8 @@ class CredentialManagerRepo(  ) {    private val requestInfo: RequestInfo    private val providerList: List<ProviderData> +  // TODO: require non-null. +  val resultReceiver: ResultReceiver?    init {      requestInfo = intent.extras?.getParcelable( @@ -52,6 +59,29 @@ class CredentialManagerRepo(        ProviderData.EXTRA_PROVIDER_DATA_LIST,        ProviderData::class.java      ) ?: testProviderList() + +    resultReceiver = intent.getParcelableExtra( +      Constants.EXTRA_RESULT_RECEIVER, +      ResultReceiver::class.java +    ) +  } + +  fun onCancel() { +    resultReceiver?.send(Activity.RESULT_CANCELED, null) +  } + +  fun onOptionSelected(providerPackageName: String, entryId: Int) { +    val userSelectionResult = UserSelectionResult( +      requestInfo.token, +      providerPackageName, +      entryId +    ) +    val resultData = Bundle() +    resultData.putParcelable( +      UserSelectionResult.EXTRA_USER_SELECTION_RESULT, +      userSelectionResult +    ) +    resultReceiver?.send(Activity.RESULT_OK, resultData)    }    fun getCredentialInitialUiState(): GetCredentialUiState { @@ -89,46 +119,42 @@ class CredentialManagerRepo(    // TODO: below are prototype functionalities. To be removed for productionization.    private fun testProviderList(): List<ProviderData> {      return listOf( -      ProviderData( +      ProviderData.Builder(          "com.google", -        listOf<Entry>( -          newEntry(1, "elisa.beckett@gmail.com", "Elisa Backett", -            "20 passwords and 7 passkeys saved"), -          newEntry(2, "elisa.work@google.com", "Elisa Backett Work", -            "20 passwords and 7 passkeys saved"), -        ), -        listOf<Entry>( -          newEntry(3, "Go to Settings", "", -            "20 passwords and 7 passkeys saved"), -          newEntry(4, "Switch Account", "", -            "20 passwords and 7 passkeys saved"), -        ), -        null -      ), -      ProviderData( +        "Google Password Manager", +        Icon.createWithResource(context, R.drawable.ic_launcher_foreground)) +        .setCredentialEntries( +          listOf<Entry>( +            newEntry(1, "elisa.beckett@gmail.com", "Elisa Backett", +                     "20 passwords and 7 passkeys saved"), +            newEntry(2, "elisa.work@google.com", "Elisa Backett Work", +                     "20 passwords and 7 passkeys saved"), +          ) +        ).setActionChips( +          listOf<Entry>( +            newEntry(3, "Go to Settings", "", +                     "20 passwords and 7 passkeys saved"), +            newEntry(4, "Switch Account", "", +                     "20 passwords and 7 passkeys saved"), +          ), +        ).build(), +      ProviderData.Builder(          "com.dashlane", -        listOf<Entry>( -          newEntry(5, "elisa.beckett@dashlane.com", "Elisa Backett", -            "20 passwords and 7 passkeys saved"), -          newEntry(6, "elisa.work@dashlane.com", "Elisa Backett Work", -            "20 passwords and 7 passkeys saved"), -        ), -        listOf<Entry>( -          newEntry(7, "Manage Accounts", "Manage your accounts in the dashlane app", -            "20 passwords and 7 passkeys saved"), -        ), -        null -      ), -      ProviderData( -        "com.lastpass", -        listOf<Entry>( -          newEntry(8, "elisa.beckett@lastpass.com", "Elisa Backett", -            "20 passwords and 7 passkeys saved"), -        ), -        listOf<Entry>(), -        null -      ) - +        "Dashlane", +        Icon.createWithResource(context, R.drawable.ic_launcher_foreground)) +        .setCredentialEntries( +          listOf<Entry>( +            newEntry(1, "elisa.beckett@dashlane.com", "Elisa Backett", +                     "20 passwords and 7 passkeys saved"), +            newEntry(2, "elisa.work@dashlane.com", "Elisa Backett Work", +                     "20 passwords and 7 passkeys saved"), +          ) +        ).setActionChips( +          listOf<Entry>( +            newEntry(3, "Manage Accounts", "Manage your accounts in the dashlane app", +                     "20 passwords and 7 passkeys saved"), +          ), +        ).build(),      )    } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt index b538ae79091f..78edaa936bcd 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -23,9 +23,15 @@ import androidx.activity.ComponentActivity  import androidx.activity.compose.setContent  import androidx.compose.material.ExperimentalMaterialApi  import androidx.compose.runtime.Composable +import androidx.lifecycle.Observer +import androidx.lifecycle.viewmodel.compose.viewModel  import com.android.credentialmanager.common.DialogType +import com.android.credentialmanager.common.DialogResult +import com.android.credentialmanager.common.ResultState  import com.android.credentialmanager.createflow.CreatePasskeyScreen +import com.android.credentialmanager.createflow.CreatePasskeyViewModel  import com.android.credentialmanager.getflow.GetCredentialScreen +import com.android.credentialmanager.getflow.GetCredentialViewModel  import com.android.credentialmanager.ui.theme.CredentialSelectorTheme  @ExperimentalMaterialApi @@ -57,10 +63,20 @@ class CredentialSelectorActivity : ComponentActivity() {      val dialogType = DialogType.toDialogType(operationType)      when (dialogType) {        DialogType.CREATE_PASSKEY -> { -        CreatePasskeyScreen(cancelActivity = onCancel) +        val viewModel: CreatePasskeyViewModel = viewModel() +        viewModel.observeDialogResult().observe( +          this@CredentialSelectorActivity, +          onCancel +        ) +        CreatePasskeyScreen(viewModel = viewModel)        }        DialogType.GET_CREDENTIALS -> { -        GetCredentialScreen(cancelActivity = onCancel) +        val viewModel: GetCredentialViewModel = viewModel() +        viewModel.observeDialogResult().observe( +          this@CredentialSelectorActivity, +          onCancel +        ) +        GetCredentialScreen(viewModel = viewModel)        }        else -> {          Log.w("AccountSelector", "Unknown type, not rendering any UI") @@ -69,7 +85,9 @@ class CredentialSelectorActivity : ComponentActivity() {      }    } -  private val onCancel = { -    this@CredentialSelectorActivity.finish() +  private val onCancel = Observer<DialogResult> { +    if (it.resultState == ResultState.COMPLETE || it.resultState == ResultState.CANCELED) { +      this@CredentialSelectorActivity.finish() +    }    }  } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index af27ce504641..6b503ff9c10b 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -35,7 +35,7 @@ class GetFlowUtils {          ProviderInfo(            // TODO: replace to extract from the service data structure when available            icon = context.getDrawable(R.drawable.ic_passkey)!!, -          name = it.packageName, +          name = it.providerId,            appDomainName = "tribank.us",            credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,            credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context) @@ -78,7 +78,7 @@ class CreateFlowUtils {          com.android.credentialmanager.createflow.ProviderInfo(            // TODO: replace to extract from the service data structure when available            icon = context.getDrawable(R.drawable.ic_passkey)!!, -          name = it.packageName, +          name = it.providerId,            appDomainName = "tribank.us",            credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,            createOptions = toCreationOptionInfoList(it.credentialEntries, context), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt index 23072a231417..b75166347c56 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt @@ -14,8 +14,13 @@   * limitations under the License.   */ -package com.android.settingslib.spa.gallery +package com.android.credentialmanager.common -import com.android.settingslib.spa.framework.DebugActivity +enum class ResultState { +  COMPLETE, +  CANCELED, +} -class GalleryDebugActivity : DebugActivity() +data class DialogResult( +  val resultState: ResultState, +)
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt index 6489d7328566..82fce9f7a98d 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt @@ -38,7 +38,6 @@ import androidx.compose.ui.res.stringResource  import androidx.compose.ui.text.style.TextAlign  import androidx.compose.ui.unit.dp  import androidx.core.graphics.drawable.toBitmap -import androidx.lifecycle.viewmodel.compose.viewModel  import com.android.credentialmanager.R  import com.android.credentialmanager.ui.theme.Grey100  import com.android.credentialmanager.ui.theme.Shapes @@ -50,8 +49,7 @@ import com.android.credentialmanager.ui.theme.lightSurface1  @ExperimentalMaterialApi  @Composable  fun CreatePasskeyScreen( -  viewModel: CreatePasskeyViewModel = viewModel(), -  cancelActivity: () -> Unit, +  viewModel: CreatePasskeyViewModel,  ) {    val state = rememberModalBottomSheetState(      initialValue = ModalBottomSheetValue.Expanded, @@ -64,17 +62,17 @@ fun CreatePasskeyScreen(        when (uiState.currentScreenState) {          CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(            onConfirm = {viewModel.onConfirmIntro()}, -          onCancel = cancelActivity, +          onCancel = {viewModel.onCancel()},          )          CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(            providerList = uiState.providers, -          onCancel = cancelActivity, +          onCancel = {viewModel.onCancel()},            onProviderSelected = {viewModel.onProviderSelected(it)}          )          CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(            providerInfo = uiState.selectedProvider!!,            onOptionSelected = {viewModel.onCreateOptionSelected(it)}, -          onCancel = cancelActivity, +          onCancel = {viewModel.onCancel()},            multiProvider = uiState.providers.size > 1,            onMoreOptionsSelected = {viewModel.onMoreOptionsSelected(it)}          ) @@ -85,6 +83,8 @@ fun CreatePasskeyScreen(              onOptionSelected = {viewModel.onMoreOptionsRowSelected(it)}            )          CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard( +          providerInfo = uiState.selectedProvider!!, +          onDefaultOrNotSelected = {viewModel.onDefaultOrNotSelected(it)}          )        }      }, @@ -93,7 +93,7 @@ fun CreatePasskeyScreen(    ) {}    LaunchedEffect(state.currentValue) {      if (state.currentValue == ModalBottomSheetValue.Hidden) { -      cancelActivity() +      viewModel.onCancel()      }    }  } @@ -284,10 +284,37 @@ fun MoreOptionsSelectionCard(  @ExperimentalMaterialApi  @Composable  fun MoreOptionsRowIntroCard( +  providerInfo: ProviderInfo, +  onDefaultOrNotSelected: (String) -> Unit,  ) {    Card(      backgroundColor = lightBackgroundColor,    ) { +    Column() { +      Text( +        text = stringResource(R.string.use_provider_for_all_title, providerInfo.name), +        style = Typography.subtitle1, +        modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally) +      ) +      Row( +        horizontalArrangement = Arrangement.SpaceBetween, +        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) +      ) { +        CancelButton( +          stringResource(R.string.use_once), +          onclick = { onDefaultOrNotSelected(providerInfo.name) } +        ) +        ConfirmButton( +          stringResource(R.string.set_as_default), +          onclick = { onDefaultOrNotSelected(providerInfo.name) } +        ) +      } +      Divider( +        thickness = 18.dp, +        color = Color.Transparent, +        modifier = Modifier.padding(bottom = 40.dp) +      ) +    }    }  } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt index 85fe31d312ab..ff44e2ee1b06 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt @@ -20,8 +20,12 @@ import android.util.Log  import androidx.compose.runtime.getValue  import androidx.compose.runtime.mutableStateOf  import androidx.compose.runtime.setValue +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData  import androidx.lifecycle.ViewModel  import com.android.credentialmanager.CredentialManagerRepo +import com.android.credentialmanager.common.DialogResult +import com.android.credentialmanager.common.ResultState  data class CreatePasskeyUiState(    val providers: List<ProviderInfo>, @@ -36,6 +40,14 @@ class CreatePasskeyViewModel(    var uiState by mutableStateOf(credManRepo.createPasskeyInitialUiState())      private set +  val dialogResult: MutableLiveData<DialogResult> by lazy { +    MutableLiveData<DialogResult>() +  } + +  fun observeDialogResult(): LiveData<DialogResult> { +    return dialogResult +  } +    fun onConfirmIntro() {      if (uiState.providers.size > 1) {        uiState = uiState.copy( @@ -60,6 +72,13 @@ class CreatePasskeyViewModel(    fun onCreateOptionSelected(createOptionId: Int) {      Log.d("Account Selector", "Option selected for creation: $createOptionId") +    CredentialManagerRepo.getInstance().onOptionSelected( +      uiState.selectedProvider!!.name, +      createOptionId +    ) +    dialogResult.value = DialogResult( +      ResultState.COMPLETE, +    )    }    fun getProviderInfoByName(providerName: String): ProviderInfo { @@ -88,4 +107,17 @@ class CreatePasskeyViewModel(        selectedProvider = getProviderInfoByName(providerName)      )    } + +  fun onCancel() { +    CredentialManagerRepo.getInstance().onCancel() +    dialogResult.value = DialogResult(ResultState.CANCELED) +  } + +  fun onDefaultOrNotSelected(providerName: String) { +    uiState = uiState.copy( +      currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION, +      selectedProvider = getProviderInfoByName(providerName) +    ) +    // TODO: implement the if choose as default or not logic later +  }  } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 0b188221e8b3..48c67bb8770d 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -44,7 +44,6 @@ import androidx.compose.ui.graphics.asImageBitmap  import androidx.compose.ui.res.stringResource  import androidx.compose.ui.unit.dp  import androidx.core.graphics.drawable.toBitmap -import androidx.lifecycle.viewmodel.compose.viewModel  import com.android.credentialmanager.R  import com.android.credentialmanager.createflow.CancelButton  import com.android.credentialmanager.ui.theme.Grey100 @@ -55,8 +54,7 @@ import com.android.credentialmanager.ui.theme.lightBackgroundColor  @ExperimentalMaterialApi  @Composable  fun GetCredentialScreen( -  viewModel: GetCredentialViewModel = viewModel(), -  cancelActivity: () -> Unit, +  viewModel: GetCredentialViewModel,  ) {    val state = rememberModalBottomSheetState(      initialValue = ModalBottomSheetValue.Expanded, @@ -69,7 +67,7 @@ fun GetCredentialScreen(        when (uiState.currentScreenState) {          GetScreenState.CREDENTIAL_SELECTION -> CredentialSelectionCard(            providerInfo = uiState.selectedProvider!!, -          onCancel = cancelActivity, +          onCancel = {viewModel.onCancel()},            onOptionSelected = {viewModel.onCredentailSelected(it)},            multiProvider = uiState.providers.size > 1,            onMoreOptionSelected = {viewModel.onMoreOptionSelected()}, @@ -81,7 +79,7 @@ fun GetCredentialScreen(    ) {}    LaunchedEffect(state.currentValue) {      if (state.currentValue == ModalBottomSheetValue.Hidden) { -      cancelActivity() +      viewModel.onCancel()      }    }  } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt index 0fdd8ecd38c9..33858f523e3c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt @@ -20,8 +20,12 @@ import android.util.Log  import androidx.compose.runtime.getValue  import androidx.compose.runtime.mutableStateOf  import androidx.compose.runtime.setValue +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData  import androidx.lifecycle.ViewModel  import com.android.credentialmanager.CredentialManagerRepo +import com.android.credentialmanager.common.DialogResult +import com.android.credentialmanager.common.ResultState  data class GetCredentialUiState(    val providers: List<ProviderInfo>, @@ -36,11 +40,31 @@ class GetCredentialViewModel(    var uiState by mutableStateOf(credManRepo.getCredentialInitialUiState())        private set +  val dialogResult: MutableLiveData<DialogResult> by lazy { +    MutableLiveData<DialogResult>() +  } + +  fun observeDialogResult(): LiveData<DialogResult> { +    return dialogResult +  } +    fun onCredentailSelected(credentialId: Int) {      Log.d("Account Selector", "credential selected: $credentialId") +    CredentialManagerRepo.getInstance().onOptionSelected( +      uiState.selectedProvider!!.name, +      credentialId +    ) +    dialogResult.value = DialogResult( +      ResultState.COMPLETE, +    )    }    fun onMoreOptionSelected() {      Log.d("Account Selector", "More Option selected")    } + +  fun onCancel() { +    CredentialManagerRepo.getInstance().onCancel() +    dialogResult.value = DialogResult(ResultState.CANCELED) +  }  } diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp index 332bebffb637..c35fb3b17880 100644 --- a/packages/SettingsLib/ActivityEmbedding/Android.bp +++ b/packages/SettingsLib/ActivityEmbedding/Android.bp @@ -26,4 +26,9 @@ android_library {          "androidx.window.extensions",          "androidx.window.sidecar",      ], + +    apex_available: [ +        "//apex_available:platform", +        "com.android.permission", +    ],  } diff --git a/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml index 27425589c822..0949e1defc2f 100644 --- a/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml +++ b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml @@ -21,6 +21,7 @@      <uses-sdk android:minSdkVersion="21" />      <application> +        <uses-library android:name="org.apache.http.legacy" android:required="false" />          <uses-library android:name="androidx.window.extensions" android:required="false" />          <uses-library android:name="androidx.window.sidecar" android:required="false" />      </application> diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle index 811cdd804f8b..68c63dad6726 100644 --- a/packages/SettingsLib/Spa/build.gradle +++ b/packages/SettingsLib/Spa/build.gradle @@ -17,6 +17,7 @@  buildscript {      ext {          spa_min_sdk = 21 +        spa_target_sdk = 33          jetpack_compose_version = '1.2.0-alpha04'          jetpack_compose_compiler_version = '1.3.2'          jetpack_compose_material3_version = '1.0.0-alpha06' diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml index 0a4972fac1bb..f1a24aff4319 100644 --- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml +++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml @@ -17,6 +17,8 @@  <manifest xmlns:android="http://schemas.android.com/apk/res/android"      package="com.android.settingslib.spa.gallery"> +    <uses-sdk android:minSdkVersion="21"/> +      <application          android:name=".GalleryApplication"          android:icon="@mipmap/ic_launcher" @@ -32,14 +34,24 @@              </intent-filter>          </activity> +        <provider +            android:name=".GalleryEntryProvider" +            android:authorities="com.android.spa.gallery.provider" +            android:enabled="true" +            android:exported="false"> +        </provider> +          <activity -            android:name=".GalleryDebugActivity" +            android:name="com.android.settingslib.spa.framework.debug.BlankActivity" +            android:exported="true"> +        </activity> +        <activity +            android:name="com.android.settingslib.spa.framework.debug.DebugActivity"              android:exported="true">          </activity> -          <provider -            android:name=".GalleryEntryProvider" -            android:authorities="com.android.spa.gallery.provider" +            android:name="com.android.settingslib.spa.framework.debug.DebugProvider" +            android:authorities="com.android.spa.gallery.debug"              android:enabled="true"              android:exported="false">          </provider> diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle index 551a0b107e3a..c1ce7d96702d 100644 --- a/packages/SettingsLib/Spa/gallery/build.gradle +++ b/packages/SettingsLib/Spa/gallery/build.gradle @@ -21,12 +21,12 @@ plugins {  android {      namespace 'com.android.settingslib.spa.gallery' -    compileSdk 33 +    compileSdk spa_target_sdk      defaultConfig {          applicationId "com.android.settingslib.spa.gallery"          minSdk spa_min_sdk -        targetSdk 33 +        targetSdk spa_target_sdk          versionCode 1          versionName "1.0"      } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt index 0f95bf6f32c7..7567c6daf996 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt @@ -33,8 +33,8 @@ import com.android.settingslib.spa.framework.common.SettingsPage  import com.android.settingslib.spa.framework.common.SettingsPageProvider  import com.android.settingslib.spa.framework.compose.navigator  import com.android.settingslib.spa.framework.theme.SettingsTheme -import com.android.settingslib.spa.widget.SettingsSlider -import com.android.settingslib.spa.widget.SettingsSliderModel +import com.android.settingslib.spa.widget.preference.SliderPreference +import com.android.settingslib.spa.widget.preference.SliderPreferenceModel  import com.android.settingslib.spa.widget.preference.Preference  import com.android.settingslib.spa.widget.preference.PreferenceModel  import com.android.settingslib.spa.widget.scaffold.RegularScaffold @@ -51,7 +51,7 @@ object SliderPageProvider : SettingsPageProvider {              SettingsEntryBuilder.create("Simple Slider", owner)                  .setIsAllowSearch(true)                  .setUiLayoutFn { -                    SettingsSlider(object : SettingsSliderModel { +                    SliderPreference(object : SliderPreferenceModel {                          override val title = "Simple Slider"                          override val initValue = 40                      }) @@ -61,7 +61,7 @@ object SliderPageProvider : SettingsPageProvider {              SettingsEntryBuilder.create("Slider with icon", owner)                  .setIsAllowSearch(true)                  .setUiLayoutFn { -                    SettingsSlider(object : SettingsSliderModel { +                    SliderPreference(object : SliderPreferenceModel {                          override val title = "Slider with icon"                          override val initValue = 30                          override val onValueChangeFinished = { @@ -78,7 +78,7 @@ object SliderPageProvider : SettingsPageProvider {                      val initValue = 0                      var icon by remember { mutableStateOf(Icons.Outlined.MusicOff) }                      var sliderPosition by remember { mutableStateOf(initValue) } -                    SettingsSlider(object : SettingsSliderModel { +                    SliderPreference(object : SliderPreferenceModel {                          override val title = "Slider with changeable icon"                          override val initValue = initValue                          override val onValueChange = { it: Int -> @@ -96,7 +96,7 @@ object SliderPageProvider : SettingsPageProvider {              SettingsEntryBuilder.create("Slider with steps", owner)                  .setIsAllowSearch(true)                  .setUiLayoutFn { -                    SettingsSlider(object : SettingsSliderModel { +                    SliderPreference(object : SliderPreferenceModel {                          override val title = "Slider with steps"                          override val initValue = 2                          override val valueRange = 1..5 diff --git a/packages/SettingsLib/Spa/spa/AndroidManifest.xml b/packages/SettingsLib/Spa/spa/AndroidManifest.xml index 410bcdb36782..62800bd34217 100644 --- a/packages/SettingsLib/Spa/spa/AndroidManifest.xml +++ b/packages/SettingsLib/Spa/spa/AndroidManifest.xml @@ -14,4 +14,7 @@    limitations under the License.    --> -<manifest package="com.android.settingslib.spa" /> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" +    package="com.android.settingslib.spa"> +    <uses-sdk android:minSdkVersion="21"/> +</manifest> diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle index 7e05e75804a7..c5874113ef32 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle +++ b/packages/SettingsLib/Spa/spa/build.gradle @@ -21,11 +21,11 @@ plugins {  android {      namespace 'com.android.settingslib.spa' -    compileSdk 33 +    compileSdk spa_target_sdk      defaultConfig {          minSdk spa_min_sdk -        targetSdk 33 +        targetSdk spa_target_sdk      }      sourceSets { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt index 532f63b67c5d..d6317085e4f9 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt @@ -16,21 +16,22 @@  package com.android.settingslib.spa.framework -import android.content.ComponentName  import android.content.ContentProvider  import android.content.ContentValues  import android.content.Context  import android.content.Intent -import android.content.Intent.URI_INTENT_SCHEME  import android.content.UriMatcher  import android.content.pm.ProviderInfo  import android.database.Cursor  import android.database.MatrixCursor  import android.net.Uri  import android.util.Log +import com.android.settingslib.spa.framework.common.ColumnEnum +import com.android.settingslib.spa.framework.common.QueryEnum  import com.android.settingslib.spa.framework.common.SettingsEntry -import com.android.settingslib.spa.framework.common.SettingsPage  import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory +import com.android.settingslib.spa.framework.common.addUri +import com.android.settingslib.spa.framework.common.getColumns  private const val TAG = "EntryProvider" @@ -39,117 +40,15 @@ private const val TAG = "EntryProvider"   * One can query the provider result by:   *   $ adb shell content query --uri content://<AuthorityPath>/<QueryPath>   * For gallery, AuthorityPath = com.android.spa.gallery.provider - * For SettingsGoogle, AuthorityPath = com.android.settings.spa.provider + * For Settings, AuthorityPath = com.android.settings.spa.provider   * Some examples: - *   $ adb shell content query --uri content://<AuthorityPath>/page_debug - *   $ adb shell content query --uri content://<AuthorityPath>/entry_debug - *   $ adb shell content query --uri content://<AuthorityPath>/page_info - *   $ adb shell content query --uri content://<AuthorityPath>/entry_info   *   $ adb shell content query --uri content://<AuthorityPath>/search_sitemap   *   $ adb shell content query --uri content://<AuthorityPath>/search_static   *   $ adb shell content query --uri content://<AuthorityPath>/search_dynamic   */  open class EntryProvider : ContentProvider() {      private val spaEnvironment get() = SpaEnvironmentFactory.instance - -    /** -     * Enum to define all column names in provider. -     */ -    enum class ColumnEnum(val id: String) { -        // Columns related to page -        PAGE_ID("pageId"), -        PAGE_NAME("pageName"), -        PAGE_ROUTE("pageRoute"), -        PAGE_INTENT_URI("pageIntent"), -        PAGE_ENTRY_COUNT("entryCount"), -        HAS_RUNTIME_PARAM("hasRuntimeParam"), -        PAGE_START_ADB("pageStartAdb"), - -        // Columns related to entry -        ENTRY_ID("entryId"), -        ENTRY_NAME("entryName"), -        ENTRY_ROUTE("entryRoute"), -        ENTRY_INTENT_URI("entryIntent"), -        ENTRY_HIERARCHY_PATH("entryPath"), -        ENTRY_START_ADB("entryStartAdb"), - -        // Columns related to search -        ENTRY_TITLE("entryTitle"), -        ENTRY_SEARCH_KEYWORD("entrySearchKw"), -    } - -    /** -     * Enum to define all queries supported in the provider. -     */ -    enum class QueryEnum( -        val queryPath: String, -        val queryMatchCode: Int, -        val columnNames: List<ColumnEnum> -    ) { -        // For debug -        PAGE_DEBUG_QUERY( -            "page_debug", 1, -            listOf(ColumnEnum.PAGE_START_ADB) -        ), -        ENTRY_DEBUG_QUERY( -            "entry_debug", 2, -            listOf(ColumnEnum.ENTRY_START_ADB) -        ), - -        // page related queries. -        PAGE_INFO_QUERY( -            "page_info", 100, -            listOf( -                ColumnEnum.PAGE_ID, -                ColumnEnum.PAGE_NAME, -                ColumnEnum.PAGE_ROUTE, -                ColumnEnum.PAGE_INTENT_URI, -                ColumnEnum.PAGE_ENTRY_COUNT, -                ColumnEnum.HAS_RUNTIME_PARAM, -            ) -        ), - -        // entry related queries -        ENTRY_INFO_QUERY( -            "entry_info", 200, -            listOf( -                ColumnEnum.ENTRY_ID, -                ColumnEnum.ENTRY_NAME, -                ColumnEnum.ENTRY_ROUTE, -                ColumnEnum.ENTRY_INTENT_URI, -            ) -        ), - -        // Search related queries -        SEARCH_SITEMAP_QUERY( -            "search_sitemap", 300, -            listOf( -                ColumnEnum.ENTRY_ID, -                ColumnEnum.ENTRY_HIERARCHY_PATH, -            ) -        ), -        SEARCH_STATIC_DATA_QUERY( -            "search_static", 301, -            listOf( -                ColumnEnum.ENTRY_ID, -                ColumnEnum.ENTRY_TITLE, -                ColumnEnum.ENTRY_SEARCH_KEYWORD, -            ) -        ), -        SEARCH_DYNAMIC_DATA_QUERY( -            "search_dynamic", 302, -            listOf( -                ColumnEnum.ENTRY_ID, -                ColumnEnum.ENTRY_TITLE, -                ColumnEnum.ENTRY_SEARCH_KEYWORD, -            ) -        ), -    } -      private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH) -    private fun addUri(authority: String, query: QueryEnum) { -        uriMatcher.addURI(authority, query.queryPath, query.queryMatchCode) -    }      override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {          TODO("Implement this to handle requests to delete one or more rows") @@ -182,13 +81,9 @@ open class EntryProvider : ContentProvider() {      override fun attachInfo(context: Context?, info: ProviderInfo?) {          if (info != null) { -            addUri(info.authority, QueryEnum.PAGE_DEBUG_QUERY) -            addUri(info.authority, QueryEnum.ENTRY_DEBUG_QUERY) -            addUri(info.authority, QueryEnum.PAGE_INFO_QUERY) -            addUri(info.authority, QueryEnum.ENTRY_INFO_QUERY) -            addUri(info.authority, QueryEnum.SEARCH_SITEMAP_QUERY) -            addUri(info.authority, QueryEnum.SEARCH_STATIC_DATA_QUERY) -            addUri(info.authority, QueryEnum.SEARCH_DYNAMIC_DATA_QUERY) +            QueryEnum.SEARCH_SITEMAP_QUERY.addUri(uriMatcher, info.authority) +            QueryEnum.SEARCH_STATIC_DATA_QUERY.addUri(uriMatcher, info.authority) +            QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.addUri(uriMatcher, info.authority)          }          super.attachInfo(context, info)      } @@ -202,10 +97,6 @@ open class EntryProvider : ContentProvider() {      ): Cursor? {          return try {              when (uriMatcher.match(uri)) { -                QueryEnum.PAGE_DEBUG_QUERY.queryMatchCode -> queryPageDebug() -                QueryEnum.ENTRY_DEBUG_QUERY.queryMatchCode -> queryEntryDebug() -                QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo() -                QueryEnum.ENTRY_INFO_QUERY.queryMatchCode -> queryEntryInfo()                  QueryEnum.SEARCH_SITEMAP_QUERY.queryMatchCode -> querySearchSitemap()                  QueryEnum.SEARCH_STATIC_DATA_QUERY.queryMatchCode -> querySearchStaticData()                  QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.queryMatchCode -> querySearchDynamicData() @@ -219,73 +110,18 @@ open class EntryProvider : ContentProvider() {          }      } -    private fun queryPageDebug(): Cursor { -        val entryRepository by spaEnvironment.entryRepository -        val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns()) -        for (pageWithEntry in entryRepository.getAllPageWithEntry()) { -            val command = createBrowsePageAdbCommand(pageWithEntry.page) -            if (command != null) { -                cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command) -            } -        } -        return cursor -    } - -    private fun queryEntryDebug(): Cursor { -        val entryRepository by spaEnvironment.entryRepository -        val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns()) -        for (entry in entryRepository.getAllEntries()) { -            val command = createBrowsePageAdbCommand(entry.containerPage(), entry.id) -            if (command != null) { -                cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command) -            } -        } -        return cursor -    } - -    private fun queryPageInfo(): Cursor { -        val entryRepository by spaEnvironment.entryRepository -        val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns()) -        for (pageWithEntry in entryRepository.getAllPageWithEntry()) { -            val page = pageWithEntry.page -            cursor.newRow() -                .add(ColumnEnum.PAGE_ID.id, page.id) -                .add(ColumnEnum.PAGE_NAME.id, page.displayName) -                .add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute()) -                .add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size) -                .add(ColumnEnum.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0) -                .add( -                    ColumnEnum.PAGE_INTENT_URI.id, -                    createBrowsePageIntent(page).toUri(URI_INTENT_SCHEME) -                ) -        } -        return cursor -    } - -    private fun queryEntryInfo(): Cursor { -        val entryRepository by spaEnvironment.entryRepository -        val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns()) -        for (entry in entryRepository.getAllEntries()) { -            cursor.newRow() -                .add(ColumnEnum.ENTRY_ID.id, entry.id) -                .add(ColumnEnum.ENTRY_NAME.id, entry.displayName) -                .add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute()) -                .add( -                    ColumnEnum.ENTRY_INTENT_URI.id, -                    createBrowsePageIntent(entry.containerPage(), entry.id).toUri(URI_INTENT_SCHEME) -                ) -        } -        return cursor -    } -      private fun querySearchSitemap(): Cursor {          val entryRepository by spaEnvironment.entryRepository          val cursor = MatrixCursor(QueryEnum.SEARCH_SITEMAP_QUERY.getColumns())          for (entry in entryRepository.getAllEntries()) {              if (!entry.isAllowSearch) continue +            val intent = entry.containerPage() +                .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id) +                ?: Intent()              cursor.newRow()                  .add(ColumnEnum.ENTRY_ID.id, entry.id)                  .add(ColumnEnum.ENTRY_HIERARCHY_PATH.id, entryRepository.getEntryPath(entry.id)) +                .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME))          }          return cursor      } @@ -321,54 +157,4 @@ open class EntryProvider : ContentProvider() {                  searchData?.keyword ?: emptyList<String>()              )      } - -    private fun createBrowsePageIntent(page: SettingsPage, entryId: String? = null): Intent { -        if (!isPageBrowsable(page)) return Intent() -        return Intent().setComponent(ComponentName(context!!, spaEnvironment.browseActivityClass!!)) -            .apply { -                putExtra(BrowseActivity.KEY_DESTINATION, page.buildRoute()) -                if (entryId != null) { -                    putExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY, entryId) -                } -            } -    } - -    private fun createBrowsePageAdbCommand(page: SettingsPage, entryId: String? = null): String? { -        if (!isPageBrowsable(page)) return null -        val packageName = context!!.packageName -        val activityName = spaEnvironment.browseActivityClass!!.name.replace(packageName, "") -        val destinationParam = " -e ${BrowseActivity.KEY_DESTINATION} ${page.buildRoute()}" -        val highlightParam = -            if (entryId != null) " -e ${BrowseActivity.KEY_HIGHLIGHT_ENTRY} $entryId" else "" -        return "adb shell am start -n $packageName/$activityName$destinationParam$highlightParam" -    } - -    private fun isPageBrowsable(page: SettingsPage): Boolean { -        return context != null && -            spaEnvironment.browseActivityClass != null && -            !page.hasRuntimeParam() -    } -} - -fun EntryProvider.QueryEnum.getColumns(): Array<String> { -    return columnNames.map { it.id }.toTypedArray() -} - -fun EntryProvider.QueryEnum.getIndex(name: EntryProvider.ColumnEnum): Int { -    return columnNames.indexOf(name) -} - -fun Cursor.getString(query: EntryProvider.QueryEnum, columnName: EntryProvider.ColumnEnum): String { -    return this.getString(query.getIndex(columnName)) -} - -fun Cursor.getInt(query: EntryProvider.QueryEnum, columnName: EntryProvider.ColumnEnum): Int { -    return this.getInt(query.getIndex(columnName)) -} - -fun Cursor.getBoolean( -    query: EntryProvider.QueryEnum, -    columnName: EntryProvider.ColumnEnum -): Boolean { -    return this.getInt(query.getIndex(columnName)) == 1  } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt new file mode 100644 index 000000000000..0707429505c8 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.framework.common + +import android.content.UriMatcher + +/** + * Enum to define all column names in provider. + */ +enum class ColumnEnum(val id: String) { +    // Columns related to page +    PAGE_ID("pageId"), +    PAGE_NAME("pageName"), +    PAGE_ROUTE("pageRoute"), +    PAGE_INTENT_URI("pageIntent"), +    PAGE_ENTRY_COUNT("entryCount"), +    HAS_RUNTIME_PARAM("hasRuntimeParam"), +    PAGE_START_ADB("pageStartAdb"), + +    // Columns related to entry +    ENTRY_ID("entryId"), +    ENTRY_NAME("entryName"), +    ENTRY_ROUTE("entryRoute"), +    ENTRY_INTENT_URI("entryIntent"), +    ENTRY_HIERARCHY_PATH("entryPath"), +    ENTRY_START_ADB("entryStartAdb"), + +    // Columns related to search +    ENTRY_TITLE("entryTitle"), +    ENTRY_SEARCH_KEYWORD("entrySearchKw"), +} + +/** + * Enum to define all queries supported in the provider. + */ +enum class QueryEnum( +    val queryPath: String, +    val queryMatchCode: Int, +    val columnNames: List<ColumnEnum> +) { +    // For debug +    PAGE_DEBUG_QUERY( +        "page_debug", 1, +        listOf(ColumnEnum.PAGE_START_ADB) +    ), +    ENTRY_DEBUG_QUERY( +        "entry_debug", 2, +        listOf(ColumnEnum.ENTRY_START_ADB) +    ), + +    // page related queries. +    PAGE_INFO_QUERY( +        "page_info", 100, +        listOf( +            ColumnEnum.PAGE_ID, +            ColumnEnum.PAGE_NAME, +            ColumnEnum.PAGE_ROUTE, +            ColumnEnum.PAGE_INTENT_URI, +            ColumnEnum.PAGE_ENTRY_COUNT, +            ColumnEnum.HAS_RUNTIME_PARAM, +        ) +    ), + +    // entry related queries +    ENTRY_INFO_QUERY( +        "entry_info", 200, +        listOf( +            ColumnEnum.ENTRY_ID, +            ColumnEnum.ENTRY_NAME, +            ColumnEnum.ENTRY_ROUTE, +            ColumnEnum.ENTRY_INTENT_URI, +        ) +    ), + +    // Search related queries +    SEARCH_SITEMAP_QUERY( +        "search_sitemap", 300, +        listOf( +            ColumnEnum.ENTRY_ID, +            ColumnEnum.ENTRY_HIERARCHY_PATH, +            ColumnEnum.ENTRY_INTENT_URI, +        ) +    ), +    SEARCH_STATIC_DATA_QUERY( +        "search_static", 301, +        listOf( +            ColumnEnum.ENTRY_ID, +            ColumnEnum.ENTRY_TITLE, +            ColumnEnum.ENTRY_SEARCH_KEYWORD, +        ) +    ), +    SEARCH_DYNAMIC_DATA_QUERY( +        "search_dynamic", 302, +        listOf( +            ColumnEnum.ENTRY_ID, +            ColumnEnum.ENTRY_TITLE, +            ColumnEnum.ENTRY_SEARCH_KEYWORD, +        ) +    ), +} + +internal fun QueryEnum.getColumns(): Array<String> { +    return columnNames.map { it.id }.toTypedArray() +} + +internal fun QueryEnum.getIndex(name: ColumnEnum): Int { +    return columnNames.indexOf(name) +} + +internal fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) { +    uriMatcher.addURI(authority, queryPath, queryMatchCode) +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt index a3aeda6f086c..fb42f01b14f8 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt @@ -17,15 +17,11 @@  package com.android.settingslib.spa.framework.common  import android.os.Bundle -import android.widget.Toast  import androidx.compose.runtime.Composable  import androidx.compose.runtime.CompositionLocalProvider  import androidx.compose.runtime.ProvidedValue  import androidx.compose.runtime.compositionLocalOf -import androidx.compose.runtime.mutableStateOf  import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.platform.LocalContext  import com.android.settingslib.spa.framework.compose.LocalNavController  const val INJECT_ENTRY_NAME = "INJECT" @@ -41,7 +37,7 @@ interface EntryData {  }  val LocalEntryDataProvider = -    compositionLocalOf<EntryData> { object : EntryData{} } +    compositionLocalOf<EntryData> { object : EntryData {} }  /**   * Defines data of a Settings entry. @@ -126,17 +122,6 @@ data class SettingsEntry(      @Composable      fun UiLayout(runtimeArguments: Bundle? = null) { -        val context = LocalContext.current -        val controller = LocalNavController.current -        val highlight = rememberSaveable { -            mutableStateOf(controller.highlightEntryId == id) -        } -        if (highlight.value) { -            highlight.value = false -            // TODO: Add highlight entry logic -            Toast.makeText(context, "entry $id highlighted", Toast.LENGTH_SHORT).show() -        } -          CompositionLocalProvider(provideLocalEntryData()) {              uiLayoutImpl(fullArgument(runtimeArguments))          } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt index 8f63c47b1a9b..07df96e778c4 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt @@ -16,8 +16,13 @@  package com.android.settingslib.spa.framework.common +import android.app.Activity +import android.content.ComponentName +import android.content.Context +import android.content.Intent  import android.os.Bundle  import androidx.navigation.NamedNavArgument +import com.android.settingslib.spa.framework.BrowseActivity  import com.android.settingslib.spa.framework.util.isRuntimeParam  import com.android.settingslib.spa.framework.util.navLink  import com.android.settingslib.spa.framework.util.normalize @@ -111,6 +116,41 @@ data class SettingsPage(              details = formatDisplayTitle()          )      } + +    fun createBrowseIntent( +        context: Context?, +        browseActivityClass: Class<out Activity>?, +        entryId: String? = null +    ): Intent? { +        if (!isBrowsable(context, browseActivityClass)) return null +        return Intent().setComponent(ComponentName(context!!, browseActivityClass!!)) +            .apply { +                putExtra(BrowseActivity.KEY_DESTINATION, buildRoute()) +                if (entryId != null) { +                    putExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY, entryId) +                } +            } +    } + +    fun createBrowseAdbCommand( +        context: Context?, +        browseActivityClass: Class<out Activity>?, +        entryId: String? = null +    ): String? { +        if (!isBrowsable(context, browseActivityClass)) return null +        val packageName = context!!.packageName +        val activityName = browseActivityClass!!.name.replace(packageName, "") +        val destinationParam = " -e ${BrowseActivity.KEY_DESTINATION} ${buildRoute()}" +        val highlightParam = +            if (entryId != null) " -e ${BrowseActivity.KEY_HIGHLIGHT_ENTRY} $entryId" else "" +        return "adb shell am start -n $packageName/$activityName$destinationParam$highlightParam" +    } + +    fun isBrowsable(context: Context?, browseActivityClass: Class<out Activity>?): Boolean { +        return context != null && +            browseActivityClass != null && +            !hasRuntimeParam() +    }  }  fun String.toHashId(): String { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/TimeMeasurer.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/TimeMeasurer.kt new file mode 100644 index 000000000000..b23f4e083a1e --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/TimeMeasurer.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalTime::class) + +package com.android.settingslib.spa.framework.compose + +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import kotlin.time.ExperimentalTime +import kotlin.time.TimeSource + +const val ENABLE_MEASURE_TIME = false + +interface TimeMeasurer { +    fun log(msg: String) {} +    fun logFirst(msg: String) {} + +    companion object { +        private object EmptyTimeMeasurer : TimeMeasurer + +        @Composable +        fun rememberTimeMeasurer(tag: String): TimeMeasurer = remember { +            if (ENABLE_MEASURE_TIME) TimeMeasurerImpl(tag) else EmptyTimeMeasurer +        } +    } +} + +private class TimeMeasurerImpl(private val tag: String) : TimeMeasurer { +    private val mark = TimeSource.Monotonic.markNow() +    private val msgLogged = mutableSetOf<String>() + +    override fun log(msg: String) { +        Log.d(tag, "Timer $msg: ${mark.elapsedNow()}") +    } + +    override fun logFirst(msg: String) { +        if (msgLogged.add(msg)) { +            Log.d(tag, "Timer $msg: ${mark.elapsedNow()}") +        } +    } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt index 6f968180e243..301508074f30 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt @@ -14,12 +14,9 @@   * limitations under the License.   */ -package com.android.settingslib.spa.framework +package com.android.settingslib.spa.framework.debug -import android.content.Intent -import android.net.Uri  import android.os.Bundle -import android.util.Log  import androidx.activity.ComponentActivity  import androidx.activity.compose.setContent  import androidx.compose.material3.Text @@ -33,8 +30,6 @@ import androidx.navigation.compose.composable  import androidx.navigation.compose.rememberNavController  import androidx.navigation.navArgument  import com.android.settingslib.spa.R -import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION -import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_HIGHLIGHT_ENTRY  import com.android.settingslib.spa.framework.common.LogCategory  import com.android.settingslib.spa.framework.common.SettingsEntry  import com.android.settingslib.spa.framework.common.SettingsPage @@ -60,11 +55,10 @@ private const val PARAM_NAME_ENTRY_ID = "eid"  /**   * The Debug Activity to display all Spa Pages & Entries.   * One can open the debug activity by: - *   $ adb shell am start -n <Activity> - * For gallery, Activity = com.android.settingslib.spa.gallery/.GalleryDebugActivity - * For SettingsGoogle, Activity = com.android.settings/.spa.SpaDebugActivity + *   $ adb shell am start -n <Package>/com.android.settingslib.spa.framework.debug.DebugActivity + * For gallery, Package = com.android.settingslib.spa.gallery   */ -open class DebugActivity : ComponentActivity() { +class DebugActivity : ComponentActivity() {      private val spaEnvironment get() = SpaEnvironmentFactory.instance      override fun onCreate(savedInstanceState: Bundle?) { @@ -79,30 +73,6 @@ open class DebugActivity : ComponentActivity() {          }      } -    private fun displayDebugMessage() { -        val entryProviderAuthorities = spaEnvironment.entryProviderAuthorities ?: return - -        try { -            val query = EntryProvider.QueryEnum.PAGE_INFO_QUERY -            contentResolver.query( -                Uri.parse("content://$entryProviderAuthorities/${query.queryPath}"), -                null, null, null -            ).use { cursor -> -                while (cursor != null && cursor.moveToNext()) { -                    val route = cursor.getString(query, EntryProvider.ColumnEnum.PAGE_ROUTE) -                    val entryCount = cursor.getInt(query, EntryProvider.ColumnEnum.PAGE_ENTRY_COUNT) -                    val hasRuntimeParam = -                        cursor.getBoolean(query, EntryProvider.ColumnEnum.HAS_RUNTIME_PARAM) -                    val message = "Page Info: $route ($entryCount) " + -                        (if (hasRuntimeParam) "with" else "no") + "-runtime-params" -                    spaEnvironment.logger.message(TAG, message, category = LogCategory.FRAMEWORK) -                } -            } -        } catch (e: Exception) { -            Log.e(TAG, "Provider querying exception:", e) -        } -    } -      @Composable      private fun MainContent() {          val navController = rememberNavController() @@ -141,11 +111,6 @@ open class DebugActivity : ComponentActivity() {                  override val title = "List All Entries (${allEntry.size})"                  override val onClick = navigator(route = ROUTE_All_ENTRIES)              }) -            Preference(object : PreferenceModel { -                override val title = "Query EntryProvider" -                override val enabled = isEntryProviderAvailable().toState() -                override val onClick = { displayDebugMessage() } -            })          }      } @@ -177,6 +142,7 @@ open class DebugActivity : ComponentActivity() {      @Composable      fun OnePage(arguments: Bundle?) { +        val context = LocalContext.current          val entryRepository by spaEnvironment.entryRepository          val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "")          val pageWithEntry = entryRepository.getPageWithEntry(id)!! @@ -186,7 +152,9 @@ open class DebugActivity : ComponentActivity() {              Text(text = "Entry size: ${pageWithEntry.entries.size}")              Preference(model = object : PreferenceModel {                  override val title = "open page" -                override val enabled = isPageClickable(pageWithEntry.page).toState() +                override val enabled = +                    pageWithEntry.page.isBrowsable(context, spaEnvironment.browseActivityClass) +                        .toState()                  override val onClick = openPage(pageWithEntry.page)              })              EntryList(pageWithEntry.entries) @@ -195,6 +163,7 @@ open class DebugActivity : ComponentActivity() {      @Composable      fun OneEntry(arguments: Bundle?) { +        val context = LocalContext.current          val entryRepository by spaEnvironment.entryRepository          val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "")          val entry = entryRepository.getEntry(id)!! @@ -202,7 +171,9 @@ open class DebugActivity : ComponentActivity() {          RegularScaffold(title = "Entry - ${entry.displayTitle()}") {              Preference(model = object : PreferenceModel {                  override val title = "open entry" -                override val enabled = isEntryClickable(entry).toState() +                override val enabled = +                    entry.containerPage().isBrowsable(context, spaEnvironment.browseActivityClass) +                        .toState()                  override val onClick = openEntry(entry)              })              Text(text = entryContent) @@ -223,12 +194,10 @@ open class DebugActivity : ComponentActivity() {      @Composable      private fun openPage(page: SettingsPage): (() -> Unit)? { -        if (!isPageClickable(page)) return null          val context = LocalContext.current +        val intent = +            page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: return null          val route = page.buildRoute() -        val intent = Intent(context, spaEnvironment.browseActivityClass).apply { -            putExtra(KEY_DESTINATION, route) -        }          return {              spaEnvironment.logger.message(                  TAG, "OpenPage: $route", category = LogCategory.FRAMEWORK @@ -239,13 +208,11 @@ open class DebugActivity : ComponentActivity() {      @Composable      private fun openEntry(entry: SettingsEntry): (() -> Unit)? { -        if (!isEntryClickable(entry)) return null          val context = LocalContext.current +        val intent = entry.containerPage() +            .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id) +            ?: return null          val route = entry.containerPage().buildRoute() -        val intent = Intent(context, spaEnvironment.browseActivityClass).apply { -            putExtra(KEY_DESTINATION, route) -            putExtra(KEY_HIGHLIGHT_ENTRY, entry.id) -        }          return {              spaEnvironment.logger.message(                  TAG, "OpenEntry: $route", category = LogCategory.FRAMEWORK @@ -253,17 +220,9 @@ open class DebugActivity : ComponentActivity() {              context.startActivity(intent)          }      } - -    private fun isEntryProviderAvailable(): Boolean { -        return spaEnvironment.entryProviderAuthorities != null -    } - -    private fun isPageClickable(page: SettingsPage): Boolean { -        return spaEnvironment.browseActivityClass != null && !page.hasRuntimeParam() -    } - -    private fun isEntryClickable(entry: SettingsEntry): Boolean { -        return spaEnvironment.browseActivityClass != null && -            !entry.containerPage().hasRuntimeParam() -    }  } + +/** + * A blank activity without any page. + */ +class BlankActivity : ComponentActivity() diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt new file mode 100644 index 000000000000..6c271094de9f --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.framework.debug + +import android.content.ContentProvider +import android.content.ContentValues +import android.content.Context +import android.content.Intent +import android.content.Intent.URI_INTENT_SCHEME +import android.content.UriMatcher +import android.content.pm.ProviderInfo +import android.database.Cursor +import android.database.MatrixCursor +import android.net.Uri +import android.util.Log +import com.android.settingslib.spa.framework.common.ColumnEnum +import com.android.settingslib.spa.framework.common.QueryEnum +import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory +import com.android.settingslib.spa.framework.common.addUri +import com.android.settingslib.spa.framework.common.getColumns + +private const val TAG = "DebugProvider" + +/** + * The content provider to return debug data. + * One can query the provider result by: + *   $ adb shell content query --uri content://<AuthorityPath>/<QueryPath> + * For gallery, AuthorityPath = com.android.spa.gallery.debug + * Some examples: + *   $ adb shell content query --uri content://<AuthorityPath>/page_debug + *   $ adb shell content query --uri content://<AuthorityPath>/entry_debug + *   $ adb shell content query --uri content://<AuthorityPath>/page_info + *   $ adb shell content query --uri content://<AuthorityPath>/entry_info + */ +class DebugProvider : ContentProvider() { +    private val spaEnvironment get() = SpaEnvironmentFactory.instance +    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH) + +    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int { +        TODO("Implement this to handle requests to delete one or more rows") +    } + +    override fun getType(uri: Uri): String? { +        TODO( +            "Implement this to handle requests for the MIME type of the data" + +                "at the given URI" +        ) +    } + +    override fun insert(uri: Uri, values: ContentValues?): Uri? { +        TODO("Implement this to handle requests to insert a new row.") +    } + +    override fun update( +        uri: Uri, +        values: ContentValues?, +        selection: String?, +        selectionArgs: Array<String>? +    ): Int { +        TODO("Implement this to handle requests to update one or more rows.") +    } + +    override fun onCreate(): Boolean { +        Log.d(TAG, "onCreate") +        return true +    } + +    override fun attachInfo(context: Context?, info: ProviderInfo?) { +        if (info != null) { +            QueryEnum.PAGE_DEBUG_QUERY.addUri(uriMatcher, info.authority) +            QueryEnum.ENTRY_DEBUG_QUERY.addUri(uriMatcher, info.authority) +            QueryEnum.PAGE_INFO_QUERY.addUri(uriMatcher, info.authority) +            QueryEnum.ENTRY_INFO_QUERY.addUri(uriMatcher, info.authority) +        } +        super.attachInfo(context, info) +    } + +    override fun query( +        uri: Uri, +        projection: Array<String>?, +        selection: String?, +        selectionArgs: Array<String>?, +        sortOrder: String? +    ): Cursor? { +        return try { +            when (uriMatcher.match(uri)) { +                QueryEnum.PAGE_DEBUG_QUERY.queryMatchCode -> queryPageDebug() +                QueryEnum.ENTRY_DEBUG_QUERY.queryMatchCode -> queryEntryDebug() +                QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo() +                QueryEnum.ENTRY_INFO_QUERY.queryMatchCode -> queryEntryInfo() +                else -> throw UnsupportedOperationException("Unknown Uri $uri") +            } +        } catch (e: UnsupportedOperationException) { +            throw e +        } catch (e: Exception) { +            Log.e(TAG, "Provider querying exception:", e) +            null +        } +    } + +    private fun queryPageDebug(): Cursor { +        val entryRepository by spaEnvironment.entryRepository +        val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns()) +        for (pageWithEntry in entryRepository.getAllPageWithEntry()) { +            val command = pageWithEntry.page.createBrowseAdbCommand( +                context, +                spaEnvironment.browseActivityClass +            ) +            if (command != null) { +                cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command) +            } +        } +        return cursor +    } + +    private fun queryEntryDebug(): Cursor { +        val entryRepository by spaEnvironment.entryRepository +        val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns()) +        for (entry in entryRepository.getAllEntries()) { +            val command = entry.containerPage() +                .createBrowseAdbCommand(context, spaEnvironment.browseActivityClass, entry.id) +            if (command != null) { +                cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command) +            } +        } +        return cursor +    } + +    private fun queryPageInfo(): Cursor { +        val entryRepository by spaEnvironment.entryRepository +        val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns()) +        for (pageWithEntry in entryRepository.getAllPageWithEntry()) { +            val page = pageWithEntry.page +            val intent = +                page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: Intent() +            cursor.newRow() +                .add(ColumnEnum.PAGE_ID.id, page.id) +                .add(ColumnEnum.PAGE_NAME.id, page.displayName) +                .add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute()) +                .add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size) +                .add(ColumnEnum.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0) +                .add(ColumnEnum.PAGE_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME)) +        } +        return cursor +    } + +    private fun queryEntryInfo(): Cursor { +        val entryRepository by spaEnvironment.entryRepository +        val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns()) +        for (entry in entryRepository.getAllEntries()) { +            val intent = entry.containerPage() +                .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id) +                ?: Intent() +            cursor.newRow() +                .add(ColumnEnum.ENTRY_ID.id, entry.id) +                .add(ColumnEnum.ENTRY_NAME.id, entry.displayName) +                .add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute()) +                .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME)) +        } +        return cursor +    } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt index 3fa8c658e198..52c489324699 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt @@ -44,3 +44,6 @@ internal fun materialColorScheme(isDarkTheme: Boolean): ColorScheme {  val ColorScheme.divider: Color      get() = onSurface.copy(SettingsOpacity.Divider) + +val ColorScheme.surfaceTone: Color +    get() = primary.copy(SettingsOpacity.SurfaceTone) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt index 11af6ce0beab..69ddf01b6170 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt @@ -20,4 +20,5 @@ object SettingsOpacity {      const val Full = 1f      const val Disabled = 0.38f      const val Divider = 0.2f +    const val SurfaceTone = 0.14f  } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt index 6c7432eee16a..8d0a35c371e3 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt @@ -23,7 +23,7 @@ import com.android.settingslib.spa.framework.common.LogEvent  import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory  @Composable -fun LogEntryEvent(): (event: LogEvent) -> Unit { +fun logEntryEvent(): (event: LogEvent) -> Unit {      val entryId = LocalEntryDataProvider.current.entryId ?: return {}      return {          SpaEnvironmentFactory.instance.logger.event(entryId, it, category = LogCategory.VIEW) @@ -31,9 +31,9 @@ fun LogEntryEvent(): (event: LogEvent) -> Unit {  }  @Composable -fun WrapOnClickWithLog(onClick: (() -> Unit)?): (() -> Unit)? { +fun wrapOnClickWithLog(onClick: (() -> Unit)?): (() -> Unit)? {      if (onClick == null) return null -    val logEvent = LogEntryEvent() +    val logEvent = logEntryEvent()      return {          logEvent(LogEvent.ENTRY_CLICK)          onClick() @@ -41,9 +41,9 @@ fun WrapOnClickWithLog(onClick: (() -> Unit)?): (() -> Unit)? {  }  @Composable -fun WrapOnSwitchWithLog(onSwitch: ((checked: Boolean) -> Unit)?): ((checked: Boolean) -> Unit)? { +fun wrapOnSwitchWithLog(onSwitch: ((checked: Boolean) -> Unit)?): ((checked: Boolean) -> Unit)? {      if (onSwitch == null) return null -    val logEvent = LogEntryEvent() +    val logEvent = logEntryEvent()      return {          val event = if (it) LogEvent.ENTRY_SWITCH_ON else LogEvent.ENTRY_SWITCH_OFF          logEvent(event) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt index f2fe7ad7635a..db95e23bb52b 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt @@ -28,26 +28,29 @@ import com.android.settingslib.spa.framework.compose.toState  import com.android.settingslib.spa.framework.theme.SettingsDimension  import com.android.settingslib.spa.framework.theme.SettingsShape  import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.util.EntryHighlight  @Composable  fun MainSwitchPreference(model: SwitchPreferenceModel) { -    Surface( -        modifier = Modifier.padding(SettingsDimension.itemPaddingEnd), -        color = when (model.checked.value) { -            true -> MaterialTheme.colorScheme.primaryContainer -            else -> MaterialTheme.colorScheme.secondaryContainer -        }, -        shape = SettingsShape.CornerLarge, -    ) { -        InternalSwitchPreference( -            title = model.title, -            checked = model.checked, -            changeable = model.changeable, -            onCheckedChange = model.onCheckedChange, -            paddingStart = 20.dp, -            paddingEnd = 20.dp, -            paddingVertical = 18.dp, -        ) +    EntryHighlight { +        Surface( +            modifier = Modifier.padding(SettingsDimension.itemPaddingEnd), +            color = when (model.checked.value) { +                true -> MaterialTheme.colorScheme.primaryContainer +                else -> MaterialTheme.colorScheme.secondaryContainer +            }, +            shape = SettingsShape.CornerLarge, +        ) { +            InternalSwitchPreference( +                title = model.title, +                checked = model.checked, +                changeable = model.changeable, +                onCheckedChange = model.onCheckedChange, +                paddingStart = 20.dp, +                paddingEnd = 20.dp, +                paddingVertical = 18.dp, +            ) +        }      }  } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt index d1021e2783b8..6ebe6bb9dfa3 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt @@ -26,8 +26,9 @@ import com.android.settingslib.spa.framework.common.EntryMacro  import com.android.settingslib.spa.framework.common.EntrySearchData  import com.android.settingslib.spa.framework.compose.navigator  import com.android.settingslib.spa.framework.compose.stateOf -import com.android.settingslib.spa.framework.util.WrapOnClickWithLog +import com.android.settingslib.spa.framework.util.wrapOnClickWithLog  import com.android.settingslib.spa.widget.ui.createSettingsIcon +import com.android.settingslib.spa.widget.util.EntryHighlight  data class SimplePreferenceMacro(      val title: String, @@ -106,7 +107,7 @@ fun Preference(      model: PreferenceModel,      singleLineSummary: Boolean = false,  ) { -    val onClickWithLog = WrapOnClickWithLog(model.onClick) +    val onClickWithLog = wrapOnClickWithLog(model.onClick)      val modifier = remember(model.enabled.value) {          if (onClickWithLog != null) {              Modifier.clickable( @@ -115,12 +116,14 @@ fun Preference(              )          } else Modifier      } -    BasePreference( -        title = model.title, -        summary = model.summary, -        singleLineSummary = singleLineSummary, -        modifier = modifier, -        icon = model.icon, -        enabled = model.enabled, -    ) +    EntryHighlight { +        BasePreference( +            title = model.title, +            summary = model.summary, +            singleLineSummary = singleLineSummary, +            modifier = modifier, +            icon = model.icon, +            enabled = model.enabled, +        ) +    }  } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/SettingsSlider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt index 4f77a89577ea..4ee2af0fa1b7 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/SettingsSlider.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt @@ -14,14 +14,13 @@   * limitations under the License.   */ -package com.android.settingslib.spa.widget +package com.android.settingslib.spa.widget.preference  import androidx.compose.material.icons.Icons  import androidx.compose.material.icons.outlined.AccessAlarm  import androidx.compose.material.icons.outlined.MusicNote  import androidx.compose.material.icons.outlined.MusicOff  import androidx.compose.material3.Icon -import androidx.compose.material3.Slider  import androidx.compose.runtime.Composable  import androidx.compose.runtime.getValue  import androidx.compose.runtime.mutableStateOf @@ -32,32 +31,32 @@ import androidx.compose.ui.Modifier  import androidx.compose.ui.graphics.vector.ImageVector  import androidx.compose.ui.tooling.preview.Preview  import com.android.settingslib.spa.framework.theme.SettingsTheme -import com.android.settingslib.spa.widget.preference.BaseLayout -import kotlin.math.roundToInt +import com.android.settingslib.spa.widget.ui.SettingsSlider +import com.android.settingslib.spa.widget.util.EntryHighlight  /** - * The widget model for [SettingsSlider] widget. + * The widget model for [SliderPreference] widget.   */ -interface SettingsSliderModel { +interface SliderPreferenceModel {      /** -     * The title of this [SettingsSlider]. +     * The title of this [SliderPreference].       */      val title: String      /** -     * The initial position of the [SettingsSlider]. +     * The initial position of the [SliderPreference].       */      val initValue: Int      /** -     * The value range for this [SettingsSlider]. +     * The value range for this [SliderPreference].       */      val valueRange: IntRange          get() = 0..100      /**       * The lambda to be invoked during the value change by dragging or a click. This callback is -     * used to get the real time value of the [SettingsSlider]. +     * used to get the real time value of the [SliderPreference].       */      val onValueChange: ((value: Int) -> Unit)?          get() = null @@ -70,7 +69,7 @@ interface SettingsSliderModel {          get() = null      /** -     * The icon image for [SettingsSlider]. If not specified, the slider hides the icon by default. +     * The icon image for [SliderPreference]. If not specified, the slider hides the icon by default.       */      val icon: ImageVector?          get() = null @@ -89,46 +88,44 @@ interface SettingsSliderModel {  /**   * Settings slider widget.   * - * Data is provided through [SettingsSliderModel]. + * Data is provided through [SliderPreferenceModel].   */  @Composable -fun SettingsSlider(model: SettingsSliderModel) { -    SettingsSlider( -        title = model.title, -        initValue = model.initValue, -        valueRange = model.valueRange, -        onValueChange = model.onValueChange, -        onValueChangeFinished = model.onValueChangeFinished, -        icon = model.icon, -        showSteps = model.showSteps, -    ) +fun SliderPreference(model: SliderPreferenceModel) { +    EntryHighlight { +        SliderPreference( +            title = model.title, +            initValue = model.initValue, +            valueRange = model.valueRange, +            onValueChange = model.onValueChange, +            onValueChangeFinished = model.onValueChangeFinished, +            icon = model.icon, +            showSteps = model.showSteps, +        ) +    }  }  @Composable -internal fun SettingsSlider( +internal fun SliderPreference(      title: String,      initValue: Int, +    modifier: Modifier = Modifier,      valueRange: IntRange = 0..100,      onValueChange: ((value: Int) -> Unit)? = null,      onValueChangeFinished: (() -> Unit)? = null,      icon: ImageVector? = null,      showSteps: Boolean = false, -    modifier: Modifier = Modifier,  ) { -    var sliderPosition by rememberSaveable { mutableStateOf(initValue.toFloat()) }      BaseLayout(          title = title,          subTitle = { -            Slider( -                value = sliderPosition, -                onValueChange = { -                    sliderPosition = it -                    onValueChange?.invoke(sliderPosition.roundToInt()) -                }, -                modifier = modifier, -                valueRange = valueRange.first.toFloat()..valueRange.last.toFloat(), -                steps = if (showSteps) (valueRange.count() - 2) else 0, -                onValueChangeFinished = onValueChangeFinished, +            SettingsSlider( +                initValue, +                modifier, +                valueRange, +                onValueChange, +                onValueChangeFinished, +                showSteps              )          },          icon = if (icon != null) ({ @@ -139,11 +136,11 @@ internal fun SettingsSlider(  @Preview  @Composable -private fun SettingsSliderPreview() { +private fun SliderPreferencePreview() {      SettingsTheme {          val initValue = 30          var sliderPosition by rememberSaveable { mutableStateOf(initValue) } -        SettingsSlider( +        SliderPreference(              title = "Alarm Volume",              initValue = 30,              onValueChange = { sliderPosition = it }, @@ -157,10 +154,10 @@ private fun SettingsSliderPreview() {  @Preview  @Composable -private fun SettingsSliderIconChangePreview() { +private fun SliderPreferenceIconChangePreview() {      SettingsTheme {          var icon by remember { mutableStateOf(Icons.Outlined.MusicNote) } -        SettingsSlider( +        SliderPreference(              title = "Media Volume",              initValue = 40,              onValueChange = { it: Int -> @@ -173,9 +170,9 @@ private fun SettingsSliderIconChangePreview() {  @Preview  @Composable -private fun SettingsSliderStepsPreview() { +private fun SliderPreferenceStepsPreview() {      SettingsTheme { -        SettingsSlider( +        SliderPreference(              title = "Display Text",              initValue = 2,              valueRange = 1..5, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt index 992ce9e6c872..2d606193872d 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt @@ -31,8 +31,9 @@ import com.android.settingslib.spa.framework.compose.stateOf  import com.android.settingslib.spa.framework.compose.toState  import com.android.settingslib.spa.framework.theme.SettingsDimension  import com.android.settingslib.spa.framework.theme.SettingsTheme -import com.android.settingslib.spa.framework.util.WrapOnSwitchWithLog +import com.android.settingslib.spa.framework.util.wrapOnSwitchWithLog  import com.android.settingslib.spa.widget.ui.SettingsSwitch +import com.android.settingslib.spa.widget.util.EntryHighlight  /**   * The widget model for [SwitchPreference] widget. @@ -79,13 +80,15 @@ interface SwitchPreferenceModel {   */  @Composable  fun SwitchPreference(model: SwitchPreferenceModel) { -    InternalSwitchPreference( -        title = model.title, -        summary = model.summary, -        checked = model.checked, -        changeable = model.changeable, -        onCheckedChange = model.onCheckedChange, -    ) +    EntryHighlight { +        InternalSwitchPreference( +            title = model.title, +            summary = model.summary, +            checked = model.checked, +            changeable = model.changeable, +            onCheckedChange = model.onCheckedChange, +        ) +    }  }  @Composable @@ -101,7 +104,7 @@ internal fun InternalSwitchPreference(  ) {      val checkedValue = checked.value      val indication = LocalIndication.current -    val onChangeWithLog = WrapOnSwitchWithLog(onCheckedChange) +    val onChangeWithLog = wrapOnSwitchWithLog(onCheckedChange)      val modifier = remember(checkedValue, changeable.value) {          if (checkedValue != null && onChangeWithLog != null) {              Modifier.toggleable( diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt index f1541b7f4008..fbfcaaa28047 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt @@ -17,6 +17,7 @@  package com.android.settingslib.spa.widget.preference  import androidx.compose.runtime.Composable +import com.android.settingslib.spa.widget.util.EntryHighlight  import com.android.settingslib.spa.widget.ui.SettingsSwitch  @Composable @@ -25,16 +26,18 @@ fun TwoTargetSwitchPreference(      icon: @Composable (() -> Unit)? = null,      onClick: () -> Unit,  ) { -    TwoTargetPreference( -        title = model.title, -        summary = model.summary, -        onClick = onClick, -        icon = icon, -    ) { -        SettingsSwitch( -            checked = model.checked, -            changeable = model.changeable, -            onCheckedChange = model.onCheckedChange, -        ) +    EntryHighlight { +        TwoTargetPreference( +            title = model.title, +            summary = model.summary, +            onClick = onClick, +            icon = icon, +        ) { +            SettingsSwitch( +                checked = model.checked, +                changeable = model.changeable, +                onCheckedChange = model.onCheckedChange, +            ) +        }      }  } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt new file mode 100644 index 000000000000..48fec3bd8ad7 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.ui + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import com.android.settingslib.spa.framework.theme.surfaceTone +import kotlin.math.roundToInt + +@Composable +fun SettingsSlider( +    initValue: Int, +    modifier: Modifier = Modifier, +    valueRange: IntRange = 0..100, +    onValueChange: ((value: Int) -> Unit)? = null, +    onValueChangeFinished: (() -> Unit)? = null, +    showSteps: Boolean = false, +) { +    var sliderPosition by rememberSaveable { mutableStateOf(initValue.toFloat()) } +    Slider( +        value = sliderPosition, +        onValueChange = { +            sliderPosition = it +            onValueChange?.invoke(sliderPosition.roundToInt()) +        }, +        modifier = modifier, +        valueRange = valueRange.first.toFloat()..valueRange.last.toFloat(), +        steps = if (showSteps) (valueRange.count() - 2) else 0, +        onValueChangeFinished = onValueChangeFinished, +        colors = SliderDefaults.colors( +            inactiveTrackColor = MaterialTheme.colorScheme.surfaceTone +        ) +    ) +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt index 82ab0be55002..b9690762845e 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt @@ -20,7 +20,7 @@ import androidx.compose.material3.Checkbox  import androidx.compose.material3.ExperimentalMaterial3Api  import androidx.compose.runtime.Composable  import androidx.compose.runtime.State -import com.android.settingslib.spa.framework.util.WrapOnSwitchWithLog +import com.android.settingslib.spa.framework.util.wrapOnSwitchWithLog  @OptIn(ExperimentalMaterial3Api::class)  @Composable @@ -35,7 +35,7 @@ fun SettingsSwitch(      if (checkedValue != null) {          Checkbox(              checked = checkedValue, -            onCheckedChange = WrapOnSwitchWithLog(onCheckedChange), +            onCheckedChange = wrapOnSwitchWithLog(onCheckedChange),              enabled = changeable.value,          )      } else { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt new file mode 100644 index 000000000000..652e54de5e39 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.util + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.android.settingslib.spa.framework.common.LocalEntryDataProvider + +@Composable +internal fun EntryHighlight(UiLayoutFn: @Composable () -> Unit) { +    val entryData = LocalEntryDataProvider.current +    val isHighlighted = rememberSaveable { entryData.isHighlighted } +    val backgroundColor = +        if (isHighlighted) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent +    Box(modifier = Modifier.background(color = backgroundColor)) { +        UiLayoutFn() +    } +} diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp index 1ce49fa520b9..74910456471e 100644 --- a/packages/SettingsLib/Spa/tests/Android.bp +++ b/packages/SettingsLib/Spa/tests/Android.bp @@ -34,4 +34,5 @@ android_test {          "truth-prebuilt",      ],      kotlincflags: ["-Xjvm-default=all"], +    min_sdk_version: "31",  } diff --git a/packages/SettingsLib/Spa/tests/AndroidManifest.xml b/packages/SettingsLib/Spa/tests/AndroidManifest.xml index c224cafa4740..e2db5943ae53 100644 --- a/packages/SettingsLib/Spa/tests/AndroidManifest.xml +++ b/packages/SettingsLib/Spa/tests/AndroidManifest.xml @@ -17,6 +17,8 @@  <manifest xmlns:android="http://schemas.android.com/apk/res/android"      package="com.android.settingslib.spa.tests"> +    <uses-sdk android:minSdkVersion="21"/> +      <application>          <uses-library android:name="android.test.runner" />      </application> diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle index f950e01b9740..b43bf1854c26 100644 --- a/packages/SettingsLib/Spa/tests/build.gradle +++ b/packages/SettingsLib/Spa/tests/build.gradle @@ -21,11 +21,11 @@ plugins {  android {      namespace 'com.android.settingslib.spa.tests' -    compileSdk 33 +    compileSdk spa_target_sdk      defaultConfig {          minSdk spa_min_sdk -        targetSdk 33 +        targetSdk spa_target_sdk          testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"      } diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/SettingsSliderTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt index 1d95e3383b51..7ae11758a0ce 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/SettingsSliderTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt @@ -14,7 +14,7 @@   * limitations under the License.   */ -package com.android.settingslib.spa.widget +package com.android.settingslib.spa.widget.preference  import androidx.compose.ui.test.assertIsDisplayed  import androidx.compose.ui.test.junit4.createComposeRule @@ -25,14 +25,14 @@ import org.junit.Test  import org.junit.runner.RunWith  @RunWith(AndroidJUnit4::class) -class SettingsSliderTest { +class SliderPreferenceTest {      @get:Rule      val composeTestRule = createComposeRule()      @Test      fun title_displayed() {          composeTestRule.setContent { -            SettingsSlider(object : SettingsSliderModel { +            SliderPreference(object : SliderPreferenceModel {                  override val title = "Slider"                  override val initValue = 40              }) @@ -41,5 +41,5 @@ class SettingsSliderTest {          composeTestRule.onNodeWithText("Slider").assertIsDisplayed()      } -    // TODO: Add more unit tests for SettingsSlider widget. +    // TODO: Add more unit tests for SliderPreference widget.  } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt index 1dc52cbfb4ec..99649263c76a 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt @@ -3,6 +3,8 @@ package com.android.settingslib.spaprivileged.framework.common  import android.app.admin.DevicePolicyManager  import android.app.usage.StorageStatsManager  import android.content.Context +import android.content.pm.verify.domain.DomainVerificationManager +import android.os.UserHandle  import android.os.UserManager  /** The [UserManager] instance. */ @@ -13,3 +15,10 @@ val Context.devicePolicyManager get() = getSystemService(DevicePolicyManager::cl  /** The [StorageStatsManager] instance. */  val Context.storageStatsManager get() = getSystemService(StorageStatsManager::class.java)!! + +/** The [DomainVerificationManager] instance. */ +val Context.domainVerificationManager +    get() = getSystemService(DomainVerificationManager::class.java)!! + +/** Gets a new [Context] for the given [UserHandle]. */ +fun Context.asUser(userHandle: UserHandle): Context = createContextAsUser(userHandle, 0) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt index 6318b4e9c186..c5ad1814a518 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.Modifier  import androidx.compose.ui.res.stringResource  import androidx.lifecycle.viewmodel.compose.viewModel  import com.android.settingslib.spa.framework.compose.LogCompositions +import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer  import com.android.settingslib.spa.framework.compose.toState  import com.android.settingslib.spa.framework.theme.SettingsDimension  import com.android.settingslib.spa.widget.ui.PlaceholderTitle @@ -66,7 +67,9 @@ private fun <T : AppRecord> AppListWidget(      listModel: AppListModel<T>,      appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,  ) { +    val timeMeasurer = rememberTimeMeasurer(TAG)      appListData.value?.let { (list, option) -> +        timeMeasurer.logFirst("app list first loaded")          if (list.isEmpty()) {              PlaceholderTitle(stringResource(R.string.no_applications))              return diff --git a/packages/SettingsLib/search/stub-src/com/android/settingslib/search/SearchIndexableResourcesBase.java b/packages/SettingsLib/search/stub-src/com/android/settingslib/search/SearchIndexableResourcesBase.java index 4870d45cff04..4063b9339c4a 100644 --- a/packages/SettingsLib/search/stub-src/com/android/settingslib/search/SearchIndexableResourcesBase.java +++ b/packages/SettingsLib/search/stub-src/com/android/settingslib/search/SearchIndexableResourcesBase.java @@ -24,11 +24,11 @@ import java.util.Collection;  public class SearchIndexableResourcesBase implements SearchIndexableResources {      @Override -    public Collection<Class> getProviderValues() { +    public Collection<SearchIndexableData> getProviderValues() {          throw new RuntimeException("STUB!");      } -    public void addIndex(Class indexClass) { +    public void addIndex(SearchIndexableData indexClass) {          throw new RuntimeException("STUB!");      }  } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index 5662ce6bd808..6bc1160a8d0a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -356,7 +356,7 @@ public class CachedBluetoothDeviceManager {       * @return {@code true}, if the device should pair automatically; Otherwise, return       * {@code false}.       */ -    public synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) { +    private synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) {          boolean isOngoingSetMemberPair = mOngoingSetMemberPair != null;          int bondState = device.getBondState();          if (isOngoingSetMemberPair || bondState != BluetoothDevice.BOND_NONE @@ -365,10 +365,44 @@ public class CachedBluetoothDeviceManager {                      + " , device.getBondState: " + bondState);              return false;          } +        return true; +    } -        Log.d(TAG, "Bond " + device.getName() + " by CSIP"); +    /** +     * Called when we found a set member of a group. The function will check the {@code groupId} if +     * it exists and the bond state of the device is BOND_NONE, and if there isn't any ongoing pair +     * , and then pair the device automatically. +     * +     * @param device The found device +     * @param groupId The group id of the found device +     */ +    public synchronized void pairDeviceByCsip(BluetoothDevice device, int groupId) { +        if (!shouldPairByCsip(device, groupId)) { +            return; +        } +        Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " by CSIP");          mOngoingSetMemberPair = device; -        return true; +        syncConfigFromMainDevice(device, groupId); +        device.createBond(BluetoothDevice.TRANSPORT_LE); +    } + +    private void syncConfigFromMainDevice(BluetoothDevice device, int groupId) { +        if (!isOngoingPairByCsip(device)) { +            return; +        } +        CachedBluetoothDevice memberDevice = findDevice(device); +        CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(memberDevice); +        if (mainDevice == null) { +            mainDevice = mCsipDeviceManager.getCachedDevice(groupId); +        } + +        if (mainDevice == null || mainDevice.equals(memberDevice)) { +            Log.d(TAG, "no mainDevice"); +            return; +        } + +        // The memberDevice set PhonebookAccessPermission +        device.setPhonebookAccessPermission(mainDevice.getDevice().getPhonebookAccessPermission());      }      /** diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java index d5de3f0525a0..20a6cd8e09ce 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java @@ -101,7 +101,14 @@ public class CsipDeviceManager {          return groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID;      } -    private CachedBluetoothDevice getCachedDevice(int groupId) { +    /** +     * To find the device with {@code groupId}. +     * +     * @param groupId The group id +     * @return if we could find a device with this {@code groupId} return this device. Otherwise, +     * return null. +     */ +    public CachedBluetoothDevice getCachedDevice(int groupId) {          log("getCachedDevice: groupId: " + groupId);          for (int i = mCachedDevices.size() - 1; i >= 0; i--) {              CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java index 62552f914459..61802a87361c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java @@ -582,4 +582,24 @@ public class CachedBluetoothDeviceManagerTest {          assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isTrue();          assertThat(mCachedDeviceManager.isSubDevice(mDevice3)).isFalse();      } + +    @Test +    public void pairDeviceByCsip_device2AndCapGroup1_device2StartsPairing() { +        doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1); +        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); +        when(mDevice1.getPhonebookAccessPermission()).thenReturn(BluetoothDevice.ACCESS_ALLOWED); +        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); +        assertThat(cachedDevice1).isNotNull(); +        when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE); +        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); +        assertThat(cachedDevice2).isNotNull(); + +        int groupId = CAP_GROUP1.keySet().stream().findFirst().orElse( +                BluetoothCsipSetCoordinator.GROUP_ID_INVALID); +        assertThat(groupId).isNotEqualTo(BluetoothCsipSetCoordinator.GROUP_ID_INVALID); +        mCachedDeviceManager.pairDeviceByCsip(mDevice2, groupId); + +        verify(mDevice2).setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); +        verify(mDevice2).createBond(BluetoothDevice.TRANSPORT_LE); +    }  } diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 6d61fd86e39d..77ddc6e573b7 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -83,7 +83,6 @@ steell@google.com  stwu@google.com  syeonlee@google.com  sunnygoyal@google.com -susikp@google.com  thiruram@google.com  tracyzhou@google.com  tsuji@google.com diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index 6780fb7fafe3..65d6c83d74a8 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -82,6 +82,14 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {          /** Mutable Y coordinate of the glyph position relative from the baseline. */          var y: Float = 0f +        /** +         * The current line of text being drawn, in a multi-line TextView. +         */ +        var lineNo: Int = 0 + +        /** +         * Mutable text size of the glyph in pixels. +         */          /** Mutable text size of the glyph in pixels. */          var textSize: Float = 0f diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt index db14fdf5930b..f9fb42cd1670 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt @@ -231,7 +231,9 @@ class TextInterpolator(layout: Layout) {                      val origin = layout.getDrawOrigin(lineNo)                      canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat()) -                    run.fontRuns.forEach { fontRun -> drawFontRun(canvas, run, fontRun, tmpPaint) } +                    run.fontRuns.forEach { fontRun -> +                        drawFontRun(canvas, run, fontRun, lineNo, tmpPaint) +                    }                  } finally {                      canvas.restore()                  } @@ -341,7 +343,7 @@ class TextInterpolator(layout: Layout) {      var glyphFilter: GlyphCallback? = null      // Draws single font run. -    private fun drawFontRun(c: Canvas, line: Run, run: FontRun, paint: Paint) { +    private fun drawFontRun(c: Canvas, line: Run, run: FontRun, lineNo: Int, paint: Paint) {          var arrayIndex = 0          val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress) @@ -360,11 +362,13 @@ class TextInterpolator(layout: Layout) {          tmpGlyph.font = font          tmpGlyph.runStart = run.start          tmpGlyph.runLength = run.end - run.start +        tmpGlyph.lineNo = lineNo          tmpPaintForGlyph.set(paint)          var prevStart = run.start          for (i in run.start until run.end) { +            tmpGlyph.glyphIndex = i              tmpGlyph.glyphId = line.glyphIds[i]              tmpGlyph.x = MathUtils.lerp(line.baseX[i], line.targetX[i], progress)              tmpGlyph.y = MathUtils.lerp(line.baseY[i], line.targetY[i], progress) diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt new file mode 100644 index 000000000000..1db072548a76 --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.intellij.psi.PsiMethod +import org.jetbrains.uast.UCallExpression + +private const val CLASS_SETTINGS = "android.provider.Settings" + +/** + * Detects usage of static methods in android.provider.Settings and suggests to use an injected + * settings provider instance instead. + */ +@Suppress("UnstableApiUsage") +class StaticSettingsProviderDetector : Detector(), SourceCodeScanner { +    override fun getApplicableMethodNames(): List<String> { +        return listOf( +            "getFloat", +            "getInt", +            "getLong", +            "getString", +            "getUriFor", +            "putFloat", +            "putInt", +            "putLong", +            "putString" +        ) +    } + +    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { +        val evaluator = context.evaluator +        val className = method.containingClass?.qualifiedName +        if ( +            className != "$CLASS_SETTINGS.Global" && +                className != "$CLASS_SETTINGS.Secure" && +                className != "$CLASS_SETTINGS.System" +        ) { +            return +        } +        if (!evaluator.isStatic(method)) { +            return +        } + +        val subclassName = className.substring(CLASS_SETTINGS.length + 1) + +        context.report( +            ISSUE, +            method, +            context.getNameLocation(node), +            "`@Inject` a ${subclassName}Settings instead" +        ) +    } + +    companion object { +        @JvmField +        val ISSUE: Issue = +            Issue.create( +                id = "StaticSettingsProvider", +                briefDescription = "Static settings provider usage", +                explanation = +                    """ +                    Static settings provider methods, such as `Settings.Global.putInt()`, should \ +                    not be used because they make testing difficult. Instead, use an injected \ +                    settings provider. For example, instead of calling `Settings.Secure.getInt()`, \ +                    annotate the class constructor with `@Inject` and add `SecureSettings` to the \ +                    parameters. +                    """, +                category = Category.CORRECTNESS, +                priority = 8, +                severity = Severity.WARNING, +                implementation = +                    Implementation( +                        StaticSettingsProviderDetector::class.java, +                        Scope.JAVA_FILE_SCOPE +                    ) +            ) +    } +} diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt index cf7c1b5e44a2..3f334c1cdb9c 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt @@ -36,6 +36,7 @@ class SystemUIIssueRegistry : IssueRegistry() {                  RegisterReceiverViaContextDetector.ISSUE,                  SoftwareBitmapDetector.ISSUE,                  NonInjectedServiceDetector.ISSUE, +                StaticSettingsProviderDetector.ISSUE          )      override val api: Int diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt index 486af9dd5d98..d4c55c0d9149 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt @@ -24,6 +24,47 @@ import org.intellij.lang.annotations.Language  @NonNull  private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(source).indented() +internal val commonSettingsCode = +    """ +public static float getFloat(ContentResolver cr, String name) { return 0.0f; } +public static long getLong(ContentResolver cr, String name) { +    return 0L; +} +public static int getInt(ContentResolver cr, String name) { +    return 0; +} +public static String getString(ContentResolver cr, String name) { +    return ""; +} +public static float getFloat(ContentResolver cr, String name, float def) { +    return 0.0f; +} +public static long getLong(ContentResolver cr, String name, long def) { +    return 0L; +} +public static int getInt(ContentResolver cr, String name, int def) { +    return 0; +} +public static String getString(ContentResolver cr, String name, String def) { +    return ""; +} +public static boolean putFloat(ContentResolver cr, String name, float value) { +    return true; +} +public static boolean putLong(ContentResolver cr, String name, long value) { +    return true; +} +public static boolean putInt(ContentResolver cr, String name, int value) { +    return true; +} +public static boolean putFloat(ContentResolver cr, String name) { +    return true; +} +public static boolean putString(ContentResolver cr, String name, String value) { +    return true; +} +""" +  /*   * This file contains stubs of framework APIs and System UI classes for testing purposes only. The   * stubs are not used in the lint detectors themselves. @@ -186,4 +227,28 @@ public @interface WorkerThread {  }  """          ), +        indentedJava( +            """ +package android.provider; + +public class Settings { +    public static final class Global { +        public static final String UNLOCK_SOUND = "unlock_sound"; +        """ + +                commonSettingsCode + +                """ +    } +    public static final class Secure { +    """ + +                commonSettingsCode + +                """ +    } +    public static final class System { +    """ + +                commonSettingsCode + +                """ +    } +} +""" +        ),      ) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt index 6ae8fd3f25a1..c35ac61a6543 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt @@ -16,18 +16,15 @@  package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest  import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask  import com.android.tools.lint.detector.api.Detector  import com.android.tools.lint.detector.api.Issue  import org.junit.Test  @Suppress("UnstableApiUsage") -class BindServiceOnMainThreadDetectorTest : LintDetectorTest() { +class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {      override fun getDetector(): Detector = BindServiceOnMainThreadDetector() -    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)      override fun getIssues(): List<Issue> = listOf(BindServiceOnMainThreadDetector.ISSUE) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt index 7d422807ae08..376acb56fac9 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt @@ -16,18 +16,15 @@  package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest  import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask  import com.android.tools.lint.detector.api.Detector  import com.android.tools.lint.detector.api.Issue  import org.junit.Test  @Suppress("UnstableApiUsage") -class BroadcastSentViaContextDetectorTest : LintDetectorTest() { +class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {      override fun getDetector(): Detector = BroadcastSentViaContextDetector() -    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)      override fun getIssues(): List<Issue> = listOf(BroadcastSentViaContextDetector.ISSUE) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt index c468af8d09e0..301c338f9b42 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt @@ -16,18 +16,15 @@  package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest  import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask  import com.android.tools.lint.detector.api.Detector  import com.android.tools.lint.detector.api.Issue  import org.junit.Test  @Suppress("UnstableApiUsage") -class NonInjectedMainThreadDetectorTest : LintDetectorTest() { +class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {      override fun getDetector(): Detector = NonInjectedMainThreadDetector() -    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)      override fun getIssues(): List<Issue> = listOf(NonInjectedMainThreadDetector.ISSUE) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt index c83a35b46ca6..0a74bfcfee57 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt @@ -16,18 +16,15 @@  package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest  import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask  import com.android.tools.lint.detector.api.Detector  import com.android.tools.lint.detector.api.Issue  import org.junit.Test  @Suppress("UnstableApiUsage") -class NonInjectedServiceDetectorTest : LintDetectorTest() { +class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {      override fun getDetector(): Detector = NonInjectedServiceDetector() -    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)      override fun getIssues(): List<Issue> = listOf(NonInjectedServiceDetector.ISSUE)      @Test diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt index ebcddebfbc28..9ed7aa029b1d 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt @@ -16,18 +16,15 @@  package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest  import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask  import com.android.tools.lint.detector.api.Detector  import com.android.tools.lint.detector.api.Issue  import org.junit.Test  @Suppress("UnstableApiUsage") -class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { +class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {      override fun getDetector(): Detector = RegisterReceiverViaContextDetector() -    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)      override fun getIssues(): List<Issue> = listOf(RegisterReceiverViaContextDetector.ISSUE) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt index b03a11c4f02f..54cac7b35598 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt @@ -16,18 +16,15 @@  package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest  import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask  import com.android.tools.lint.detector.api.Detector  import com.android.tools.lint.detector.api.Issue  import org.junit.Test  @Suppress("UnstableApiUsage") -class SlowUserQueryDetectorTest : LintDetectorTest() { +class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {      override fun getDetector(): Detector = SlowUserQueryDetector() -    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)      override fun getIssues(): List<Issue> =          listOf( diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt index fb6537e92d15..090ddf88fa3c 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt @@ -16,18 +16,15 @@  package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest  import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask  import com.android.tools.lint.detector.api.Detector  import com.android.tools.lint.detector.api.Issue  import org.junit.Test  @Suppress("UnstableApiUsage") -class SoftwareBitmapDetectorTest : LintDetectorTest() { +class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {      override fun getDetector(): Detector = SoftwareBitmapDetector() -    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)      override fun getIssues(): List<Issue> = listOf(SoftwareBitmapDetector.ISSUE) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt new file mode 100644 index 000000000000..b83ed7067bc3 --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.checks.infrastructure.TestFiles +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import org.junit.Test + +@Suppress("UnstableApiUsage") +class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() { + +    override fun getDetector(): Detector = StaticSettingsProviderDetector() +    override fun getIssues(): List<Issue> = listOf(StaticSettingsProviderDetector.ISSUE) + +    @Test +    fun testGetServiceWithString() { +        lint() +            .files( +                TestFiles.java( +                        """ +                        package test.pkg; + +                        import android.provider.Settings; +                        import android.provider.Settings.Global; +                        import android.provider.Settings.Secure; + +                        public class TestClass { +                            public void getSystemServiceWithoutDagger(Context context) { +                                final ContentResolver cr = mContext.getContentResolver(); +                                Global.getFloat(cr, Settings.Global.UNLOCK_SOUND); +                                Global.getInt(cr, Settings.Global.UNLOCK_SOUND); +                                Global.getLong(cr, Settings.Global.UNLOCK_SOUND); +                                Global.getString(cr, Settings.Global.UNLOCK_SOUND); +                                Global.getFloat(cr, Settings.Global.UNLOCK_SOUND, 1f); +                                Global.getInt(cr, Settings.Global.UNLOCK_SOUND, 1); +                                Global.getLong(cr, Settings.Global.UNLOCK_SOUND, 1L); +                                Global.getString(cr, Settings.Global.UNLOCK_SOUND, "1"); +                                Global.putFloat(cr, Settings.Global.UNLOCK_SOUND, 1f); +                                Global.putInt(cr, Settings.Global.UNLOCK_SOUND, 1); +                                Global.putLong(cr, Settings.Global.UNLOCK_SOUND, 1L); +                                Global.putString(cr, Settings.Global.UNLOCK_SOUND, "1"); + +                                Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); +                                Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); +                                Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); +                                Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); +                                Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f); +                                Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1); +                                Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L); +                                Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1"); +                                Secure.putFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f); +                                Secure.putInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1); +                                Secure.putLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L); +                                Secure.putString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1"); + +                                Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT); +                                Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT); +                                Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT); +                                Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT); +                                Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f); +                                Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1); +                                Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L); +                                Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT, "1"); +                                Settings.System.putFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f); +                                Settings.System.putInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1); +                                Settings.System.putLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L); +                                Settings.System.putString(cr, Settings.Global.UNLOCK_SOUND, "1"); +                            } +                        } +                        """ +                    ) +                    .indented(), +                *stubs +            ) +            .issues(StaticSettingsProviderDetector.ISSUE) +            .run() +            .expect( +                """ +                src/test/pkg/TestClass.java:10: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] +                        Global.getFloat(cr, Settings.Global.UNLOCK_SOUND); +                               ~~~~~~~~ +                src/test/pkg/TestClass.java:11: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] +                        Global.getInt(cr, Settings.Global.UNLOCK_SOUND); +                               ~~~~~~ +                src/test/pkg/TestClass.java:12: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] +                        Global.getLong(cr, Settings.Global.UNLOCK_SOUND); +                               ~~~~~~~ +                src/test/pkg/TestClass.java:13: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] +                        Global.getString(cr, Settings.Global.UNLOCK_SOUND); +                               ~~~~~~~~~ +                src/test/pkg/TestClass.java:14: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] +                        Global.getFloat(cr, Settings.Global.UNLOCK_SOUND, 1f); +                               ~~~~~~~~ +                src/test/pkg/TestClass.java:15: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] +                        Global.getInt(cr, Settings.Global.UNLOCK_SOUND, 1); +                               ~~~~~~ +                src/test/pkg/TestClass.java:16: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] +                        Global.getLong(cr, Settings.Global.UNLOCK_SOUND, 1L); +                               ~~~~~~~ +                src/test/pkg/TestClass.java:17: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] +                        Global.getString(cr, Settings.Global.UNLOCK_SOUND, "1"); +                               ~~~~~~~~~ +                src/test/pkg/TestClass.java:18: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] +                        Global.putFloat(cr, Settings.Global.UNLOCK_SOUND, 1f); +                               ~~~~~~~~ +                src/test/pkg/TestClass.java:19: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] +                        Global.putInt(cr, Settings.Global.UNLOCK_SOUND, 1); +                               ~~~~~~ +                src/test/pkg/TestClass.java:20: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] +                        Global.putLong(cr, Settings.Global.UNLOCK_SOUND, 1L); +                               ~~~~~~~ +                src/test/pkg/TestClass.java:21: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] +                        Global.putString(cr, Settings.Global.UNLOCK_SOUND, "1"); +                               ~~~~~~~~~ +                src/test/pkg/TestClass.java:23: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] +                        Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); +                               ~~~~~~~~ +                src/test/pkg/TestClass.java:24: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] +                        Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); +                               ~~~~~~ +                src/test/pkg/TestClass.java:25: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] +                        Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); +                               ~~~~~~~ +                src/test/pkg/TestClass.java:26: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] +                        Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); +                               ~~~~~~~~~ +                src/test/pkg/TestClass.java:27: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] +                        Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f); +                               ~~~~~~~~ +                src/test/pkg/TestClass.java:28: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] +                        Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1); +                               ~~~~~~ +                src/test/pkg/TestClass.java:29: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] +                        Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L); +                               ~~~~~~~ +                src/test/pkg/TestClass.java:30: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] +                        Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1"); +                               ~~~~~~~~~ +                src/test/pkg/TestClass.java:31: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] +                        Secure.putFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f); +                               ~~~~~~~~ +                src/test/pkg/TestClass.java:32: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] +                        Secure.putInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1); +                               ~~~~~~ +                src/test/pkg/TestClass.java:33: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] +                        Secure.putLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L); +                               ~~~~~~~ +                src/test/pkg/TestClass.java:34: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] +                        Secure.putString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1"); +                               ~~~~~~~~~ +                src/test/pkg/TestClass.java:36: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] +                        Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT); +                                        ~~~~~~~~ +                src/test/pkg/TestClass.java:37: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] +                        Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT); +                                        ~~~~~~ +                src/test/pkg/TestClass.java:38: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] +                        Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT); +                                        ~~~~~~~ +                src/test/pkg/TestClass.java:39: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] +                        Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT); +                                        ~~~~~~~~~ +                src/test/pkg/TestClass.java:40: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] +                        Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f); +                                        ~~~~~~~~ +                src/test/pkg/TestClass.java:41: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] +                        Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1); +                                        ~~~~~~ +                src/test/pkg/TestClass.java:42: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] +                        Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L); +                                        ~~~~~~~ +                src/test/pkg/TestClass.java:43: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] +                        Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT, "1"); +                                        ~~~~~~~~~ +                src/test/pkg/TestClass.java:44: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] +                        Settings.System.putFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f); +                                        ~~~~~~~~ +                src/test/pkg/TestClass.java:45: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] +                        Settings.System.putInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1); +                                        ~~~~~~ +                src/test/pkg/TestClass.java:46: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] +                        Settings.System.putLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L); +                                        ~~~~~~~ +                src/test/pkg/TestClass.java:47: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] +                        Settings.System.putString(cr, Settings.Global.UNLOCK_SOUND, "1"); +                                        ~~~~~~~~~ +                0 errors, 36 warnings +                """ +            ) +    } + +    private val stubs = androidStubs +} diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt new file mode 100644 index 000000000000..2183b3805eed --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt @@ -0,0 +1,15 @@ +package com.android.internal.systemui.lint + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestLintTask +import java.io.File + +@Suppress("UnstableApiUsage") +abstract class SystemUILintDetectorTest : LintDetectorTest() { +    /** +     * Customize the lint task to disable SDK usage completely. This ensures that running the tests +     * in Android Studio has the same result as running the tests in atest +     */ +    override fun lint(): TestLintTask = +        super.lint().allowMissingSdk(true).sdkHome(File("/dev/null")) +} diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp index cafaaf854eed..7709f210f22f 100644 --- a/packages/SystemUI/plugin/Android.bp +++ b/packages/SystemUI/plugin/Android.bp @@ -33,6 +33,7 @@ java_library {      static_libs: [          "androidx.annotation_annotation", +        "error_prone_annotations",          "PluginCoreLib",          "SystemUIAnimationLib",      ], diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 1e74c3d68efc..dabb43b6074d 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -14,6 +14,7 @@  package com.android.systemui.plugins  import android.content.res.Resources +import android.graphics.Rect  import android.graphics.drawable.Drawable  import android.view.View  import com.android.systemui.plugins.annotations.ProvidesInterface @@ -114,6 +115,17 @@ interface ClockAnimations {      /** Runs the battery animation (if any). */      fun charge() { } + +    /** Move the clock, for example, if the notification tray appears in split-shade mode. */ +    fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) { } + +    /** +     * Whether this clock has a custom position update animation. If true, the keyguard will call +     * `onPositionUpdated` to notify the clock of a position update animation. If false, a default +     * animation will be used (e.g. a simple translation). +     */ +    val hasCustomPositionUpdatedAnimation +        get() = false  }  /** Events that have specific data about the related face */ diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt index 6124e10144f2..6436dcb5f613 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt @@ -14,12 +14,11 @@   * limitations under the License.   */ -package com.android.systemui.log +package com.android.systemui.plugins.log  import android.os.Trace  import android.util.Log -import com.android.systemui.log.dagger.LogModule -import com.android.systemui.util.collection.RingBuffer +import com.android.systemui.plugins.util.RingBuffer  import com.google.errorprone.annotations.CompileTimeConstant  import java.io.PrintWriter  import java.util.concurrent.ArrayBlockingQueue @@ -61,15 +60,18 @@ import kotlin.math.max   * In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or   * the first letter of any of the previous.   * - * Buffers are provided by [LogModule]. Instances should be created using a [LogBufferFactory]. + * In SystemUI, buffers are provided by LogModule. Instances should be created using a SysUI + * LogBufferFactory.   *   * @param name The name of this buffer, printed when the buffer is dumped and in some other   * situations.   * @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start - * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches - * the maximum, it behaves like a ring buffer. + * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches the + * maximum, it behaves like a ring buffer.   */ -class LogBuffer @JvmOverloads constructor( +class LogBuffer +@JvmOverloads +constructor(      private val name: String,      private val maxSize: Int,      private val logcatEchoTracker: LogcatEchoTracker, @@ -78,7 +80,7 @@ class LogBuffer @JvmOverloads constructor(      private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }      private val echoMessageQueue: BlockingQueue<LogMessage>? = -            if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null +        if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null      init {          if (logcatEchoTracker.logInBackgroundThread && echoMessageQueue != null) { @@ -133,11 +135,11 @@ class LogBuffer @JvmOverloads constructor(       */      @JvmOverloads      inline fun log( -            tag: String, -            level: LogLevel, -            messageInitializer: MessageInitializer, -            noinline messagePrinter: MessagePrinter, -            exception: Throwable? = null, +        tag: String, +        level: LogLevel, +        messageInitializer: MessageInitializer, +        noinline messagePrinter: MessagePrinter, +        exception: Throwable? = null,      ) {          val message = obtain(tag, level, messagePrinter, exception)          messageInitializer(message) @@ -152,14 +154,13 @@ class LogBuffer @JvmOverloads constructor(       * log message is built during runtime, use the [LogBuffer.log] overloaded method that takes in       * an initializer and a message printer.       * -     * Log buffers are limited by the number of entries, so logging more frequently -     * will limit the time window that the LogBuffer covers in a bug report.  Richer logs, on the -     * other hand, make a bug report more actionable, so using the [log] with a messagePrinter to -     * add more detail to every log may do more to improve overall logging than adding more logs -     * with this method. +     * Log buffers are limited by the number of entries, so logging more frequently will limit the +     * time window that the LogBuffer covers in a bug report. Richer logs, on the other hand, make a +     * bug report more actionable, so using the [log] with a messagePrinter to add more detail to +     * every log may do more to improve overall logging than adding more logs with this method.       */      fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) = -            log(tag, level, {str1 = message}, { str1!! }) +        log(tag, level, { str1 = message }, { str1!! })      /**       * You should call [log] instead of this method. @@ -172,10 +173,10 @@ class LogBuffer @JvmOverloads constructor(       */      @Synchronized      fun obtain( -            tag: String, -            level: LogLevel, -            messagePrinter: MessagePrinter, -            exception: Throwable? = null, +        tag: String, +        level: LogLevel, +        messagePrinter: MessagePrinter, +        exception: Throwable? = null,      ): LogMessage {          if (!mutable) {              return FROZEN_MESSAGE @@ -189,8 +190,7 @@ class LogBuffer @JvmOverloads constructor(       * You should call [log] instead of this method.       *       * After acquiring a message via [obtain], call this method to signal to the buffer that you -     * have finished filling in its data fields. The message will be echoed to logcat if -     * necessary. +     * have finished filling in its data fields. The message will be echoed to logcat if necessary.       */      @Synchronized      fun commit(message: LogMessage) { @@ -213,7 +213,8 @@ class LogBuffer @JvmOverloads constructor(      /** Sends message to echo after determining whether to use Logcat and/or systrace. */      private fun echoToDesiredEndpoints(message: LogMessage) { -        val includeInLogcat = logcatEchoTracker.isBufferLoggable(name, message.level) || +        val includeInLogcat = +            logcatEchoTracker.isBufferLoggable(name, message.level) ||                  logcatEchoTracker.isTagLoggable(message.tag, message.level)          echo(message, toLogcat = includeInLogcat, toSystrace = systrace)      } @@ -221,7 +222,12 @@ class LogBuffer @JvmOverloads constructor(      /** Converts the entire buffer to a newline-delimited string */      @Synchronized      fun dump(pw: PrintWriter, tailLength: Int) { -        val iterationStart = if (tailLength <= 0) { 0 } else { max(0, buffer.size - tailLength) } +        val iterationStart = +            if (tailLength <= 0) { +                0 +            } else { +                max(0, buffer.size - tailLength) +            }          for (i in iterationStart until buffer.size) {              buffer[i].dump(pw) @@ -229,9 +235,9 @@ class LogBuffer @JvmOverloads constructor(      }      /** -     * "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called. -     * Calls to [log], [obtain], and [commit] will not affect the buffer and will return dummy -     * values if necessary. +     * "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called. Calls +     * to [log], [obtain], and [commit] will not affect the buffer and will return dummy values if +     * necessary.       */      @Synchronized      fun freeze() { @@ -241,9 +247,7 @@ class LogBuffer @JvmOverloads constructor(          }      } -    /** -     * Undoes the effects of calling [freeze]. -     */ +    /** Undoes the effects of calling [freeze]. */      @Synchronized      fun unfreeze() {          if (frozen) { @@ -265,8 +269,11 @@ class LogBuffer @JvmOverloads constructor(      }      private fun echoToSystrace(message: LogMessage, strMessage: String) { -        Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", -            "$name - ${message.level.shortString} ${message.tag}: $strMessage") +        Trace.instantForTrack( +            Trace.TRACE_TAG_APP, +            "UI Events", +            "$name - ${message.level.shortString} ${message.tag}: $strMessage" +        )      }      private fun echoToLogcat(message: LogMessage, strMessage: String) { diff --git a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt index 53f231c9f9d2..b036cf0be1d6 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt @@ -14,17 +14,12 @@   * limitations under the License.   */ -package com.android.systemui.log +package com.android.systemui.plugins.log  import android.util.Log -/** - * Enum version of @Log.Level - */ -enum class LogLevel( -    @Log.Level val nativeLevel: Int, -    val shortString: String -) { +/** Enum version of @Log.Level */ +enum class LogLevel(@Log.Level val nativeLevel: Int, val shortString: String) {      VERBOSE(Log.VERBOSE, "V"),      DEBUG(Log.DEBUG, "D"),      INFO(Log.INFO, "I"), diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt index dae2592e116c..9468681289bf 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt @@ -14,7 +14,7 @@   * limitations under the License.   */ -package com.android.systemui.log +package com.android.systemui.plugins.log  import java.io.PrintWriter  import java.text.SimpleDateFormat @@ -29,9 +29,10 @@ import java.util.Locale   *   * When a message is logged, the code doing the logging stores data in one or more of the generic   * fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the - * [messagePrinter] function reads the data stored in the generic fields and converts that to a human- - * readable string. Thus, for every log type there must be a specialized initializer function that - * stores data specific to that log type and a specialized printer function that prints that data. + * [messagePrinter] function reads the data stored in the generic fields and converts that to a + * human- readable string. Thus, for every log type there must be a specialized initializer function + * that stores data specific to that log type and a specialized printer function that prints that + * data.   *   * See [LogBuffer.log] for more information.   */ @@ -55,9 +56,7 @@ interface LogMessage {      var bool3: Boolean      var bool4: Boolean -    /** -     * Function that dumps the [LogMessage] to the provided [writer]. -     */ +    /** Function that dumps the [LogMessage] to the provided [writer]. */      fun dump(writer: PrintWriter) {          val formattedTimestamp = DATE_FORMAT.format(timestamp)          val shortLevel = level.shortString @@ -68,12 +67,12 @@ interface LogMessage {  }  /** - * A function that will be called if and when the message needs to be dumped to - * logcat or a bug report. It should read the data stored by the initializer and convert it to - * a human-readable string. The value of `this` will be the LogMessage to be printed. - * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any - * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance - * of the printer for each call, thwarting our attempts at avoiding any sort of allocation. + * A function that will be called if and when the message needs to be dumped to logcat or a bug + * report. It should read the data stored by the initializer and convert it to a human-readable + * string. The value of `this` will be the LogMessage to be printed. **IMPORTANT:** The printer + * should ONLY ever reference fields on the LogMessage and NEVER any variables in its enclosing + * scope. Otherwise, the runtime will need to allocate a new instance of the printer for each call, + * thwarting our attempts at avoiding any sort of allocation.   */  typealias MessagePrinter = LogMessage.() -> String diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt index 4dd6f652d1c7..f2a6a91adcdf 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt @@ -14,11 +14,9 @@   * limitations under the License.   */ -package com.android.systemui.log +package com.android.systemui.plugins.log -/** - * Recyclable implementation of [LogMessage]. - */ +/** Recyclable implementation of [LogMessage]. */  data class LogMessageImpl(      override var level: LogLevel,      override var tag: String, @@ -68,23 +66,24 @@ data class LogMessageImpl(      companion object Factory {          fun create(): LogMessageImpl {              return LogMessageImpl( -                    LogLevel.DEBUG, -                    DEFAULT_TAG, -                    0, -                    DEFAULT_PRINTER, -                    null, -                    null, -                    null, -                    null, -                    0, -                    0, -                    0, -                    0, -                    0.0, -                    false, -                    false, -                    false, -                    false) +                LogLevel.DEBUG, +                DEFAULT_TAG, +                0, +                DEFAULT_PRINTER, +                null, +                null, +                null, +                null, +                0, +                0, +                0, +                0, +                0.0, +                false, +                false, +                false, +                false +            )          }      }  } diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt index 8cda4236bc87..cfe894f276a0 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt @@ -14,24 +14,16 @@   * limitations under the License.   */ -package com.android.systemui.log +package com.android.systemui.plugins.log -/** - * Keeps track of which [LogBuffer] messages should also appear in logcat. - */ +/** Keeps track of which [LogBuffer] messages should also appear in logcat. */  interface LogcatEchoTracker { -    /** -     * Whether [bufferName] should echo messages of [level] or higher to logcat. -     */ +    /** Whether [bufferName] should echo messages of [level] or higher to logcat. */      fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean -    /** -     * Whether [tagName] should echo messages of [level] or higher to logcat. -     */ +    /** Whether [tagName] should echo messages of [level] or higher to logcat. */      fun isTagLoggable(tagName: String, level: LogLevel): Boolean -    /** -     * Whether to log messages in a background thread. -     */ +    /** Whether to log messages in a background thread. */      val logInBackgroundThread: Boolean  } diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt index 40b0cdc173d8..d3fabaccb6d3 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt @@ -14,7 +14,7 @@   * limitations under the License.   */ -package com.android.systemui.log +package com.android.systemui.plugins.log  import android.content.ContentResolver  import android.database.ContentObserver @@ -36,19 +36,15 @@ import android.provider.Settings   * $ adb shell settings put global systemui/tag/<tag> <level>   * ```   */ -class LogcatEchoTrackerDebug private constructor( -    private val contentResolver: ContentResolver -) : LogcatEchoTracker { +class LogcatEchoTrackerDebug private constructor(private val contentResolver: ContentResolver) : +    LogcatEchoTracker {      private val cachedBufferLevels: MutableMap<String, LogLevel> = mutableMapOf()      private val cachedTagLevels: MutableMap<String, LogLevel> = mutableMapOf()      override val logInBackgroundThread = true      companion object Factory {          @JvmStatic -        fun create( -            contentResolver: ContentResolver, -            mainLooper: Looper -        ): LogcatEchoTrackerDebug { +        fun create(contentResolver: ContentResolver, mainLooper: Looper): LogcatEchoTrackerDebug {              val tracker = LogcatEchoTrackerDebug(contentResolver)              tracker.attach(mainLooper)              return tracker @@ -57,37 +53,35 @@ class LogcatEchoTrackerDebug private constructor(      private fun attach(mainLooper: Looper) {          contentResolver.registerContentObserver( -                Settings.Global.getUriFor(BUFFER_PATH), -                true, -                object : ContentObserver(Handler(mainLooper)) { -                    override fun onChange(selfChange: Boolean, uri: Uri?) { -                        super.onChange(selfChange, uri) -                        cachedBufferLevels.clear() -                    } -                }) +            Settings.Global.getUriFor(BUFFER_PATH), +            true, +            object : ContentObserver(Handler(mainLooper)) { +                override fun onChange(selfChange: Boolean, uri: Uri?) { +                    super.onChange(selfChange, uri) +                    cachedBufferLevels.clear() +                } +            } +        )          contentResolver.registerContentObserver( -                Settings.Global.getUriFor(TAG_PATH), -                true, -                object : ContentObserver(Handler(mainLooper)) { -                    override fun onChange(selfChange: Boolean, uri: Uri?) { -                        super.onChange(selfChange, uri) -                        cachedTagLevels.clear() -                    } -                }) +            Settings.Global.getUriFor(TAG_PATH), +            true, +            object : ContentObserver(Handler(mainLooper)) { +                override fun onChange(selfChange: Boolean, uri: Uri?) { +                    super.onChange(selfChange, uri) +                    cachedTagLevels.clear() +                } +            } +        )      } -    /** -     * Whether [bufferName] should echo messages of [level] or higher to logcat. -     */ +    /** Whether [bufferName] should echo messages of [level] or higher to logcat. */      @Synchronized      override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean {          return level.ordinal >= getLogLevel(bufferName, BUFFER_PATH, cachedBufferLevels).ordinal      } -    /** -     * Whether [tagName] should echo messages of [level] or higher to logcat. -     */ +    /** Whether [tagName] should echo messages of [level] or higher to logcat. */      @Synchronized      override fun isTagLoggable(tagName: String, level: LogLevel): Boolean {          return level >= getLogLevel(tagName, TAG_PATH, cachedTagLevels) diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt index 1a4ad1907ff1..3c8bda4a44e0 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt @@ -14,11 +14,9 @@   * limitations under the License.   */ -package com.android.systemui.log +package com.android.systemui.plugins.log -/** - * Production version of [LogcatEchoTracker] that isn't configurable. - */ +/** Production version of [LogcatEchoTracker] that isn't configurable. */  class LogcatEchoTrackerProd : LogcatEchoTracker {      override val logInBackgroundThread = false diff --git a/packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt index 97dc842ec699..68d78907f028 100644 --- a/packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt @@ -14,7 +14,7 @@   * limitations under the License.   */ -package com.android.systemui.util.collection +package com.android.systemui.plugins.util  import kotlin.math.max @@ -32,19 +32,16 @@ import kotlin.math.max   * @param factory A function that creates a fresh instance of T. Used by the buffer while it's   * growing to [maxSize].   */ -class RingBuffer<T>( -    private val maxSize: Int, -    private val factory: () -> T -) : Iterable<T> { +class RingBuffer<T>(private val maxSize: Int, private val factory: () -> T) : Iterable<T> {      private val buffer = MutableList<T?>(maxSize) { null }      /**       * An abstract representation that points to the "end" of the buffer. Increments every time -     * [advance] is called and never wraps. Use [indexOf] to calculate the associated index into -     * the backing array. Always points to the "next" available slot in the buffer. Before the -     * buffer has completely filled, the value pointed to will be null. Afterward, it will be the -     * value at the "beginning" of the buffer. +     * [advance] is called and never wraps. Use [indexOf] to calculate the associated index into the +     * backing array. Always points to the "next" available slot in the buffer. Before the buffer +     * has completely filled, the value pointed to will be null. Afterward, it will be the value at +     * the "beginning" of the buffer.       *       * This value is unlikely to overflow. Assuming [advance] is called at rate of 100 calls/ms,       * omega will overflow after a little under three million years of continuous operation. @@ -60,24 +57,23 @@ class RingBuffer<T>(      /**       * Advances the buffer's position by one and returns the value that is now present at the "end" -     * of the buffer. If the buffer is not yet full, uses [factory] to create a new item. -     * Otherwise, reuses the value that was previously at the "beginning" of the buffer. +     * of the buffer. If the buffer is not yet full, uses [factory] to create a new item. Otherwise, +     * reuses the value that was previously at the "beginning" of the buffer.       * -     * IMPORTANT: The value is returned as-is, without being reset. It will retain any data that -     * was previously stored on it. +     * IMPORTANT: The value is returned as-is, without being reset. It will retain any data that was +     * previously stored on it.       */      fun advance(): T {          val index = indexOf(omega)          omega += 1 -        val entry = buffer[index] ?: factory().also { -            buffer[index] = it -        } +        val entry = buffer[index] ?: factory().also { buffer[index] = it }          return entry      }      /**       * Returns the value stored at [index], which can range from 0 (the "start", or oldest element -     * of the buffer) to [size] - 1 (the "end", or newest element of the buffer). +     * of the buffer) to [size] +     * - 1 (the "end", or newest element of the buffer).       */      operator fun get(index: Int): T {          if (index < 0 || index >= size) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/plugin/tests/log/LogBufferTest.kt index 56aff3c2fc8b..a39b856f0f49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt +++ b/packages/SystemUI/plugin/tests/log/LogBufferTest.kt @@ -2,6 +2,7 @@ package com.android.systemui.log  import androidx.test.filters.SmallTest  import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.log.LogBuffer  import com.google.common.truth.Truth.assertThat  import java.io.PrintWriter  import java.io.StringWriter @@ -18,8 +19,7 @@ class LogBufferTest : SysuiTestCase() {      private lateinit var outputWriter: StringWriter -    @Mock -    private lateinit var logcatEchoTracker: LogcatEchoTracker +    @Mock private lateinit var logcatEchoTracker: LogcatEchoTracker      @Before      fun setup() { @@ -67,15 +67,17 @@ class LogBufferTest : SysuiTestCase() {      @Test      fun dump_writesCauseAndStacktrace() {          buffer = createBuffer() -        val exception = createTestException("Exception message", +        val exception = +            createTestException( +                "Exception message",                  "TestClass", -                cause = createTestException("The real cause!", "TestClass")) +                cause = createTestException("The real cause!", "TestClass") +            )          buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)          val dumpedString = dumpBuffer() -        assertThat(dumpedString) -                .contains("Caused by: java.lang.RuntimeException: The real cause!") +        assertThat(dumpedString).contains("Caused by: java.lang.RuntimeException: The real cause!")          assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:1)")          assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:2)")      } @@ -85,49 +87,47 @@ class LogBufferTest : SysuiTestCase() {          buffer = createBuffer()          val exception = RuntimeException("Root exception message")          exception.addSuppressed( -                createTestException( -                        "First suppressed exception", -                        "FirstClass", -                        createTestException("Cause of suppressed exp", "ThirdClass") -                )) -        exception.addSuppressed( -                createTestException("Second suppressed exception", "SecondClass")) +            createTestException( +                "First suppressed exception", +                "FirstClass", +                createTestException("Cause of suppressed exp", "ThirdClass") +            ) +        ) +        exception.addSuppressed(createTestException("Second suppressed exception", "SecondClass"))          buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)          val dumpedStr = dumpBuffer()          // first suppressed exception          assertThat(dumpedStr) -                .contains("Suppressed: " + -                        "java.lang.RuntimeException: First suppressed exception") +            .contains("Suppressed: " + "java.lang.RuntimeException: First suppressed exception")          assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:1)")          assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:2)")          assertThat(dumpedStr) -                .contains("Caused by: java.lang.RuntimeException: Cause of suppressed exp") +            .contains("Caused by: java.lang.RuntimeException: Cause of suppressed exp")          assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:1)")          assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:2)")          // second suppressed exception          assertThat(dumpedStr) -                .contains("Suppressed: " + -                        "java.lang.RuntimeException: Second suppressed exception") +            .contains("Suppressed: " + "java.lang.RuntimeException: Second suppressed exception")          assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:1)")          assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:2)")      }      private fun createTestException( -            message: String, -            errorClass: String, -            cause: Throwable? = null, +        message: String, +        errorClass: String, +        cause: Throwable? = null,      ): Exception {          val exception = RuntimeException(message, cause) -        exception.stackTrace = (1..5).map { lineNumber -> -            StackTraceElement(errorClass, -                    "TestMethod", -                    "$errorClass.java", -                    lineNumber) -        }.toTypedArray() +        exception.stackTrace = +            (1..5) +                .map { lineNumber -> +                    StackTraceElement(errorClass, "TestMethod", "$errorClass.java", lineNumber) +                } +                .toTypedArray()          return exception      } diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml index 3ad7c8c4369c..d64587dcf362 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml @@ -37,6 +37,7 @@          android:layout_width="match_parent"          android:layout_height="match_parent"          android:layout_marginTop="@dimen/keyguard_large_clock_top_margin" +        android:clipChildren="false"          android:visibility="gone" />      <!-- Not quite optimal but needed to translate these items as a group. The diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml index 5dc34b9db594..a565988c14ad 100644 --- a/packages/SystemUI/res/layout/combined_qs_header.xml +++ b/packages/SystemUI/res/layout/combined_qs_header.xml @@ -73,8 +73,8 @@          android:singleLine="true"          android:textDirection="locale"          android:textAppearance="@style/TextAppearance.QS.Status" -        android:transformPivotX="0sp" -        android:transformPivotY="20sp" +        android:transformPivotX="0dp" +        android:transformPivotY="24dp"          android:scaleX="1"          android:scaleY="1"      /> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 01c9ac1b9d15..66f0e7543469 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -519,7 +519,7 @@      <dimen name="qs_tile_margin_horizontal">8dp</dimen>      <dimen name="qs_tile_margin_vertical">@dimen/qs_tile_margin_horizontal</dimen>      <dimen name="qs_tile_margin_top_bottom">4dp</dimen> -    <dimen name="qs_brightness_margin_top">8dp</dimen> +    <dimen name="qs_brightness_margin_top">12dp</dimen>      <dimen name="qs_brightness_margin_bottom">16dp</dimen>      <dimen name="qqs_layout_margin_top">16dp</dimen>      <dimen name="qqs_layout_padding_bottom">24dp</dimen> @@ -572,6 +572,7 @@      <dimen name="qs_header_row_min_height">48dp</dimen>      <dimen name="qs_header_non_clickable_element_height">24dp</dimen> +    <dimen name="new_qs_header_non_clickable_element_height">20dp</dimen>      <dimen name="qs_footer_padding">20dp</dimen>      <dimen name="qs_security_footer_height">88dp</dimen> diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml index 3164ed1e6751..e30d4415a0c4 100644 --- a/packages/SystemUI/res/values/integers.xml +++ b/packages/SystemUI/res/values/integers.xml @@ -28,4 +28,11 @@      <!-- The time it takes for the over scroll release animation to complete, in milli seconds.  -->      <integer name="lockscreen_shade_over_scroll_release_duration">0</integer> + +    <!-- Values for transition of QS Headers --> +    <integer name="fade_out_complete_frame">14</integer> +    <integer name="fade_in_start_frame">58</integer> +    <!-- Percentage of displacement for items in QQS to guarantee matching with bottom of clock at +         fade_out_complete_frame --> +    <dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen>  </resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index a734fa744b48..475ca919c3bf 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -128,11 +128,10 @@      <!-- This is hard coded to be sans-serif-condensed to match the icons -->      <style name="TextAppearance.QS.Status"> -        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> +        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>          <item name="android:textColor">?android:attr/textColorPrimary</item>          <item name="android:textSize">14sp</item>          <item name="android:letterSpacing">0.01</item> -        <item name="android:lineHeight">20sp</item>      </style>      <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status"> @@ -143,12 +142,10 @@      <style name="TextAppearance.QS.Status.Carriers" />      <style name="TextAppearance.QS.Status.Carriers.NoCarrierText"> -        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>          <item name="android:textColor">?android:attr/textColorSecondary</item>      </style>      <style name="TextAppearance.QS.Status.Build"> -        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>          <item name="android:textColor">?android:attr/textColorSecondary</item>      </style> diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml index f3866c08cbfc..de855e275f5f 100644 --- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml +++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml @@ -27,67 +27,60 @@              <KeyPosition                  app:keyPositionType="deltaRelative"                  app:percentX="0" -                app:percentY="0" -                app:framePosition="49" +                app:percentY="@dimen/percent_displacement_at_fade_out" +                app:framePosition="@integer/fade_out_complete_frame"                  app:sizePercent="0"                  app:curveFit="linear"                  app:motionTarget="@id/date" />              <KeyPosition                  app:keyPositionType="deltaRelative"                  app:percentX="1" -                app:percentY="0.51" +                app:percentY="0.5"                  app:sizePercent="1" -                app:framePosition="51" +                app:framePosition="50"                  app:curveFit="linear"                  app:motionTarget="@id/date" />              <KeyAttribute                  app:motionTarget="@id/date" -                app:framePosition="30" +                app:framePosition="14"                  android:alpha="0"                  />              <KeyAttribute                  app:motionTarget="@id/date" -                app:framePosition="70" +                app:framePosition="@integer/fade_in_start_frame"                  android:alpha="0"                  />              <KeyPosition -                app:keyPositionType="pathRelative" -                app:percentX="0" -                app:percentY="0" -                app:framePosition="0" -                app:curveFit="linear" -                app:motionTarget="@id/statusIcons" /> -            <KeyPosition -                app:keyPositionType="pathRelative" +                app:keyPositionType="deltaRelative"                  app:percentX="0" -                app:percentY="0" -                app:framePosition="50" +                app:percentY="@dimen/percent_displacement_at_fade_out" +                app:framePosition="@integer/fade_out_complete_frame"                  app:sizePercent="0"                  app:curveFit="linear"                  app:motionTarget="@id/statusIcons" />              <KeyPosition                  app:keyPositionType="deltaRelative"                  app:percentX="1" -                app:percentY="0.51" -                app:framePosition="51" +                app:percentY="0.5" +                app:framePosition="50"                  app:sizePercent="1"                  app:curveFit="linear"                  app:motionTarget="@id/statusIcons" />              <KeyAttribute                  app:motionTarget="@id/statusIcons" -                app:framePosition="30" +                app:framePosition="@integer/fade_out_complete_frame"                  android:alpha="0"                  />              <KeyAttribute                  app:motionTarget="@id/statusIcons" -                app:framePosition="70" +                app:framePosition="@integer/fade_in_start_frame"                  android:alpha="0"                  />              <KeyPosition                  app:keyPositionType="deltaRelative"                  app:percentX="0" -                app:percentY="0" -                app:framePosition="50" +                app:percentY="@dimen/percent_displacement_at_fade_out" +                app:framePosition="@integer/fade_out_complete_frame"                  app:percentWidth="1"                  app:percentHeight="1"                  app:curveFit="linear" @@ -95,27 +88,27 @@              <KeyPosition                  app:keyPositionType="deltaRelative"                  app:percentX="1" -                app:percentY="0.51" -                app:framePosition="51" +                app:percentY="0.5" +                app:framePosition="50"                  app:percentWidth="1"                  app:percentHeight="1"                  app:curveFit="linear"                  app:motionTarget="@id/batteryRemainingIcon" />              <KeyAttribute                  app:motionTarget="@id/batteryRemainingIcon" -                app:framePosition="30" +                app:framePosition="@integer/fade_out_complete_frame"                  android:alpha="0"                  />              <KeyAttribute                  app:motionTarget="@id/batteryRemainingIcon" -                app:framePosition="70" +                app:framePosition="@integer/fade_in_start_frame"                  android:alpha="0"                  />              <KeyPosition                  app:motionTarget="@id/carrier_group"                  app:percentX="1" -                app:percentY="0.51" -                app:framePosition="51" +                app:percentY="0.5" +                app:framePosition="50"                  app:percentWidth="1"                  app:percentHeight="1"                  app:curveFit="linear" @@ -126,7 +119,7 @@                  android:alpha="0" />              <KeyAttribute                  app:motionTarget="@id/carrier_group" -                app:framePosition="70" +                app:framePosition="@integer/fade_in_start_frame"                  android:alpha="0" />          </KeyFrameSet>      </Transition> diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml index a82684d0358b..88b4f43b440b 100644 --- a/packages/SystemUI/res/xml/qqs_header.xml +++ b/packages/SystemUI/res/xml/qqs_header.xml @@ -43,7 +43,8 @@          android:id="@+id/date">          <Layout              android:layout_width="0dp" -            android:layout_height="@dimen/qs_header_non_clickable_element_height" +            android:layout_height="@dimen/new_qs_header_non_clickable_element_height" +            android:layout_marginStart="8dp"              app:layout_constrainedWidth="true"              app:layout_constraintStart_toEndOf="@id/clock"              app:layout_constraintEnd_toStartOf="@id/barrier" @@ -57,8 +58,8 @@          android:id="@+id/statusIcons">          <Layout              android:layout_width="0dp" -            android:layout_height="@dimen/qs_header_non_clickable_element_height" -            app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height" +            android:layout_height="@dimen/new_qs_header_non_clickable_element_height" +            app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"              app:layout_constraintStart_toEndOf="@id/date"              app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"              app:layout_constraintTop_toTopOf="parent" @@ -71,9 +72,9 @@          android:id="@+id/batteryRemainingIcon">          <Layout              android:layout_width="wrap_content" -            android:layout_height="@dimen/qs_header_non_clickable_element_height" +            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"              app:layout_constrainedWidth="true" -            app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height" +            app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"              app:layout_constraintStart_toEndOf="@id/statusIcons"              app:layout_constraintEnd_toEndOf="@id/end_guide"              app:layout_constraintTop_toTopOf="parent" diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml index f39e6bd65b86..d8a4e7752960 100644 --- a/packages/SystemUI/res/xml/qs_header_new.xml +++ b/packages/SystemUI/res/xml/qs_header_new.xml @@ -40,13 +40,13 @@              android:layout_height="@dimen/large_screen_shade_header_min_height"              app:layout_constraintStart_toStartOf="parent"              app:layout_constraintTop_toBottomOf="@id/privacy_container" -            app:layout_constraintBottom_toTopOf="@id/date" +            app:layout_constraintBottom_toBottomOf="@id/carrier_group"              app:layout_constraintEnd_toStartOf="@id/carrier_group"              app:layout_constraintHorizontal_bias="0"          />          <Transform -            android:scaleX="2.4" -            android:scaleY="2.4" +            android:scaleX="2.57" +            android:scaleY="2.57"              />      </Constraint> @@ -54,11 +54,11 @@          android:id="@+id/date">          <Layout              android:layout_width="0dp" -            android:layout_height="@dimen/qs_header_non_clickable_element_height" +            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"              app:layout_constraintStart_toStartOf="parent"              app:layout_constraintEnd_toStartOf="@id/space"              app:layout_constraintBottom_toBottomOf="parent" -            app:layout_constraintTop_toBottomOf="@id/clock" +            app:layout_constraintTop_toBottomOf="@id/carrier_group"              app:layout_constraintHorizontal_bias="0"              app:layout_constraintHorizontal_chainStyle="spread_inside"          /> @@ -87,7 +87,7 @@          android:id="@+id/statusIcons">          <Layout              android:layout_width="0dp" -            android:layout_height="@dimen/qs_header_non_clickable_element_height" +            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"              app:layout_constrainedWidth="true"              app:layout_constraintStart_toEndOf="@id/space"              app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" @@ -101,8 +101,8 @@          android:id="@+id/batteryRemainingIcon">          <Layout              android:layout_width="wrap_content" -            android:layout_height="@dimen/qs_header_non_clickable_element_height" -            app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height" +            android:layout_height="@dimen/new_qs_header_non_clickable_element_height" +            app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"              app:layout_constraintStart_toEndOf="@id/statusIcons"              app:layout_constraintEnd_toEndOf="parent"              app:layout_constraintTop_toTopOf="@id/date" diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 860a5da44088..134f3bc93847 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -20,16 +20,15 @@ import android.annotation.ColorInt  import android.annotation.FloatRange  import android.annotation.IntRange  import android.annotation.SuppressLint -import android.app.compat.ChangeIdStateCache.invalidate  import android.content.Context  import android.graphics.Canvas +import android.graphics.Rect  import android.text.Layout  import android.text.TextUtils  import android.text.format.DateFormat  import android.util.AttributeSet +import android.util.MathUtils  import android.widget.TextView -import com.android.internal.R.attr.contentDescription -import com.android.internal.R.attr.format  import com.android.internal.annotations.VisibleForTesting  import com.android.systemui.animation.GlyphCallback  import com.android.systemui.animation.Interpolators @@ -39,6 +38,8 @@ import java.io.PrintWriter  import java.util.Calendar  import java.util.Locale  import java.util.TimeZone +import kotlin.math.max +import kotlin.math.min  /**   * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30) @@ -311,7 +312,24 @@ class AnimatableClockView @JvmOverloads constructor(          )      } -    private val glyphFilter: GlyphCallback? = null // Add text animation tweak here. +    // The offset of each glyph from where it should be. +    private var glyphOffsets = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f) + +    private var lastSeenAnimationProgress = 1.0f + +    // If the animation is being reversed, the target offset for each glyph for the "stop". +    private var animationCancelStartPosition = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f) +    private var animationCancelStopPosition = 0.0f + +    // Whether the currently playing animation needed a stop (and thus, is shortened). +    private var currentAnimationNeededStop = false + +    private val glyphFilter: GlyphCallback = { positionedGlyph, _ -> +        val offset = positionedGlyph.lineNo * DIGITS_PER_LINE + positionedGlyph.glyphIndex +        if (offset < glyphOffsets.size) { +            positionedGlyph.x += glyphOffsets[offset] +        } +    }      /**       * Set text style with an optional animation. @@ -421,6 +439,124 @@ class AnimatableClockView @JvmOverloads constructor(          pw.println("    time=$time")      } +    fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) { +        // Do we need to cancel an in-flight animation? +        // Need to also check against 0.0f here; we can sometimes get two calls with fraction == 0, +        // which trips up the check otherwise. +        if (lastSeenAnimationProgress != 1.0f && +                lastSeenAnimationProgress != 0.0f && +                fraction == 0.0f) { +            // New animation, but need to stop the old one. Figure out where each glyph currently +            // is in relation to the box position. After that, use the leading digit's current +            // position as the stop target. +            currentAnimationNeededStop = true + +            // We assume that the current glyph offsets would be relative to the "from" position. +            val moveAmount = toRect.left - fromRect.left + +            // Remap the current glyph offsets to be relative to the new "end" position, and figure +            // out the start/end positions for the stop animation. +            for (i in 0 until NUM_DIGITS) { +                glyphOffsets[i] = -moveAmount + glyphOffsets[i] +                animationCancelStartPosition[i] = glyphOffsets[i] +            } + +            // Use the leading digit's offset as the stop position. +            if (toRect.left > fromRect.left) { +                // It _was_ moving left +                animationCancelStopPosition = glyphOffsets[0] +            } else { +                // It was moving right +                animationCancelStopPosition = glyphOffsets[1] +            } +        } + +        // Is there a cancellation in progress? +        if (currentAnimationNeededStop && fraction < ANIMATION_CANCELLATION_TIME) { +            val animationStopProgress = MathUtils.constrainedMap( +                    0.0f, 1.0f, 0.0f, ANIMATION_CANCELLATION_TIME, fraction +            ) + +            // One of the digits has already stopped. +            val animationStopStep = 1.0f / (NUM_DIGITS - 1) + +            for (i in 0 until NUM_DIGITS) { +                val stopAmount = if (toRect.left > fromRect.left) { +                    // It was moving left (before flipping) +                    MOVE_LEFT_DELAYS[i] * animationStopStep +                } else { +                    // It was moving right (before flipping) +                    MOVE_RIGHT_DELAYS[i] * animationStopStep +                } + +                // Leading digit stops immediately. +                if (stopAmount == 0.0f) { +                    glyphOffsets[i] = animationCancelStopPosition +                } else { +                    val actualStopAmount = MathUtils.constrainedMap( +                            0.0f, 1.0f, 0.0f, stopAmount, animationStopProgress +                    ) +                    val easedProgress = MOVE_INTERPOLATOR.getInterpolation(actualStopAmount) +                    val glyphMoveAmount = +                            animationCancelStopPosition - animationCancelStartPosition[i] +                    glyphOffsets[i] = +                            animationCancelStartPosition[i] + glyphMoveAmount * easedProgress +                } +            } +        } else { +            // Normal part of the animation. +            // Do we need to remap the animation progress to take account of the cancellation? +            val actualFraction = if (currentAnimationNeededStop) { +                MathUtils.constrainedMap( +                        0.0f, 1.0f, ANIMATION_CANCELLATION_TIME, 1.0f, fraction +                ) +            } else { +                fraction +            } + +            val digitFractions = (0 until NUM_DIGITS).map { +                // The delay for each digit, in terms of fraction (i.e. the digit should not move +                // during 0.0 - 0.1). +                val initialDelay = if (toRect.left > fromRect.left) { +                    MOVE_RIGHT_DELAYS[it] * MOVE_DIGIT_STEP +                } else { +                    MOVE_LEFT_DELAYS[it] * MOVE_DIGIT_STEP +                } + +                val f = MathUtils.constrainedMap( +                        0.0f, 1.0f, +                        initialDelay, initialDelay + AVAILABLE_ANIMATION_TIME, +                        actualFraction +                ) +                MOVE_INTERPOLATOR.getInterpolation(max(min(f, 1.0f), 0.0f)) +            } + +            // Was there an animation halt? +            val moveAmount = if (currentAnimationNeededStop) { +                // Only need to animate over the remaining space if the animation was aborted. +                -animationCancelStopPosition +            } else { +                toRect.left.toFloat() - fromRect.left.toFloat() +            } + +            for (i in 0 until NUM_DIGITS) { +                glyphOffsets[i] = -moveAmount + (moveAmount * digitFractions[i]) +            } +        } + +        invalidate() + +        if (fraction == 1.0f) { +            // Reset +            currentAnimationNeededStop = false +        } + +        lastSeenAnimationProgress = fraction + +        // Ensure that the actual clock container is always in the "end" position. +        this.setLeftTopRightBottom(toRect.left, toRect.top, toRect.right, toRect.bottom) +    } +      // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.      // This is an optimization to ensure we only recompute the patterns when the inputs change.      private object Patterns { @@ -458,5 +594,36 @@ class AnimatableClockView @JvmOverloads constructor(          private const val APPEAR_ANIM_DURATION: Long = 350          private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500          private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000 + +        // Constants for the animation +        private val MOVE_INTERPOLATOR = Interpolators.STANDARD + +        // Calculate the positions of all of the digits... +        // Offset each digit by, say, 0.1 +        // This means that each digit needs to move over a slice of "fractions", i.e. digit 0 should +        // move from 0.0 - 0.7, digit 1 from 0.1 - 0.8, digit 2 from 0.2 - 0.9, and digit 3 +        // from 0.3 - 1.0. +        private const val NUM_DIGITS = 4 +        private const val DIGITS_PER_LINE = 2 + +        // How much of "fraction" to spend on canceling the animation, if needed +        private const val ANIMATION_CANCELLATION_TIME = 0.4f + +        // Delays. Each digit's animation should have a slight delay, so we get a nice +        // "stepping" effect. When moving right, the second digit of the hour should move first. +        // When moving left, the first digit of the hour should move first. The lists encode +        // the delay for each digit (hour[0], hour[1], minute[0], minute[1]), to be multiplied +        // by delayMultiplier. +        private val MOVE_LEFT_DELAYS = listOf(0, 1, 2, 3) +        private val MOVE_RIGHT_DELAYS = listOf(1, 0, 3, 2) + +        // How much delay to apply to each subsequent digit. This is measured in terms of "fraction" +        // (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc +        // before moving). +        private const val MOVE_DIGIT_STEP = 0.1f + +        // Total available transition time for each digit, taking into account the step. If step is +        // 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7. +        private val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)      }  } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt index f03fee4b0c2d..e3c21cca2263 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -22,6 +22,7 @@ import android.os.UserHandle  import android.provider.Settings  import android.util.Log  import com.android.systemui.dagger.qualifiers.Main +import com.android.internal.annotations.Keep  import com.android.systemui.plugins.ClockController  import com.android.systemui.plugins.ClockId  import com.android.systemui.plugins.ClockMetadata @@ -201,6 +202,7 @@ open class ClockRegistry(          val provider: ClockProvider      ) +    @Keep      private data class ClockSetting(          val clockId: ClockId,          val _applied_timestamp: Long? diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt index b88795157a43..6fd61daee6ff 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -16,6 +16,7 @@ package com.android.systemui.shared.clocks  import android.content.Context  import android.content.res.Resources  import android.graphics.Color +import android.graphics.Rect  import android.icu.text.NumberFormat  import android.util.TypedValue  import android.view.LayoutInflater @@ -130,6 +131,10 @@ class DefaultClockController(              lp.topMargin = (-0.5f * view.bottom).toInt()              view.setLayoutParams(lp)          } + +        fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) { +            view.moveForSplitShade(fromRect, toRect, fraction) +        }      }      inner class DefaultClockEvents : ClockEvents { @@ -209,6 +214,13 @@ class DefaultClockController(                  clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }              }          } + +        override fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) { +            largeClock.moveForSplitShade(fromRect, toRect, fraction) +        } + +        override val hasCustomPositionUpdatedAnimation: Boolean +            get() = true      }      private class AnimationState( diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt index dd2e55d4e7d7..cd4b9994ccca 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt @@ -15,6 +15,7 @@   */  package com.android.systemui.shared.regionsampling +import android.graphics.Color  import android.graphics.Rect  import android.view.View  import androidx.annotation.VisibleForTesting @@ -33,18 +34,19 @@ open class RegionSamplingInstance(          regionSamplingEnabled: Boolean,          updateFun: UpdateColorCallback  ) { -    private var isDark = RegionDarkness.DEFAULT +    private var regionDarkness = RegionDarkness.DEFAULT      private var samplingBounds = Rect()      private val tmpScreenLocation = IntArray(2)      @VisibleForTesting var regionSampler: RegionSamplingHelper? = null - +    private var lightForegroundColor = Color.WHITE +    private var darkForegroundColor = Color.BLACK      /**       * Interface for method to be passed into RegionSamplingHelper       */      @FunctionalInterface      interface UpdateColorCallback {          /** -         * Method to update the text colors after clock darkness changed. +         * Method to update the foreground colors after clock darkness changed.           */          fun updateColors()      } @@ -59,6 +61,30 @@ open class RegionSamplingInstance(          return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor)      } +    /** +     * Sets the colors to be used for Dark and Light Foreground. +     * +     * @param lightColor The color used for Light Foreground. +     * @param darkColor The color used for Dark Foreground. +     */ +    fun setForegroundColors(lightColor: Int, darkColor: Int) { +        lightForegroundColor = lightColor +        darkForegroundColor = darkColor +    } + +    /** +     * Determines which foreground color to use based on region darkness. +     * +     * @return the determined foreground color +     */ +    fun currentForegroundColor(): Int{ +        return if (regionDarkness.isDark) { +            lightForegroundColor +        } else { +            darkForegroundColor +        } +    } +      private fun convertToClockDarkness(isRegionDark: Boolean): RegionDarkness {          return if (isRegionDark) {              RegionDarkness.DARK @@ -68,7 +94,7 @@ open class RegionSamplingInstance(      }      fun currentRegionDarkness(): RegionDarkness { -        return isDark +        return regionDarkness      }      /** @@ -97,7 +123,7 @@ open class RegionSamplingInstance(              regionSampler = createRegionSamplingHelper(sampledView,                      object : SamplingCallback {                          override fun onRegionDarknessChanged(isRegionDark: Boolean) { -                            isDark = convertToClockDarkness(isRegionDark) +                            regionDarkness = convertToClockDarkness(isRegionDark)                              updateFun.updateColors()                          }                          /** diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java deleted file mode 100644 index 30c062b66da9..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (C) 2018 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.shared.system; - -import android.graphics.HardwareRenderer; -import android.graphics.Matrix; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Handler.Callback; -import android.os.Message; -import android.os.Trace; -import android.view.SurfaceControl; -import android.view.SurfaceControl.Transaction; -import android.view.View; -import android.view.ViewRootImpl; - -import java.util.function.Consumer; - -/** - * Helper class to apply surface transactions in sync with RenderThread. - * - * NOTE: This is a modification of {@link android.view.SyncRtSurfaceTransactionApplier}, we can't  - *       currently reference that class from the shared lib as it is hidden. - */ -public class SyncRtSurfaceTransactionApplierCompat { - -    public static final int FLAG_ALL = 0xffffffff; -    public static final int FLAG_ALPHA = 1; -    public static final int FLAG_MATRIX = 1 << 1; -    public static final int FLAG_WINDOW_CROP = 1 << 2; -    public static final int FLAG_LAYER = 1 << 3; -    public static final int FLAG_CORNER_RADIUS = 1 << 4; -    public static final int FLAG_BACKGROUND_BLUR_RADIUS = 1 << 5; -    public static final int FLAG_VISIBILITY = 1 << 6; -    public static final int FLAG_RELATIVE_LAYER = 1 << 7; -    public static final int FLAG_SHADOW_RADIUS = 1 << 8; - -    private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0; - -    private final SurfaceControl mBarrierSurfaceControl; -    private final ViewRootImpl mTargetViewRootImpl; -    private final Handler mApplyHandler; - -    private int mSequenceNumber = 0; -    private int mPendingSequenceNumber = 0; -    private Runnable mAfterApplyCallback; - -    /** -     * @param targetView The view in the surface that acts as synchronization anchor. -     */ -    public SyncRtSurfaceTransactionApplierCompat(View targetView) { -        mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null; -        mBarrierSurfaceControl = mTargetViewRootImpl != null -            ? mTargetViewRootImpl.getSurfaceControl() : null; - -        mApplyHandler = new Handler(new Callback() { -            @Override -            public boolean handleMessage(Message msg) { -                if (msg.what == MSG_UPDATE_SEQUENCE_NUMBER) { -                    onApplyMessage(msg.arg1); -                    return true; -                } -                return false; -            } -        }); -    } - -    private void onApplyMessage(int seqNo) { -        mSequenceNumber = seqNo; -        if (mSequenceNumber == mPendingSequenceNumber && mAfterApplyCallback != null) { -            Runnable r = mAfterApplyCallback; -            mAfterApplyCallback = null; -            r.run(); -        } -    } - -    /** -     * Schedules applying surface parameters on the next frame. -     * -     * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into -     *               this method to avoid synchronization issues. -     */ -    public void scheduleApply(final SyncRtSurfaceTransactionApplierCompat.SurfaceParams... params) { -        if (mTargetViewRootImpl == null || mTargetViewRootImpl.getView() == null) { -            return; -        } - -        mPendingSequenceNumber++; -        final int toApplySeqNo = mPendingSequenceNumber; -        mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() { -            @Override -            public void onFrameDraw(long frame) { -                if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) { -                    Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0) -                            .sendToTarget(); -                    return; -                } -                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Sync transaction frameNumber=" + frame); -                Transaction t = new Transaction(); -                for (int i = params.length - 1; i >= 0; i--) { -                    SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams = -                            params[i]; -                    surfaceParams.applyTo(t); -                } -                if (mTargetViewRootImpl != null) { -                    mTargetViewRootImpl.mergeWithNextTransaction(t, frame); -                } else { -                    t.apply(); -                } -                Trace.traceEnd(Trace.TRACE_TAG_VIEW); -                Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0) -                        .sendToTarget(); -            } -        }); - -        // Make sure a frame gets scheduled. -        mTargetViewRootImpl.getView().invalidate(); -    } - -    /** -     * Calls the runnable when any pending apply calls have completed -     */ -    public void addAfterApplyCallback(final Runnable afterApplyCallback) { -        if (mSequenceNumber == mPendingSequenceNumber) { -            afterApplyCallback.run(); -        } else { -            if (mAfterApplyCallback == null) { -                mAfterApplyCallback = afterApplyCallback; -            } else { -                final Runnable oldCallback = mAfterApplyCallback; -                mAfterApplyCallback = new Runnable() { -                    @Override -                    public void run() { -                        afterApplyCallback.run(); -                        oldCallback.run(); -                    } -                }; -            } -        } -    } - -    public static void applyParams(TransactionCompat t, -            SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) { -        params.applyTo(t.mTransaction); -    } - -    /** -     * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is -     * attached if necessary. -     */ -    public static void create(final View targetView, -            final Consumer<SyncRtSurfaceTransactionApplierCompat> callback) { -        if (targetView == null) { -            // No target view, no applier -            callback.accept(null); -        } else if (targetView.getViewRootImpl() != null) { -            // Already attached, we're good to go -            callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView)); -        } else { -            // Haven't been attached before we can get the view root -            targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { -                @Override -                public void onViewAttachedToWindow(View v) { -                    targetView.removeOnAttachStateChangeListener(this); -                    callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView)); -                } - -                @Override -                public void onViewDetachedFromWindow(View v) { -                    // Do nothing -                } -            }); -        } -    } - -    public static class SurfaceParams { -        public static class Builder { -            final SurfaceControl surface; -            int flags; -            float alpha; -            float cornerRadius; -            int backgroundBlurRadius; -            Matrix matrix; -            Rect windowCrop; -            int layer; -            SurfaceControl relativeTo; -            int relativeLayer; -            boolean visible; -            float shadowRadius; - -            /** -             * @param surface The surface to modify. -             */ -            public Builder(SurfaceControl surface) { -                this.surface = surface; -            } - -            /** -             * @param alpha The alpha value to apply to the surface. -             * @return this Builder -             */ -            public Builder withAlpha(float alpha) { -                this.alpha = alpha; -                flags |= FLAG_ALPHA; -                return this; -            } - -            /** -             * @param matrix The matrix to apply to the surface. -             * @return this Builder -             */ -            public Builder withMatrix(Matrix matrix) { -                this.matrix = new Matrix(matrix); -                flags |= FLAG_MATRIX; -                return this; -            } - -            /** -             * @param windowCrop The window crop to apply to the surface. -             * @return this Builder -             */ -            public Builder withWindowCrop(Rect windowCrop) { -                this.windowCrop = new Rect(windowCrop); -                flags |= FLAG_WINDOW_CROP; -                return this; -            } - -            /** -             * @param layer The layer to assign the surface. -             * @return this Builder -             */ -            public Builder withLayer(int layer) { -                this.layer = layer; -                flags |= FLAG_LAYER; -                return this; -            } - -            /** -             * @param relativeTo The surface that's set relative layer to. -             * @param relativeLayer The relative layer. -             * @return this Builder -             */ -            public Builder withRelativeLayerTo(SurfaceControl relativeTo, int relativeLayer) { -                this.relativeTo = relativeTo; -                this.relativeLayer = relativeLayer; -                flags |= FLAG_RELATIVE_LAYER; -                return this; -            } - -            /** -             * @param radius the Radius for rounded corners to apply to the surface. -             * @return this Builder -             */ -            public Builder withCornerRadius(float radius) { -                this.cornerRadius = radius; -                flags |= FLAG_CORNER_RADIUS; -                return this; -            } - -            /** -             * @param radius the Radius for the shadows to apply to the surface. -             * @return this Builder -             */ -            public Builder withShadowRadius(float radius) { -                this.shadowRadius = radius; -                flags |= FLAG_SHADOW_RADIUS; -                return this; -            } - -            /** -             * @param radius the Radius for blur to apply to the background surfaces. -             * @return this Builder -             */ -            public Builder withBackgroundBlur(int radius) { -                this.backgroundBlurRadius = radius; -                flags |= FLAG_BACKGROUND_BLUR_RADIUS; -                return this; -            } - -            /** -             * @param visible The visibility to apply to the surface. -             * @return this Builder -             */ -            public Builder withVisibility(boolean visible) { -                this.visible = visible; -                flags |= FLAG_VISIBILITY; -                return this; -            } - -            /** -             * @return a new SurfaceParams instance -             */ -            public SurfaceParams build() { -                return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer, -                        relativeTo, relativeLayer, cornerRadius, backgroundBlurRadius, visible, -                        shadowRadius); -            } -        } - -        private SurfaceParams(SurfaceControl surface, int flags, float alpha, Matrix matrix, -                Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer, -                float cornerRadius, int backgroundBlurRadius, boolean visible, float shadowRadius) { -            this.flags = flags; -            this.surface = surface; -            this.alpha = alpha; -            this.matrix = matrix; -            this.windowCrop = windowCrop; -            this.layer = layer; -            this.relativeTo = relativeTo; -            this.relativeLayer = relativeLayer; -            this.cornerRadius = cornerRadius; -            this.backgroundBlurRadius = backgroundBlurRadius; -            this.visible = visible; -            this.shadowRadius = shadowRadius; -        } - -        private final int flags; -        private final float[] mTmpValues = new float[9]; - -        public final SurfaceControl surface; -        public final float alpha; -        public final float cornerRadius; -        public final int backgroundBlurRadius; -        public final Matrix matrix; -        public final Rect windowCrop; -        public final int layer; -        public final SurfaceControl relativeTo; -        public final int relativeLayer; -        public final boolean visible; -        public final float shadowRadius; - -        public void applyTo(SurfaceControl.Transaction t) { -            if ((flags & FLAG_MATRIX) != 0) { -                t.setMatrix(surface, matrix, mTmpValues); -            } -            if ((flags & FLAG_WINDOW_CROP) != 0) { -                t.setWindowCrop(surface, windowCrop); -            } -            if ((flags & FLAG_ALPHA) != 0) { -                t.setAlpha(surface, alpha); -            } -            if ((flags & FLAG_LAYER) != 0) { -                t.setLayer(surface, layer); -            } -            if ((flags & FLAG_CORNER_RADIUS) != 0) { -                t.setCornerRadius(surface, cornerRadius); -            } -            if ((flags & FLAG_BACKGROUND_BLUR_RADIUS) != 0) { -                t.setBackgroundBlurRadius(surface, backgroundBlurRadius); -            } -            if ((flags & FLAG_VISIBILITY) != 0) { -                if (visible) { -                    t.show(surface); -                } else { -                    t.hide(surface); -                } -            } -            if ((flags & FLAG_RELATIVE_LAYER) != 0) { -                t.setRelativeLayer(surface, relativeTo, relativeLayer); -            } -            if ((flags & FLAG_SHADOW_RADIUS) != 0) { -                t.setShadowRadius(surface, shadowRadius); -            } -        } -    } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java deleted file mode 100644 index 43a882a5f6be..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2018 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.shared.system; - -import android.graphics.Matrix; -import android.graphics.Rect; -import android.view.SurfaceControl; -import android.view.SurfaceControl.Transaction; - -public class TransactionCompat { - -    final Transaction mTransaction; - -    final float[] mTmpValues = new float[9]; - -    public TransactionCompat() { -        mTransaction = new Transaction(); -    } - -    public void apply() { -        mTransaction.apply(); -    } - -    public TransactionCompat show(SurfaceControl surfaceControl) { -        mTransaction.show(surfaceControl); -        return this; -    } - -    public TransactionCompat hide(SurfaceControl surfaceControl) { -        mTransaction.hide(surfaceControl); -        return this; -    } - -    public TransactionCompat setPosition(SurfaceControl surfaceControl, float x, float y) { -        mTransaction.setPosition(surfaceControl, x, y); -        return this; -    } - -    public TransactionCompat setSize(SurfaceControl surfaceControl, int w, int h) { -        mTransaction.setBufferSize(surfaceControl, w, h); -        return this; -    } - -    public TransactionCompat setLayer(SurfaceControl surfaceControl, int z) { -        mTransaction.setLayer(surfaceControl, z); -        return this; -    } - -    public TransactionCompat setAlpha(SurfaceControl surfaceControl, float alpha) { -        mTransaction.setAlpha(surfaceControl, alpha); -        return this; -    } - -    public TransactionCompat setOpaque(SurfaceControl surfaceControl, boolean opaque) { -        mTransaction.setOpaque(surfaceControl, opaque); -        return this; -    } - -    public TransactionCompat setMatrix(SurfaceControl surfaceControl, float dsdx, float dtdx, -            float dtdy, float dsdy) { -        mTransaction.setMatrix(surfaceControl, dsdx, dtdx, dtdy, dsdy); -        return this; -    } - -    public TransactionCompat setMatrix(SurfaceControl surfaceControl, Matrix matrix) { -        mTransaction.setMatrix(surfaceControl, matrix, mTmpValues); -        return this; -    } - -    public TransactionCompat setWindowCrop(SurfaceControl surfaceControl, Rect crop) { -        mTransaction.setWindowCrop(surfaceControl, crop); -        return this; -    } - -    public TransactionCompat setCornerRadius(SurfaceControl surfaceControl, float radius) { -        mTransaction.setCornerRadius(surfaceControl, radius); -        return this; -    } - -    public TransactionCompat setBackgroundBlurRadius(SurfaceControl surfaceControl, int radius) { -        mTransaction.setBackgroundBlurRadius(surfaceControl, radius); -        return this; -    } - -    public TransactionCompat setColor(SurfaceControl surfaceControl, float[] color) { -        mTransaction.setColor(surfaceControl, color); -        return this; -    } - -    public static void setRelativeLayer(Transaction t, SurfaceControl surfaceControl, -            SurfaceControl relativeTo, int z) { -        t.setRelativeLayer(surfaceControl, relativeTo, z); -    } -} diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt index 0075ddd73cd3..5277e40492e4 100644 --- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt +++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt @@ -16,19 +16,29 @@  package com.android.keyguard +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet +import android.animation.ObjectAnimator  import android.content.Context  import android.content.res.ColorStateList  import android.content.res.TypedArray  import android.graphics.Color  import android.util.AttributeSet +import android.view.View  import com.android.settingslib.Utils +import com.android.systemui.animation.Interpolators  /** Displays security messages for the keyguard bouncer. */ -class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) : +open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :      KeyguardMessageArea(context, attrs) {      private val DEFAULT_COLOR = -1      private var mDefaultColorState: ColorStateList? = null      private var mNextMessageColorState: ColorStateList? = ColorStateList.valueOf(DEFAULT_COLOR) +    private val animatorSet = AnimatorSet() +    private var textAboutToShow: CharSequence? = null +    protected open val SHOW_DURATION_MILLIS = 150L +    protected open val HIDE_DURATION_MILLIS = 200L      override fun updateTextColor() {          var colorState = mDefaultColorState @@ -58,4 +68,46 @@ class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :          mDefaultColorState = Utils.getColorAttr(context, android.R.attr.textColorPrimary)          super.reloadColor()      } + +    override fun setMessage(msg: CharSequence?) { +        if (msg == textAboutToShow || msg == text) { +            return +        } +        textAboutToShow = msg + +        if (animatorSet.isRunning) { +            animatorSet.cancel() +            textAboutToShow = null +        } + +        val hideAnimator = +            ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f).apply { +                duration = HIDE_DURATION_MILLIS +                interpolator = Interpolators.STANDARD_ACCELERATE +            } + +        hideAnimator.addListener( +            object : AnimatorListenerAdapter() { +                override fun onAnimationEnd(animation: Animator?) { +                    super@BouncerKeyguardMessageArea.setMessage(msg) +                } +            } +        ) +        val showAnimator = +            ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f).apply { +                duration = SHOW_DURATION_MILLIS +                interpolator = Interpolators.STANDARD_DECELERATE +            } + +        showAnimator.addListener( +            object : AnimatorListenerAdapter() { +                override fun onAnimationEnd(animation: Animator?) { +                    textAboutToShow = null +                } +            } +        ) + +        animatorSet.playSequentially(hideAnimator, showAnimator) +        animatorSet.start() +    }  } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index b450ec35db32..20d064b960d2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -40,6 +40,7 @@ import com.android.systemui.dump.DumpManager;  import com.android.systemui.flags.FeatureFlags;  import com.android.systemui.flags.Flags;  import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.plugins.ClockAnimations;  import com.android.systemui.plugins.ClockController;  import com.android.systemui.plugins.statusbar.StatusBarStateController;  import com.android.systemui.shared.clocks.ClockRegistry; @@ -404,5 +405,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS              clock.dump(pw);          }      } -} +    /** Gets the animations for the current clock. */ +    public ClockAnimations getClockAnimations() { +        return getClock().getAnimations(); +    } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index f26b9057dc7c..73229c321079 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -152,6 +152,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>      }      public void startAppearAnimation() { +        mMessageAreaController.setMessage(getInitialMessageResId());          mView.startAppearAnimation();      } @@ -169,6 +170,11 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>          return view.indexOfChild(mView);      } +    /** Determines the message to show in the bouncer when it first appears. */ +    protected int getInitialMessageResId() { +        return 0; +    } +      /** Factory for a {@link KeyguardInputViewController}. */      public static class Factory {          private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java index c2802f7b6843..2bd3ca59b740 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java @@ -18,7 +18,6 @@ package com.android.keyguard;  import android.content.res.ColorStateList;  import android.content.res.Configuration; -import android.text.TextUtils;  import com.android.systemui.statusbar.policy.ConfigurationController;  import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -100,15 +99,6 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>          mView.setMessage(resId);      } -    /** -     * Set Text if KeyguardMessageArea is empty. -     */ -    public void setMessageIfEmpty(int resId) { -        if (TextUtils.isEmpty(mView.getText())) { -            setMessage(resId); -        } -    } -      public void setNextMessageColor(ColorStateList colorState) {          mView.setNextMessageColor(colorState);      } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 29e912fdab32..0025986c0e5c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -187,7 +187,7 @@ public class KeyguardPasswordViewController      @Override      void resetState() {          mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); -        mMessageAreaController.setMessage(""); +        mMessageAreaController.setMessage(getInitialMessageResId());          final boolean wasDisabled = mPasswordEntry.isEnabled();          mView.setPasswordEntryEnabled(true);          mView.setPasswordEntryInputEnabled(true); @@ -207,7 +207,6 @@ public class KeyguardPasswordViewController          if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {              showInput();          } -        mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_password);      }      private void showInput() { @@ -324,4 +323,9 @@ public class KeyguardPasswordViewController                  //enabled input method subtype (The current IME should be LatinIME.)                  || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;      } + +    @Override +    protected int getInitialMessageResId() { +        return R.string.keyguard_enter_your_password; +    }  } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index 987164557a7a..1f0bd54f8e09 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -298,12 +298,6 @@ public class KeyguardPatternViewController      }      @Override -    public void onResume(int reason) { -        super.onResume(reason); -        mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pattern); -    } - -    @Override      public boolean needsInput() {          return false;      } @@ -361,7 +355,7 @@ public class KeyguardPatternViewController      }      private void displayDefaultSecurityMessage() { -        mMessageAreaController.setMessage(""); +        mMessageAreaController.setMessage(getInitialMessageResId());      }      private void handleAttemptLockout(long elapsedRealtimeDeadline) { @@ -392,4 +386,9 @@ public class KeyguardPatternViewController          }.start();      } + +    @Override +    protected int getInitialMessageResId() { +        return R.string.keyguard_enter_your_pattern; +    }  } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index 59a018ad51df..f7423ed12e68 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -127,7 +127,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB      public void onResume(int reason) {          super.onResume(reason);          mPasswordEntry.requestFocus(); -        mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);      }      @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 89fcc47caf57..7876f071fdf5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -76,20 +76,13 @@ public class KeyguardPinViewController      }      @Override -    void resetState() { -        super.resetState(); -        mMessageAreaController.setMessage(""); -    } - -    @Override -    public void startAppearAnimation() { -        mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin); -        super.startAppearAnimation(); -    } - -    @Override      public boolean startDisappearAnimation(Runnable finishRunnable) {          return mView.startDisappearAnimation(                  mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);      } + +    @Override +    protected int getInitialMessageResId() { +        return R.string.keyguard_enter_your_pin; +    }  } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index e9f06eddf261..784974778af5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -20,6 +20,7 @@ import android.graphics.Rect;  import android.util.Slog;  import com.android.keyguard.KeyguardClockSwitch.ClockSize; +import com.android.systemui.plugins.ClockAnimations;  import com.android.systemui.statusbar.notification.AnimatableProperty;  import com.android.systemui.statusbar.notification.PropertyAnimator;  import com.android.systemui.statusbar.notification.stack.AnimationProperties; @@ -232,4 +233,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV              mView.setClipBounds(null);          }      } + +    /** Gets the animations for the current clock. */ +    public ClockAnimations getClockAnimations() { +        return mKeyguardClockSwitchController.getClockAnimations(); +    }  } diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt index 2c2ab7b39161..6264ce7273f1 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt @@ -17,9 +17,9 @@  package com.android.keyguard.logging  import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG  import com.android.systemui.log.dagger.BiometricMessagesLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG  import javax.inject.Inject  /** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */ diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt index 50012a589b5a..46f3d4e5f6aa 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt @@ -16,15 +16,15 @@  package com.android.keyguard.logging -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.VERBOSE -import com.android.systemui.log.LogLevel.WARNING -import com.android.systemui.log.MessageInitializer -import com.android.systemui.log.MessagePrinter  import com.android.systemui.log.dagger.KeyguardLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.ERROR +import com.android.systemui.plugins.log.LogLevel.VERBOSE +import com.android.systemui.plugins.log.LogLevel.WARNING +import com.android.systemui.plugins.log.MessageInitializer +import com.android.systemui.plugins.log.MessagePrinter  import com.google.errorprone.annotations.CompileTimeConstant  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 2eee95738b7b..82b32cf616ec 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -22,13 +22,13 @@ import android.telephony.SubscriptionInfo  import com.android.keyguard.ActiveUnlockConfig  import com.android.keyguard.KeyguardListenModel  import com.android.keyguard.KeyguardUpdateMonitorCallback -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.VERBOSE -import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.ERROR +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogLevel.VERBOSE +import com.android.systemui.plugins.log.LogLevel.WARNING  import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog  import com.google.errorprone.annotations.CompileTimeConstant  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt index a3351e1a6440..5d52056d8b17 100644 --- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt +++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt @@ -86,30 +86,38 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView {          onUpdate()      } -    fun onDisplayChanged(newDisplayUniqueId: String?) { +    fun updateConfiguration(newDisplayUniqueId: String?) { +        val info = DisplayInfo() +        context.display?.getDisplayInfo(info)          val oldMode: Display.Mode? = displayMode -        val display: Display? = context.display -        displayMode = display?.mode +        displayMode = info.mode -        if (displayUniqueId != display?.uniqueId) { -            displayUniqueId = display?.uniqueId -            shouldDrawCutout = DisplayCutout.getFillBuiltInDisplayCutout( -                context.resources, displayUniqueId -            ) -        } +        updateDisplayUniqueId(info.uniqueId)          // Skip if display mode or cutout hasn't changed.          if (!displayModeChanged(oldMode, displayMode) && -                display?.cutout == displayInfo.displayCutout) { +                displayInfo.displayCutout == info.displayCutout && +                displayRotation == info.rotation) {              return          } -        if (newDisplayUniqueId == display?.uniqueId) { +        if (newDisplayUniqueId == info.uniqueId) { +            displayRotation = info.rotation              updateCutout()              updateProtectionBoundingPath()              onUpdate()          }      } +    open fun updateDisplayUniqueId(newDisplayUniqueId: String?) { +        if (displayUniqueId != newDisplayUniqueId) { +            displayUniqueId = newDisplayUniqueId +            shouldDrawCutout = DisplayCutout.getFillBuiltInDisplayCutout( +                    context.resources, displayUniqueId +            ) +            invalidate() +        } +    } +      open fun updateRotation(rotation: Int) {          displayRotation = rotation          updateCutout() diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index b5f42a164495..11d579d481c1 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -456,7 +456,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {                      }                  } -                boolean needToUpdateProviderViews = false;                  final String newUniqueId = mDisplayInfo.uniqueId;                  if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {                      mDisplayUniqueId = newUniqueId; @@ -474,37 +473,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {                          setupDecorations();                          return;                      } - -                    if (mScreenDecorHwcLayer != null) { -                        updateHwLayerRoundedCornerDrawable(); -                        updateHwLayerRoundedCornerExistAndSize(); -                    } -                    needToUpdateProviderViews = true; -                } - -                final float newRatio = getPhysicalPixelDisplaySizeRatio(); -                if (mRoundedCornerResDelegate.getPhysicalPixelDisplaySizeRatio() != newRatio) { -                    mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(newRatio); -                    if (mScreenDecorHwcLayer != null) { -                        updateHwLayerRoundedCornerExistAndSize(); -                    } -                    needToUpdateProviderViews = true; -                } - -                if (needToUpdateProviderViews) { -                    updateOverlayProviderViews(null); -                } else { -                    updateOverlayProviderViews(new Integer[] { -                            mFaceScanningViewId, -                            R.id.display_cutout, -                            R.id.display_cutout_left, -                            R.id.display_cutout_right, -                            R.id.display_cutout_bottom, -                    }); -                } - -                if (mScreenDecorHwcLayer != null) { -                    mScreenDecorHwcLayer.onDisplayChanged(newUniqueId);                  }              }          }; @@ -1070,9 +1038,11 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {                  && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) {              mRotation = newRotation;              mDisplayMode = newMod; +            mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( +                    getPhysicalPixelDisplaySizeRatio());              if (mScreenDecorHwcLayer != null) {                  mScreenDecorHwcLayer.pendingConfigChange = false; -                mScreenDecorHwcLayer.updateRotation(mRotation); +                mScreenDecorHwcLayer.updateConfiguration(mDisplayUniqueId);                  updateHwLayerRoundedCornerExistAndSize();                  updateHwLayerRoundedCornerDrawable();              } @@ -1111,7 +1081,8 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {                  context.getResources(), context.getDisplay().getUniqueId());      } -    private void updateOverlayProviderViews(@Nullable Integer[] filterIds) { +    @VisibleForTesting +    void updateOverlayProviderViews(@Nullable Integer[] filterIds) {          if (mOverlays == null) {              return;          } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 242a5983a59d..9493975ca00f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -792,7 +792,11 @@ public class AuthController implements CoreStartable,  CommandQueue.Callbacks,              mUdfpsBounds = udfpsProp.getLocation().getRect();              mUdfpsBounds.scale(mScaleFactor);              mUdfpsController.updateOverlayParams(udfpsProp.sensorId, -                    new UdfpsOverlayParams(mUdfpsBounds, mCachedDisplayInfo.getNaturalWidth(), +                    new UdfpsOverlayParams(mUdfpsBounds, new Rect( +                            0, mCachedDisplayInfo.getNaturalHeight() / 2, +                            mCachedDisplayInfo.getNaturalWidth(), +                            mCachedDisplayInfo.getNaturalHeight()), +                            mCachedDisplayInfo.getNaturalWidth(),                              mCachedDisplayInfo.getNaturalHeight(), mScaleFactor,                              mCachedDisplayInfo.rotation));              if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds)) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt index 39199d194cc9..0d08b4307f12 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt @@ -16,12 +16,12 @@  package com.android.systemui.biometrics -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.VERBOSE -import com.android.systemui.log.LogLevel.WARNING  import com.android.systemui.log.dagger.UdfpsLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogLevel.ERROR +import com.android.systemui.plugins.log.LogLevel.VERBOSE +import com.android.systemui.plugins.log.LogLevel.WARNING  import com.google.errorprone.annotations.CompileTimeConstant  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt new file mode 100644 index 000000000000..6e78f3d3d6aa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.PixelFormat +import android.graphics.Rect +import android.hardware.fingerprint.FingerprintManager +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback +import android.os.Handler +import android.view.MotionEvent +import android.view.View +import android.view.WindowManager +import android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.Execution +import java.util.* +import java.util.concurrent.Executor +import javax.inject.Inject + +private const val TAG = "UdfpsOverlay" + +@SuppressLint("ClickableViewAccessibility") +@SysUISingleton +class UdfpsOverlay +@Inject +constructor( +    private val context: Context, +    private val execution: Execution, +    private val windowManager: WindowManager, +    private val fingerprintManager: FingerprintManager?, +    private val handler: Handler, +    private val biometricExecutor: Executor, +    private val alternateTouchProvider: Optional<AlternateUdfpsTouchProvider>, +    private val fgExecutor: DelayableExecutor, +    private val keyguardUpdateMonitor: KeyguardUpdateMonitor, +    private val authController: AuthController, +    private val udfpsLogger: UdfpsLogger +) : CoreStartable { + +    /** The view, when [isShowing], or null. */ +    var overlayView: UdfpsOverlayView? = null +        private set + +    private var requestId: Long = 0 +    private var onFingerDown = false +    val size = windowManager.maximumWindowMetrics.bounds +    val udfpsProps: MutableList<FingerprintSensorPropertiesInternal> = mutableListOf() + +    private var params: UdfpsOverlayParams = UdfpsOverlayParams() + +    private val coreLayoutParams = +        WindowManager.LayoutParams( +                WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, +                0 /* flags set in computeLayoutParams() */, +                PixelFormat.TRANSLUCENT +            ) +            .apply { +                title = TAG +                fitInsetsTypes = 0 +                gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT +                layoutInDisplayCutoutMode = +                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS +                flags = Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS +                privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY +                // Avoid announcing window title. +                accessibilityTitle = " " +                inputFeatures = INPUT_FEATURE_SPY +            } + +    fun onTouch(v: View, event: MotionEvent): Boolean { +        val view = v as UdfpsOverlayView + +        return when (event.action) { +            MotionEvent.ACTION_DOWN, +            MotionEvent.ACTION_MOVE -> { +                onFingerDown = true +                if (!view.isDisplayConfigured && alternateTouchProvider.isPresent) { +                    biometricExecutor.execute { +                        alternateTouchProvider +                            .get() +                            .onPointerDown( +                                requestId, +                                event.x.toInt(), +                                event.y.toInt(), +                                event.touchMinor, +                                event.touchMajor +                            ) +                    } +                    fgExecutor.execute { +                        if (keyguardUpdateMonitor.isFingerprintDetectionRunning) { +                            keyguardUpdateMonitor.onUdfpsPointerDown(requestId.toInt()) +                        } +                    } + +                    view.configureDisplay { +                        biometricExecutor.execute { alternateTouchProvider.get().onUiReady() } +                    } +                } + +                true +            } +            MotionEvent.ACTION_UP, +            MotionEvent.ACTION_CANCEL -> { +                if (onFingerDown && alternateTouchProvider.isPresent) { +                    biometricExecutor.execute { +                        alternateTouchProvider.get().onPointerUp(requestId) +                    } +                    fgExecutor.execute { +                        if (keyguardUpdateMonitor.isFingerprintDetectionRunning) { +                            keyguardUpdateMonitor.onUdfpsPointerUp(requestId.toInt()) +                        } +                    } +                } +                onFingerDown = false +                if (view.isDisplayConfigured) { +                    view.unconfigureDisplay() +                } + +                true +            } +            else -> false +        } +    } + +    fun show(requestId: Long): Boolean { +        this.requestId = requestId +        if (overlayView == null && alternateTouchProvider.isPresent) { +            UdfpsOverlayView(context, null).let { +                it.overlayParams = params +                it.setUdfpsDisplayMode( +                    UdfpsDisplayMode(context, execution, authController, udfpsLogger) +                ) +                it.setOnTouchListener { v, event -> onTouch(v, event) } +                overlayView = it +            } +            windowManager.addView(overlayView, coreLayoutParams) +            return true +        } + +        return false +    } + +    fun hide() { +        overlayView?.apply { +            windowManager.removeView(this) +            setOnTouchListener(null) +        } + +        overlayView = null +    } + +    @Override +    override fun start() { +        fingerprintManager?.addAuthenticatorsRegisteredCallback( +            object : IFingerprintAuthenticatorsRegisteredCallback.Stub() { +                override fun onAllAuthenticatorsRegistered( +                    sensors: List<FingerprintSensorPropertiesInternal> +                ) { +                    handler.post { handleAllFingerprintAuthenticatorsRegistered(sensors) } +                } +            } +        ) +    } + +    private fun handleAllFingerprintAuthenticatorsRegistered( +        sensors: List<FingerprintSensorPropertiesInternal> +    ) { +        for (props in sensors) { +            if (props.isAnyUdfpsType) { +                udfpsProps.add(props) +            } +        } + +        // Setup param size +        if (udfpsProps.isNotEmpty()) { +            params = +                UdfpsOverlayParams( +                    sensorBounds = udfpsProps[0].location.rect, +                    overlayBounds = Rect(0, size.height() / 2, size.width(), size.height()), +                    naturalDisplayWidth = size.width(), +                    naturalDisplayHeight = size.height(), +                    scaleFactor = 1f +                ) +        } +    } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt index d725dfbfe216..c23b0f09f099 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt @@ -20,6 +20,7 @@ import android.view.Surface.Rotation  data class UdfpsOverlayParams(      val sensorBounds: Rect = Rect(), +    val overlayBounds: Rect = Rect(),      val naturalDisplayWidth: Int = 0,      val naturalDisplayHeight: Int = 0,      val scaleFactor: Float = 1f, @@ -40,4 +41,4 @@ data class UdfpsOverlayParams(          } else {              naturalDisplayHeight          } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt new file mode 100644 index 000000000000..d37133239531 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.RectF +import android.util.AttributeSet +import android.widget.FrameLayout + +private const val TAG = "UdfpsOverlayView" + +class UdfpsOverlayView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { + +    private val sensorRect = RectF() +    var overlayParams = UdfpsOverlayParams() +    private var mUdfpsDisplayMode: UdfpsDisplayMode? = null + +    var overlayPaint = Paint() +    var sensorPaint = Paint() +    val centerPaint = Paint() + +    /** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */ +    var isDisplayConfigured: Boolean = false +        private set + +    init { +        this.setWillNotDraw(false) +    } + +    override fun onAttachedToWindow() { +        super.onAttachedToWindow() + +        overlayPaint.color = Color.argb(120, 255, 0, 0) +        overlayPaint.style = Paint.Style.FILL + +        sensorPaint.color = Color.argb(150, 134, 204, 255) +        sensorPaint.style = Paint.Style.FILL +    } + +    override fun onDraw(canvas: Canvas) { +        super.onDraw(canvas) + +        canvas.drawRect(overlayParams.overlayBounds, overlayPaint) +        canvas.drawRect(overlayParams.sensorBounds, sensorPaint) +        canvas.drawCircle( +            overlayParams.sensorBounds.exactCenterX(), +            overlayParams.sensorBounds.exactCenterY(), +            overlayParams.sensorBounds.width().toFloat() / 2, +            centerPaint +        ) +    } + +    fun setUdfpsDisplayMode(udfpsDisplayMode: UdfpsDisplayMode?) { +        mUdfpsDisplayMode = udfpsDisplayMode +    } + +    fun configureDisplay(onDisplayConfigured: Runnable) { +        isDisplayConfigured = true +        mUdfpsDisplayMode?.enable(onDisplayConfigured) +    } + +    fun unconfigureDisplay() { +        isDisplayConfigured = false +        mUdfpsDisplayMode?.disable(null /* onDisabled */) +    } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt index b1d6e003319a..75640b787a62 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt @@ -16,6 +16,7 @@  package com.android.systemui.biometrics +import android.content.Context  import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP  import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD  import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER @@ -23,9 +24,9 @@ import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTING  import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING  import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR  import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN -import android.hardware.fingerprint.IUdfpsOverlayController  import android.hardware.fingerprint.IUdfpsOverlayControllerCallback  import android.util.Log +import android.view.LayoutInflater  import com.android.systemui.dagger.SysUISingleton  import com.android.systemui.statusbar.commandline.Command  import com.android.systemui.statusbar.commandline.CommandRegistry @@ -41,14 +42,17 @@ private const val SENSOR_ID = 0   */  @SysUISingleton  class UdfpsShell @Inject constructor( -    commandRegistry: CommandRegistry +    commandRegistry: CommandRegistry, +    private val udfpsOverlay: UdfpsOverlay  ) : Command {      /**       * Set in [UdfpsController.java] constructor, used to show and hide the UDFPS overlay.       * TODO: inject after b/229290039 is resolved       */ -    var udfpsOverlayController: IUdfpsOverlayController? = null +    var udfpsOverlayController: UdfpsController.UdfpsOverlayController? = null +    var context: Context? = null +    var inflater: LayoutInflater? = null      init {          commandRegistry.registerCommand("udfps") { this } @@ -57,6 +61,11 @@ class UdfpsShell @Inject constructor(      override fun execute(pw: PrintWriter, args: List<String>) {          if (args.size == 1 && args[0] == "hide") {              hideOverlay() +        } else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "show") { +            hideOverlay() +            showUdfpsOverlay() +        } else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "hide") { +            hideUdfpsOverlay()          } else if (args.size == 2 && args[0] == "show") {              showOverlay(getEnrollmentReason(args[1]))          } else { @@ -104,7 +113,17 @@ class UdfpsShell @Inject constructor(          )      } +    private fun showUdfpsOverlay() { +        Log.v(TAG, "showUdfpsOverlay") +        udfpsOverlay.show(REQUEST_ID) +    } + +    private fun hideUdfpsOverlay() { +        Log.v(TAG, "hideUdfpsOverlay") +        udfpsOverlay.hide() +    } +      private fun hideOverlay() {          udfpsOverlayController?.hideUdfpsOverlay(SENSOR_ID)      } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt index 96af42bfda22..d99625a9fbf2 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt @@ -17,9 +17,9 @@  package com.android.systemui.bluetooth  import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.BluetoothLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import javax.inject.Inject  /** Helper class for logging bluetooth events. */ diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt index 5b3a982ab5e2..d27708fc04d7 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt @@ -20,11 +20,11 @@ import android.content.BroadcastReceiver  import android.content.Context  import android.content.Intent  import android.content.IntentFilter -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogMessage +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogMessage  import com.android.systemui.log.dagger.BroadcastDispatcherLog  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt index 77b65233c112..d3b5d0edd222 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt @@ -21,6 +21,8 @@ import android.content.Context  import android.content.Intent  import android.content.IntentFilter  import android.os.Bundle +import android.os.RemoteException +import android.service.dreams.IDreamManager  import android.view.View  import android.view.ViewGroup  import android.view.WindowInsets @@ -40,11 +42,13 @@ import javax.inject.Inject   */  class ControlsActivity @Inject constructor(      private val uiController: ControlsUiController, -    private val broadcastDispatcher: BroadcastDispatcher +    private val broadcastDispatcher: BroadcastDispatcher, +    private val dreamManager: IDreamManager,  ) : ComponentActivity() {      private lateinit var parent: ViewGroup      private lateinit var broadcastReceiver: BroadcastReceiver +    private var mExitToDream: Boolean = false      override fun onCreate(savedInstanceState: Bundle?) {          super.onCreate(savedInstanceState) @@ -81,17 +85,36 @@ class ControlsActivity @Inject constructor(          parent = requireViewById<ViewGroup>(R.id.global_actions_controls)          parent.alpha = 0f -        uiController.show(parent, { finish() }, this) +        uiController.show(parent, { finishOrReturnToDream() }, this)          ControlsAnimations.enterAnimation(parent).start()      } -    override fun onBackPressed() { +    override fun onResume() { +        super.onResume() +        mExitToDream = intent.getBooleanExtra(ControlsUiController.EXIT_TO_DREAM, false) +    } + +    fun finishOrReturnToDream() { +        if (mExitToDream) { +            try { +                mExitToDream = false +                dreamManager.dream() +                return +            } catch (e: RemoteException) { +                // Fall through +            } +        }          finish()      } +    override fun onBackPressed() { +        finishOrReturnToDream() +    } +      override fun onStop() {          super.onStop() +        mExitToDream = false          uiController.hide()      } @@ -106,7 +129,8 @@ class ControlsActivity @Inject constructor(          broadcastReceiver = object : BroadcastReceiver() {              override fun onReceive(context: Context, intent: Intent) {                  val action = intent.getAction() -                if (Intent.ACTION_SCREEN_OFF.equals(action)) { +                if (action == Intent.ACTION_SCREEN_OFF || +                    action == Intent.ACTION_DREAMING_STARTED) {                      finish()                  }              } @@ -114,6 +138,7 @@ class ControlsActivity @Inject constructor(          val filter = IntentFilter()          filter.addAction(Intent.ACTION_SCREEN_OFF) +        filter.addAction(Intent.ACTION_DREAMING_STARTED)          broadcastDispatcher.registerReceiver(broadcastReceiver, filter)      }  } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt index 822f8f2e6191..c1cfbcb0c211 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -27,6 +27,7 @@ interface ControlsUiController {      companion object {          public const val TAG = "ControlsUiController"          public const val EXTRA_ANIMATE = "extra_animate" +        public const val EXIT_TO_DREAM = "extra_exit_to_dream"      }      fun show(parent: ViewGroup, onDismiss: Runnable, activityContext: Context) diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 721c0ba4f865..09743ef7aebf 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -25,6 +25,7 @@ import com.android.systemui.SliceBroadcastRelayHandler  import com.android.systemui.accessibility.SystemActions  import com.android.systemui.accessibility.WindowMagnification  import com.android.systemui.biometrics.AuthController +import com.android.systemui.biometrics.UdfpsOverlay  import com.android.systemui.clipboardoverlay.ClipboardListener  import com.android.systemui.dagger.qualifiers.PerUser  import com.android.systemui.globalactions.GlobalActionsComponent @@ -218,6 +219,12 @@ abstract class SystemUICoreStartableModule {      @ClassKey(KeyguardLiftController::class)      abstract fun bindKeyguardLiftController(sysui: KeyguardLiftController): CoreStartable +    /** Inject into UdfpsOverlay.  */ +    @Binds +    @IntoMap +    @ClassKey(UdfpsOverlay::class) +    abstract fun bindUdfpsOverlay(sysui: UdfpsOverlay): CoreStartable +      /** Inject into MediaTttSenderCoordinator. */      @Binds      @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt index 991b54e8035e..ded0fb79cd6f 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt @@ -59,7 +59,7 @@ class CutoutDecorProviderImpl(          (view as? DisplayCutoutView)?.let { cutoutView ->              cutoutView.setColor(tintColor)              cutoutView.updateRotation(rotation) -            cutoutView.onDisplayChanged(displayUniqueId) +            cutoutView.updateConfiguration(displayUniqueId)          }      }  } diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt index ec0013bb5000..5fdd198c19d7 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt @@ -124,7 +124,7 @@ class FaceScanningOverlayProviderImpl(              view.layoutParams = it              (view as? FaceScanningOverlay)?.let { overlay ->                  overlay.setColor(tintColor) -                overlay.onDisplayChanged(displayUniqueId) +                overlay.updateConfiguration(displayUniqueId)              }          }      } diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt index a25286438387..8b4aeefb6ed4 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt @@ -78,23 +78,18 @@ class RoundedCornerResDelegate(          reloadMeasures()      } -    private fun reloadAll(newReloadToken: Int) { -        if (reloadToken == newReloadToken) { -            return -        } -        reloadToken = newReloadToken -        reloadRes() -        reloadMeasures() -    } -      fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {          if (displayUniqueId != newDisplayUniqueId) {              displayUniqueId = newDisplayUniqueId              newReloadToken ?.let { reloadToken = it }              reloadRes()              reloadMeasures() -        } else { -            newReloadToken?.let { reloadAll(it) } +        } else if (newReloadToken != null) { +            if (reloadToken == newReloadToken) { +                return +            } +            reloadToken = newReloadToken +            reloadMeasures()          }      } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index cc5766210406..0e1bfba8aadb 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -19,10 +19,10 @@ package com.android.systemui.doze  import android.view.Display  import com.android.systemui.doze.DozeLog.Reason  import com.android.systemui.doze.DozeLog.reasonToString -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.ERROR +import com.android.systemui.plugins.log.LogLevel.INFO  import com.android.systemui.log.dagger.DozeLog  import com.android.systemui.statusbar.policy.DevicePostureController  import java.text.SimpleDateFormat diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index ae412152b10e..96c35d43052e 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -20,7 +20,6 @@ import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWA  import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;  import android.annotation.MainThread; -import android.app.UiModeManager;  import android.content.res.Configuration;  import android.hardware.display.AmbientDisplayConfiguration;  import android.os.Trace; @@ -145,10 +144,9 @@ public class DozeMachine {      private final Service mDozeService;      private final WakeLock mWakeLock; -    private final AmbientDisplayConfiguration mConfig; +    private final AmbientDisplayConfiguration mAmbientDisplayConfig;      private final WakefulnessLifecycle mWakefulnessLifecycle;      private final DozeHost mDozeHost; -    private final UiModeManager mUiModeManager;      private final DockManager mDockManager;      private final Part[] mParts; @@ -156,18 +154,18 @@ public class DozeMachine {      private State mState = State.UNINITIALIZED;      private int mPulseReason;      private boolean mWakeLockHeldForCurrentState = false; +    private int mUiModeType = Configuration.UI_MODE_TYPE_NORMAL;      @Inject -    public DozeMachine(@WrappedService Service service, AmbientDisplayConfiguration config, +    public DozeMachine(@WrappedService Service service, +            AmbientDisplayConfiguration ambientDisplayConfig,              WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle, -            UiModeManager uiModeManager,              DozeLog dozeLog, DockManager dockManager,              DozeHost dozeHost, Part[] parts) {          mDozeService = service; -        mConfig = config; +        mAmbientDisplayConfig = ambientDisplayConfig;          mWakefulnessLifecycle = wakefulnessLifecycle;          mWakeLock = wakeLock; -        mUiModeManager = uiModeManager;          mDozeLog = dozeLog;          mDockManager = dockManager;          mDozeHost = dozeHost; @@ -187,6 +185,18 @@ public class DozeMachine {      }      /** +     * Notifies the {@link DozeMachine} that {@link Configuration} has changed. +     */ +    public void onConfigurationChanged(Configuration newConfiguration) { +        int newUiModeType = newConfiguration.uiMode & Configuration.UI_MODE_TYPE_MASK; +        if (mUiModeType == newUiModeType) return; +        mUiModeType = newUiModeType; +        for (Part part : mParts) { +            part.onUiModeTypeChanged(mUiModeType); +        } +    } + +    /**       * Requests transitioning to {@code requestedState}.       *       * This can be called during a state transition, in which case it will be queued until all @@ -211,6 +221,14 @@ public class DozeMachine {          requestState(State.DOZE_REQUEST_PULSE, pulseReason);      } +    /** +     * @return true if {@link DozeMachine} is currently in either {@link State#UNINITIALIZED} +     *  or {@link State#FINISH} +     */ +    public boolean isUninitializedOrFinished() { +        return mState == State.UNINITIALIZED || mState == State.FINISH; +    } +      void onScreenState(int state) {          mDozeLog.traceDisplayState(state);          for (Part part : mParts) { @@ -360,7 +378,7 @@ public class DozeMachine {          if (mState == State.FINISH) {              return State.FINISH;          } -        if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR +        if (mUiModeType == Configuration.UI_MODE_TYPE_CAR                  && (requestedState.canPulse() || requestedState.staysAwake())) {              Log.i(TAG, "Doze is suppressed with all triggers disabled as car mode is active");              mDozeLog.traceCarModeStarted(); @@ -411,7 +429,7 @@ public class DozeMachine {                      nextState = State.FINISH;                  } else if (mDockManager.isDocked()) {                      nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED; -                } else if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) { +                } else if (mAmbientDisplayConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {                      nextState = State.DOZE_AOD;                  } else {                      nextState = State.DOZE; @@ -427,6 +445,7 @@ public class DozeMachine {      /** Dumps the current state */      public void dump(PrintWriter pw) {          pw.print(" state="); pw.println(mState); +        pw.print(" mUiModeType="); pw.println(mUiModeType);          pw.print(" wakeLockHeldForCurrentState="); pw.println(mWakeLockHeldForCurrentState);          pw.print(" wakeLock="); pw.println(mWakeLock);          pw.println("Parts:"); @@ -459,6 +478,19 @@ public class DozeMachine {          /** Sets the {@link DozeMachine} when this Part is associated with one. */          default void setDozeMachine(DozeMachine dozeMachine) {} + +        /** +         * Notifies the Part about a change in {@link Configuration#uiMode}. +         * +         * @param newUiModeType {@link Configuration#UI_MODE_TYPE_NORMAL}, +         *                   {@link Configuration#UI_MODE_TYPE_DESK}, +         *                   {@link Configuration#UI_MODE_TYPE_CAR}, +         *                   {@link Configuration#UI_MODE_TYPE_TELEVISION}, +         *                   {@link Configuration#UI_MODE_TYPE_APPLIANCE}, +         *                   {@link Configuration#UI_MODE_TYPE_WATCH}, +         *                   or {@link Configuration#UI_MODE_TYPE_VR_HEADSET} +         */ +        default void onUiModeTypeChanged(int newUiModeType) {}      }      /** A wrapper interface for {@link android.service.dreams.DreamService} */ diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index a2eb4e3bb640..e8d7e4642e3e 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java @@ -17,6 +17,7 @@  package com.android.systemui.doze;  import android.content.Context; +import android.content.res.Configuration;  import android.os.PowerManager;  import android.os.SystemClock;  import android.service.dreams.DreamService; @@ -59,6 +60,7 @@ public class DozeService extends DreamService          mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */);          DozeComponent dozeComponent = mDozeComponentBuilder.build(this);          mDozeMachine = dozeComponent.getDozeMachine(); +        mDozeMachine.onConfigurationChanged(getResources().getConfiguration());      }      @Override @@ -127,6 +129,12 @@ public class DozeService extends DreamService      }      @Override +    public void onConfigurationChanged(Configuration newConfig) { +        super.onConfigurationChanged(newConfig); +        mDozeMachine.onConfigurationChanged(newConfig); +    } + +    @Override      public void onRequestHideDoze() {          if (mDozeMachine != null) {              mDozeMachine.requestState(DozeMachine.State.DOZE); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java index 7ed4b35e1ee7..e6d98655b119 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java @@ -16,21 +16,13 @@  package com.android.systemui.doze; -import static android.app.UiModeManager.ACTION_ENTER_CAR_MODE; -import static android.app.UiModeManager.ACTION_EXIT_CAR_MODE; - -import android.app.UiModeManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Configuration; +import static android.content.res.Configuration.UI_MODE_TYPE_CAR; +  import android.hardware.display.AmbientDisplayConfiguration;  import android.os.PowerManager;  import android.os.UserHandle;  import android.text.TextUtils; -import com.android.systemui.broadcast.BroadcastDispatcher;  import com.android.systemui.doze.dagger.DozeScope;  import com.android.systemui.statusbar.phone.BiometricUnlockController; @@ -43,7 +35,9 @@ import dagger.Lazy;  /**   * Handles suppressing doze on:   * 1. INITIALIZED, don't allow dozing at all when: - *      - in CAR_MODE + *      - in CAR_MODE, in this scenario the device is asleep and won't listen for any triggers + *      to wake up. In this state, no UI shows. Unlike other conditions, this suppression is only + *      temporary and stops when the device exits CAR_MODE   *      - device is NOT provisioned   *      - there's a pending authentication   * 2. PowerSaveMode active @@ -57,35 +51,47 @@ import dagger.Lazy;   */  @DozeScope  public class DozeSuppressor implements DozeMachine.Part { -    private static final String TAG = "DozeSuppressor";      private DozeMachine mMachine;      private final DozeHost mDozeHost;      private final AmbientDisplayConfiguration mConfig;      private final DozeLog mDozeLog; -    private final BroadcastDispatcher mBroadcastDispatcher; -    private final UiModeManager mUiModeManager;      private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; -    private boolean mBroadcastReceiverRegistered; +    private boolean mIsCarModeEnabled = false;      @Inject      public DozeSuppressor(              DozeHost dozeHost,              AmbientDisplayConfiguration config,              DozeLog dozeLog, -            BroadcastDispatcher broadcastDispatcher, -            UiModeManager uiModeManager,              Lazy<BiometricUnlockController> biometricUnlockControllerLazy) {          mDozeHost = dozeHost;          mConfig = config;          mDozeLog = dozeLog; -        mBroadcastDispatcher = broadcastDispatcher; -        mUiModeManager = uiModeManager;          mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;      }      @Override +    public void onUiModeTypeChanged(int newUiModeType) { +        boolean isCarModeEnabled = newUiModeType == UI_MODE_TYPE_CAR; +        if (mIsCarModeEnabled == isCarModeEnabled) { +            return; +        } +        mIsCarModeEnabled = isCarModeEnabled; +        // Do not handle the event if doze machine is not initialized yet. +        // It will be handled upon initialization. +        if (mMachine.isUninitializedOrFinished()) { +            return; +        } +        if (mIsCarModeEnabled) { +            handleCarModeStarted(); +        } else { +            handleCarModeExited(); +        } +    } + +    @Override      public void setDozeMachine(DozeMachine dozeMachine) {          mMachine = dozeMachine;      } @@ -94,7 +100,6 @@ public class DozeSuppressor implements DozeMachine.Part {      public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {          switch (newState) {              case INITIALIZED: -                registerBroadcastReceiver();                  mDozeHost.addCallback(mHostCallback);                  checkShouldImmediatelyEndDoze();                  checkShouldImmediatelySuspendDoze(); @@ -108,14 +113,12 @@ public class DozeSuppressor implements DozeMachine.Part {      @Override      public void destroy() { -        unregisterBroadcastReceiver();          mDozeHost.removeCallback(mHostCallback);      }      private void checkShouldImmediatelySuspendDoze() { -        if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) { -            mDozeLog.traceCarModeStarted(); -            mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS); +        if (mIsCarModeEnabled) { +            handleCarModeStarted();          }      } @@ -135,7 +138,7 @@ public class DozeSuppressor implements DozeMachine.Part {      @Override      public void dump(PrintWriter pw) { -        pw.println(" uiMode=" + mUiModeManager.getCurrentModeType()); +        pw.println(" isCarModeEnabled=" + mIsCarModeEnabled);          pw.println(" hasPendingAuth="                  + mBiometricUnlockControllerLazy.get().hasPendingAuthentication());          pw.println(" isProvisioned=" + mDozeHost.isProvisioned()); @@ -143,40 +146,18 @@ public class DozeSuppressor implements DozeMachine.Part {          pw.println(" aodPowerSaveActive=" + mDozeHost.isPowerSaveActive());      } -    private void registerBroadcastReceiver() { -        if (mBroadcastReceiverRegistered) { -            return; -        } -        IntentFilter filter = new IntentFilter(ACTION_ENTER_CAR_MODE); -        filter.addAction(ACTION_EXIT_CAR_MODE); -        mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter); -        mBroadcastReceiverRegistered = true; +    private void handleCarModeExited() { +        mDozeLog.traceCarModeEnded(); +        mMachine.requestState(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) +                ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE);      } -    private void unregisterBroadcastReceiver() { -        if (!mBroadcastReceiverRegistered) { -            return; -        } -        mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); -        mBroadcastReceiverRegistered = false; +    private void handleCarModeStarted() { +        mDozeLog.traceCarModeStarted(); +        mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);      } -    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { -        @Override -        public void onReceive(Context context, Intent intent) { -            String action = intent.getAction(); -            if (ACTION_ENTER_CAR_MODE.equals(action)) { -                mDozeLog.traceCarModeStarted(); -                mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS); -            } else if (ACTION_EXIT_CAR_MODE.equals(action)) { -                mDozeLog.traceCarModeEnded(); -                mMachine.requestState(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) -                        ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE); -            } -        } -    }; - -    private DozeHost.Callback mHostCallback = new DozeHost.Callback() { +    private final DozeHost.Callback mHostCallback = new DozeHost.Callback() {          @Override          public void onPowerSaveChanged(boolean active) {              // handles suppression changes, while DozeMachine#transitionPolicy handles gating diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java index 0ccb222c8acc..cedd850ac2ef 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java @@ -210,7 +210,8 @@ public class DreamHomeControlsComplication implements Complication {              final Intent intent = new Intent(mContext, ControlsActivity.class)                      .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK) -                    .putExtra(ControlsUiController.EXTRA_ANIMATE, true); +                    .putExtra(ControlsUiController.EXTRA_ANIMATE, true) +                    .putExtra(ControlsUiController.EXIT_TO_DREAM, true);              final ActivityLaunchAnimator.Controller controller =                      v != null ? ActivityLaunchAnimator.Controller.fromView(v, null /* cujType */) diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt index 08ef8f3d025f..478f86169718 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt @@ -24,7 +24,7 @@ import com.android.systemui.R  import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL  import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH  import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL -import com.android.systemui.log.LogBuffer +import com.android.systemui.plugins.log.LogBuffer  import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager  import java.io.PrintWriter  import javax.inject.Inject @@ -235,6 +235,7 @@ class DumpHandler @Inject constructor(          pw.println("$ <invocation> buffers")          pw.println("$ <invocation> bugreport-critical")          pw.println("$ <invocation> bugreport-normal") +        pw.println("$ <invocation> config")          pw.println()          pw.println("Targets can be listed:") @@ -313,13 +314,21 @@ class DumpHandler @Inject constructor(          const val PRIORITY_ARG_CRITICAL = "CRITICAL"          const val PRIORITY_ARG_HIGH = "HIGH"          const val PRIORITY_ARG_NORMAL = "NORMAL" +        const val PROTO = "--sysui_proto"      }  }  private val PRIORITY_OPTIONS =          arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_HIGH, PRIORITY_ARG_NORMAL) -private val COMMANDS = arrayOf("bugreport-critical", "bugreport-normal", "buffers", "dumpables") +private val COMMANDS = arrayOf( +        "bugreport-critical", +        "bugreport-normal", +        "buffers", +        "dumpables", +        "config", +        "help" +)  private class ParsedArgs(      val rawArgs: Array<String>, diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt index cca04da8f426..dbca65122fcb 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt @@ -18,7 +18,7 @@ package com.android.systemui.dump  import android.util.ArrayMap  import com.android.systemui.Dumpable -import com.android.systemui.log.LogBuffer +import com.android.systemui.plugins.log.LogBuffer  import java.io.PrintWriter  import javax.inject.Inject  import javax.inject.Singleton diff --git a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt index 0eab1afc4119..8299b13d305f 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt @@ -19,7 +19,7 @@ package com.android.systemui.dump  import android.content.Context  import android.util.Log  import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer +import com.android.systemui.plugins.log.LogBuffer  import com.android.systemui.util.io.Files  import com.android.systemui.util.time.SystemClock  import java.io.IOException diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 9beb1e98c118..906153704076 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -54,9 +54,6 @@ public class Flags {      public static final UnreleasedFlag NSSL_DEBUG_REMOVE_ANIMATION =              new UnreleasedFlag(106); -    public static final UnreleasedFlag NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE = -            new UnreleasedFlag(107); -      public static final ResourceBooleanFlag NOTIFICATION_DRAG_TO_CONTENTS =              new ResourceBooleanFlag(108, R.bool.config_notificationToContents); @@ -73,7 +70,11 @@ public class Flags {      public static final UnreleasedFlag NOTIFICATION_DISMISSAL_FADE = new UnreleasedFlag(113, true); -    // next id: 114 +    public static final UnreleasedFlag STABILITY_INDEX_FIX = new UnreleasedFlag(114, true); + +    public static final UnreleasedFlag SEMI_STABLE_SORT = new UnreleasedFlag(115, true); + +    // next id: 116      /***************************************/      // 200 - keyguard/lockscreen @@ -111,8 +112,8 @@ public class Flags {       * <p>If this is {@code false}, the interactor and repo skip the controller and directly access       * the framework APIs.       */ -    public static final ReleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER = -            new ReleasedFlag(210); +    public static final UnreleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER = +            new UnreleasedFlag(210);      /**       * Whether `UserSwitcherController` should use the user interactor. @@ -123,7 +124,13 @@ public class Flags {       * <p>Note: do not set this to true if {@link #USER_INTERACTOR_AND_REPO_USE_CONTROLLER} is       * {@code true} as it would created a cycle between controller -> interactor -> controller.       */ -    public static final UnreleasedFlag USER_CONTROLLER_USES_INTERACTOR = new UnreleasedFlag(211); +    public static final ReleasedFlag USER_CONTROLLER_USES_INTERACTOR = new ReleasedFlag(211); + +    /** +     * Whether the clock on a wide lock screen should use the new "stepping" animation for moving +     * the digits when the clock moves. +     */ +    public static final UnreleasedFlag STEP_CLOCK_ANIMATION = new UnreleasedFlag(212);      /***************************************/      // 300 - power menu diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt index 5651399cb891..f9e341c8629a 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt @@ -19,6 +19,9 @@ package com.android.systemui.log  import android.app.ActivityManager  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.LogcatEchoTracker +  import javax.inject.Inject  @SysUISingleton @@ -26,7 +29,7 @@ class LogBufferFactory @Inject constructor(      private val dumpManager: DumpManager,      private val logcatEchoTracker: LogcatEchoTracker  ) { -    /* limit the size of maxPoolSize for low ram (Go) devices */ +    /* limitiometricMessageDeferralLogger the size of maxPoolSize for low ram (Go) devices */      private fun adjustMaxSize(requestedMaxSize: Int): Int {          return if (ActivityManager.isLowRamDeviceStatic()) {              minOf(requestedMaxSize, 20) /* low ram max log size*/ diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java index 7f1ad6d20c16..eeadf406060d 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java @@ -23,7 +23,7 @@ import java.lang.annotation.RetentionPolicy;  import javax.inject.Qualifier;  /** - * A {@link com.android.systemui.log.LogBuffer} for BiometricMessages processing such as + * A {@link com.android.systemui.plugins.log.LogBuffer} for BiometricMessages processing such as   * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}   */  @Qualifier diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java index 7d1f1c2709fa..5cca1ab2abe7 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java index 9ca0293fbd86..1d016d837b02 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java index 7c5f4025117f..c9f78bcdeef8 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java index 08d969b5eb77..76d20bea4bdf 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 28aa19e18e80..00bf2104b7f2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -22,11 +22,11 @@ import android.os.Looper;  import com.android.systemui.dagger.SysUISingleton;  import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.log.LogBuffer;  import com.android.systemui.log.LogBufferFactory; -import com.android.systemui.log.LogcatEchoTracker; -import com.android.systemui.log.LogcatEchoTrackerDebug; -import com.android.systemui.log.LogcatEchoTrackerProd; +import com.android.systemui.plugins.log.LogBuffer; +import com.android.systemui.plugins.log.LogcatEchoTracker; +import com.android.systemui.plugins.log.LogcatEchoTrackerDebug; +import com.android.systemui.plugins.log.LogcatEchoTrackerProd;  import com.android.systemui.statusbar.notification.NotifPipelineFlags;  import com.android.systemui.util.Compile; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java index 1d7ba94af4ed..90ced0291805 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java index b03655a543f7..e5ac3e2e893b 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java index c67d8bebe313..73690ab6c24d 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java index 53963fc8d431..99ec05bc8d94 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java index 5c572e8ef554..1570d434bc62 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java index edab8c319f87..bf216c6991d2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java index 75a34fc22c3c..8c904eab409e 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java index b1c6dcfcb13b..6d91f0c97c8a 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java index 20fc6ff445a6..26af4964f7b8 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java index fcc184a317b8..61daf9c8d71c 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java index 760fbf3928b6..a59afa0fed1b 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java index a0b686487bec..6f8ea7ff2e9b 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java index 8c8753a07339..835d3490293c 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java index 7259eebf19b6..6e2bd7b2e1b5 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java index e96e532f94bf..77b1bf5fd630 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java index 557a254e5c09..9fd166b759d2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java index dd5010cf39a8..dd168bac5654 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java index bd0d298ebdee..d24bfcb88188 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java index b237f2d74483..67cdb722055b 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java index f26b3164f488..af0f7c518e64 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java index dd6837563a74..4c276e2bfaab 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java index 8671dbfdf1fe..ba8b27c23ec1 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;  import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import java.lang.annotation.Documented;  import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt index b1018f9544c0..d40624bfc63a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt @@ -17,9 +17,9 @@  package com.android.systemui.media  import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.MediaCarouselControllerLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import javax.inject.Inject  /** A debug logger for [MediaCarouselController]. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt index b52565d57f27..cc06b6c67879 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt @@ -33,7 +33,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher  import com.android.systemui.dagger.SysUISingleton  import com.android.systemui.dagger.qualifiers.Background  import com.android.systemui.dump.DumpManager -import com.android.systemui.people.widget.PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE  import com.android.systemui.tuner.TunerService  import com.android.systemui.util.Utils  import com.android.systemui.util.time.SystemClock diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt index d9c58c0d0d76..8c9e2d88c694 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt @@ -18,11 +18,10 @@ package com.android.systemui.media  import android.media.session.PlaybackState  import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.MediaTimeoutListenerLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import javax.inject.Inject -  private const val TAG = "MediaTimeout"  /** diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt index 73868189b362..51c658cb6c54 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt @@ -17,9 +17,9 @@  package com.android.systemui.media  import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.MediaViewLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import javax.inject.Inject  private const val TAG = "MediaView" diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt index 41f735486c7e..a9c5c61dddbb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt @@ -18,9 +18,9 @@ package com.android.systemui.media  import android.content.ComponentName  import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.MediaBrowserLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import javax.inject.Inject  /** A logger for events in [ResumeMediaBrowser]. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index a8a84331050d..e15e2d3dcccf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -17,7 +17,6 @@  package com.android.systemui.media.dagger;  import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.log.LogBuffer;  import com.android.systemui.log.dagger.MediaTttReceiverLogBuffer;  import com.android.systemui.log.dagger.MediaTttSenderLogBuffer;  import com.android.systemui.media.MediaDataManager; @@ -33,6 +32,7 @@ import com.android.systemui.media.taptotransfer.MediaTttFlags;  import com.android.systemui.media.taptotransfer.common.MediaTttLogger;  import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger;  import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger; +import com.android.systemui.plugins.log.LogBuffer;  import java.util.Optional; diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt index 78f4e012da03..5ace3ea8a05b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt @@ -1,9 +1,9 @@  package com.android.systemui.media.muteawait  import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.MediaMuteAwaitLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import javax.inject.Inject  /** Log messages for [MediaMuteAwaitConnectionManager]. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt index 46b2cc141b3c..78408fce5a36 100644 --- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt @@ -1,9 +1,9 @@  package com.android.systemui.media.nearby  import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.NearbyMediaDevicesLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import javax.inject.Inject  /** Log messages for [NearbyMediaDevicesManager]. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt index b565f3c22f24..38c971ed3f7d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt @@ -16,8 +16,8 @@  package com.android.systemui.media.taptotransfer.common -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import com.android.systemui.temporarydisplay.TemporaryViewLogger  /** diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt index 1ea93474f954..03503fd1ff61 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt @@ -17,10 +17,10 @@  package com.android.systemui.privacy.logging  import android.permission.PermissionGroupUsage -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogMessage  import com.android.systemui.log.dagger.PrivacyLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogMessage  import com.android.systemui.privacy.PrivacyDialog  import com.android.systemui.privacy.PrivacyItem  import java.text.SimpleDateFormat diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt index e5d86cc96f25..025fb228b829 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt @@ -1,8 +1,8 @@  package com.android.systemui.qs -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.QSFragmentDisableLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import com.android.systemui.statusbar.disableflags.DisableFlagsLogger  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index 4cacbbacec2f..5d03da3cc113 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -35,6 +35,7 @@ import androidx.annotation.Nullable;  import com.android.internal.statusbar.StatusBarIcon;  import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.SysUISingleton;  import com.android.systemui.dagger.qualifiers.Main;  import com.android.systemui.qs.QSTileHost;  import com.android.systemui.settings.UserTracker; @@ -53,6 +54,7 @@ import javax.inject.Provider;  /**   * Runs the day-to-day operations of which tiles should be bound and when.   */ +@SysUISingleton  public class TileServices extends IQSService.Stub {      static final int DEFAULT_MAX_BOUND = 3;      static final int REDUCED_MAX_BOUND = 1; diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index 60380064e098..931dc8df151a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -17,12 +17,12 @@  package com.android.systemui.qs.logging  import android.service.quicksettings.Tile -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.VERBOSE -import com.android.systemui.log.LogMessage  import com.android.systemui.log.dagger.QSLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.VERBOSE +import com.android.systemui.plugins.log.LogMessage  import com.android.systemui.plugins.qs.QSTile  import com.android.systemui.statusbar.StatusBarState  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java index d2d5063c7ae0..b6b657ec82f6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java @@ -26,6 +26,7 @@ import android.view.LayoutInflater;  import android.view.View;  import android.view.ViewGroup; +import androidx.annotation.NonNull;  import androidx.annotation.Nullable;  import com.android.internal.logging.MetricsLogger; @@ -43,6 +44,9 @@ import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;  import com.android.systemui.statusbar.policy.UserSwitcherController;  import com.android.systemui.user.data.source.UserRecord; +import java.util.List; +import java.util.stream.Collectors; +  import javax.inject.Inject;  /** @@ -83,6 +87,13 @@ public class UserDetailView extends PseudoGridView {          private final FalsingManager mFalsingManager;          private @Nullable UserSwitchDialogController.DialogShower mDialogShower; +        @NonNull +        @Override +        protected List<UserRecord> getUsers() { +            return super.getUsers().stream().filter( +                    userRecord -> !userRecord.isManageUsers).collect(Collectors.toList()); +        } +          @Inject          public Adapter(Context context, UserSwitcherController controller,                  UiEventLogger uiEventLogger, FalsingManager falsingManager) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt index a494f42985ac..6b540aa9f392 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt @@ -292,6 +292,7 @@ class LargeScreenShadeHeaderController @Inject constructor(              clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->                  val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f                  v.pivotX = newPivot +                v.pivotY = v.height.toFloat() / 2              }          } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt index 07e8b9fe3123..754036d3baa9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt @@ -16,7 +16,7 @@ package com.android.systemui.shade  import android.view.MotionEvent  import com.android.systemui.dump.DumpsysTableLogger  import com.android.systemui.dump.Row -import com.android.systemui.util.collection.RingBuffer +import com.android.systemui.plugins.util.RingBuffer  import java.text.SimpleDateFormat  import java.util.Locale diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 20f0655aa06a..fc93751b7f91 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -79,7 +79,10 @@ import android.os.UserManager;  import android.os.VibrationEffect;  import android.provider.Settings;  import android.transition.ChangeBounds; +import android.transition.Transition;  import android.transition.TransitionManager; +import android.transition.TransitionSet; +import android.transition.TransitionValues;  import android.util.IndentingPrintWriter;  import android.util.Log;  import android.util.MathUtils; @@ -1667,9 +1670,40 @@ public final class NotificationPanelViewController {                      // horizontally properly.                      transition.excludeTarget(R.id.status_view_media_container, true);                  } +                  transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);                  transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); -                TransitionManager.beginDelayedTransition(mNotificationContainerParent, transition); + +                boolean customClockAnimation = +                            mKeyguardStatusViewController.getClockAnimations() != null +                            && mKeyguardStatusViewController.getClockAnimations() +                                    .getHasCustomPositionUpdatedAnimation(); + +                if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) { +                    // Find the clock, so we can exclude it from this transition. +                    FrameLayout clockContainerView = +                            mView.findViewById(R.id.lockscreen_clock_view_large); +                    View clockView = clockContainerView.getChildAt(0); + +                    transition.excludeTarget(clockView, /* exclude= */ true); + +                    TransitionSet set = new TransitionSet(); +                    set.addTransition(transition); + +                    SplitShadeTransitionAdapter adapter = +                            new SplitShadeTransitionAdapter(mKeyguardStatusViewController); + +                    // Use linear here, so the actual clock can pick its own interpolator. +                    adapter.setInterpolator(Interpolators.LINEAR); +                    adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); +                    adapter.addTarget(clockView); +                    set.addTransition(adapter); + +                    TransitionManager.beginDelayedTransition(mNotificationContainerParent, set); +                } else { +                    TransitionManager.beginDelayedTransition( +                            mNotificationContainerParent, transition); +                }              }              constraintSet.applyTo(mNotificationContainerParent); @@ -2093,7 +2127,8 @@ public final class NotificationPanelViewController {          animator.start();      } -    private void onFlingEnd(boolean cancelled) { +    @VisibleForTesting +    void onFlingEnd(boolean cancelled) {          mIsFlinging = false;          // No overshoot when the animation ends          setOverExpansionInternal(0, false /* isFromGesture */); @@ -3834,12 +3869,14 @@ public final class NotificationPanelViewController {          }      } -    private void setIsClosing(boolean isClosing) { +    @VisibleForTesting +    void setIsClosing(boolean isClosing) {          boolean wasClosing = isClosing();          mClosing = isClosing;          if (wasClosing != isClosing) {              mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing);          } +        mAmbientState.setIsClosing(isClosing);      }      private void updateDozingVisibilities(boolean animate) { @@ -3869,17 +3906,17 @@ public final class NotificationPanelViewController {          switch (mBarState) {              case KEYGUARD:                  if (!mDozingOnDown) { -                    if (mUpdateMonitor.isFaceEnrolled() -                            && !mUpdateMonitor.isFaceDetectionRunning() -                            && !mUpdateMonitor.getUserCanSkipBouncer( -                            KeyguardUpdateMonitor.getCurrentUser())) { -                        mUpdateMonitor.requestFaceAuth(true, -                                FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED); -                    } else { -                        mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT, -                                0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); -                        mLockscreenGestureLogger -                                .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT); +                    mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false"); +                    // Try triggering face auth, this "might" run. Check +                    // KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run. +                    mUpdateMonitor.requestFaceAuth(true, +                            FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED); + +                    mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT, +                            0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); +                    mLockscreenGestureLogger +                            .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT); +                    if (!mUpdateMonitor.isFaceDetectionRunning()) {                          startUnlockHintAnimation();                      }                      if (mUpdateMonitor.isFaceEnrolled()) { @@ -4630,14 +4667,16 @@ public final class NotificationPanelViewController {          Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));      } -    private void notifyExpandingStarted() { +    @VisibleForTesting +    void notifyExpandingStarted() {          if (!mExpanding) {              mExpanding = true;              onExpandingStarted();          }      } -    private void notifyExpandingFinished() { +    @VisibleForTesting +    void notifyExpandingFinished() {          endClosing();          if (mExpanding) {              mExpanding = false; @@ -6251,4 +6290,54 @@ public final class NotificationPanelViewController {              loadDimens();          }      } + +    static class SplitShadeTransitionAdapter extends Transition { +        private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds"; +        private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS }; + +        private final KeyguardStatusViewController mController; + +        SplitShadeTransitionAdapter(KeyguardStatusViewController controller) { +            mController = controller; +        } + +        private void captureValues(TransitionValues transitionValues) { +            Rect boundsRect = new Rect(); +            boundsRect.left = transitionValues.view.getLeft(); +            boundsRect.top = transitionValues.view.getTop(); +            boundsRect.right = transitionValues.view.getRight(); +            boundsRect.bottom = transitionValues.view.getBottom(); +            transitionValues.values.put(PROP_BOUNDS, boundsRect); +        } + +        @Override +        public void captureEndValues(TransitionValues transitionValues) { +            captureValues(transitionValues); +        } + +        @Override +        public void captureStartValues(TransitionValues transitionValues) { +            captureValues(transitionValues); +        } + +        @Override +        public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, +                TransitionValues endValues) { +            ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + +            Rect from = (Rect) startValues.values.get(PROP_BOUNDS); +            Rect to = (Rect) endValues.values.get(PROP_BOUNDS); + +            anim.addUpdateListener( +                    animation -> mController.getClockAnimations().onPositionUpdated( +                            from, to, animation.getAnimatedFraction())); + +            return anim; +        } + +        @Override +        public String[] getTransitionProperties() { +            return TRANSITION_PROPERTIES; +        } +    }  } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index 7bee0ba17afc..2b788d85a14c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -1,10 +1,10 @@  package com.android.systemui.shade  import android.view.MotionEvent -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogMessage  import com.android.systemui.log.dagger.ShadeLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogMessage  import com.google.errorprone.annotations.CompileTimeConstant  import javax.inject.Inject @@ -12,64 +12,69 @@ private const val TAG = "systemui.shade"  /** Lightweight logging utility for the Shade. */  class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { -  fun v(@CompileTimeConstant msg: String) { -    buffer.log(TAG, LogLevel.VERBOSE, msg) -  } +    fun v(@CompileTimeConstant msg: String) { +        buffer.log(TAG, LogLevel.VERBOSE, msg) +    } -  private inline fun log( -      logLevel: LogLevel, -      initializer: LogMessage.() -> Unit, -      noinline printer: LogMessage.() -> String -  ) { -    buffer.log(TAG, logLevel, initializer, printer) -  } +    private inline fun log( +        logLevel: LogLevel, +        initializer: LogMessage.() -> Unit, +        noinline printer: LogMessage.() -> String +    ) { +        buffer.log(TAG, logLevel, initializer, printer) +    } -  fun onQsInterceptMoveQsTrackingEnabled(h: Float) { -    log( -        LogLevel.VERBOSE, -        { double1 = h.toDouble() }, -        { "onQsIntercept: move action, QS tracking enabled. h = $double1" }) -  } +    fun onQsInterceptMoveQsTrackingEnabled(h: Float) { +        log( +            LogLevel.VERBOSE, +            { double1 = h.toDouble() }, +            { "onQsIntercept: move action, QS tracking enabled. h = $double1" } +        ) +    } -  fun logQsTrackingNotStarted( -      initialTouchY: Float, -      y: Float, -      h: Float, -      touchSlop: Float, -      qsExpanded: Boolean, -      collapsedOnDown: Boolean, -      keyguardShowing: Boolean, -      qsExpansionEnabled: Boolean -  ) { -    log( -        LogLevel.VERBOSE, -        { -          int1 = initialTouchY.toInt() -          int2 = y.toInt() -          long1 = h.toLong() -          double1 = touchSlop.toDouble() -          bool1 = qsExpanded -          bool2 = collapsedOnDown -          bool3 = keyguardShowing -          bool4 = qsExpansionEnabled -        }, -        { -          "QsTrackingNotStarted: initTouchY=$int1,y=$int2,h=$long1,slop=$double1,qsExpanded=" + -              "$bool1,collapsedDown=$bool2,keyguardShowing=$bool3,qsExpansion=$bool4" -        }) -  } +    fun logQsTrackingNotStarted( +        initialTouchY: Float, +        y: Float, +        h: Float, +        touchSlop: Float, +        qsExpanded: Boolean, +        collapsedOnDown: Boolean, +        keyguardShowing: Boolean, +        qsExpansionEnabled: Boolean +    ) { +        log( +            LogLevel.VERBOSE, +            { +                int1 = initialTouchY.toInt() +                int2 = y.toInt() +                long1 = h.toLong() +                double1 = touchSlop.toDouble() +                bool1 = qsExpanded +                bool2 = collapsedOnDown +                bool3 = keyguardShowing +                bool4 = qsExpansionEnabled +            }, +            { +                "QsTrackingNotStarted: initTouchY=$int1,y=$int2,h=$long1,slop=$double1,qsExpanded" + +                    "=$bool1,collapsedDown=$bool2,keyguardShowing=$bool3,qsExpansion=$bool4" +            } +        ) +    } -  fun logMotionEvent(event: MotionEvent, message: String) { -    log( -        LogLevel.VERBOSE, -        { -          str1 = message -          long1 = event.eventTime -          long2 = event.downTime -          int1 = event.action -          int2 = event.classification -          double1 = event.y.toDouble() -        }, -        { "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,classification=$int2" }) -  } +    fun logMotionEvent(event: MotionEvent, message: String) { +        log( +            LogLevel.VERBOSE, +            { +                str1 = message +                long1 = event.eventTime +                long2 = event.downTime +                int1 = event.action +                int2 = event.classification +                double1 = event.y.toDouble() +            }, +            { +                "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2" +            } +        ) +    }  } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt index 7f7ff9cf4881..90c52bd8c9f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt @@ -17,9 +17,9 @@  package com.android.systemui.statusbar  import android.app.PendingIntent -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.NotifInteractionLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import com.android.systemui.statusbar.notification.collection.NotificationEntry  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index ea7ec4f7fc39..450b757295bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -71,9 +71,9 @@ import com.android.systemui.dagger.qualifiers.Main;  import com.android.systemui.demomode.DemoMode;  import com.android.systemui.demomode.DemoModeController;  import com.android.systemui.dump.DumpManager; -import com.android.systemui.log.LogBuffer; -import com.android.systemui.log.LogLevel;  import com.android.systemui.log.dagger.StatusBarNetworkControllerLog; +import com.android.systemui.plugins.log.LogBuffer; +import com.android.systemui.plugins.log.LogLevel;  import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;  import com.android.systemui.settings.CurrentUserTracker;  import com.android.systemui.statusbar.policy.ConfigurationController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt index 17feaa842165..9bdff928c44b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt @@ -16,9 +16,9 @@  package com.android.systemui.statusbar.gesture -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.SwipeStatusBarAwayLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import javax.inject.Inject  /** Log messages for [SwipeStatusBarAwayGestureHandler]. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index dfba8cd4a081..fc984618f1b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -119,6 +119,7 @@ class LockscreenSmartspaceController @Inject constructor(                      regionSamplingEnabled,                      updateFun              ) +            initializeTextColors(regionSamplingInstance)              regionSamplingInstance.startRegionSampler()              regionSamplingInstances.put(v, regionSamplingInstance)              connectSession() @@ -362,18 +363,20 @@ class LockscreenSmartspaceController @Inject constructor(          }      } +    private fun initializeTextColors(regionSamplingInstance: RegionSamplingInstance) { +        val lightThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_LightWallpaper) +        val darkColor = Utils.getColorAttrDefaultColor(lightThemeContext, R.attr.wallpaperTextColor) + +        val darkThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI) +        val lightColor = Utils.getColorAttrDefaultColor(darkThemeContext, R.attr.wallpaperTextColor) + +        regionSamplingInstance.setForegroundColors(lightColor, darkColor) +    } +      private fun updateTextColorFromRegionSampler() {          smartspaceViews.forEach { -            val isRegionDark = regionSamplingInstances.getValue(it).currentRegionDarkness() -            val themeID = if (isRegionDark.isDark) { -                R.style.Theme_SystemUI -            } else { -                R.style.Theme_SystemUI_LightWallpaper -            } -            val themedContext = ContextThemeWrapper(context, themeID) -            val wallpaperTextColor = -                    Utils.getColorAttrDefaultColor(themedContext, R.attr.wallpaperTextColor) -            it.setPrimaryTextColor(wallpaperTextColor) +            val textColor = regionSamplingInstances.getValue(it).currentForegroundColor() +            it.setPrimaryTextColor(textColor)          }      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt index 7fbdd35796c1..36b8333688ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt @@ -17,30 +17,14 @@  package com.android.systemui.statusbar.notification  import android.content.Context -import android.util.Log -import android.widget.Toast  import com.android.systemui.flags.FeatureFlags  import com.android.systemui.flags.Flags -import com.android.systemui.util.Compile  import javax.inject.Inject  class NotifPipelineFlags @Inject constructor(      val context: Context,      val featureFlags: FeatureFlags  ) { -    fun checkLegacyPipelineEnabled(): Boolean { -        if (Compile.IS_DEBUG) { -            Toast.makeText(context, "Old pipeline code running!", Toast.LENGTH_SHORT).show() -        } -        if (featureFlags.isEnabled(Flags.NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE)) { -            throw RuntimeException("Old pipeline code running with new pipeline enabled") -        } else { -            Log.d("NotifPipeline", "Old pipeline code running with new pipeline enabled", -                    Exception()) -        } -        return false -    } -      fun isDevLoggingEnabled(): Boolean =          featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING) @@ -53,4 +37,12 @@ class NotifPipelineFlags @Inject constructor(      fun fullScreenIntentRequiresKeyguard(): Boolean =          featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD) + +    val isStabilityIndexFixEnabled: Boolean by lazy { +        featureFlags.isEnabled(Flags.STABILITY_INDEX_FIX) +    } + +    val isSemiStableSortEnabled: Boolean by lazy { +        featureFlags.isEnabled(Flags.SEMI_STABLE_SORT) +    }  } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt index ad3dfedcdb96..3058fbbc1031 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt @@ -16,9 +16,9 @@  package com.android.systemui.statusbar.notification -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.NotifInteractionLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import com.android.systemui.statusbar.notification.collection.NotificationEntry  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt index f8449ae8807b..84ab0d1190f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt @@ -68,6 +68,9 @@ data class ListAttachState private constructor(       */      var stableIndex: Int = -1 +    /** Access the index of the [section] or -1 if the entry does not have one */ +    val sectionIndex: Int get() = section?.index ?: -1 +      /** Copies the state of another instance. */      fun clone(other: ListAttachState) {          parent = other.parent @@ -95,11 +98,13 @@ data class ListAttachState private constructor(       * This can happen if the entry is removed from a group that was broken up or if the entry was       * filtered out during any of the filtering steps.       */ -    fun detach() { +    fun detach(includingStableIndex: Boolean) {          parent = null          section = null          promoter = null -        // stableIndex = -1  // TODO(b/241229236): Clear this once we fix the stability fragility +        if (includingStableIndex) { +            stableIndex = -1 +        }      }      companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index e129ee45817a..3ae2545e4e10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -54,6 +54,9 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo  import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;  import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;  import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState; +import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort; +import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort.StableOrder; +import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper;  import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger;  import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.DefaultNotifStabilityManager;  import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator; @@ -96,11 +99,14 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {      // used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated      // TODO replace temp with collection pool for readability      private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>(); +    private NotifPipelineFlags mFlags;      private final boolean mAlwaysLogList;      private List<ListEntry> mNotifList = new ArrayList<>();      private List<ListEntry> mNewNotifList = new ArrayList<>(); +    private final SemiStableSort mSemiStableSort = new SemiStableSort(); +    private final StableOrder<ListEntry> mStableOrder = this::getStableOrderRank;      private final PipelineState mPipelineState = new PipelineState();      private final Map<String, GroupEntry> mGroups = new ArrayMap<>();      private Collection<NotificationEntry> mAllEntries = Collections.emptyList(); @@ -141,6 +147,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {      ) {          mSystemClock = systemClock;          mLogger = logger; +        mFlags = flags;          mAlwaysLogList = flags.isDevLoggingEnabled();          mInteractionTracker = interactionTracker;          mChoreographer = pipelineChoreographer; @@ -527,7 +534,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {              List<NotifFilter> filters) {          Trace.beginSection("ShadeListBuilder.filterNotifs");          final long now = mSystemClock.uptimeMillis(); -        for (ListEntry entry : entries)  { +        for (ListEntry entry : entries) {              if (entry instanceof GroupEntry) {                  final GroupEntry groupEntry = (GroupEntry) entry; @@ -958,7 +965,8 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {       * filtered out during any of the filtering steps.       */      private void annulAddition(ListEntry entry) { -        entry.getAttachState().detach(); +        // NOTE(b/241229236): Don't clear stableIndex until we fix stability fragility +        entry.getAttachState().detach(/* includingStableIndex= */ mFlags.isSemiStableSortEnabled());      }      private void assignSections() { @@ -978,7 +986,16 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {      private void sortListAndGroups() {          Trace.beginSection("ShadeListBuilder.sortListAndGroups"); -        // Assign sections to top-level elements and sort their children +        if (mFlags.isSemiStableSortEnabled()) { +            sortWithSemiStableSort(); +        } else { +            sortWithLegacyStability(); +        } +        Trace.endSection(); +    } + +    private void sortWithLegacyStability() { +        // Sort all groups and the top level list          for (ListEntry entry : mNotifList) {              if (entry instanceof GroupEntry) {                  GroupEntry parent = (GroupEntry) entry; @@ -991,16 +1008,15 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {          // Check for suppressed order changes          if (!getStabilityManager().isEveryChangeAllowed()) {              mForceReorderable = true; -            boolean isSorted = isShadeSorted(); +            boolean isSorted = isShadeSortedLegacy();              mForceReorderable = false;              if (!isSorted) {                  getStabilityManager().onEntryReorderSuppressed();              }          } -        Trace.endSection();      } -    private boolean isShadeSorted() { +    private boolean isShadeSortedLegacy() {          if (!isSorted(mNotifList, mTopLevelComparator)) {              return false;          } @@ -1014,6 +1030,43 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {          return true;      } +    private void sortWithSemiStableSort() { +        // Sort each group's children +        boolean allSorted = true; +        for (ListEntry entry : mNotifList) { +            if (entry instanceof GroupEntry) { +                GroupEntry parent = (GroupEntry) entry; +                allSorted &= sortGroupChildren(parent.getRawChildren()); +            } +        } +        // Sort each section within the top level list +        mNotifList.sort(mTopLevelComparator); +        if (!getStabilityManager().isEveryChangeAllowed()) { +            for (List<ListEntry> subList : getSectionSubLists(mNotifList)) { +                allSorted &= mSemiStableSort.stabilizeTo(subList, mStableOrder, mNewNotifList); +            } +            applyNewNotifList(); +        } +        assignIndexes(mNotifList); +        if (!allSorted) { +            // Report suppressed order changes +            getStabilityManager().onEntryReorderSuppressed(); +        } +    } + +    private Iterable<List<ListEntry>> getSectionSubLists(List<ListEntry> entries) { +        return ShadeListBuilderHelper.INSTANCE.getSectionSubLists(entries); +    } + +    private boolean sortGroupChildren(List<NotificationEntry> entries) { +        if (getStabilityManager().isEveryChangeAllowed()) { +            entries.sort(mGroupChildrenComparator); +            return true; +        } else { +            return mSemiStableSort.sort(entries, mStableOrder, mGroupChildrenComparator); +        } +    } +      /** Determine whether the items in the list are sorted according to the comparator */      @VisibleForTesting      public static <T> boolean isSorted(List<T> items, Comparator<? super T> comparator) { @@ -1036,27 +1089,41 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {      /**       * Assign the index of each notification relative to the total order       */ -    private static void assignIndexes(List<ListEntry> notifList) { +    private void assignIndexes(List<ListEntry> notifList) {          if (notifList.size() == 0) return;          NotifSection currentSection = requireNonNull(notifList.get(0).getSection());          int sectionMemberIndex = 0;          for (int i = 0; i < notifList.size(); i++) { -            ListEntry entry = notifList.get(i); +            final ListEntry entry = notifList.get(i);              NotifSection section = requireNonNull(entry.getSection());              if (section.getIndex() != currentSection.getIndex()) {                  sectionMemberIndex = 0;                  currentSection = section;              } -            entry.getAttachState().setStableIndex(sectionMemberIndex); -            if (entry instanceof GroupEntry) { -                GroupEntry parent = (GroupEntry) entry; -                for (int j = 0; j < parent.getChildren().size(); j++) { -                    entry = parent.getChildren().get(j); -                    entry.getAttachState().setStableIndex(sectionMemberIndex); -                    sectionMemberIndex++; +            if (mFlags.isStabilityIndexFixEnabled()) { +                entry.getAttachState().setStableIndex(sectionMemberIndex++); +                if (entry instanceof GroupEntry) { +                    final GroupEntry parent = (GroupEntry) entry; +                    final NotificationEntry summary = parent.getSummary(); +                    if (summary != null) { +                        summary.getAttachState().setStableIndex(sectionMemberIndex++); +                    } +                    for (NotificationEntry child : parent.getChildren()) { +                        child.getAttachState().setStableIndex(sectionMemberIndex++); +                    } +                } +            } else { +                // This old implementation uses the same index number for the group as the first +                // child, and fails to assign an index to the summary.  Remove once tested. +                entry.getAttachState().setStableIndex(sectionMemberIndex); +                if (entry instanceof GroupEntry) { +                    final GroupEntry parent = (GroupEntry) entry; +                    for (NotificationEntry child : parent.getChildren()) { +                        child.getAttachState().setStableIndex(sectionMemberIndex++); +                    }                  } +                sectionMemberIndex++;              } -            sectionMemberIndex++;          }      } @@ -1196,7 +1263,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {                  o2.getSectionIndex());          if (cmp != 0) return cmp; -        cmp = Integer.compare( +        cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare(                  getStableOrderIndex(o1),                  getStableOrderIndex(o2));          if (cmp != 0) return cmp; @@ -1225,7 +1292,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {      private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> { -        int cmp = Integer.compare( +        int cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare(                  getStableOrderIndex(o1),                  getStableOrderIndex(o2));          if (cmp != 0) return cmp; @@ -1256,9 +1323,25 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {              // let the stability manager constrain or allow reordering              return -1;          } +        // NOTE(b/241229236): Can't use cleared section index until we fix stability fragility          return entry.getPreviousAttachState().getStableIndex();      } +    @Nullable +    private Integer getStableOrderRank(ListEntry entry) { +        if (getStabilityManager().isEntryReorderingAllowed(entry)) { +            // let the stability manager constrain or allow reordering +            return null; +        } +        if (entry.getAttachState().getSectionIndex() +                != entry.getPreviousAttachState().getSectionIndex()) { +            // stable index is only valid within the same section; otherwise we allow reordering +            return null; +        } +        final int stableIndex = entry.getPreviousAttachState().getStableIndex(); +        return stableIndex == -1 ? null : stableIndex; +    } +      private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {          final NotifFilter filter = findRejectingFilter(entry, now, filters);          entry.getAttachState().setExcludingFilter(filter); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt index 211e37473a70..68d1319699d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt @@ -16,9 +16,9 @@  package com.android.systemui.statusbar.notification.collection.coalescer -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import javax.inject.Inject  class GroupCoalescerLogger @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt index e8f352f60da0..2919def16304 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt @@ -1,8 +1,8 @@  package com.android.systemui.statusbar.notification.collection.coordinator -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import com.android.systemui.statusbar.notification.row.NotificationGuts  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt index 8625cdbc89d5..dfaa291c6bb6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt @@ -1,9 +1,10 @@  package com.android.systemui.statusbar.notification.collection.coordinator  import android.util.Log -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +  import com.android.systemui.log.dagger.NotificationHeadsUpLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import javax.inject.Inject  private const val TAG = "HeadsUpCoordinator" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 93146f9b3bf3..6e76691ae1b1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -407,10 +407,7 @@ public class PreparationCoordinator implements Coordinator {              mLogger.logGroupInflationTookTooLong(group);              return false;          } -        // Only delay release if the summary is not inflated. -        // TODO(253454977): Once we ensure that all other pipeline filtering and pruning has been -        //  done by this point, we can revert back to checking for mInflatingNotifs.contains(...) -        if (!isInflated(group.getSummary())) { +        if (mInflatingNotifs.contains(group.getSummary())) {              mLogger.logDelayingGroupRelease(group, group.getSummary());              return true;          } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt index c4f4ed54e2fa..9558f47af795 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt @@ -16,9 +16,9 @@  package com.android.systemui.statusbar.notification.collection.coordinator -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import com.android.systemui.statusbar.notification.collection.GroupEntry  import com.android.systemui.statusbar.notification.collection.NotificationEntry  import com.android.systemui.statusbar.notification.logKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt index c687e1bacbc9..d80445491bda 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt @@ -16,9 +16,9 @@  package com.android.systemui.statusbar.notification.collection.coordinator -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import javax.inject.Inject  private const val TAG = "ShadeEventCoordinator" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt new file mode 100644 index 000000000000..9ec8e07e73b3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.listbuilder + +import androidx.annotation.VisibleForTesting +import kotlin.math.sign + +class SemiStableSort { +    val preallocatedWorkspace by lazy { ArrayList<Any>() } +    val preallocatedAdditions by lazy { ArrayList<Any>() } +    val preallocatedMapToIndex by lazy { HashMap<Any, Int>() } +    val preallocatedMapToIndexComparator: Comparator<Any> by lazy { +        Comparator.comparingInt { item -> preallocatedMapToIndex[item] ?: -1 } +    } + +    /** +     * Sort the given [items] such that items which have a [stableOrder] will all be in that order, +     * items without a [stableOrder] will be sorted according to the comparator, and the two sets of +     * items will be combined to have the fewest elements out of order according to the [comparator] +     * . The result will be placed into the original [items] list. +     */ +    fun <T : Any> sort( +        items: MutableList<T>, +        stableOrder: StableOrder<in T>, +        comparator: Comparator<in T>, +    ): Boolean = +        withWorkspace<T, Boolean> { workspace -> +            val ordered = +                sortTo( +                    items, +                    stableOrder, +                    comparator, +                    workspace, +                ) +            items.clear() +            items.addAll(workspace) +            return ordered +        } + +    /** +     * Sort the given [items] such that items which have a [stableOrder] will all be in that order, +     * items without a [stableOrder] will be sorted according to the comparator, and the two sets of +     * items will be combined to have the fewest elements out of order according to the [comparator] +     * . The result will be put into [output]. +     */ +    fun <T : Any> sortTo( +        items: Iterable<T>, +        stableOrder: StableOrder<in T>, +        comparator: Comparator<in T>, +        output: MutableList<T>, +    ): Boolean { +        if (DEBUG) println("\n> START from ${items.map { it to stableOrder.getRank(it) }}") +        // If array already has elements, use subList to ensure we only append +        val result = output.takeIf { it.isEmpty() } ?: output.subList(output.size, output.size) +        items.filterTo(result) { stableOrder.getRank(it) != null } +        result.sortBy { stableOrder.getRank(it)!! } +        val isOrdered = result.isSorted(comparator) +        withAdditions<T> { additions -> +            items.filterTo(additions) { stableOrder.getRank(it) == null } +            additions.sortWith(comparator) +            insertPreSortedElementsWithFewestMisOrderings(result, additions, comparator) +        } +        return isOrdered +    } + +    /** +     * Rearrange the [sortedItems] to enforce that items are in the [stableOrder], and store the +     * result in [output]. Items with a [stableOrder] will be in that order, items without a +     * [stableOrder] will remain in same relative order as the input, and the two sets of items will +     * be combined to have the fewest elements moved from their locations in the original. +     */ +    fun <T : Any> stabilizeTo( +        sortedItems: Iterable<T>, +        stableOrder: StableOrder<in T>, +        output: MutableList<T>, +    ): Boolean { +        // Append to the output array if present +        val result = output.takeIf { it.isEmpty() } ?: output.subList(output.size, output.size) +        sortedItems.filterTo(result) { stableOrder.getRank(it) != null } +        val stableRankComparator = compareBy<T> { stableOrder.getRank(it)!! } +        val isOrdered = result.isSorted(stableRankComparator) +        if (!isOrdered) { +            result.sortWith(stableRankComparator) +        } +        if (result.isEmpty()) { +            sortedItems.filterTo(result) { stableOrder.getRank(it) == null } +            return isOrdered +        } +        withAdditions<T> { additions -> +            sortedItems.filterTo(additions) { stableOrder.getRank(it) == null } +            if (additions.isNotEmpty()) { +                withIndexOfComparator(sortedItems) { comparator -> +                    insertPreSortedElementsWithFewestMisOrderings(result, additions, comparator) +                } +            } +        } +        return isOrdered +    } + +    private inline fun <T : Any, R> withWorkspace(block: (ArrayList<T>) -> R): R { +        preallocatedWorkspace.clear() +        val result = block(preallocatedWorkspace as ArrayList<T>) +        preallocatedWorkspace.clear() +        return result +    } + +    private inline fun <T : Any> withAdditions(block: (ArrayList<T>) -> Unit) { +        preallocatedAdditions.clear() +        block(preallocatedAdditions as ArrayList<T>) +        preallocatedAdditions.clear() +    } + +    private inline fun <T : Any> withIndexOfComparator( +        sortedItems: Iterable<T>, +        block: (Comparator<in T>) -> Unit +    ) { +        preallocatedMapToIndex.clear() +        sortedItems.forEachIndexed { i, item -> preallocatedMapToIndex[item] = i } +        block(preallocatedMapToIndexComparator as Comparator<in T>) +        preallocatedMapToIndex.clear() +    } + +    companion object { + +        /** +         * This is the core of the algorithm. +         * +         * Insert [preSortedAdditions] (the elements to be inserted) into [existing] without +         * changing the relative order of any elements already in [existing], even though those +         * elements may be mis-ordered relative to the [comparator], such that the total number of +         * elements which are ordered incorrectly according to the [comparator] is fewest. +         */ +        private fun <T> insertPreSortedElementsWithFewestMisOrderings( +            existing: MutableList<T>, +            preSortedAdditions: Iterable<T>, +            comparator: Comparator<in T>, +        ) { +            if (DEBUG) println("  To $existing insert $preSortedAdditions with fewest misordering") +            var iStart = 0 +            preSortedAdditions.forEach { toAdd -> +                if (DEBUG) println("    need to add $toAdd to $existing, starting at $iStart") +                var cmpSum = 0 +                var cmpSumMax = 0 +                var iCmpSumMax = iStart +                if (DEBUG) print("      ") +                for (i in iCmpSumMax until existing.size) { +                    val cmp = comparator.compare(toAdd, existing[i]).sign +                    cmpSum += cmp +                    if (cmpSum > cmpSumMax) { +                        cmpSumMax = cmpSum +                        iCmpSumMax = i + 1 +                    } +                    if (DEBUG) print("sum[$i]=$cmpSum, ") +                } +                if (DEBUG) println("inserting $toAdd at $iCmpSumMax") +                existing.add(iCmpSumMax, toAdd) +                iStart = iCmpSumMax + 1 +            } +        } + +        /** Determines if a list is correctly sorted according to the given comparator */ +        @VisibleForTesting +        fun <T> List<T>.isSorted(comparator: Comparator<T>): Boolean { +            if (this.size <= 1) { +                return true +            } +            val iterator = this.iterator() +            var previous = iterator.next() +            var current: T? +            while (iterator.hasNext()) { +                current = iterator.next() +                if (comparator.compare(previous, current) > 0) { +                    return false +                } +                previous = current +            } +            return true +        } +    } + +    fun interface StableOrder<T> { +        fun getRank(item: T): Int? +    } +} + +val DEBUG = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt new file mode 100644 index 000000000000..d8f75f61c05a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.listbuilder + +import com.android.systemui.statusbar.notification.collection.ListEntry + +object ShadeListBuilderHelper { +    fun getSectionSubLists(entries: List<ListEntry>): Iterable<List<ListEntry>> = +        getContiguousSubLists(entries, minLength = 1) { it.sectionIndex } + +    inline fun <T : Any, K : Any> getContiguousSubLists( +        itemList: List<T>, +        minLength: Int = 1, +        key: (T) -> K, +    ): Iterable<List<T>> { +        val subLists = mutableListOf<List<T>>() +        val numEntries = itemList.size +        var currentSectionStartIndex = 0 +        var currentSectionKey: K? = null +        for (i in 0 until numEntries) { +            val sectionKey = key(itemList[i]) +            if (currentSectionKey == null) { +                currentSectionKey = sectionKey +            } else if (currentSectionKey != sectionKey) { +                val length = i - currentSectionStartIndex +                if (length >= minLength) { +                    subLists.add(itemList.subList(currentSectionStartIndex, i)) +                } +                currentSectionStartIndex = i +                currentSectionKey = sectionKey +            } +        } +        val length = numEntries - currentSectionStartIndex +        if (length >= minLength) { +            subLists.add(itemList.subList(currentSectionStartIndex, numEntries)) +        } +        return subLists +    } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt index d8dae5d23f42..8e052c7dcc5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -16,11 +16,11 @@  package com.android.systemui.statusbar.notification.collection.listbuilder -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING  import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogLevel.WARNING  import com.android.systemui.statusbar.notification.NotifPipelineFlags  import com.android.systemui.statusbar.notification.collection.GroupEntry  import com.android.systemui.statusbar.notification.collection.ListEntry diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index aa27e1e407f0..911a2d0c2b36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -20,13 +20,13 @@ import android.os.RemoteException  import android.service.notification.NotificationListenerService  import android.service.notification.NotificationListenerService.RankingMap  import android.service.notification.StatusBarNotification -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING -import com.android.systemui.log.LogLevel.WTF  import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.ERROR +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogLevel.WARNING +import com.android.systemui.plugins.log.LogLevel.WTF  import com.android.systemui.statusbar.notification.collection.NotifCollection  import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason  import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt index 38e3d496a60c..9c71e5c1054c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt @@ -16,9 +16,9 @@  package com.android.systemui.statusbar.notification.collection.render -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import com.android.systemui.statusbar.notification.NotifPipelineFlags  import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection  import com.android.systemui.util.Compile diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt index 6d1071c283e3..b4b9438cd6be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt @@ -16,9 +16,9 @@  package com.android.systemui.statusbar.notification.collection.render -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import java.lang.RuntimeException  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt index 5dbec8dcba20..d4f11fc141f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt @@ -1,8 +1,8 @@  package com.android.systemui.statusbar.notification.interruption -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO  import com.android.systemui.log.dagger.NotificationHeadsUpLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.INFO  import com.android.systemui.statusbar.notification.collection.NotificationEntry  import com.android.systemui.statusbar.notification.logKey  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt index 99d320d1c7ca..073b6b041b81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt @@ -16,11 +16,11 @@  package com.android.systemui.statusbar.notification.interruption -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING  import com.android.systemui.log.dagger.NotificationInterruptLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogLevel.WARNING  import com.android.systemui.statusbar.notification.collection.NotificationEntry  import com.android.systemui.statusbar.notification.logKey  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt index fe03b2ad6a32..10197a38527e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt @@ -16,9 +16,9 @@  package com.android.systemui.statusbar.notification.logging -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO  import com.android.systemui.log.dagger.NotificationRenderLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.INFO  import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow  import com.android.systemui.statusbar.notification.row.ExpandableView  import com.android.systemui.statusbar.notification.stack.NotificationSection diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt index ab91926d466a..46fef3f973a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt @@ -16,9 +16,9 @@  package com.android.systemui.statusbar.notification.row -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO  import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.INFO  import com.android.systemui.statusbar.notification.collection.NotificationEntry  import com.android.systemui.statusbar.notification.logKey  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt index f9923b2254d7..8a5d29a1ae2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt @@ -16,9 +16,9 @@  package com.android.systemui.statusbar.notification.row -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO  import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.INFO  import com.android.systemui.statusbar.notification.collection.NotificationEntry  import com.android.systemui.statusbar.notification.logKey  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 2719dd88b7be..b2628e40e77e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -142,6 +142,11 @@ public class AmbientState implements Dumpable {       */      private boolean mIsFlingRequiredAfterLockScreenSwipeUp = false; +    /** +     * Whether the shade is currently closing. +     */ +    private boolean mIsClosing; +      @VisibleForTesting      public boolean isFlingRequiredAfterLockScreenSwipeUp() {          return mIsFlingRequiredAfterLockScreenSwipeUp; @@ -717,6 +722,20 @@ public class AmbientState implements Dumpable {                  && mStatusBarKeyguardViewManager.isBouncerInTransit();      } +    /** +     * @param isClosing Whether the shade is currently closing. +     */ +    public void setIsClosing(boolean isClosing) { +        mIsClosing = isClosing; +    } + +    /** +     * @return Whether the shade is currently closing. +     */ +    public boolean isClosing() { +        return mIsClosing; +    } +      @Override      public void dump(PrintWriter pw, String[] args) {          pw.println("mTopPadding=" + mTopPadding); @@ -761,5 +780,6 @@ public class AmbientState implements Dumpable {                  + mIsFlingRequiredAfterLockScreenSwipeUp);          pw.println("mZDistanceBetweenElements=" + mZDistanceBetweenElements);          pw.println("mBaseZHeight=" + mBaseZHeight); +        pw.println("mIsClosing=" + mIsClosing);      }  } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt index cb7dfe87f7fb..b61c55edadcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt @@ -17,9 +17,9 @@  package com.android.systemui.statusbar.notification.stack  import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.NotificationSectionLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import javax.inject.Inject  private const val TAG = "NotifSections" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 55c577f1ea39..2272411b4314 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -255,7 +255,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable      private boolean mClearAllInProgress;      private FooterClearAllListener mFooterClearAllListener;      private boolean mFlingAfterUpEvent; -      /**       * Was the scroller scrolled to the top when the down motion was observed?       */ @@ -4020,8 +4019,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable          setOwnScrollY(0);      } +    @VisibleForTesting      @ShadeViewRefactor(RefactorComponent.COORDINATOR) -    private void setIsExpanded(boolean isExpanded) { +    void setIsExpanded(boolean isExpanded) {          boolean changed = isExpanded != mIsExpanded;          mIsExpanded = isExpanded;          mStackScrollAlgorithm.setIsExpanded(isExpanded); @@ -4842,13 +4842,21 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable          }      } +    @VisibleForTesting      @ShadeViewRefactor(RefactorComponent.COORDINATOR) -    private void setOwnScrollY(int ownScrollY) { +    void setOwnScrollY(int ownScrollY) {          setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */);      }      @ShadeViewRefactor(RefactorComponent.COORDINATOR)      private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) { +        // Avoid Flicking during clear all +        // when the shade finishes closing, onExpansionStopped will call +        // resetScrollPosition to setOwnScrollY to 0 +        if (mAmbientState.isClosing()) { +            return; +        } +          if (ownScrollY != mOwnScrollY) {              // We still want to call the normal scrolled changed for accessibility reasons              onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt index 5f79c0e3913a..4c52db7f8732 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt @@ -1,8 +1,8 @@  package com.android.systemui.statusbar.notification.stack -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO  import com.android.systemui.log.dagger.NotificationHeadsUpLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.INFO  import com.android.systemui.statusbar.notification.collection.NotificationEntry  import com.android.systemui.statusbar.notification.logKey  import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt index cb4a0884fea4..f5de678a8536 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt @@ -1,8 +1,8 @@  package com.android.systemui.statusbar.notification.stack -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.NotificationHeadsUpLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import com.android.systemui.statusbar.notification.logKey  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt index 02b235493715..4839fe6a7bef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt @@ -19,9 +19,9 @@ package com.android.systemui.statusbar.phone  import android.util.DisplayMetrics  import android.view.View  import com.android.internal.logging.nano.MetricsProto.MetricsEvent -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.LSShadeTransitionLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow  import com.android.systemui.statusbar.notification.row.ExpandableView  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt index b9a1413ff791..81edff45c505 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt @@ -17,12 +17,12 @@  package com.android.systemui.statusbar.phone  import android.app.PendingIntent -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING  import com.android.systemui.log.dagger.NotifInteractionLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.ERROR +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogLevel.WARNING  import com.android.systemui.statusbar.notification.collection.NotificationEntry  import com.android.systemui.statusbar.notification.logKey  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt index 28ed0806a181..d64bc58a0c37 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt @@ -16,9 +16,9 @@  package com.android.systemui.statusbar.phone.fragment -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.CollapsedSbFragmentLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  import com.android.systemui.statusbar.disableflags.DisableFlagsLogger  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt index dbb1aa54d8ee..d3cf32fb44ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt @@ -18,10 +18,10 @@ package com.android.systemui.statusbar.pipeline.shared  import android.net.Network  import android.net.NetworkCapabilities -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel  import com.android.systemui.log.dagger.StatusBarConnectivityLog +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.shared.ConnectivityPipelineLogger.Companion.toString  import javax.inject.Inject  import kotlinx.coroutines.flow.Flow diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt index 2f0ebf752a23..28a9b97b8ea6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt @@ -43,11 +43,7 @@ protected constructor(      }      override fun getCount(): Int { -        return if (controller.isKeyguardShowing) { -            users.count { !it.isRestricted } -        } else { -            users.size -        } +        return users.size      }      override fun getItem(position: Int): UserRecord { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt index d7c81af53d8b..df1e80b78c9b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt @@ -16,10 +16,10 @@  package com.android.systemui.statusbar.policy -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.VERBOSE  import com.android.systemui.log.dagger.NotificationHeadsUpLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogLevel.VERBOSE  import com.android.systemui.statusbar.notification.collection.NotificationEntry  import com.android.systemui.statusbar.notification.logKey  import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt index 606a11a84686..a7185cb18c40 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt @@ -16,8 +16,8 @@  package com.android.systemui.temporarydisplay -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel  /** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */  open class TemporaryViewLogger( diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt index 51541bd3032e..fda511433143 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt @@ -16,11 +16,11 @@  package com.android.systemui.toast -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogMessage  import com.android.systemui.log.dagger.ToastLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogMessage  import javax.inject.Inject  private const val TAG = "ToastLog" diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 919e699652bc..d768b6dc195a 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -321,6 +321,7 @@ constructor(          return when {              isAddUser -> false              isAddSupervisedUser -> false +            isManageUsers -> false              isGuest -> info != null              else -> true          } @@ -346,6 +347,7 @@ constructor(              isAddUser -> UserActionModel.ADD_USER              isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER              isGuest -> UserActionModel.ENTER_GUEST_MODE +            isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT              else -> error("Don't know how to convert to UserActionModel: $this")          }      } diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt index ba5a82a42d94..0d5c64b83e6e 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt @@ -236,18 +236,7 @@ constructor(                      }                      .flatMapLatest { isActionable ->                          if (isActionable) { -                            repository.actions.map { actions -> -                                actions + -                                    if (actions.isNotEmpty()) { -                                        // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT -                                        // because that's a user switcher specific action that is -                                        // not known to the our data source or other features. -                                        listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) -                                    } else { -                                        // If no actions, don't add the navigate action. -                                        emptyList() -                                    } -                            } +                            repository.actions                          } else {                              // If not actionable it means that we're not allowed to show actions                              // when diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt index 91c592177d19..f7e19c0ca810 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt @@ -30,6 +30,7 @@ import com.android.systemui.flags.Flags  import com.android.systemui.plugins.FalsingManager  import com.android.systemui.user.domain.interactor.UserInteractor  import com.android.systemui.user.domain.model.ShowDialogRequestModel +import dagger.Lazy  import javax.inject.Inject  import kotlinx.coroutines.CoroutineScope  import kotlinx.coroutines.flow.collect @@ -41,19 +42,19 @@ import kotlinx.coroutines.launch  class UserSwitcherDialogCoordinator  @Inject  constructor( -    @Application private val context: Context, -    @Application private val applicationScope: CoroutineScope, -    private val falsingManager: FalsingManager, -    private val broadcastSender: BroadcastSender, -    private val dialogLaunchAnimator: DialogLaunchAnimator, -    private val interactor: UserInteractor, -    private val featureFlags: FeatureFlags, +    @Application private val context: Lazy<Context>, +    @Application private val applicationScope: Lazy<CoroutineScope>, +    private val falsingManager: Lazy<FalsingManager>, +    private val broadcastSender: Lazy<BroadcastSender>, +    private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>, +    private val interactor: Lazy<UserInteractor>, +    private val featureFlags: Lazy<FeatureFlags>,  ) : CoreStartable {      private var currentDialog: Dialog? = null      override fun start() { -        if (featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) { +        if (featureFlags.get().isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {              return          } @@ -62,8 +63,8 @@ constructor(      }      private fun startHandlingDialogShowRequests() { -        applicationScope.launch { -            interactor.dialogShowRequests.filterNotNull().collect { request -> +        applicationScope.get().launch { +            interactor.get().dialogShowRequests.filterNotNull().collect { request ->                  currentDialog?.let {                      if (it.isShowing) {                          it.cancel() @@ -74,48 +75,48 @@ constructor(                      when (request) {                          is ShowDialogRequestModel.ShowAddUserDialog ->                              AddUserDialog( -                                context = context, +                                context = context.get(),                                  userHandle = request.userHandle,                                  isKeyguardShowing = request.isKeyguardShowing,                                  showEphemeralMessage = request.showEphemeralMessage, -                                falsingManager = falsingManager, -                                broadcastSender = broadcastSender, -                                dialogLaunchAnimator = dialogLaunchAnimator, +                                falsingManager = falsingManager.get(), +                                broadcastSender = broadcastSender.get(), +                                dialogLaunchAnimator = dialogLaunchAnimator.get(),                              )                          is ShowDialogRequestModel.ShowUserCreationDialog ->                              UserCreatingDialog( -                                context, +                                context.get(),                                  request.isGuest,                              )                          is ShowDialogRequestModel.ShowExitGuestDialog ->                              ExitGuestDialog( -                                context = context, +                                context = context.get(),                                  guestUserId = request.guestUserId,                                  isGuestEphemeral = request.isGuestEphemeral,                                  targetUserId = request.targetUserId,                                  isKeyguardShowing = request.isKeyguardShowing, -                                falsingManager = falsingManager, -                                dialogLaunchAnimator = dialogLaunchAnimator, +                                falsingManager = falsingManager.get(), +                                dialogLaunchAnimator = dialogLaunchAnimator.get(),                                  onExitGuestUserListener = request.onExitGuestUser,                              )                      }                  currentDialog?.show() -                interactor.onDialogShown() +                interactor.get().onDialogShown()              }          }      }      private fun startHandlingDialogDismissRequests() { -        applicationScope.launch { -            interactor.dialogDismissRequests.filterNotNull().collect { +        applicationScope.get().launch { +            interactor.get().dialogDismissRequests.filterNotNull().collect {                  currentDialog?.let {                      if (it.isShowing) {                          it.cancel()                      }                  } -                interactor.onDialogDismissed() +                interactor.get().onDialogDismissed()              }          }      } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt index 219dae29117f..d857e85bac53 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt @@ -62,17 +62,7 @@ private constructor(      val isMenuVisible: Flow<Boolean> = _isMenuVisible      /** The user action menu. */      val menu: Flow<List<UserActionViewModel>> = -        userInteractor.actions.map { actions -> -            if (isNewImpl && actions.isNotEmpty()) { -                    // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT because that's a user -                    // switcher specific action that is not known to the our data source or other -                    // features. -                    actions + listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) -                } else { -                    actions -                } -                .map { action -> toViewModel(action) } -        } +        userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } }      /** Whether the button to open the user action menu is visible. */      val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt new file mode 100644 index 000000000000..9d6aff219148 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard + +import android.content.Context +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.util.AttributeSet +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class BouncerKeyguardMessageAreaTest : SysuiTestCase() { +    class FakeBouncerKeyguardMessageArea(context: Context, attrs: AttributeSet?) : +        BouncerKeyguardMessageArea(context, attrs) { +        override val SHOW_DURATION_MILLIS = 0L +        override val HIDE_DURATION_MILLIS = 0L +    } +    lateinit var underTest: BouncerKeyguardMessageArea + +    @Before +    fun setup() { +        underTest = FakeBouncerKeyguardMessageArea(context, null) +    } + +    @Test +    fun testSetSameMessage() { +        val underTestSpy = spy(underTest) +        underTestSpy.setMessage("abc") +        underTestSpy.setMessage("abc") +        verify(underTestSpy, times(1)).text = "abc" +    } + +    @Test +    fun testSetDifferentMessage() { +        underTest.setMessage("abc") +        underTest.setMessage("def") +        assertThat(underTest.text).isEqualTo("def") +    } + +    @Test +    fun testSetNullMessage() { +        underTest.setMessage(null) +        assertThat(underTest.text).isEqualTo("") +    } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 400caa3a352a..9b2bba612106 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.when;  import android.content.res.Resources;  import android.database.ContentObserver; +import android.graphics.Rect;  import android.net.Uri;  import android.os.UserHandle;  import android.provider.Settings; @@ -45,6 +46,7 @@ import com.android.systemui.SysuiTestCase;  import com.android.systemui.dump.DumpManager;  import com.android.systemui.flags.FeatureFlags;  import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.plugins.ClockAnimations;  import com.android.systemui.plugins.ClockController;  import com.android.systemui.plugins.statusbar.StatusBarStateController;  import com.android.systemui.shared.clocks.AnimatableClockView; @@ -262,6 +264,19 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {          verify(mView).switchToClock(KeyguardClockSwitch.SMALL, /* animate */ true);      } +    @Test +    public void testGetClockAnimationsForwardsToClock() { +        ClockController mockClockController = mock(ClockController.class); +        ClockAnimations mockClockAnimations = mock(ClockAnimations.class); +        when(mClockEventController.getClock()).thenReturn(mockClockController); +        when(mockClockController.getAnimations()).thenReturn(mockClockAnimations); + +        Rect r1 = new Rect(1, 2, 3, 4); +        Rect r2 = new Rect(5, 6, 7, 8); +        mController.getClockAnimations().onPositionUpdated(r1, r2, 0.2f); +        verify(mockClockAnimations).onPositionUpdated(r1, r2, 0.2f); +    } +      private void verifyAttachment(VerificationMode times) {          verify(mClockRegistry, times).registerClockChangeListener(                  any(ClockRegistry.ClockChangeListener.class)); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java index 69524e5a4537..5d2b0ca4e7ea 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java @@ -17,13 +17,11 @@  package com.android.keyguard;  import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never;  import static org.mockito.Mockito.verify;  import android.test.suitebuilder.annotation.SmallTest;  import android.testing.AndroidTestingRunner; -import com.android.systemui.R;  import com.android.systemui.SysuiTestCase;  import com.android.systemui.statusbar.policy.ConfigurationController;  import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -92,19 +90,4 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase {          mMessageAreaController.setIsVisible(true);          verify(mKeyguardMessageArea).setIsVisible(true);      } - -    @Test -    public void testSetMessageIfEmpty_empty() { -        mMessageAreaController.setMessage(""); -        mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin); -        verify(mKeyguardMessageArea).setMessage(R.string.keyguard_enter_your_pin); -    } - -    @Test -    public void testSetMessageIfEmpty_notEmpty() { -        mMessageAreaController.setMessage("abc"); -        mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin); -        verify(mKeyguardMessageArea, never()).setMessage(getContext() -                .getResources().getText(R.string.keyguard_enter_your_pin)); -    }  } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index b89dbd98968a..b369098cafc0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -114,9 +114,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {      }      @Test -    fun onResume_testSetInitialText() { -        keyguardPasswordViewController.onResume(KeyguardSecurityView.SCREEN_ON) -        verify(mKeyguardMessageAreaController) -            .setMessageIfEmpty(R.string.keyguard_enter_your_password) +    fun startAppearAnimation() { +        keyguardPasswordViewController.startAppearAnimation() +        verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)      }  } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index 3262a77b7711..9eff70487c74 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -100,16 +100,16 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {      }      @Test -    fun onPause_clearsTextField() { +    fun onPause_resetsText() {          mKeyguardPatternViewController.init()          mKeyguardPatternViewController.onPause() -        verify(mKeyguardMessageAreaController).setMessage("") +        verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)      } +      @Test -    fun onResume_setInitialText() { -        mKeyguardPatternViewController.onResume(KeyguardSecurityView.SCREEN_ON) -        verify(mKeyguardMessageAreaController) -            .setMessageIfEmpty(R.string.keyguard_enter_your_pattern) +    fun startAppearAnimation() { +        mKeyguardPatternViewController.startAppearAnimation() +        verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)      }  } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index 97d556b04aa4..ce1101f389c0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -113,11 +113,4 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {          mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);          verify(mPasswordEntry).requestFocus();      } - -    @Test -    public void onResume_setInitialText() { -        mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON); -        verify(mKeyguardMessageAreaController).setMessageIfEmpty(R.string.keyguard_enter_your_pin); -    }  } - diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 9e5bfe53ea05..d9efdeaea04c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -98,6 +98,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {      @Test      fun startAppearAnimation() {          pinViewController.startAppearAnimation() -        verify(keyguardMessageAreaController).setMessageIfEmpty(R.string.keyguard_enter_your_pin) +        verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin)      }  } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index 4dcaa7cf8c09..c94c97c9b638 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -16,12 +16,16 @@  package com.android.keyguard; +import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.graphics.Rect;  import android.test.suitebuilder.annotation.SmallTest;  import android.testing.AndroidTestingRunner;  import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.ClockAnimations;  import com.android.systemui.statusbar.phone.DozeParameters;  import com.android.systemui.statusbar.phone.ScreenOffAnimationController;  import com.android.systemui.statusbar.policy.ConfigurationController; @@ -108,4 +112,16 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase {          configurationListenerArgumentCaptor.getValue().onLocaleListChanged();          verify(mKeyguardClockSwitchController).onLocaleListChanged();      } + +    @Test +    public void getClockAnimations_forwardsToClockSwitch() { +        ClockAnimations mockClockAnimations = mock(ClockAnimations.class); +        when(mKeyguardClockSwitchController.getClockAnimations()).thenReturn(mockClockAnimations); + +        Rect r1 = new Rect(1, 2, 3, 4); +        Rect r2 = new Rect(5, 6, 7, 8); +        mController.getClockAnimations().onPositionUpdated(r1, r2, 0.3f); + +        verify(mockClockAnimations).onPositionUpdated(r1, r2, 0.3f); +    }  } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 2319f4386798..181839ab512f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -36,6 +36,7 @@ import static org.mockito.ArgumentMatchers.any;  import static org.mockito.ArgumentMatchers.eq;  import static org.mockito.ArgumentMatchers.isA;  import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing;  import static org.mockito.Mockito.doReturn;  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.never; @@ -255,6 +256,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {          });          mScreenDecorations.mDisplayInfo = mDisplayInfo;          doReturn(1f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio(); +        doNothing().when(mScreenDecorations).updateOverlayProviderViews(any());          reset(mTunerService);          try { @@ -1005,18 +1007,13 @@ public class ScreenDecorationsTest extends SysuiTestCase {          assertEquals(new Size(3, 3), resDelegate.getTopRoundedSize());          assertEquals(new Size(4, 4), resDelegate.getBottomRoundedSize()); -        setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, -                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px) -                /* roundedTopDrawable */, -                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px) -                /* roundedBottomDrawable */, -                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/); +        doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();          mDisplayInfo.rotation = Surface.ROTATION_270;          mScreenDecorations.onConfigurationChanged(null); -        assertEquals(new Size(4, 4), resDelegate.getTopRoundedSize()); -        assertEquals(new Size(5, 5), resDelegate.getBottomRoundedSize()); +        assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize()); +        assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());      }      @Test @@ -1293,51 +1290,6 @@ public class ScreenDecorationsTest extends SysuiTestCase {      }      @Test -    public void testOnDisplayChanged_hwcLayer() { -        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, -                null /* roundedTopDrawable */, null /* roundedBottomDrawable */, -                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); -        final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); -        decorationSupport.format = PixelFormat.R_8; -        doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); - -        // top cutout -        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); - -        mScreenDecorations.start(); - -        final ScreenDecorHwcLayer hwcLayer = mScreenDecorations.mScreenDecorHwcLayer; -        spyOn(hwcLayer); -        doReturn(mDisplay).when(hwcLayer).getDisplay(); - -        mScreenDecorations.mDisplayListener.onDisplayChanged(1); - -        verify(hwcLayer, times(1)).onDisplayChanged(any()); -    } - -    @Test -    public void testOnDisplayChanged_nonHwcLayer() { -        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, -                null /* roundedTopDrawable */, null /* roundedBottomDrawable */, -                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); - -        // top cutout -        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); - -        mScreenDecorations.start(); - -        final ScreenDecorations.DisplayCutoutView cutoutView = (ScreenDecorations.DisplayCutoutView) -                mScreenDecorations.getOverlayView(R.id.display_cutout); -        assertNotNull(cutoutView); -        spyOn(cutoutView); -        doReturn(mDisplay).when(cutoutView).getDisplay(); - -        mScreenDecorations.mDisplayListener.onDisplayChanged(1); - -        verify(cutoutView, times(1)).onDisplayChanged(any()); -    } - -    @Test      public void testHasSameProvidersWithNullOverlays() {          setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,                  null /* roundedTopDrawable */, null /* roundedBottomDrawable */, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index baeabc577fb7..cd50144bf2e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -163,6 +163,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {          val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT)          overlayParams = UdfpsOverlayParams(              sensorBounds, +            sensorBounds,              DISPLAY_WIDTH,              DISPLAY_HEIGHT,              scaleFactor = 1f, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index f210708806ab..eff47bd2ee98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -414,7 +414,7 @@ public class UdfpsControllerTest extends SysuiTestCase {          final float[] scaleFactor = new float[]{1f, displayHeight[1] / (float) displayHeight[0]};          final int[] rotation = new int[]{Surface.ROTATION_0, Surface.ROTATION_90};          final UdfpsOverlayParams oldParams = new UdfpsOverlayParams(sensorBounds[0], -                displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0]); +                sensorBounds[0], displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0]);          for (int i1 = 0; i1 <= 1; ++i1) {              for (int i2 = 0; i2 <= 1; ++i2) { @@ -422,8 +422,8 @@ public class UdfpsControllerTest extends SysuiTestCase {                      for (int i4 = 0; i4 <= 1; ++i4) {                          for (int i5 = 0; i5 <= 1; ++i5) {                              final UdfpsOverlayParams newParams = new UdfpsOverlayParams( -                                    sensorBounds[i1], displayWidth[i2], displayHeight[i3], -                                    scaleFactor[i4], rotation[i5]); +                                    sensorBounds[i1], sensorBounds[i1], displayWidth[i2], +                                    displayHeight[i3], scaleFactor[i4], rotation[i5]);                              if (newParams.equals(oldParams)) {                                  continue; @@ -466,8 +466,8 @@ public class UdfpsControllerTest extends SysuiTestCase {          // Initialize the overlay.          mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, -                new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor, -                        rotation)); +                new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, +                        scaleFactor, rotation));          // Show the overlay.          mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID, @@ -477,8 +477,8 @@ public class UdfpsControllerTest extends SysuiTestCase {          // Update overlay with the same parameters.          mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, -                new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor, -                        rotation)); +                new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, +                        scaleFactor, rotation));          mFgExecutor.runAllReady();          // Ensure the overlay was not recreated. @@ -525,8 +525,8 @@ public class UdfpsControllerTest extends SysuiTestCase {          // Test ROTATION_0          mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, -                new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor, -                        Surface.ROTATION_0)); +                new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, +                        scaleFactor, Surface.ROTATION_0));          MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor,                  touchMajor);          mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); @@ -542,8 +542,8 @@ public class UdfpsControllerTest extends SysuiTestCase {          // Test ROTATION_90          reset(mAlternateTouchProvider);          mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, -                new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor, -                        Surface.ROTATION_90)); +                new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, +                        scaleFactor, Surface.ROTATION_90));          event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor);          mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);          mBiometricsExecutor.runAllReady(); @@ -558,8 +558,8 @@ public class UdfpsControllerTest extends SysuiTestCase {          // Test ROTATION_270          reset(mAlternateTouchProvider);          mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, -                new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor, -                        Surface.ROTATION_270)); +                new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, +                        scaleFactor, Surface.ROTATION_270));          event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor);          mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);          mBiometricsExecutor.runAllReady(); @@ -574,8 +574,8 @@ public class UdfpsControllerTest extends SysuiTestCase {          // Test ROTATION_180          reset(mAlternateTouchProvider);          mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, -                new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor, -                        Surface.ROTATION_180)); +                new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, +                        scaleFactor, Surface.ROTATION_180));          // ROTATION_180 is not supported. It should be treated like ROTATION_0.          event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor, touchMajor);          mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt index b78c06391057..ac936e1a77c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt @@ -68,7 +68,8 @@ class UdfpsViewTest : SysuiTestCase() {          view = LayoutInflater.from(context).inflate(R.layout.udfps_view, null) as UdfpsView          view.animationViewController = animationViewController          val sensorBounds = SensorLocationInternal("", SENSOR_X, SENSOR_Y, SENSOR_RADIUS).rect -        view.overlayParams = UdfpsOverlayParams(sensorBounds, 1920, 1080, 1f, Surface.ROTATION_0) +        view.overlayParams = UdfpsOverlayParams(sensorBounds, sensorBounds, 1920, +            1080, 1f, Surface.ROTATION_0)          view.setUdfpsDisplayModeProvider(hbmProvider)          ViewUtils.attachView(view)      } @@ -133,7 +134,8 @@ class UdfpsViewTest : SysuiTestCase() {      @Test      fun isNotWithinSensorArea() {          whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f)) -        assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat())).isFalse() +        assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat())) +            .isFalse()          assertThat(view.isWithinSensorArea(SENSOR_RADIUS.toFloat(), SENSOR_RADIUS * 2.5f)).isFalse()      } diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt index f93336134900..93a1868b72f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt @@ -24,12 +24,11 @@ import androidx.annotation.DrawableRes  import androidx.test.filters.SmallTest  import com.android.internal.R as InternalR  import com.android.systemui.R as SystemUIR -import com.android.systemui.tests.R  import com.android.systemui.SysuiTestCase +import com.android.systemui.tests.R  import org.junit.Assert.assertEquals  import org.junit.Before  import org.junit.Test -  import org.junit.runner.RunWith  import org.mockito.Mock  import org.mockito.MockitoAnnotations @@ -102,14 +101,11 @@ class RoundedCornerResDelegateTest : SysuiTestCase() {          assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)          assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize) -        setupResources(radius = 100, -                roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px), -                roundedBottomDrawable = getTestsDrawable(R.drawable.rounded5px)) - +        roundedCornerResDelegate.physicalPixelDisplaySizeRatio = 2f          roundedCornerResDelegate.updateDisplayUniqueId(null, 1) -        assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize) -        assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize) +        assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize) +        assertEquals(Size(8, 8), roundedCornerResDelegate.bottomRoundedSize)      }      @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index 6a55a60c2fda..5bbd8109d8f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -16,6 +16,9 @@  package com.android.systemui.doze; +import static android.content.res.Configuration.UI_MODE_NIGHT_YES; +import static android.content.res.Configuration.UI_MODE_TYPE_CAR; +  import static com.android.systemui.doze.DozeMachine.State.DOZE;  import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;  import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED; @@ -38,16 +41,17 @@ import static org.mockito.Mockito.doAnswer;  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.never;  import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times;  import static org.mockito.Mockito.verify;  import static org.mockito.Mockito.when; -import android.app.UiModeManager;  import android.content.res.Configuration;  import android.hardware.display.AmbientDisplayConfiguration;  import android.testing.AndroidTestingRunner;  import android.testing.UiThreadTest;  import android.view.Display; +import androidx.annotation.NonNull;  import androidx.test.filters.SmallTest;  import com.android.systemui.SysuiTestCase; @@ -78,25 +82,30 @@ public class DozeMachineTest extends SysuiTestCase {      @Mock      private DozeHost mHost;      @Mock -    private UiModeManager mUiModeManager; +    private DozeMachine.Part mPartMock; +    @Mock +    private DozeMachine.Part mAnotherPartMock;      private DozeServiceFake mServiceFake;      private WakeLockFake mWakeLockFake; -    private AmbientDisplayConfiguration mConfigMock; -    private DozeMachine.Part mPartMock; +    private AmbientDisplayConfiguration mAmbientDisplayConfigMock;      @Before      public void setUp() {          MockitoAnnotations.initMocks(this);          mServiceFake = new DozeServiceFake();          mWakeLockFake = new WakeLockFake(); -        mConfigMock = mock(AmbientDisplayConfiguration.class); -        mPartMock = mock(DozeMachine.Part.class); +        mAmbientDisplayConfigMock = mock(AmbientDisplayConfiguration.class);          when(mDockManager.isDocked()).thenReturn(false);          when(mDockManager.isHidden()).thenReturn(false); -        mMachine = new DozeMachine(mServiceFake, mConfigMock, mWakeLockFake, -                mWakefulnessLifecycle, mUiModeManager, mDozeLog, mDockManager, -                mHost, new DozeMachine.Part[]{mPartMock}); +        mMachine = new DozeMachine(mServiceFake, +                mAmbientDisplayConfigMock, +                mWakeLockFake, +                mWakefulnessLifecycle, +                mDozeLog, +                mDockManager, +                mHost, +                new DozeMachine.Part[]{mPartMock, mAnotherPartMock});      }      @Test @@ -108,7 +117,7 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testInitialize_goesToDoze() { -        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); +        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);          mMachine.requestState(INITIALIZED); @@ -118,7 +127,7 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testInitialize_goesToAod() { -        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); +        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);          mMachine.requestState(INITIALIZED); @@ -138,7 +147,7 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testInitialize_afterDockPaused_goesToDoze() { -        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); +        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);          when(mDockManager.isDocked()).thenReturn(true);          when(mDockManager.isHidden()).thenReturn(true); @@ -151,7 +160,7 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testInitialize_alwaysOnSuppressed_alwaysOnDisabled_goesToDoze() {          when(mHost.isAlwaysOnSuppressed()).thenReturn(true); -        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); +        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);          mMachine.requestState(INITIALIZED); @@ -162,7 +171,7 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testInitialize_alwaysOnSuppressed_alwaysOnEnabled_goesToDoze() {          when(mHost.isAlwaysOnSuppressed()).thenReturn(true); -        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); +        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);          mMachine.requestState(INITIALIZED); @@ -184,7 +193,7 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testInitialize_alwaysOnSuppressed_alwaysOnDisabled_afterDockPaused_goesToDoze() {          when(mHost.isAlwaysOnSuppressed()).thenReturn(true); -        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); +        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);          when(mDockManager.isDocked()).thenReturn(true);          when(mDockManager.isHidden()).thenReturn(true); @@ -197,7 +206,7 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testInitialize_alwaysOnSuppressed_alwaysOnEnabled_afterDockPaused_goesToDoze() {          when(mHost.isAlwaysOnSuppressed()).thenReturn(true); -        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); +        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);          when(mDockManager.isDocked()).thenReturn(true);          when(mDockManager.isHidden()).thenReturn(true); @@ -209,7 +218,7 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testPulseDone_goesToDoze() { -        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); +        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);          mMachine.requestState(INITIALIZED);          mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);          mMachine.requestState(DOZE_PULSING); @@ -222,7 +231,7 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testPulseDone_goesToAoD() { -        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); +        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);          mMachine.requestState(INITIALIZED);          mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);          mMachine.requestState(DOZE_PULSING); @@ -236,7 +245,7 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testPulseDone_alwaysOnSuppressed_goesToSuppressed() {          when(mHost.isAlwaysOnSuppressed()).thenReturn(true); -        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); +        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);          mMachine.requestState(INITIALIZED);          mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);          mMachine.requestState(DOZE_PULSING); @@ -287,7 +296,7 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testPulseDone_afterDockPaused_goesToDoze() { -        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); +        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);          when(mDockManager.isDocked()).thenReturn(true);          when(mDockManager.isHidden()).thenReturn(true);          mMachine.requestState(INITIALIZED); @@ -303,7 +312,7 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testPulseDone_alwaysOnSuppressed_afterDockPaused_goesToDoze() {          when(mHost.isAlwaysOnSuppressed()).thenReturn(true); -        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); +        when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);          when(mDockManager.isDocked()).thenReturn(true);          when(mDockManager.isHidden()).thenReturn(true);          mMachine.requestState(INITIALIZED); @@ -471,7 +480,9 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testTransitionToInitialized_carModeIsEnabled() { -        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); +        Configuration configuration = configWithCarNightUiMode(); + +        mMachine.onConfigurationChanged(configuration);          mMachine.requestState(INITIALIZED);          verify(mPartMock).transitionTo(UNINITIALIZED, INITIALIZED); @@ -481,7 +492,9 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testTransitionToFinish_carModeIsEnabled() { -        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); +        Configuration configuration = configWithCarNightUiMode(); + +        mMachine.onConfigurationChanged(configuration);          mMachine.requestState(INITIALIZED);          mMachine.requestState(FINISH); @@ -490,7 +503,9 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testDozeToDozeSuspendTriggers_carModeIsEnabled() { -        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); +        Configuration configuration = configWithCarNightUiMode(); + +        mMachine.onConfigurationChanged(configuration);          mMachine.requestState(INITIALIZED);          mMachine.requestState(DOZE); @@ -499,7 +514,9 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testDozeAoDToDozeSuspendTriggers_carModeIsEnabled() { -        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); +        Configuration configuration = configWithCarNightUiMode(); + +        mMachine.onConfigurationChanged(configuration);          mMachine.requestState(INITIALIZED);          mMachine.requestState(DOZE_AOD); @@ -508,7 +525,9 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testDozePulsingBrightDozeSuspendTriggers_carModeIsEnabled() { -        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); +        Configuration configuration = configWithCarNightUiMode(); + +        mMachine.onConfigurationChanged(configuration);          mMachine.requestState(INITIALIZED);          mMachine.requestState(DOZE_PULSING_BRIGHT); @@ -517,7 +536,9 @@ public class DozeMachineTest extends SysuiTestCase {      @Test      public void testDozeAodDockedDozeSuspendTriggers_carModeIsEnabled() { -        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); +        Configuration configuration = configWithCarNightUiMode(); + +        mMachine.onConfigurationChanged(configuration);          mMachine.requestState(INITIALIZED);          mMachine.requestState(DOZE_AOD_DOCKED); @@ -525,7 +546,35 @@ public class DozeMachineTest extends SysuiTestCase {      }      @Test +    public void testOnConfigurationChanged_propagatesUiModeTypeToParts() { +        Configuration newConfig = configWithCarNightUiMode(); + +        mMachine.onConfigurationChanged(newConfig); + +        verify(mPartMock).onUiModeTypeChanged(UI_MODE_TYPE_CAR); +        verify(mAnotherPartMock).onUiModeTypeChanged(UI_MODE_TYPE_CAR); +    } + +    @Test +    public void testOnConfigurationChanged_propagatesOnlyUiModeChangesToParts() { +        Configuration newConfig = configWithCarNightUiMode(); + +        mMachine.onConfigurationChanged(newConfig); +        mMachine.onConfigurationChanged(newConfig); + +        verify(mPartMock, times(1)).onUiModeTypeChanged(UI_MODE_TYPE_CAR); +        verify(mAnotherPartMock, times(1)).onUiModeTypeChanged(UI_MODE_TYPE_CAR); +    } + +    @Test      public void testDozeSuppressTriggers_screenState() {          assertEquals(Display.STATE_OFF, DOZE_SUSPEND_TRIGGERS.screenState(null));      } + +    @NonNull +    private Configuration configWithCarNightUiMode() { +        Configuration configuration = Configuration.EMPTY; +        configuration.uiMode = UI_MODE_TYPE_CAR | UI_MODE_NIGHT_YES; +        return configuration; +    }  } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java index 0f29dcd5a939..32b994538e12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java @@ -10,14 +10,14 @@   * 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 andatest + * See the License for the specific language governing permissions and   * limitations under the License.   */  package com.android.systemui.doze; -import static android.app.UiModeManager.ACTION_ENTER_CAR_MODE; -import static android.app.UiModeManager.ACTION_EXIT_CAR_MODE; +import static android.content.res.Configuration.UI_MODE_TYPE_CAR; +import static android.content.res.Configuration.UI_MODE_TYPE_NORMAL;  import static com.android.systemui.doze.DozeMachine.State.DOZE;  import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD; @@ -26,17 +26,16 @@ import static com.android.systemui.doze.DozeMachine.State.FINISH;  import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;  import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq;  import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times;  import static org.mockito.Mockito.verify;  import static org.mockito.Mockito.when; -import android.app.UiModeManager; -import android.content.BroadcastReceiver; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Configuration;  import android.hardware.display.AmbientDisplayConfiguration;  import android.testing.AndroidTestingRunner;  import android.testing.UiThreadTest; @@ -44,13 +43,13 @@ import android.testing.UiThreadTest;  import androidx.test.filters.SmallTest;  import com.android.systemui.SysuiTestCase; -import com.android.systemui.broadcast.BroadcastDispatcher;  import com.android.systemui.statusbar.phone.BiometricUnlockController;  import org.junit.After;  import org.junit.Before;  import org.junit.Test;  import org.junit.runner.RunWith; +import org.mockito.AdditionalMatchers;  import org.mockito.ArgumentCaptor;  import org.mockito.Captor;  import org.mockito.Mock; @@ -71,10 +70,6 @@ public class DozeSuppressorTest extends SysuiTestCase {      @Mock      private AmbientDisplayConfiguration mConfig;      @Mock -    private BroadcastDispatcher mBroadcastDispatcher; -    @Mock -    private UiModeManager mUiModeManager; -    @Mock      private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;      @Mock      private BiometricUnlockController mBiometricUnlockController; @@ -83,13 +78,6 @@ public class DozeSuppressorTest extends SysuiTestCase {      private DozeMachine mDozeMachine;      @Captor -    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor; -    @Captor -    private ArgumentCaptor<IntentFilter> mIntentFilterCaptor; -    private BroadcastReceiver mBroadcastReceiver; -    private IntentFilter mIntentFilter; - -    @Captor      private ArgumentCaptor<DozeHost.Callback> mDozeHostCaptor;      private DozeHost.Callback mDozeHostCallback; @@ -106,8 +94,6 @@ public class DozeSuppressorTest extends SysuiTestCase {                  mDozeHost,                  mConfig,                  mDozeLog, -                mBroadcastDispatcher, -                mUiModeManager,                  mBiometricUnlockControllerLazy);          mDozeSuppressor.setDozeMachine(mDozeMachine); @@ -122,36 +108,35 @@ public class DozeSuppressorTest extends SysuiTestCase {      public void testRegistersListenersOnInitialized_unregisteredOnFinish() {          // check that receivers and callbacks registered          mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED); -        captureBroadcastReceiver();          captureDozeHostCallback();          // check that receivers and callbacks are unregistered          mDozeSuppressor.transitionTo(INITIALIZED, FINISH); -        verify(mBroadcastDispatcher).unregisterReceiver(mBroadcastReceiver);          verify(mDozeHost).removeCallback(mDozeHostCallback);      }      @Test      public void testSuspendTriggersDoze_carMode() {          // GIVEN car mode -        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); +        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);          // WHEN dozing begins          mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);          // THEN doze continues with all doze triggers disabled. -        verify(mDozeMachine).requestState(DOZE_SUSPEND_TRIGGERS); +        verify(mDozeMachine, atLeastOnce()).requestState(DOZE_SUSPEND_TRIGGERS); +        verify(mDozeMachine, never()) +                .requestState(AdditionalMatchers.not(eq(DOZE_SUSPEND_TRIGGERS)));      }      @Test      public void testSuspendTriggersDoze_enterCarMode() {          // GIVEN currently dozing          mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED); -        captureBroadcastReceiver();          mDozeSuppressor.transitionTo(INITIALIZED, DOZE);          // WHEN car mode entered -        mBroadcastReceiver.onReceive(null, new Intent(ACTION_ENTER_CAR_MODE)); +        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);          // THEN doze continues with all doze triggers disabled.          verify(mDozeMachine).requestState(DOZE_SUSPEND_TRIGGERS); @@ -160,13 +145,13 @@ public class DozeSuppressorTest extends SysuiTestCase {      @Test      public void testDozeResume_exitCarMode() {          // GIVEN currently suspended, with AOD not enabled +        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);          when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(false);          mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED); -        captureBroadcastReceiver();          mDozeSuppressor.transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS);          // WHEN exiting car mode -        mBroadcastReceiver.onReceive(null, new Intent(ACTION_EXIT_CAR_MODE)); +        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_NORMAL);          // THEN doze is resumed          verify(mDozeMachine).requestState(DOZE); @@ -175,19 +160,53 @@ public class DozeSuppressorTest extends SysuiTestCase {      @Test      public void testDozeAoDResume_exitCarMode() {          // GIVEN currently suspended, with AOD not enabled +        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);          when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(true);          mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED); -        captureBroadcastReceiver();          mDozeSuppressor.transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS);          // WHEN exiting car mode -        mBroadcastReceiver.onReceive(null, new Intent(ACTION_EXIT_CAR_MODE)); +        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_NORMAL);          // THEN doze AOD is resumed          verify(mDozeMachine).requestState(DOZE_AOD);      }      @Test +    public void testUiModeDoesNotChange_noStateTransition() { +        mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED); +        clearInvocations(mDozeMachine); + +        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); +        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); +        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); + +        verify(mDozeMachine, times(1)).requestState(DOZE_SUSPEND_TRIGGERS); +        verify(mDozeMachine, never()) +                .requestState(AdditionalMatchers.not(eq(DOZE_SUSPEND_TRIGGERS))); +    } + +    @Test +    public void testUiModeTypeChange_whenDozeMachineIsNotReady_doesNotDoAnything() { +        when(mDozeMachine.isUninitializedOrFinished()).thenReturn(true); + +        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); + +        verify(mDozeMachine, never()).requestState(any()); +    } + +    @Test +    public void testUiModeTypeChange_CarModeEnabledAndDozeMachineNotReady_suspendsTriggersAfter() { +        when(mDozeMachine.isUninitializedOrFinished()).thenReturn(true); +        mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); +        verify(mDozeMachine, never()).requestState(any()); + +        mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED); + +        verify(mDozeMachine, times(1)).requestState(DOZE_SUSPEND_TRIGGERS); +    } + +    @Test      public void testEndDoze_unprovisioned() {          // GIVEN device unprovisioned          when(mDozeHost.isProvisioned()).thenReturn(false); @@ -276,14 +295,4 @@ public class DozeSuppressorTest extends SysuiTestCase {          verify(mDozeHost).addCallback(mDozeHostCaptor.capture());          mDozeHostCallback = mDozeHostCaptor.getValue();      } - -    private void captureBroadcastReceiver() { -        verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverCaptor.capture(), -                mIntentFilterCaptor.capture()); -        mBroadcastReceiver = mBroadcastReceiverCaptor.getValue(); -        mIntentFilter = mIntentFilterCaptor.getValue(); -        assertEquals(2, mIntentFilter.countActions()); -        org.hamcrest.MatcherAssert.assertThat(() -> mIntentFilter.actionsIterator(), -                containsInAnyOrder(ACTION_ENTER_CAR_MODE, ACTION_EXIT_CAR_MODE)); -    }  } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt index fc672016a886..65b44a14d2ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt @@ -17,11 +17,13 @@  package com.android.systemui.dump  import androidx.test.filters.SmallTest +import com.android.systemui.CoreStartable  import com.android.systemui.Dumpable  import com.android.systemui.SysuiTestCase -import com.android.systemui.log.LogBuffer +import com.android.systemui.plugins.log.LogBuffer  import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager  import com.android.systemui.util.mockito.any +import com.google.common.truth.Truth.assertThat  import org.junit.Before  import org.junit.Test  import org.mockito.Mock @@ -30,6 +32,8 @@ import org.mockito.Mockito.never  import org.mockito.Mockito.verify  import org.mockito.MockitoAnnotations  import java.io.PrintWriter +import java.io.StringWriter +import javax.inject.Provider  @SmallTest  class DumpHandlerTest : SysuiTestCase() { @@ -66,7 +70,9 @@ class DumpHandlerTest : SysuiTestCase() {              mContext,              dumpManager,              logBufferEulogizer, -            mutableMapOf(), +            mutableMapOf( +                EmptyCoreStartable::class.java to Provider { EmptyCoreStartable() } +            ),              exceptionHandlerManager          )      } @@ -154,4 +160,20 @@ class DumpHandlerTest : SysuiTestCase() {          verify(buffer1).dump(pw, 0)          verify(buffer2).dump(pw, 0)      } -}
\ No newline at end of file + +    @Test +    fun testConfigDump() { +        // GIVEN a StringPrintWriter +        val stringWriter = StringWriter() +        val spw = PrintWriter(stringWriter) + +        // When a config dump is requested +        dumpHandler.dump(spw, arrayOf("config")) + +        assertThat(stringWriter.toString()).contains(EmptyCoreStartable::class.java.simpleName) +    } + +    private class EmptyCoreStartable : CoreStartable { +        override fun start() {} +    } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt index bd029a727ee3..64547f4463d1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt @@ -16,9 +16,9 @@  package com.android.systemui.dump -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogcatEchoTracker  /**   * Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests. diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt index 1078cdaa57c4..e009e8651f2a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt @@ -19,9 +19,9 @@ package com.android.systemui.media.taptotransfer.common  import androidx.test.filters.SmallTest  import com.android.systemui.SysuiTestCase  import com.android.systemui.dump.DumpManager -import com.android.systemui.log.LogBuffer  import com.android.systemui.log.LogBufferFactory -import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogcatEchoTracker  import com.google.common.truth.Truth.assertThat  import java.io.PrintWriter  import java.io.StringWriter diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt index aacc695ef301..68c10f20f6f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt @@ -20,7 +20,7 @@ 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.log.LogcatEchoTracker +import com.android.systemui.plugins.log.LogcatEchoTracker  import com.android.systemui.statusbar.disableflags.DisableFlagsLogger  import com.google.common.truth.Truth.assertThat  import java.io.PrintWriter diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt index da52a9b1a3c2..bc27bbc13f81 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.qs.QSUserSwitcherEvent  import com.android.systemui.statusbar.policy.UserSwitcherController  import com.android.systemui.user.data.source.UserRecord  import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull  import org.junit.Before  import org.junit.Test  import org.junit.runner.RunWith @@ -139,6 +140,11 @@ class UserDetailViewAdapterTest : SysuiTestCase() {          clickableTest(false, false, mUserDetailItemView, true)      } +    @Test +    fun testManageUsersIsNotAvailable() { +        assertNull(adapter.users.find { it.isManageUsers }) +    } +      private fun createUserRecord(current: Boolean, guest: Boolean) =          UserRecord(              UserInfo(0 /* id */, "name", 0 /* flags */), diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt index 0151822f871c..14a3bc147808 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt @@ -659,6 +659,51 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {          verify(privacyIconsController, never()).onParentInvisible()      } +    @Test +    fun clockPivotYInCenter() { +        val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java) +        verify(clock).addOnLayoutChangeListener(capture(captor)) +        var height = 100 +        val width = 50 + +        clock.executeLayoutChange(0, 0, width, height, captor.value) +        verify(clock).pivotY = height.toFloat() / 2 + +        height = 150 +        clock.executeLayoutChange(0, 0, width, height, captor.value) +        verify(clock).pivotY = height.toFloat() / 2 +    } + +    private fun View.executeLayoutChange( +            left: Int, +            top: Int, +            right: Int, +            bottom: Int, +            listener: View.OnLayoutChangeListener +    ) { +        val oldLeft = this.left +        val oldTop = this.top +        val oldRight = this.right +        val oldBottom = this.bottom +        whenever(this.left).thenReturn(left) +        whenever(this.top).thenReturn(top) +        whenever(this.right).thenReturn(right) +        whenever(this.bottom).thenReturn(bottom) +        whenever(this.height).thenReturn(bottom - top) +        whenever(this.width).thenReturn(right - left) +        listener.onLayoutChange( +                this, +                oldLeft, +                oldTop, +                oldRight, +                oldBottom, +                left, +                top, +                right, +                bottom +        ) +    } +      private fun createWindowInsets(          topCutout: Rect? = Rect()      ): WindowInsets { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index c0dae03023c5..d095add1c660 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -33,11 +33,13 @@ 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.anyString;  import static org.mockito.ArgumentMatchers.eq;  import static org.mockito.Mockito.atLeast;  import static org.mockito.Mockito.atLeastOnce;  import static org.mockito.Mockito.clearInvocations;  import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder;  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.never;  import static org.mockito.Mockito.times; @@ -76,6 +78,7 @@ import com.android.internal.logging.UiEventLogger;  import com.android.internal.logging.testing.UiEventLoggerFake;  import com.android.internal.util.CollectionUtils;  import com.android.internal.util.LatencyTracker; +import com.android.keyguard.FaceAuthApiRequestReason;  import com.android.keyguard.KeyguardClockSwitch;  import com.android.keyguard.KeyguardClockSwitchController;  import com.android.keyguard.KeyguardStatusView; @@ -93,7 +96,6 @@ import com.android.systemui.biometrics.AuthController;  import com.android.systemui.camera.CameraGestureHelper;  import com.android.systemui.classifier.FalsingCollectorFake;  import com.android.systemui.classifier.FalsingManagerFake; -import com.android.systemui.controls.dagger.ControlsComponent;  import com.android.systemui.doze.DozeLog;  import com.android.systemui.dump.DumpManager;  import com.android.systemui.flags.FeatureFlags; @@ -109,7 +111,7 @@ import com.android.systemui.model.SysUiState;  import com.android.systemui.navigationbar.NavigationModeController;  import com.android.systemui.plugins.FalsingManager;  import com.android.systemui.plugins.qs.QS; -import com.android.systemui.qrcodescanner.controller.QRCodeScannerController; +import com.android.systemui.plugins.statusbar.StatusBarStateController;  import com.android.systemui.qs.QSFragment;  import com.android.systemui.screenrecord.RecordingController;  import com.android.systemui.shade.transition.ShadeTransitionController; @@ -165,7 +167,6 @@ import com.android.systemui.statusbar.window.StatusBarWindowStateController;  import com.android.systemui.unfold.SysUIUnfoldComponent;  import com.android.systemui.util.time.FakeSystemClock;  import com.android.systemui.util.time.SystemClock; -import com.android.systemui.wallet.controller.QuickAccessWalletController;  import com.android.wm.shell.animation.FlingAnimationUtils;  import org.junit.After; @@ -173,6 +174,8 @@ import org.junit.Before;  import org.junit.Test;  import org.junit.runner.RunWith;  import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder;  import org.mockito.Mock;  import org.mockito.MockitoAnnotations;  import org.mockito.stubbing.Answer; @@ -257,11 +260,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {      @Mock private KeyguardIndicationController mKeyguardIndicationController;      @Mock private FragmentService mFragmentService;      @Mock private FragmentHostManager mFragmentHostManager; -    @Mock private QuickAccessWalletController mQuickAccessWalletController; -    @Mock private QRCodeScannerController mQrCodeScannerController;      @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;      @Mock private RecordingController mRecordingController; -    @Mock private ControlsComponent mControlsComponent;      @Mock private LockscreenGestureLogger mLockscreenGestureLogger;      @Mock private DumpManager mDumpManager;      @Mock private InteractionJankMonitor mInteractionJankMonitor; @@ -282,6 +282,10 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {      @Mock private ViewTreeObserver mViewTreeObserver;      @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;      @Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; +    @Mock private MotionEvent mDownMotionEvent; +    @Captor +    private ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener> +            mEmptySpaceClickListenerCaptor;      private NotificationPanelViewController.TouchHandler mTouchHandler;      private ConfigurationController mConfigurationController; @@ -425,6 +429,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {          when(mView.getViewTreeObserver()).thenReturn(mViewTreeObserver);          when(mView.getParent()).thenReturn(mViewParent);          when(mQs.getHeader()).thenReturn(mQsHeader); +        when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN);          mMainHandler = new Handler(Looper.getMainLooper());          NotificationPanelViewController.PanelEventsEmitter panelEventsEmitter = @@ -512,6 +517,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {                  .addCallback(mNotificationPanelViewController.mStatusBarStateListener);          mNotificationPanelViewController                  .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class)); +        verify(mNotificationStackScrollLayoutController) +                .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture());      }      @After @@ -1540,6 +1547,103 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {          );      } +    @Test +    public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() { +        StatusBarStateController.StateListener statusBarStateListener = +                mNotificationPanelViewController.mStatusBarStateListener; +        statusBarStateListener.onStateChanged(KEYGUARD); +        mNotificationPanelViewController.setDozing(false, false); + +        // This sets the dozing state that is read when onMiddleClicked is eventually invoked. +        mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); +        mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); + +        verify(mUpdateMonitor).requestFaceAuth(true, +                FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED); +    } + +    @Test +    public void onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation() { +        StatusBarStateController.StateListener statusBarStateListener = +                mNotificationPanelViewController.mStatusBarStateListener; +        statusBarStateListener.onStateChanged(KEYGUARD); +        mNotificationPanelViewController.setDozing(false, false); +        when(mUpdateMonitor.isFaceDetectionRunning()).thenReturn(false); + +        // This sets the dozing state that is read when onMiddleClicked is eventually invoked. +        mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); +        mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); + +        verify(mNotificationStackScrollLayoutController).setUnlockHintRunning(true); +    } + +    @Test +    public void onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint() { +        StatusBarStateController.StateListener statusBarStateListener = +                mNotificationPanelViewController.mStatusBarStateListener; +        statusBarStateListener.onStateChanged(KEYGUARD); +        mNotificationPanelViewController.setDozing(false, false); +        when(mUpdateMonitor.isFaceDetectionRunning()).thenReturn(true); + +        // This sets the dozing state that is read when onMiddleClicked is eventually invoked. +        mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); +        mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); + +        verify(mNotificationStackScrollLayoutController, never()).setUnlockHintRunning(true); +    } + +    @Test +    public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() { +        StatusBarStateController.StateListener statusBarStateListener = +                mNotificationPanelViewController.mStatusBarStateListener; +        statusBarStateListener.onStateChanged(KEYGUARD); +        mNotificationPanelViewController.setDozing(true, false); + +        // This sets the dozing state that is read when onMiddleClicked is eventually invoked. +        mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); +        mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); + +        verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString()); +    } + +    @Test +    public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() { +        StatusBarStateController.StateListener statusBarStateListener = +                mNotificationPanelViewController.mStatusBarStateListener; +        statusBarStateListener.onStateChanged(SHADE_LOCKED); + +        mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); + +        verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString()); + +    } + +    /** +     * When shade is flinging to close and this fling is not intercepted, +     * {@link AmbientState#setIsClosing(boolean)} should be called before +     * {@link NotificationStackScrollLayoutController#onExpansionStopped()} +     * to ensure scrollY can be correctly set to be 0 +     */ +    @Test +    public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() { +        // Given: Shade is expanded +        mNotificationPanelViewController.notifyExpandingFinished(); +        mNotificationPanelViewController.setIsClosing(false); + +        // When: Shade flings to close not canceled +        mNotificationPanelViewController.notifyExpandingStarted(); +        mNotificationPanelViewController.setIsClosing(true); +        mNotificationPanelViewController.onFlingEnd(false); + +        // Then: AmbientState's mIsClosing should be set to false +        // before mNotificationStackScrollLayoutController.onExpansionStopped() is called +        // to ensure NotificationStackScrollLayout.resetScrollPosition() -> resetScrollPosition +        // -> setOwnScrollY(0) can set scrollY to 0 when shade is closed +        InOrder inOrder = inOrder(mAmbientState, mNotificationStackScrollLayoutController); +        inOrder.verify(mAmbientState).setIsClosing(false); +        inOrder.verify(mNotificationStackScrollLayoutController).onExpansionStopped(); +    } +      private static MotionEvent createMotionEvent(int x, int y, int action) {          return MotionEvent.obtain(                  /* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt index eb34561d15a0..cc45cf88fa18 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt @@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest  import com.android.systemui.R  import com.android.systemui.SysuiTestCase  import com.android.systemui.animation.TextAnimator +import com.android.systemui.util.mockito.any  import org.junit.Before  import org.junit.Rule  import org.junit.Test @@ -55,7 +56,7 @@ class AnimatableClockViewTest : SysuiTestCase() {          clockView.animateAppearOnLockscreen()          clockView.measure(50, 50) -        verify(mockTextAnimator).glyphFilter = null +        verify(mockTextAnimator).glyphFilter = any()          verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, false, 350L, null, 0L, null)          verifyNoMoreInteractions(mockTextAnimator)      } @@ -66,7 +67,7 @@ class AnimatableClockViewTest : SysuiTestCase() {          clockView.measure(50, 50)          clockView.animateAppearOnLockscreen() -        verify(mockTextAnimator, times(2)).glyphFilter = null +        verify(mockTextAnimator, times(2)).glyphFilter = any()          verify(mockTextAnimator).setTextStyle(100, -1.0f, 200, false, 0L, null, 0L, null)          verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, true, 350L, null, 0L, null)          verifyNoMoreInteractions(mockTextAnimator) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt index 5b34a95d4fb0..b761647e24e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt @@ -17,58 +17,58 @@ import org.mockito.MockitoAnnotations  @SmallTest  class UncaughtExceptionPreHandlerTest : SysuiTestCase() { -  private lateinit var preHandlerManager: UncaughtExceptionPreHandlerManager +    private lateinit var preHandlerManager: UncaughtExceptionPreHandlerManager -  @Mock private lateinit var mockHandler: UncaughtExceptionHandler +    @Mock private lateinit var mockHandler: UncaughtExceptionHandler -  @Mock private lateinit var mockHandler2: UncaughtExceptionHandler +    @Mock private lateinit var mockHandler2: UncaughtExceptionHandler -  @Before -  fun setUp() { -    MockitoAnnotations.initMocks(this) -    Thread.setUncaughtExceptionPreHandler(null) -    preHandlerManager = UncaughtExceptionPreHandlerManager() -  } +    @Before +    fun setUp() { +        MockitoAnnotations.initMocks(this) +        Thread.setUncaughtExceptionPreHandler(null) +        preHandlerManager = UncaughtExceptionPreHandlerManager() +    } -  @Test -  fun registerHandler_registersOnceOnly() { -    preHandlerManager.registerHandler(mockHandler) -    preHandlerManager.registerHandler(mockHandler) -    preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception()) -    verify(mockHandler, only()).uncaughtException(any(), any()) -  } +    @Test +    fun registerHandler_registersOnceOnly() { +        preHandlerManager.registerHandler(mockHandler) +        preHandlerManager.registerHandler(mockHandler) +        preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception()) +        verify(mockHandler, only()).uncaughtException(any(), any()) +    } -  @Test -  fun registerHandler_setsUncaughtExceptionPreHandler() { -    Thread.setUncaughtExceptionPreHandler(null) -    preHandlerManager.registerHandler(mockHandler) -    assertThat(Thread.getUncaughtExceptionPreHandler()).isNotNull() -  } +    @Test +    fun registerHandler_setsUncaughtExceptionPreHandler() { +        Thread.setUncaughtExceptionPreHandler(null) +        preHandlerManager.registerHandler(mockHandler) +        assertThat(Thread.getUncaughtExceptionPreHandler()).isNotNull() +    } -  @Test -  fun registerHandler_preservesOriginalHandler() { -    Thread.setUncaughtExceptionPreHandler(mockHandler) -    preHandlerManager.registerHandler(mockHandler2) -    preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception()) -    verify(mockHandler, only()).uncaughtException(any(), any()) -  } +    @Test +    fun registerHandler_preservesOriginalHandler() { +        Thread.setUncaughtExceptionPreHandler(mockHandler) +        preHandlerManager.registerHandler(mockHandler2) +        preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception()) +        verify(mockHandler, only()).uncaughtException(any(), any()) +    } -  @Test -  @Ignore -  fun registerHandler_toleratesHandlersThatThrow() { -    `when`(mockHandler2.uncaughtException(any(), any())).thenThrow(RuntimeException()) -    preHandlerManager.registerHandler(mockHandler2) -    preHandlerManager.registerHandler(mockHandler) -    preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception()) -    verify(mockHandler2, only()).uncaughtException(any(), any()) -    verify(mockHandler, only()).uncaughtException(any(), any()) -  } +    @Test +    @Ignore +    fun registerHandler_toleratesHandlersThatThrow() { +        `when`(mockHandler2.uncaughtException(any(), any())).thenThrow(RuntimeException()) +        preHandlerManager.registerHandler(mockHandler2) +        preHandlerManager.registerHandler(mockHandler) +        preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception()) +        verify(mockHandler2, only()).uncaughtException(any(), any()) +        verify(mockHandler, only()).uncaughtException(any(), any()) +    } -  @Test -  fun registerHandler_doesNotSetUpTwice() { -    UncaughtExceptionPreHandlerManager().registerHandler(mockHandler2) -    assertThrows(IllegalStateException::class.java) { -      preHandlerManager.registerHandler(mockHandler) +    @Test +    fun registerHandler_doesNotSetUpTwice() { +        UncaughtExceptionPreHandlerManager().registerHandler(mockHandler2) +        assertThrows(IllegalStateException::class.java) { +            preHandlerManager.registerHandler(mockHandler) +        }      } -  }  } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt index 8cb530c355bd..5fc0ffe42f55 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt @@ -4,7 +4,7 @@ import android.testing.AndroidTestingRunner  import android.util.DisplayMetrics  import androidx.test.filters.SmallTest  import com.android.systemui.SysuiTestCase -import com.android.systemui.log.LogBuffer +import com.android.systemui.plugins.log.LogBuffer  import com.android.systemui.statusbar.notification.row.ExpandableView  import com.android.systemui.statusbar.phone.LSShadeTransitionLogger  import com.android.systemui.statusbar.phone.LockscreenGestureLogger diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java index f8a0d2fc415c..9c65fac1af45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java @@ -70,7 +70,7 @@ import com.android.systemui.SysuiTestCase;  import com.android.systemui.broadcast.BroadcastDispatcher;  import com.android.systemui.demomode.DemoModeController;  import com.android.systemui.dump.DumpManager; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import com.android.systemui.statusbar.policy.DeviceProvisionedController;  import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;  import com.android.systemui.telephony.TelephonyListenerManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java index ed8a3e16cdd1..4bed4a19b3d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java @@ -38,7 +38,7 @@ import android.testing.TestableLooper.RunWithLooper;  import com.android.settingslib.mobile.TelephonyIcons;  import com.android.settingslib.net.DataUsageController;  import com.android.systemui.dump.DumpManager; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import com.android.systemui.statusbar.policy.DeviceProvisionedController;  import com.android.systemui.util.CarrierConfigTracker; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java index a76676e01c15..d5f5105036d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java @@ -43,7 +43,7 @@ import com.android.settingslib.mobile.TelephonyIcons;  import com.android.settingslib.net.DataUsageController;  import com.android.systemui.R;  import com.android.systemui.dump.DumpManager; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer;  import com.android.systemui.statusbar.policy.DeviceProvisionedController;  import com.android.systemui.util.CarrierConfigTracker; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 82e32b2fdc64..09f8a10f88c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -34,10 +34,12 @@ import static org.mockito.Mockito.atLeast;  import static org.mockito.Mockito.atLeastOnce;  import static org.mockito.Mockito.clearInvocations;  import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.never;  import static org.mockito.Mockito.spy;  import static org.mockito.Mockito.times;  import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when;  import static java.util.Arrays.asList;  import static java.util.Collections.singletonList; @@ -135,6 +137,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {      public void setUp() {          MockitoAnnotations.initMocks(this);          allowTestableLooperAsMainThread(); +        when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(true);          mListBuilder = new ShadeListBuilder(                  mDumpManager, @@ -1995,22 +1998,89 @@ public class ShadeListBuilderTest extends SysuiTestCase {      }      @Test +    public void testActiveOrdering_withLegacyStability() { +        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(false); +        assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); // no change +        assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG", true); // X +        assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG", true); // no change +        assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG", true); // Z and X +        assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG", true); // Z and X + gap +    } + +    @Test +    public void testStableOrdering_withLegacyStability() { +        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(false); +        mStabilityManager.setAllowEntryReordering(false); +        assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); // no change +        assertOrder("ABCDEFG", "ACDEFXBG", "XABCDEFG", false); // X +        assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG", false); // no change +        assertOrder("ABCDEFG", "ACDEFBXZG", "XZABCDEFG", false); // Z and X +        assertOrder("ABCDEFG", "AXCDEZFBG", "XZABCDEFG", false); // Z and X + gap +    } + +    @Test      public void testStableOrdering() { +        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true);          mStabilityManager.setAllowEntryReordering(false); -        assertOrder("ABCDEFG", "ACDEFXBG", "XABCDEFG"); // X -        assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG"); // no change -        assertOrder("ABCDEFG", "ACDEFBXZG", "XZABCDEFG"); // Z and X -        assertOrder("ABCDEFG", "AXCDEZFBG", "XZABCDEFG"); // Z and X + gap -        verify(mStabilityManager, times(4)).onEntryReorderSuppressed(); +        // No input or output +        assertOrder("", "", "", true); +        // Remove everything +        assertOrder("ABCDEFG", "", "", true); +        // Literally no changes +        assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); + +        // No stable order +        assertOrder("", "ABCDEFG", "ABCDEFG", true); + +        // F moved after A, and... +        assertOrder("ABCDEFG", "AFBCDEG", "ABCDEFG", false);   // No other changes +        assertOrder("ABCDEFG", "AXFBCDEG", "AXBCDEFG", false); // Insert X before F +        assertOrder("ABCDEFG", "AFXBCDEG", "AXBCDEFG", false); // Insert X after F +        assertOrder("ABCDEFG", "AFBCDEXG", "ABCDEFXG", false); // Insert X where F was + +        // B moved after F, and... +        assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG", false);   // No other changes +        assertOrder("ABCDEFG", "ACDEFXBG", "ABCDEFXG", false); // Insert X before B +        assertOrder("ABCDEFG", "ACDEFBXG", "ABCDEFXG", false); // Insert X after B +        assertOrder("ABCDEFG", "AXCDEFBG", "AXBCDEFG", false); // Insert X where B was + +        // Swap F and B, and... +        assertOrder("ABCDEFG", "AFCDEBG", "ABCDEFG", false);   // No other changes +        assertOrder("ABCDEFG", "AXFCDEBG", "AXBCDEFG", false); // Insert X before F +        assertOrder("ABCDEFG", "AFXCDEBG", "AXBCDEFG", false); // Insert X after F +        assertOrder("ABCDEFG", "AFCXDEBG", "AXBCDEFG", false); // Insert X between CD (or: ABCXDEFG) +        assertOrder("ABCDEFG", "AFCDXEBG", "ABCDXEFG", false); // Insert X between DE (or: ABCDEFXG) +        assertOrder("ABCDEFG", "AFCDEXBG", "ABCDEFXG", false); // Insert X before B +        assertOrder("ABCDEFG", "AFCDEBXG", "ABCDEFXG", false); // Insert X after B + +        // Remove a bunch of entries at once +        assertOrder("ABCDEFGHIJKL", "ACEGHI", "ACEGHI", true); + +        // Remove a bunch of entries and scramble +        assertOrder("ABCDEFGHIJKL", "GCEHAI", "ACEGHI", false); + +        // Add a bunch of entries at once +        assertOrder("ABCDEFG", "AVBWCXDYZEFG", "AVBWCXDYZEFG", true); + +        // Add a bunch of entries and reverse originals +        // NOTE: Some of these don't have obviously correct answers +        assertOrder("ABCDEFG", "GFEBCDAVWXYZ", "ABCDEFGVWXYZ", false); // appended +        assertOrder("ABCDEFG", "VWXYZGFEBCDA", "VWXYZABCDEFG", false); // prepended +        assertOrder("ABCDEFG", "GFEBVWXYZCDA", "ABCDEFGVWXYZ", false); // closer to back: append +        assertOrder("ABCDEFG", "GFEVWXYZBCDA", "VWXYZABCDEFG", false); // closer to front: prepend +        assertOrder("ABCDEFG", "GFEVWBXYZCDA", "VWABCDEFGXYZ", false); // split new entries + +        // Swap 2 pairs ("*BC*NO*"->"*NO*CB*"), remove EG, add UVWXYZ throughout +        assertOrder("ABCDEFGHIJKLMNOP", "AUNOVDFHWXIJKLMYCBZP", "AUVBCDFHWXIJKLMNOYZP", false);      }      @Test      public void testActiveOrdering() { -        assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG"); // X -        assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG"); // no change -        assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG"); // Z and X -        assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG"); // Z and X + gap -        verify(mStabilityManager, never()).onEntryReorderSuppressed(); +        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true); +        assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG", true); // X +        assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG", true); // no change +        assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG", true); // Z and X +        assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG", true); // Z and X + gap      }      @Test @@ -2062,6 +2132,52 @@ public class ShadeListBuilderTest extends SysuiTestCase {      }      @Test +    public void stableOrderingDisregardedWithSectionChange() { +        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true); +        // GIVEN the first sectioner's packages can be changed from run-to-run +        List<String> mutableSectionerPackages = new ArrayList<>(); +        mutableSectionerPackages.add(PACKAGE_1); +        mListBuilder.setSectioners(asList( +                new PackageSectioner(mutableSectionerPackages, null), +                new PackageSectioner(List.of(PACKAGE_1, PACKAGE_2, PACKAGE_3), null))); +        mStabilityManager.setAllowEntryReordering(false); + +        // WHEN the list is originally built with reordering disabled (and section changes allowed) +        addNotif(0, PACKAGE_1).setRank(4); +        addNotif(1, PACKAGE_1).setRank(5); +        addNotif(2, PACKAGE_2).setRank(1); +        addNotif(3, PACKAGE_2).setRank(2); +        addNotif(4, PACKAGE_3).setRank(3); +        dispatchBuild(); + +        // VERIFY the order and that entry reordering has not been suppressed +        verifyBuiltList( +                notif(0), +                notif(1), +                notif(2), +                notif(3), +                notif(4) +        ); +        verify(mStabilityManager, never()).onEntryReorderSuppressed(); + +        // WHEN the first section now claims PACKAGE_3 notifications +        mutableSectionerPackages.add(PACKAGE_3); +        dispatchBuild(); + +        // VERIFY the re-sectioned notification is inserted at #1 of the first section, which +        // is the correct position based on its rank, rather than #3 in the new section simply +        // because it was #3 in its previous section. +        verifyBuiltList( +                notif(4), +                notif(0), +                notif(1), +                notif(2), +                notif(3) +        ); +        verify(mStabilityManager, never()).onEntryReorderSuppressed(); +    } + +    @Test      public void testStableChildOrdering() {          // WHEN the list is originally built with reordering disabled          mStabilityManager.setAllowEntryReordering(false); @@ -2112,6 +2228,85 @@ public class ShadeListBuilderTest extends SysuiTestCase {          );      } +    @Test +    public void groupRevertingToSummaryDoesNotRetainStablePositionWithLegacyIndexLogic() { +        when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(false); + +        // GIVEN a notification group is on screen +        mStabilityManager.setAllowEntryReordering(false); + +        // WHEN the list is originally built with reordering disabled (and section changes allowed) +        addNotif(0, PACKAGE_1).setRank(2); +        addNotif(1, PACKAGE_1).setRank(3); +        addGroupSummary(2, PACKAGE_1, "group").setRank(4); +        addGroupChild(3, PACKAGE_1, "group").setRank(5); +        addGroupChild(4, PACKAGE_1, "group").setRank(6); +        dispatchBuild(); + +        verifyBuiltList( +                notif(0), +                notif(1), +                group( +                        summary(2), +                        child(3), +                        child(4) +                ) +        ); + +        // WHEN the notification summary rank increases and children removed +        setNewRank(notif(2).entry, 1); +        mEntrySet.remove(4); +        mEntrySet.remove(3); +        dispatchBuild(); + +        // VERIFY the summary (incorrectly) moves to the top of the section where it is ranked, +        // despite visual stability being active +        verifyBuiltList( +                notif(2), +                notif(0), +                notif(1) +        ); +    } + +    @Test +    public void groupRevertingToSummaryRetainsStablePosition() { +        when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(true); + +        // GIVEN a notification group is on screen +        mStabilityManager.setAllowEntryReordering(false); + +        // WHEN the list is originally built with reordering disabled (and section changes allowed) +        addNotif(0, PACKAGE_1).setRank(2); +        addNotif(1, PACKAGE_1).setRank(3); +        addGroupSummary(2, PACKAGE_1, "group").setRank(4); +        addGroupChild(3, PACKAGE_1, "group").setRank(5); +        addGroupChild(4, PACKAGE_1, "group").setRank(6); +        dispatchBuild(); + +        verifyBuiltList( +                notif(0), +                notif(1), +                group( +                        summary(2), +                        child(3), +                        child(4) +                ) +        ); + +        // WHEN the notification summary rank increases and children removed +        setNewRank(notif(2).entry, 1); +        mEntrySet.remove(4); +        mEntrySet.remove(3); +        dispatchBuild(); + +        // VERIFY the summary stays in the same location on rebuild +        verifyBuiltList( +                notif(0), +                notif(1), +                notif(2) +        ); +    } +      private static void setNewRank(NotificationEntry entry, int rank) {          entry.setRanking(new RankingBuilder(entry.getRanking()).setRank(rank).build());      } @@ -2255,26 +2450,35 @@ public class ShadeListBuilderTest extends SysuiTestCase {          return addGroupChildWithTag(index, packageId, groupId, null);      } -    private void assertOrder(String visible, String active, String expected) { +    private void assertOrder(String visible, String active, String expected, +            boolean isOrderedCorrectly) {          StringBuilder differenceSb = new StringBuilder(); +        NotifSection section = new NotifSection(mock(NotifSectioner.class), 0);          for (char c : active.toCharArray()) {              if (visible.indexOf(c) < 0) differenceSb.append(c);          }          String difference = differenceSb.toString(); +        int globalIndex = 0;          for (int i = 0; i < visible.length(); i++) { -            addNotif(i, String.valueOf(visible.charAt(i))) -                    .setRank(active.indexOf(visible.charAt(i))) +            final char c = visible.charAt(i); +            // Skip notifications which aren't active anymore +            if (!active.contains(String.valueOf(c))) continue; +            addNotif(globalIndex++, String.valueOf(c)) +                    .setRank(active.indexOf(c)) +                    .setSection(section)                      .setStableIndex(i); -          } -        for (int i = 0; i < difference.length(); i++) { -            addNotif(i + visible.length(), String.valueOf(difference.charAt(i))) -                    .setRank(active.indexOf(difference.charAt(i))) +        for (char c : difference.toCharArray()) { +            addNotif(globalIndex++, String.valueOf(c)) +                    .setRank(active.indexOf(c)) +                    .setSection(section)                      .setStableIndex(-1);          } +        clearInvocations(mStabilityManager); +          dispatchBuild();          StringBuilder resultSb = new StringBuilder();          for (int i = 0; i < expected.length(); i++) { @@ -2284,6 +2488,9 @@ public class ShadeListBuilderTest extends SysuiTestCase {          assertEquals("visible [" + visible + "] active [" + active + "]",                  expected, resultSb.toString());          mEntrySet.clear(); + +        verify(mStabilityManager, isOrderedCorrectly ? never() : times(1)) +                .onEntryReorderSuppressed();      }      private int nextId(String packageName) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index dcf245525f10..f4adf6927e31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -181,7 +181,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {      @Test      public void testInflatesNewNotification() {          // WHEN there is a new notification -        mCollectionListener.onEntryInit(mEntry); +        mCollectionListener.onEntryAdded(mEntry);          mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));          // THEN we inflate it @@ -194,7 +194,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {      @Test      public void testRebindsInflatedNotificationsOnUpdate() {          // GIVEN an inflated notification -        mCollectionListener.onEntryInit(mEntry); +        mCollectionListener.onEntryAdded(mEntry);          mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));          verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());          mNotifInflater.invokeInflateCallbackForEntry(mEntry); @@ -213,7 +213,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {      @Test      public void testEntrySmartReplyAdditionWillRebindViews() {          // GIVEN an inflated notification -        mCollectionListener.onEntryInit(mEntry); +        mCollectionListener.onEntryAdded(mEntry);          mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));          verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());          mNotifInflater.invokeInflateCallbackForEntry(mEntry); @@ -232,7 +232,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {      @Test      public void testEntryChangedToMinimizedSectionWillRebindViews() {          // GIVEN an inflated notification -        mCollectionListener.onEntryInit(mEntry); +        mCollectionListener.onEntryAdded(mEntry);          mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));          verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());          assertFalse(mParamsCaptor.getValue().isLowPriority()); @@ -254,36 +254,28 @@ public class PreparationCoordinatorTest extends SysuiTestCase {      public void testMinimizedEntryMovedIntoGroupWillRebindViews() {          // GIVEN an inflated, minimized notification          setSectionIsLowPriority(true); -        mCollectionListener.onEntryInit(mEntry); +        mCollectionListener.onEntryAdded(mEntry);          mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));          verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());          assertTrue(mParamsCaptor.getValue().isLowPriority());          mNotifInflater.invokeInflateCallbackForEntry(mEntry);          // WHEN notification is moved under a parent -        NotificationEntry groupSummary = getNotificationEntryBuilder() -                .setParent(ROOT_ENTRY) -                .setGroupSummary(mContext, true) -                .setGroup(mContext, TEST_GROUP_KEY) -                .build(); -        GroupEntry parent = mock(GroupEntry.class); -        when(parent.getSummary()).thenReturn(groupSummary); -        NotificationEntryBuilder.setNewParent(mEntry, parent); -        mCollectionListener.onEntryInit(groupSummary); -        mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry, groupSummary)); +        NotificationEntryBuilder.setNewParent(mEntry, mock(GroupEntry.class)); +        mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));          // THEN we rebind it as not-minimized          verify(mNotifInflater).rebindViews(eq(mEntry), mParamsCaptor.capture(), any());          assertFalse(mParamsCaptor.getValue().isLowPriority()); -        // THEN we filter it because the parent summary is not yet inflated. -        assertTrue(mUninflatedFilter.shouldFilterOut(mEntry, 0)); +        // THEN we do not filter it because it's not the first inflation. +        assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0));      }      @Test      public void testEntryRankChangeWillNotRebindViews() {          // GIVEN an inflated notification -        mCollectionListener.onEntryInit(mEntry); +        mCollectionListener.onEntryAdded(mEntry);          mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));          verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());          mNotifInflater.invokeInflateCallbackForEntry(mEntry); @@ -302,7 +294,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {      @Test      public void testDoesntFilterInflatedNotifs() {          // GIVEN an inflated notification -        mCollectionListener.onEntryInit(mEntry); +        mCollectionListener.onEntryAdded(mEntry);          mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));          verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());          mNotifInflater.invokeInflateCallbackForEntry(mEntry); @@ -338,9 +330,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase {              mCollectionListener.onEntryInit(entry);          } -        mCollectionListener.onEntryInit(summary); +        mCollectionListener.onEntryAdded(summary);          for (NotificationEntry entry : children) { -            mCollectionListener.onEntryInit(entry); +            mCollectionListener.onEntryAdded(entry);          }          mBeforeFilterListener.onBeforeFinalizeFilter(List.of(groupEntry)); @@ -401,40 +393,6 @@ public class PreparationCoordinatorTest extends SysuiTestCase {      }      @Test -    public void testPartiallyInflatedGroupsAreNotFilteredOutIfSummaryReinflate() { -        // GIVEN a newly-posted group with a summary and two children -        final String groupKey = "test_reinflate_group"; -        final int summaryId = 1; -        final GroupEntry group = new GroupEntryBuilder() -                .setKey(groupKey) -                .setCreationTime(400) -                .setSummary(getNotificationEntryBuilder().setId(summaryId).setImportance(1).build()) -                .addChild(getNotificationEntryBuilder().setId(2).build()) -                .addChild(getNotificationEntryBuilder().setId(3).build()) -                .build(); -        fireAddEvents(List.of(group)); -        final NotificationEntry summary = group.getSummary(); -        final NotificationEntry child0 = group.getChildren().get(0); -        final NotificationEntry child1 = group.getChildren().get(1); -        mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group)); - -        // WHEN all of the children (but not the summary) finish inflating -        mNotifInflater.invokeInflateCallbackForEntry(child0); -        mNotifInflater.invokeInflateCallbackForEntry(child1); -        mNotifInflater.invokeInflateCallbackForEntry(summary); - -        // WHEN the summary is updated and starts re-inflating -        summary.setRanking(new RankingBuilder(summary.getRanking()).setImportance(4).build()); -        fireUpdateEvents(summary); -        mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group)); - -        // THEN the entire group is still not filtered out -        assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401)); -        assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401)); -        assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401)); -    } - -    @Test      public void testCompletedInflatedGroupsAreReleased() {          // GIVEN a newly-posted group with a summary and two children          final GroupEntry group = new GroupEntryBuilder() @@ -454,7 +412,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {          mNotifInflater.invokeInflateCallbackForEntry(child1);          mNotifInflater.invokeInflateCallbackForEntry(summary); -        // THEN the entire group is no longer filtered out +        // THEN the entire group is still filtered out          assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401));          assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));          assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401)); @@ -536,11 +494,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {      private void fireAddEvents(NotificationEntry entry) {          mCollectionListener.onEntryInit(entry); -        mCollectionListener.onEntryInit(entry); -    } - -    private void fireUpdateEvents(NotificationEntry entry) { -        mCollectionListener.onEntryUpdated(entry); +        mCollectionListener.onEntryAdded(entry);      }      private static final String TEST_MESSAGE = "TEST_MESSAGE"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt new file mode 100644 index 000000000000..1cdd023dd01c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.listbuilder + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.util.Log +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class SemiStableSortTest : SysuiTestCase() { + +    var shuffleInput: Boolean = false +    var testStabilizeTo: Boolean = false +    var sorter: SemiStableSort? = null + +    @Before +    fun setUp() { +        shuffleInput = false +        sorter = null +    } + +    private fun stringStabilizeTo( +        stableOrder: String, +        activeOrder: String, +    ): Pair<String, Boolean> { +        val actives = activeOrder.toMutableList() +        val result = mutableListOf<Char>() +        return (sorter ?: SemiStableSort()) +            .stabilizeTo( +                actives, +                { ch -> stableOrder.indexOf(ch).takeIf { it >= 0 } }, +                result, +            ) +            .let { ordered -> result.joinToString("") to ordered } +    } + +    private fun stringSort( +        stableOrder: String, +        activeOrder: String, +    ): Pair<String, Boolean> { +        val actives = activeOrder.toMutableList() +        if (shuffleInput) { +            actives.shuffle() +        } +        return (sorter ?: SemiStableSort()) +            .sort( +                actives, +                { ch -> stableOrder.indexOf(ch).takeIf { it >= 0 } }, +                compareBy { activeOrder.indexOf(it) }, +            ) +            .let { ordered -> actives.joinToString("") to ordered } +    } + +    private fun testCase( +        stableOrder: String, +        activeOrder: String, +        expected: String, +        expectOrdered: Boolean, +    ) { +        val (mergeResult, ordered) = +            if (testStabilizeTo) stringStabilizeTo(stableOrder, activeOrder) +            else stringSort(stableOrder, activeOrder) +        val resultPass = expected == mergeResult +        val orderedPass = ordered == expectOrdered +        val pass = resultPass && orderedPass +        val resultSuffix = +            if (resultPass) "result=$expected" else "expected=$expected got=$mergeResult" +        val orderedSuffix = +            if (orderedPass) "ordered=$ordered" else "expected ordered to be $expectOrdered" +        val readableResult = "stable=$stableOrder active=$activeOrder $resultSuffix $orderedSuffix" +        Log.d("SemiStableSortTest", "${if (pass) "PASS" else "FAIL"}: $readableResult") +        if (!pass) { +            throw AssertionError("Test case failed: $readableResult") +        } +    } + +    private fun runAllTestCases() { +        // No input or output +        testCase("", "", "", true) +        // Remove everything +        testCase("ABCDEFG", "", "", true) +        // Literally no changes +        testCase("ABCDEFG", "ABCDEFG", "ABCDEFG", true) + +        // No stable order +        testCase("", "ABCDEFG", "ABCDEFG", true) + +        // F moved after A, and... +        testCase("ABCDEFG", "AFBCDEG", "ABCDEFG", false) // No other changes +        testCase("ABCDEFG", "AXFBCDEG", "AXBCDEFG", false) // Insert X before F +        testCase("ABCDEFG", "AFXBCDEG", "AXBCDEFG", false) // Insert X after F +        testCase("ABCDEFG", "AFBCDEXG", "ABCDEFXG", false) // Insert X where F was + +        // B moved after F, and... +        testCase("ABCDEFG", "ACDEFBG", "ABCDEFG", false) // No other changes +        testCase("ABCDEFG", "ACDEFXBG", "ABCDEFXG", false) // Insert X before B +        testCase("ABCDEFG", "ACDEFBXG", "ABCDEFXG", false) // Insert X after B +        testCase("ABCDEFG", "AXCDEFBG", "AXBCDEFG", false) // Insert X where B was + +        // Swap F and B, and... +        testCase("ABCDEFG", "AFCDEBG", "ABCDEFG", false) // No other changes +        testCase("ABCDEFG", "AXFCDEBG", "AXBCDEFG", false) // Insert X before F +        testCase("ABCDEFG", "AFXCDEBG", "AXBCDEFG", false) // Insert X after F +        testCase("ABCDEFG", "AFCXDEBG", "AXBCDEFG", false) // Insert X between CD (Alt: ABCXDEFG) +        testCase("ABCDEFG", "AFCDXEBG", "ABCDXEFG", false) // Insert X between DE (Alt: ABCDEFXG) +        testCase("ABCDEFG", "AFCDEXBG", "ABCDEFXG", false) // Insert X before B +        testCase("ABCDEFG", "AFCDEBXG", "ABCDEFXG", false) // Insert X after B + +        // Remove a bunch of entries at once +        testCase("ABCDEFGHIJKL", "ACEGHI", "ACEGHI", true) + +        // Remove a bunch of entries and scramble +        testCase("ABCDEFGHIJKL", "GCEHAI", "ACEGHI", false) + +        // Add a bunch of entries at once +        testCase("ABCDEFG", "AVBWCXDYZEFG", "AVBWCXDYZEFG", true) + +        // Add a bunch of entries and reverse originals +        // NOTE: Some of these don't have obviously correct answers +        testCase("ABCDEFG", "GFEBCDAVWXYZ", "ABCDEFGVWXYZ", false) // appended +        testCase("ABCDEFG", "VWXYZGFEBCDA", "VWXYZABCDEFG", false) // prepended +        testCase("ABCDEFG", "GFEBVWXYZCDA", "ABCDEFGVWXYZ", false) // closer to back: append +        testCase("ABCDEFG", "GFEVWXYZBCDA", "VWXYZABCDEFG", false) // closer to front: prepend +        testCase("ABCDEFG", "GFEVWBXYZCDA", "VWABCDEFGXYZ", false) // split new entries + +        // Swap 2 pairs ("*BC*NO*"->"*NO*CB*"), remove EG, add UVWXYZ throughout +        testCase("ABCDEFGHIJKLMNOP", "AUNOVDFHWXIJKLMYCBZP", "AUVBCDFHWXIJKLMNOYZP", false) +    } + +    @Test +    fun testSort() { +        testStabilizeTo = false +        shuffleInput = false +        sorter = null +        runAllTestCases() +    } + +    @Test +    fun testSortWithSingleInstance() { +        testStabilizeTo = false +        shuffleInput = false +        sorter = SemiStableSort() +        runAllTestCases() +    } + +    @Test +    fun testSortWithShuffledInput() { +        testStabilizeTo = false +        shuffleInput = true +        sorter = null +        runAllTestCases() +    } + +    @Test +    fun testStabilizeTo() { +        testStabilizeTo = true +        sorter = null +        runAllTestCases() +    } + +    @Test +    fun testStabilizeToWithSingleInstance() { +        testStabilizeTo = true +        sorter = SemiStableSort() +        runAllTestCases() +    } + +    @Test +    fun testIsSorted() { +        val intCmp = Comparator<Int> { x, y -> Integer.compare(x, y) } +        SemiStableSort.apply { +            assertTrue(emptyList<Int>().isSorted(intCmp)) +            assertTrue(listOf(1).isSorted(intCmp)) +            assertTrue(listOf(1, 2).isSorted(intCmp)) +            assertTrue(listOf(1, 2, 3).isSorted(intCmp)) +            assertTrue(listOf(1, 2, 3, 4).isSorted(intCmp)) +            assertTrue(listOf(1, 2, 3, 4, 5).isSorted(intCmp)) +            assertTrue(listOf(1, 1, 1, 1, 1).isSorted(intCmp)) +            assertTrue(listOf(1, 1, 2, 2, 3, 3).isSorted(intCmp)) +            assertFalse(listOf(2, 1).isSorted(intCmp)) +            assertFalse(listOf(2, 1, 2).isSorted(intCmp)) +            assertFalse(listOf(1, 2, 1).isSorted(intCmp)) +            assertFalse(listOf(1, 2, 3, 2, 5).isSorted(intCmp)) +            assertFalse(listOf(5, 2, 3, 4, 5).isSorted(intCmp)) +            assertFalse(listOf(1, 2, 3, 4, 1).isSorted(intCmp)) +        } +    } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt new file mode 100644 index 000000000000..20369546d68a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.listbuilder + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper.getContiguousSubLists +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class ShadeListBuilderHelperTest : SysuiTestCase() { + +    @Test +    fun testGetContiguousSubLists() { +        assertThat(getContiguousSubLists("AAAAAA".toList()) { it }) +            .containsExactly( +                listOf('A', 'A', 'A', 'A', 'A', 'A'), +            ) +            .inOrder() +        assertThat(getContiguousSubLists("AAABBB".toList()) { it }) +            .containsExactly( +                listOf('A', 'A', 'A'), +                listOf('B', 'B', 'B'), +            ) +            .inOrder() +        assertThat(getContiguousSubLists("AAABAA".toList()) { it }) +            .containsExactly( +                listOf('A', 'A', 'A'), +                listOf('B'), +                listOf('A', 'A'), +            ) +            .inOrder() +        assertThat(getContiguousSubLists("AAABAA".toList(), minLength = 2) { it }) +            .containsExactly( +                listOf('A', 'A', 'A'), +                listOf('A', 'A'), +            ) +            .inOrder() +        assertThat(getContiguousSubLists("AAABBBBCCDEEE".toList()) { it }) +            .containsExactly( +                listOf('A', 'A', 'A'), +                listOf('B', 'B', 'B', 'B'), +                listOf('C', 'C'), +                listOf('D'), +                listOf('E', 'E', 'E'), +            ) +            .inOrder() +        assertThat(getContiguousSubLists("AAABBBBCCDEEE".toList(), minLength = 2) { it }) +            .containsExactly( +                listOf('A', 'A', 'A'), +                listOf('B', 'B', 'B', 'B'), +                listOf('C', 'C'), +                listOf('E', 'E', 'E'), +            ) +            .inOrder() +    } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt index 11798a7a4f96..87f4c323b7cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt @@ -361,6 +361,22 @@ class AmbientStateTest : SysuiTestCase() {          assertThat(sut.isOnKeyguard).isFalse()      }      // endregion + +    // region mIsClosing +    @Test +    fun isClosing_whenShadeClosing_shouldReturnTrue() { +        sut.setIsClosing(true) + +        assertThat(sut.isClosing).isTrue() +    } + +    @Test +    fun isClosing_whenShadeFinishClosing_shouldReturnFalse() { +        sut.setIsClosing(false) + +        assertThat(sut.isClosing).isFalse() +    } +    // endregion  }  // region Arrange helper methods. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 43530365360b..35c8b61b6383 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -728,6 +728,57 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {          verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat());      } +    @Test +    public void testSetOwnScrollY_shadeNotClosing_scrollYChanges() { +        // Given: shade is not closing, scrollY is 0 +        mAmbientState.setScrollY(0); +        assertEquals(0, mAmbientState.getScrollY()); +        mAmbientState.setIsClosing(false); + +        // When: call NotificationStackScrollLayout.setOwnScrollY to set scrollY to 1 +        mStackScroller.setOwnScrollY(1); + +        // Then: scrollY should be set to 1 +        assertEquals(1, mAmbientState.getScrollY()); + +        // Reset scrollY back to 0 to avoid interfering with other tests +        mStackScroller.setOwnScrollY(0); +        assertEquals(0, mAmbientState.getScrollY()); +    } + +    @Test +    public void testSetOwnScrollY_shadeClosing_scrollYDoesNotChange() { +        // Given: shade is closing, scrollY is 0 +        mAmbientState.setScrollY(0); +        assertEquals(0, mAmbientState.getScrollY()); +        mAmbientState.setIsClosing(true); + +        // When: call NotificationStackScrollLayout.setOwnScrollY to set scrollY to 1 +        mStackScroller.setOwnScrollY(1); + +        // Then: scrollY should not change, it should still be 0 +        assertEquals(0, mAmbientState.getScrollY()); + +        // Reset scrollY and mAmbientState.mIsClosing to avoid interfering with other tests +        mAmbientState.setIsClosing(false); +        mStackScroller.setOwnScrollY(0); +        assertEquals(0, mAmbientState.getScrollY()); +    } + +    @Test +    public void onShadeFlingClosingEnd_scrollYShouldBeSetToZero() { +        // Given: mAmbientState.mIsClosing is set to be true +        // mIsExpanded is set to be false +        mAmbientState.setIsClosing(true); +        mStackScroller.setIsExpanded(false); + +        // When: onExpansionStopped is called +        mStackScroller.onExpansionStopped(); + +        // Then: mAmbientState.scrollY should be set to be 0 +        assertEquals(mAmbientState.getScrollY(), 0); +    } +      private void setBarStateForTest(int state) {          // Can't inject this through the listener or we end up on the actual implementation          // rather than the mock because the spy just coppied the anonymous inner /shruggie. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt index 65e2964ea332..3a0a94ddd511 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt @@ -20,7 +20,7 @@ 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.log.LogcatEchoTracker +import com.android.systemui.plugins.log.LogcatEchoTracker  import com.android.systemui.statusbar.disableflags.DisableFlagsLogger  import com.google.common.truth.Truth.assertThat  import java.io.PrintWriter diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 3a006adb1933..36e76f47ca4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -49,9 +49,9 @@ import com.android.systemui.R;  import com.android.systemui.SysuiBaseFragmentTest;  import com.android.systemui.dump.DumpManager;  import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.log.LogBuffer; -import com.android.systemui.log.LogcatEchoTracker;  import com.android.systemui.plugins.DarkIconDispatcher; +import com.android.systemui.plugins.log.LogBuffer; +import com.android.systemui.plugins.log.LogcatEchoTracker;  import com.android.systemui.plugins.statusbar.StatusBarStateController;  import com.android.systemui.shade.NotificationPanelViewController;  import com.android.systemui.shade.ShadeExpansionStateManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt index 0e75c74ef6f5..b32058fca109 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt @@ -22,7 +22,7 @@ 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.log.LogcatEchoTracker +import com.android.systemui.plugins.log.LogcatEchoTracker  import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange  import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange  import com.google.common.truth.Truth.assertThat diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt index c9f2b4db81ef..13e9f608158e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt @@ -19,9 +19,9 @@ package com.android.systemui.temporarydisplay  import androidx.test.filters.SmallTest  import com.android.systemui.SysuiTestCase  import com.android.systemui.dump.DumpManager -import com.android.systemui.log.LogBuffer  import com.android.systemui.log.LogBufferFactory -import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogcatEchoTracker  import com.google.common.truth.Truth.assertThat  import java.io.PrintWriter  import java.io.StringWriter diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt index d4b41c18e123..a363a037c499 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt @@ -97,6 +97,7 @@ class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {                          createUserRecord(2),                          createActionRecord(UserActionModel.ADD_SUPERVISED_USER),                          createActionRecord(UserActionModel.ENTER_GUEST_MODE), +                        createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),                      )                  )              var models: List<UserModel>? = null @@ -176,15 +177,17 @@ class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {                          createUserRecord(2),                          createActionRecord(UserActionModel.ADD_SUPERVISED_USER),                          createActionRecord(UserActionModel.ENTER_GUEST_MODE), +                        createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),                      )                  )              var models: List<UserActionModel>? = null              val job = underTest.actions.onEach { models = it }.launchIn(this) -            assertThat(models).hasSize(3) +            assertThat(models).hasSize(4)              assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)              assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)              assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE) +            assertThat(models?.get(3)).isEqualTo(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)              job.cancel()          } @@ -200,6 +203,7 @@ class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {              isAddUser = action == UserActionModel.ADD_USER,              isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,              isGuest = action == UserActionModel.ENTER_GUEST_MODE, +            isManageUsers = action == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,          )      }  } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt index c3a9705bf6ba..6a17c8ddc63d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt @@ -64,13 +64,7 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() {      @Test      fun `actions - not actionable when locked and not locked`() =          runBlocking(IMMEDIATE) { -            userRepository.setActions( -                listOf( -                    UserActionModel.ENTER_GUEST_MODE, -                    UserActionModel.ADD_USER, -                    UserActionModel.ADD_SUPERVISED_USER, -                ) -            ) +            setActions()              userRepository.setActionableWhenLocked(false)              keyguardRepository.setKeyguardShowing(false) @@ -92,13 +86,7 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() {      @Test      fun `actions - actionable when locked and not locked`() =          runBlocking(IMMEDIATE) { -            userRepository.setActions( -                listOf( -                    UserActionModel.ENTER_GUEST_MODE, -                    UserActionModel.ADD_USER, -                    UserActionModel.ADD_SUPERVISED_USER, -                ) -            ) +            setActions()              userRepository.setActionableWhenLocked(true)              keyguardRepository.setKeyguardShowing(false) @@ -120,13 +108,7 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() {      @Test      fun `actions - actionable when locked and locked`() =          runBlocking(IMMEDIATE) { -            userRepository.setActions( -                listOf( -                    UserActionModel.ENTER_GUEST_MODE, -                    UserActionModel.ADD_USER, -                    UserActionModel.ADD_SUPERVISED_USER, -                ) -            ) +            setActions()              userRepository.setActionableWhenLocked(true)              keyguardRepository.setKeyguardShowing(true) @@ -182,6 +164,10 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() {          verify(activityStarter).startActivity(any(), anyBoolean())      } +    private fun setActions() { +        userRepository.setActions(UserActionModel.values().toList()) +    } +      companion object {          private val IMMEDIATE = Dispatchers.Main.immediate      } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index 0344e3f991e2..c12a868dbaed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -268,6 +268,26 @@ class UserSwitcherViewModelTest : SysuiTestCase() {          }      @Test +    fun `menu actions`() = +        runBlocking(IMMEDIATE) { +            userRepository.setActions(UserActionModel.values().toList()) +            var actions: List<UserActionViewModel>? = null +            val job = underTest.menu.onEach { actions = it }.launchIn(this) + +            assertThat(actions?.map { it.viewKey }) +                .isEqualTo( +                    listOf( +                        UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(), +                        UserActionModel.ADD_USER.ordinal.toLong(), +                        UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(), +                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(), +                    ) +                ) + +            job.cancel() +        } + +    @Test      fun `isFinishRequested - finishes when user is switched`() =          runBlocking(IMMEDIATE) {              setUsers(count = 2) diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt deleted file mode 100644 index 5e09b81da4e8..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - *      http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util.collection - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertSame -import org.junit.Assert.assertThrows -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class RingBufferTest : SysuiTestCase() { - -    private val buffer = RingBuffer(5) { TestElement() } - -    private val history = mutableListOf<TestElement>() - -    @Before -    fun setUp() { -        MockitoAnnotations.initMocks(this) -    } - -    @Test -    fun testBarelyFillBuffer() { -        fillBuffer(5) - -        assertEquals(0, buffer[0].id) -        assertEquals(1, buffer[1].id) -        assertEquals(2, buffer[2].id) -        assertEquals(3, buffer[3].id) -        assertEquals(4, buffer[4].id) -    } - -    @Test -    fun testPartiallyFillBuffer() { -        fillBuffer(3) - -        assertEquals(3, buffer.size) - -        assertEquals(0, buffer[0].id) -        assertEquals(1, buffer[1].id) -        assertEquals(2, buffer[2].id) - -        assertThrows(IndexOutOfBoundsException::class.java) { buffer[3] } -        assertThrows(IndexOutOfBoundsException::class.java) { buffer[4] } -    } - -    @Test -    fun testSpinBuffer() { -        fillBuffer(277) - -        assertEquals(272, buffer[0].id) -        assertEquals(273, buffer[1].id) -        assertEquals(274, buffer[2].id) -        assertEquals(275, buffer[3].id) -        assertEquals(276, buffer[4].id) -        assertThrows(IndexOutOfBoundsException::class.java) { buffer[5] } - -        assertEquals(5, buffer.size) -    } - -    @Test -    fun testElementsAreRecycled() { -        fillBuffer(23) - -        assertSame(history[4], buffer[1]) -        assertSame(history[9], buffer[1]) -        assertSame(history[14], buffer[1]) -        assertSame(history[19], buffer[1]) -    } - -    @Test -    fun testIterator() { -        fillBuffer(13) - -        val iterator = buffer.iterator() - -        for (i in 0 until 5) { -            assertEquals(history[8 + i], iterator.next()) -        } -        assertFalse(iterator.hasNext()) -        assertThrows(NoSuchElementException::class.java) { iterator.next() } -    } - -    @Test -    fun testForEach() { -        fillBuffer(13) -        var i = 8 - -        buffer.forEach { -            assertEquals(history[i], it) -            i++ -        } -        assertEquals(13, i) -    } - -    private fun fillBuffer(count: Int) { -        for (i in 0 until count) { -            val elem = buffer.advance() -            elem.id = history.size -            history.add(elem) -        } -    } -} - -private class TestElement(var id: Int = 0) { -    override fun toString(): String { -        return "{TestElement $id}" -    } -}
\ No newline at end of file diff --git a/services/core/Android.bp b/services/core/Android.bp index 3aed1678df58..553146d0448d 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -156,6 +156,8 @@ java_library_static {          "android.hardware.health-translate-java",          "android.hardware.light-V1-java",          "android.hardware.tv.cec-V1.1-java", +        "android.hardware.tv.cec-V1-java", +        "android.hardware.tv.hdmi-V1-java",          "android.hardware.weaver-V1.0-java",          "android.hardware.biometrics.face-V1.0-java",          "android.hardware.biometrics.fingerprint-V2.3-java", diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 202f47759272..5d46de335781 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -152,6 +152,8 @@ final class UiModeManagerService extends SystemService {      // flag set by resource, whether to start dream immediately upon docking even if unlocked.      private boolean mStartDreamImmediatelyOnDock = true; +    // flag set by resource, whether to disable dreams when ambient mode suppression is enabled. +    private boolean mDreamsDisabledByAmbientModeSuppression = false;      // flag set by resource, whether to enable Car dock launch when starting car mode.      private boolean mEnableCarDockLaunch = true;      // flag set by resource, whether to lock UI mode to the default one or not. @@ -364,6 +366,11 @@ final class UiModeManagerService extends SystemService {          mStartDreamImmediatelyOnDock = startDreamImmediatelyOnDock;      } +    @VisibleForTesting +    void setDreamsDisabledByAmbientModeSuppression(boolean disabledByAmbientModeSuppression) { +        mDreamsDisabledByAmbientModeSuppression = disabledByAmbientModeSuppression; +    } +      @Override      public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {          mCurrentUser = to.getUserIdentifier(); @@ -424,6 +431,8 @@ final class UiModeManagerService extends SystemService {          final Resources res = context.getResources();          mStartDreamImmediatelyOnDock = res.getBoolean(                  com.android.internal.R.bool.config_startDreamImmediatelyOnDock); +        mDreamsDisabledByAmbientModeSuppression = res.getBoolean( +                com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);          mNightMode = res.getInteger(                  com.android.internal.R.integer.config_defaultNightMode);          mDefaultUiModeType = res.getInteger( @@ -1827,10 +1836,14 @@ final class UiModeManagerService extends SystemService {          // Send the new configuration.          applyConfigurationExternallyLocked(); +        final boolean dreamsSuppressed = mDreamsDisabledByAmbientModeSuppression +                && mLocalPowerManager.isAmbientDisplaySuppressed(); +          // If we did not start a dock app, then start dreaming if appropriate. -        if (category != null && !dockAppStarted && (mStartDreamImmediatelyOnDock -                || mWindowManager.isKeyguardShowingAndNotOccluded() -                || !mPowerManager.isInteractive())) { +        if (category != null && !dockAppStarted && !dreamsSuppressed && ( +                mStartDreamImmediatelyOnDock +                        || mWindowManager.isKeyguardShowingAndNotOccluded() +                        || !mPowerManager.isInteractive())) {              mInjector.startDreamWhenDockedIfAppropriate(getContext());          }      } diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 1e1ebeba5c23..e421c61a2bd6 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -44,6 +44,7 @@ import android.annotation.Nullable;  import android.annotation.UptimeMillisLong;  import android.app.Activity;  import android.app.ActivityManager; +import android.app.BroadcastOptions;  import android.app.IApplicationThread;  import android.app.RemoteServiceException.CannotDeliverBroadcastException;  import android.app.UidObserver; @@ -534,6 +535,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue {              }, mBroadcastConsumerSkipAndCanceled, true);          } +        final int policy = (r.options != null) +                ? r.options.getDeliveryGroupPolicy() : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL; +        if (policy == BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) { +            forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> { +                // We only allow caller to remove broadcasts they enqueued +                return (r.callingUid == testRecord.callingUid) +                        && (r.userId == testRecord.userId) +                        && r.matchesDeliveryGroup(testRecord); +            }, mBroadcastConsumerSkipAndCanceled, true); +        } +          if (r.isReplacePending()) {              // Leave the skipped broadcasts intact in queue, so that we can              // replace them at their current position during enqueue below diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 2d825955ed6c..4f640033d1a4 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -796,6 +796,16 @@ final class BroadcastRecord extends Binder {          }      } +    public boolean matchesDeliveryGroup(@NonNull BroadcastRecord other) { +        final String key = (options != null) ? options.getDeliveryGroupKey() : null; +        final String otherKey = (other.options != null) +                ? other.options.getDeliveryGroupKey() : null; +        if (key == null && otherKey == null) { +            return intent.filterEquals(other.intent); +        } +        return Objects.equals(key, otherKey); +    } +      @Override      public String toString() {          if (mCachedToString == null) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 216a48ec699c..3fa41c0f0420 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -16,7 +16,6 @@  package com.android.server.am; -import static android.Manifest.permission.CREATE_USERS;  import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;  import static android.Manifest.permission.INTERACT_ACROSS_USERS;  import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; @@ -1482,7 +1481,7 @@ class UserController implements Handler.Callback {      // defined      boolean startUserOnSecondaryDisplay(@UserIdInt int userId, int displayId) {          checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay", -                MANAGE_USERS, CREATE_USERS); +                MANAGE_USERS, INTERACT_ACROSS_USERS);          // DEFAULT_DISPLAY is used for the current foreground user only          Preconditions.checkArgument(displayId != Display.DEFAULT_DISPLAY, diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java index dec1b559556a..5bc9d2341535 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java @@ -54,7 +54,7 @@ public class AuthSessionCoordinator implements AuthSessionListener {      private AuthResultCoordinator mAuthResultCoordinator;      public AuthSessionCoordinator() { -        this(SystemClock.currentNetworkTimeClock()); +        this(SystemClock.elapsedRealtimeClock());      }      @VisibleForTesting diff --git a/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java index d9bd04d3f1c8..6605d49ece9b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java +++ b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java @@ -22,7 +22,6 @@ import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMET  import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;  import android.hardware.biometrics.BiometricManager; -import android.os.SystemClock;  import android.util.Slog;  import java.time.Clock; @@ -43,10 +42,6 @@ class MultiBiometricLockoutState {      private final Map<Integer, Map<Integer, AuthenticatorState>> mCanUserAuthenticate;      private final Clock mClock; -    MultiBiometricLockoutState() { -        this(SystemClock.currentNetworkTimeClock()); -    } -      MultiBiometricLockoutState(Clock clock) {          mCanUserAuthenticate = new HashMap<>();          mClock = clock; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index f599acac7ed4..2e5663db57b5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -16,6 +16,7 @@  package com.android.server.biometrics.sensors.fingerprint.aidl; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;  import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;  import android.annotation.NonNull; @@ -59,6 +60,7 @@ import com.android.server.biometrics.sensors.SensorOverlays;  import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;  import com.android.server.biometrics.sensors.fingerprint.Udfps; +import java.time.Clock;  import java.util.ArrayList;  import java.util.function.Supplier; @@ -92,7 +94,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>      private long mWaitForAuthKeyguard;      private long mWaitForAuthBp;      private long mIgnoreAuthFor; +    private long mSideFpsLastAcquireStartTime;      private Runnable mAuthSuccessRunnable; +    private final Clock mClock;      FingerprintAuthenticationClient(              @NonNull Context context, @@ -117,7 +121,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>              boolean allowBackgroundAuthentication,              @NonNull FingerprintSensorPropertiesInternal sensorProps,              @NonNull Handler handler, -            @Authenticators.Types int biometricStrength) { +            @Authenticators.Types int biometricStrength, +            @NonNull Clock clock) {          super(                  context,                  lazyDaemon, @@ -161,6 +166,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>                          R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage);          mBiometricStrength = biometricStrength;          mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator(); +        mSideFpsLastAcquireStartTime = -1; +        mClock = clock;          if (mSensorProps.isAnySidefpsType()) {              if (Build.isDebuggable()) { @@ -246,8 +253,14 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>                              return;                          }                          delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp; -                        Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps waiting for power for: " -                                + delay + "ms"); + +                        if (mSideFpsLastAcquireStartTime != -1) { +                            delay = Math.max(0, +                                    delay - (mClock.millis() - mSideFpsLastAcquireStartTime)); +                        } + +                        Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps " +                                + "waiting for power until: " + delay + "ms");                      }                      if (mHandler.hasMessages(MESSAGE_FINGER_UP)) { @@ -271,13 +284,15 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>          mSensorOverlays.ifUdfps(controller -> controller.onAcquired(getSensorId(), acquiredInfo));          super.onAcquired(acquiredInfo, vendorCode);          if (mSensorProps.isAnySidefpsType()) { +            if (acquiredInfo == FINGERPRINT_ACQUIRED_START) { +                mSideFpsLastAcquireStartTime = mClock.millis(); +            }              final boolean shouldLookForVendor =                      mSkipWaitForPowerAcquireMessage == FINGERPRINT_ACQUIRED_VENDOR;              final boolean acquireMessageMatch = acquiredInfo == mSkipWaitForPowerAcquireMessage;              final boolean vendorMessageMatch = vendorCode == mSkipWaitForPowerVendorAcquireMessage;              final boolean ignorePowerPress = -                    (acquireMessageMatch && !shouldLookForVendor) || (shouldLookForVendor -                            && acquireMessageMatch && vendorMessageMatch); +                    acquireMessageMatch && (!shouldLookForVendor || vendorMessageMatch);              if (ignorePowerPress) {                  Slog.d(TAG, "(sideFPS) onFingerUp"); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 774aff1dd72c..650894db431a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -47,6 +47,7 @@ import android.os.IBinder;  import android.os.Looper;  import android.os.RemoteException;  import android.os.ServiceManager; +import android.os.SystemClock;  import android.os.UserManager;  import android.util.Slog;  import android.util.SparseArray; @@ -449,7 +450,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi                      mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),                      mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,                      mSensors.get(sensorId).getSensorProperties(), mHandler, -                    Utils.getCurrentStrength(sensorId)); +                    Utils.getCurrentStrength(sensorId), +                    SystemClock.elapsedRealtimeClock());              scheduleForSensor(sensorId, client, mBiometricStateCallback);          });      } diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java index 0770062cd4d3..6a010424db13 100644 --- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java +++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java @@ -29,6 +29,8 @@ import android.os.ServiceManager;  import android.util.IndentingPrintWriter;  import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl;  import com.android.server.utils.Slogf;  import java.io.FileDescriptor; @@ -47,7 +49,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {      private static final List<String> SERVICE_NAMES = Arrays.asList(              IBroadcastRadio.DESCRIPTOR + "/amfm", IBroadcastRadio.DESCRIPTOR + "/dab"); -    private final com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl mHalAidl; +    private final BroadcastRadioServiceImpl mHalAidl;      private final BroadcastRadioService mService;      /** @@ -65,10 +67,15 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {      }      IRadioServiceAidlImpl(BroadcastRadioService service, ArrayList<String> serviceList) { +        this(service, new BroadcastRadioServiceImpl(serviceList));          Slogf.i(TAG, "Initialize BroadcastRadioServiceAidl(%s)", service); -        mService = Objects.requireNonNull(service); -        mHalAidl = -                new com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl(serviceList); +    } + +    @VisibleForTesting +    IRadioServiceAidlImpl(BroadcastRadioService service, BroadcastRadioServiceImpl halAidl) { +        mService = Objects.requireNonNull(service, "Broadcast radio service cannot be null"); +        mHalAidl = Objects.requireNonNull(halAidl, +                "Broadcast radio service implementation for AIDL HAL cannot be null");      }      @Override @@ -96,8 +103,8 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {          if (isDebugEnabled()) {              Slogf.d(TAG, "Adding announcement listener for %s", Arrays.toString(enabledTypes));          } -        Objects.requireNonNull(enabledTypes); -        Objects.requireNonNull(listener); +        Objects.requireNonNull(enabledTypes, "Enabled announcement types cannot be null"); +        Objects.requireNonNull(listener, "Announcement listener cannot be null");          mService.enforcePolicyAccess();          return mHalAidl.addAnnouncementListener(enabledTypes, listener); diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java index 28b6d02581be..a8e4034e3f86 100644 --- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java +++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java @@ -27,6 +27,7 @@ import android.util.IndentingPrintWriter;  import android.util.Log;  import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting;  import com.android.server.broadcastradio.hal2.AnnouncementAggregator;  import java.io.FileDescriptor; @@ -53,7 +54,7 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {      private final List<RadioManager.ModuleProperties> mV1Modules;      IRadioServiceHidlImpl(BroadcastRadioService service) { -        mService = Objects.requireNonNull(service); +        mService = Objects.requireNonNull(service, "broadcast radio service cannot be null");          mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService(mLock);          mV1Modules = mHal1.loadModules();          OptionalInt max = mV1Modules.stream().mapToInt(RadioManager.ModuleProperties::getId).max(); @@ -61,6 +62,18 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {                  max.isPresent() ? max.getAsInt() + 1 : 0, mLock);      } +    @VisibleForTesting +    IRadioServiceHidlImpl(BroadcastRadioService service, +            com.android.server.broadcastradio.hal1.BroadcastRadioService hal1, +            com.android.server.broadcastradio.hal2.BroadcastRadioService hal2) { +        mService = Objects.requireNonNull(service, "Broadcast radio service cannot be null"); +        mHal1 = Objects.requireNonNull(hal1, +                "Broadcast radio service implementation for HIDL 1 HAL cannot be null"); +        mV1Modules = mHal1.loadModules(); +        mHal2 = Objects.requireNonNull(hal2, +                "Broadcast radio service implementation for HIDL 2 HAL cannot be null"); +    } +      @Override      public List<RadioManager.ModuleProperties> listModules() {          mService.enforcePolicyAccess(); @@ -95,8 +108,8 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {          if (isDebugEnabled()) {              Slog.d(TAG, "Adding announcement listener for " + Arrays.toString(enabledTypes));          } -        Objects.requireNonNull(enabledTypes); -        Objects.requireNonNull(listener); +        Objects.requireNonNull(enabledTypes, "Enabled announcement types cannot be null"); +        Objects.requireNonNull(listener, "Announcement listener cannot be null");          mService.enforcePolicyAccess();          synchronized (mLock) { diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index b8af1bfcc254..819b719dd22e 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -16,6 +16,9 @@  package com.android.server.dreams; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; + +import android.app.ActivityTaskManager;  import android.content.ComponentName;  import android.content.Context;  import android.content.Intent; @@ -34,8 +37,6 @@ import android.os.UserHandle;  import android.service.dreams.DreamService;  import android.service.dreams.IDreamService;  import android.util.Slog; -import android.view.IWindowManager; -import android.view.WindowManagerGlobal;  import com.android.internal.logging.MetricsLogger;  import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -60,7 +61,7 @@ final class DreamController {      private final Context mContext;      private final Handler mHandler;      private final Listener mListener; -    private final IWindowManager mIWindowManager; +    private final ActivityTaskManager mActivityTaskManager;      private long mDreamStartTime;      private String mSavedStopReason; @@ -93,7 +94,7 @@ final class DreamController {          mContext = context;          mHandler = handler;          mListener = listener; -        mIWindowManager = WindowManagerGlobal.getWindowManagerService(); +        mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);          mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);          mCloseNotificationShadeIntent.putExtra("reason", "dream");      } @@ -229,6 +230,8 @@ final class DreamController {              }              oldDream.releaseWakeLockIfNeeded(); +            mActivityTaskManager.removeRootTasksWithActivityTypes(new int[] {ACTIVITY_TYPE_DREAM}); +              mHandler.post(() -> mListener.onDreamStopped(oldDream.mToken));          } finally {              Trace.traceEnd(Trace.TRACE_TAG_POWER); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index 5aa3fa4db4f1..5c1b33cb3120 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -19,20 +19,25 @@ package com.android.server.hdmi;  import android.annotation.NonNull;  import android.annotation.Nullable;  import android.hardware.hdmi.HdmiPortInfo; -import android.hardware.tv.cec.V1_0.CecMessage; +import android.hardware.tv.cec.CecMessage; +import android.hardware.tv.cec.IHdmiCec; +import android.hardware.tv.cec.IHdmiCecCallback;  import android.hardware.tv.cec.V1_0.HotplugEvent; -import android.hardware.tv.cec.V1_0.IHdmiCec;  import android.hardware.tv.cec.V1_0.IHdmiCec.getPhysicalAddressCallback; -import android.hardware.tv.cec.V1_0.IHdmiCecCallback; +import android.hardware.tv.cec.V1_0.OptionKey;  import android.hardware.tv.cec.V1_0.Result;  import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.hardware.tv.hdmi.IHdmi; +import android.hardware.tv.hdmi.IHdmiCallback;  import android.icu.util.IllformedLocaleException;  import android.icu.util.ULocale;  import android.os.Binder;  import android.os.Handler; +import android.os.IBinder;  import android.os.IHwBinder;  import android.os.Looper;  import android.os.RemoteException; +import android.os.ServiceManager;  import android.stats.hdmi.HdmiStatsEnums;  import android.util.Slog; @@ -170,8 +175,14 @@ final class HdmiCecController {       *         returns {@code null}.       */      static HdmiCecController create(HdmiControlService service, HdmiCecAtomWriter atomWriter) { -        HdmiCecController controller = createWithNativeWrapper(service, new NativeWrapperImpl11(), -                atomWriter); +        HdmiCecController controller = +                createWithNativeWrapper(service, new NativeWrapperImplAidl(), atomWriter); +        if (controller != null) { +            return controller; +        } +        HdmiLogger.warning("Unable to use CEC and HDMI AIDL HALs"); + +        controller = createWithNativeWrapper(service, new NativeWrapperImpl11(), atomWriter);          if (controller != null) {              return controller;          } @@ -362,16 +373,43 @@ final class HdmiCecController {      }      /** -     * Set an option to CEC HAL. +     * Configures the TV panel device wakeup behaviour in standby mode when it receives an OTP +     * (One Touch Play) from a source device. +     * +     * @param value If true, the TV device will wake up when OTP is received and if false, the TV +     *     device will not wake up for an OTP. +     */ +    @ServiceThreadOnly +    void enableWakeupByOtp(boolean enabled) { +        assertRunOnServiceThread(); +        HdmiLogger.debug("enableWakeupByOtp: %b", enabled); +        mNativeWrapperImpl.enableWakeupByOtp(enabled); +    } + +    /** +     * Switch to enable or disable CEC on the device. +     * +     * @param value If true, the device will have all CEC functionalities and if false, the device +     *     will not perform any CEC functions. +     */ +    @ServiceThreadOnly +    void enableCec(boolean enabled) { +        assertRunOnServiceThread(); +        HdmiLogger.debug("enableCec: %b", enabled); +        mNativeWrapperImpl.enableCec(enabled); +    } + +    /** +     * Configures the module that processes CEC messages - the Android framework or the HAL.       * -     * @param flag key of option -     * @param enabled whether to enable/disable the given option. +     * @param value If true, the Android framework will actively process CEC messages and if false, +     *     only the HAL will process the CEC messages.       */      @ServiceThreadOnly -    void setOption(int flag, boolean enabled) { +    void enableSystemCecControl(boolean enabled) {          assertRunOnServiceThread(); -        HdmiLogger.debug("setOption: [flag:%d, enabled:%b]", flag, enabled); -        mNativeWrapperImpl.nativeSetOption(flag, enabled); +        HdmiLogger.debug("enableSystemCecControl: %b", enabled); +        mNativeWrapperImpl.enableSystemCecControl(enabled);      }      /** @@ -829,12 +867,233 @@ final class HdmiCecController {          int nativeGetVersion();          int nativeGetVendorId();          HdmiPortInfo[] nativeGetPortInfos(); -        void nativeSetOption(int flag, boolean enabled); + +        void enableWakeupByOtp(boolean enabled); + +        void enableCec(boolean enabled); + +        void enableSystemCecControl(boolean enabled); +          void nativeSetLanguage(String language);          void nativeEnableAudioReturnChannel(int port, boolean flag);          boolean nativeIsConnected(int port);      } +    private static final class NativeWrapperImplAidl +            implements NativeWrapper, IBinder.DeathRecipient { +        private IHdmiCec mHdmiCec; +        private IHdmi mHdmi; +        @Nullable private HdmiCecCallback mCallback; + +        private final Object mLock = new Object(); + +        @Override +        public String nativeInit() { +            return connectToHal() ? mHdmiCec.toString() + " " + mHdmi.toString() : null; +        } + +        boolean connectToHal() { +            mHdmiCec = +                    IHdmiCec.Stub.asInterface( +                            ServiceManager.getService(IHdmiCec.DESCRIPTOR + "/default")); +            if (mHdmiCec == null) { +                HdmiLogger.error("Could not initialize HDMI CEC AIDL HAL"); +                return false; +            } +            try { +                mHdmiCec.asBinder().linkToDeath(this, 0); +            } catch (RemoteException e) { +                HdmiLogger.error("Couldn't link to death : ", e); +            } + +            mHdmi = +                    IHdmi.Stub.asInterface( +                            ServiceManager.getService(IHdmi.DESCRIPTOR + "/default")); +            if (mHdmi == null) { +                HdmiLogger.error("Could not initialize HDMI AIDL HAL"); +                return false; +            } +            try { +                mHdmi.asBinder().linkToDeath(this, 0); +            } catch (RemoteException e) { +                HdmiLogger.error("Couldn't link to death : ", e); +            } +            return true; +        } + +        @Override +        public void binderDied() { +            // One of the services died, try to reconnect to both. +            mHdmiCec.asBinder().unlinkToDeath(this, 0); +            mHdmi.asBinder().unlinkToDeath(this, 0); +            HdmiLogger.error("HDMI or CEC service died, reconnecting"); +            connectToHal(); +            // Reconnect the callback +            if (mCallback != null) { +                setCallback(mCallback); +            } +        } + +        @Override +        public void setCallback(HdmiCecCallback callback) { +            mCallback = callback; +            try { +                // Create an AIDL callback that can callback onCecMessage +                mHdmiCec.setCallback(new HdmiCecCallbackAidl(callback)); +            } catch (RemoteException e) { +                HdmiLogger.error("Couldn't initialise tv.cec callback : ", e); +            } +            try { +                // Create an AIDL callback that can callback onHotplugEvent +                mHdmi.setCallback(new HdmiCallbackAidl(callback)); +            } catch (RemoteException e) { +                HdmiLogger.error("Couldn't initialise tv.hdmi callback : ", e); +            } +        } + +        @Override +        public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) { +            CecMessage message = new CecMessage(); +            message.initiator = (byte) (srcAddress & 0xF); +            message.destination = (byte) (dstAddress & 0xF); +            message.body = body; +            try { +                return mHdmiCec.sendMessage(message); +            } catch (RemoteException e) { +                HdmiLogger.error("Failed to send CEC message : ", e); +                return SendMessageResult.FAIL; +            } +        } + +        @Override +        public int nativeAddLogicalAddress(int logicalAddress) { +            try { +                return mHdmiCec.addLogicalAddress((byte) logicalAddress); +            } catch (RemoteException e) { +                HdmiLogger.error("Failed to add a logical address : ", e); +                return Result.FAILURE_INVALID_ARGS; +            } +        } + +        @Override +        public void nativeClearLogicalAddress() { +            try { +                mHdmiCec.clearLogicalAddress(); +            } catch (RemoteException e) { +                HdmiLogger.error("Failed to clear logical address : ", e); +            } +        } + +        @Override +        public int nativeGetPhysicalAddress() { +            try { +                return mHdmiCec.getPhysicalAddress(); +            } catch (RemoteException e) { +                HdmiLogger.error("Failed to get physical address : ", e); +                return INVALID_PHYSICAL_ADDRESS; +            } +        } + +        @Override +        public int nativeGetVersion() { +            try { +                return mHdmiCec.getCecVersion(); +            } catch (RemoteException e) { +                HdmiLogger.error("Failed to get cec version : ", e); +                return Result.FAILURE_UNKNOWN; +            } +        } + +        @Override +        public int nativeGetVendorId() { +            try { +                return mHdmiCec.getVendorId(); +            } catch (RemoteException e) { +                HdmiLogger.error("Failed to get vendor id : ", e); +                return Result.FAILURE_UNKNOWN; +            } +        } + +        @Override +        public void enableWakeupByOtp(boolean enabled) { +            try { +                mHdmiCec.enableWakeupByOtp(enabled); +            } catch (RemoteException e) { +                HdmiLogger.error("Failed call to enableWakeupByOtp : ", e); +            } +        } + +        @Override +        public void enableCec(boolean enabled) { +            try { +                mHdmiCec.enableCec(enabled); +            } catch (RemoteException e) { +                HdmiLogger.error("Failed call to enableCec : ", e); +            } +        } + +        @Override +        public void enableSystemCecControl(boolean enabled) { +            try { +                mHdmiCec.enableSystemCecControl(enabled); +            } catch (RemoteException e) { +                HdmiLogger.error("Failed call to enableSystemCecControl : ", e); +            } +        } + +        @Override +        public void nativeSetLanguage(String language) { +            try { +                mHdmiCec.setLanguage(language); +            } catch (RemoteException e) { +                HdmiLogger.error("Failed to set language : ", e); +            } +        } + +        @Override +        public void nativeEnableAudioReturnChannel(int port, boolean flag) { +            try { +                mHdmiCec.enableAudioReturnChannel(port, flag); +            } catch (RemoteException e) { +                HdmiLogger.error("Failed to enable/disable ARC : ", e); +            } +        } + +        @Override +        public HdmiPortInfo[] nativeGetPortInfos() { +            try { +                android.hardware.tv.hdmi.HdmiPortInfo[] hdmiPortInfos = mHdmi.getPortInfo(); +                HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.length]; +                int i = 0; +                for (android.hardware.tv.hdmi.HdmiPortInfo portInfo : hdmiPortInfos) { +                    hdmiPortInfo[i] = +                            new HdmiPortInfo( +                                    portInfo.portId, +                                    portInfo.type, +                                    portInfo.physicalAddress, +                                    portInfo.cecSupported, +                                    false, +                                    portInfo.arcSupported); +                    i++; +                } +                return hdmiPortInfo; +            } catch (RemoteException e) { +                HdmiLogger.error("Failed to get port information : ", e); +                return null; +            } +        } + +        @Override +        public boolean nativeIsConnected(int port) { +            try { +                return mHdmi.isConnected(port); +            } catch (RemoteException e) { +                HdmiLogger.error("Failed to get connection info : ", e); +                return false; +            } +        } +    } +      private static final class NativeWrapperImpl11 implements NativeWrapper,              IHwBinder.DeathRecipient, getPhysicalAddressCallback {          private android.hardware.tv.cec.V1_1.IHdmiCec mHdmiCec; @@ -985,8 +1244,7 @@ final class HdmiCecController {              }          } -        @Override -        public void nativeSetOption(int flag, boolean enabled) { +        private void nativeSetOption(int flag, boolean enabled) {              try {                  mHdmiCec.setOption(flag, enabled);              } catch (RemoteException e) { @@ -995,6 +1253,21 @@ final class HdmiCecController {          }          @Override +        public void enableWakeupByOtp(boolean enabled) { +            nativeSetOption(OptionKey.WAKEUP, enabled); +        } + +        @Override +        public void enableCec(boolean enabled) { +            nativeSetOption(OptionKey.ENABLE_CEC, enabled); +        } + +        @Override +        public void enableSystemCecControl(boolean enabled) { +            nativeSetOption(OptionKey.SYSTEM_CEC_CONTROL, enabled); +        } + +        @Override          public void nativeSetLanguage(String language) {              try {                  mHdmiCec.setLanguage(language); @@ -1038,7 +1311,7 @@ final class HdmiCecController {          boolean connectToHal() {              try { -                mHdmiCec = IHdmiCec.getService(true); +                mHdmiCec = android.hardware.tv.cec.V1_0.IHdmiCec.getService(true);                  try {                      mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE);                  } catch (RemoteException e) { @@ -1063,7 +1336,8 @@ final class HdmiCecController {          @Override          public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) { -            CecMessage message = new CecMessage(); +            android.hardware.tv.cec.V1_0.CecMessage message = +                    new android.hardware.tv.cec.V1_0.CecMessage();              message.initiator = srcAddress;              message.destination = dstAddress;              message.body = new ArrayList<>(body.length); @@ -1151,8 +1425,7 @@ final class HdmiCecController {              }          } -        @Override -        public void nativeSetOption(int flag, boolean enabled) { +        private void nativeSetOption(int flag, boolean enabled) {              try {                  mHdmiCec.setOption(flag, enabled);              } catch (RemoteException e) { @@ -1161,6 +1434,21 @@ final class HdmiCecController {          }          @Override +        public void enableWakeupByOtp(boolean enabled) { +            nativeSetOption(OptionKey.WAKEUP, enabled); +        } + +        @Override +        public void enableCec(boolean enabled) { +            nativeSetOption(OptionKey.ENABLE_CEC, enabled); +        } + +        @Override +        public void enableSystemCecControl(boolean enabled) { +            nativeSetOption(OptionKey.SYSTEM_CEC_CONTROL, enabled); +        } + +        @Override          public void nativeSetLanguage(String language) {              try {                  mHdmiCec.setLanguage(language); @@ -1221,7 +1509,8 @@ final class HdmiCecController {          }      } -    private static final class HdmiCecCallback10 extends IHdmiCecCallback.Stub { +    private static final class HdmiCecCallback10 +            extends android.hardware.tv.cec.V1_0.IHdmiCecCallback.Stub {          private final HdmiCecCallback mHdmiCecCallback;          HdmiCecCallback10(HdmiCecCallback hdmiCecCallback) { @@ -1229,7 +1518,8 @@ final class HdmiCecController {          }          @Override -        public void onCecMessage(CecMessage message) throws RemoteException { +        public void onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message) +                throws RemoteException {              byte[] body = new byte[message.body.size()];              for (int i = 0; i < message.body.size(); i++) {                  body[i] = message.body.get(i); @@ -1262,7 +1552,8 @@ final class HdmiCecController {          }          @Override -        public void onCecMessage(CecMessage message) throws RemoteException { +        public void onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message) +                throws RemoteException {              byte[] body = new byte[message.body.size()];              for (int i = 0; i < message.body.size(); i++) {                  body[i] = message.body.get(i); @@ -1276,6 +1567,52 @@ final class HdmiCecController {          }      } +    private static final class HdmiCecCallbackAidl extends IHdmiCecCallback.Stub { +        private final HdmiCecCallback mHdmiCecCallback; + +        HdmiCecCallbackAidl(HdmiCecCallback hdmiCecCallback) { +            mHdmiCecCallback = hdmiCecCallback; +        } + +        @Override +        public void onCecMessage(CecMessage message) throws RemoteException { +            mHdmiCecCallback.onCecMessage(message.initiator, message.destination, message.body); +        } + +        @Override +        public synchronized String getInterfaceHash() throws android.os.RemoteException { +            return IHdmiCecCallback.Stub.HASH; +        } + +        @Override +        public int getInterfaceVersion() throws android.os.RemoteException { +            return IHdmiCecCallback.Stub.VERSION; +        } +    } + +    private static final class HdmiCallbackAidl extends IHdmiCallback.Stub { +        private final HdmiCecCallback mHdmiCecCallback; + +        HdmiCallbackAidl(HdmiCecCallback hdmiCecCallback) { +            mHdmiCecCallback = hdmiCecCallback; +        } + +        @Override +        public void onHotplugEvent(boolean connected, int portId) throws RemoteException { +            mHdmiCecCallback.onHotplugEvent(portId, connected); +        } + +        @Override +        public synchronized String getInterfaceHash() throws android.os.RemoteException { +            return IHdmiCallback.Stub.HASH; +        } + +        @Override +        public int getInterfaceVersion() throws android.os.RemoteException { +            return IHdmiCallback.Stub.VERSION; +        } +    } +      public abstract static class Dumpable {          protected final long mTime; diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 3ee35036e9fd..1ae1b5b5185d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -56,7 +56,6 @@ import android.hardware.hdmi.IHdmiMhlVendorCommandListener;  import android.hardware.hdmi.IHdmiRecordListener;  import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;  import android.hardware.hdmi.IHdmiVendorCommandListener; -import android.hardware.tv.cec.V1_0.OptionKey;  import android.hardware.tv.cec.V1_0.SendMessageResult;  import android.media.AudioAttributes;  import android.media.AudioDeviceAttributes; @@ -656,7 +655,7 @@ public class HdmiControlService extends SystemService {          if (mHdmiControlEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) {              initializeCec(INITIATED_BY_BOOT_UP);          } else { -            mCecController.setOption(OptionKey.ENABLE_CEC, false); +            mCecController.enableCec(false);          }          mMhlDevices = Collections.emptyList(); @@ -730,10 +729,11 @@ public class HdmiControlService extends SystemService {                      @Override                      public void onChange(String setting) {                          if (isTvDeviceEnabled()) { -                            setCecOption(OptionKey.WAKEUP, tv().getAutoWakeup()); +                            mCecController.enableWakeupByOtp(tv().getAutoWakeup());                          }                      } -                }, mServiceThreadExecutor); +                }, +                mServiceThreadExecutor);      }      /** Returns true if the device screen is off */ @@ -854,7 +854,7 @@ public class HdmiControlService extends SystemService {          mWakeUpMessageReceived = false;          if (isTvDeviceEnabled()) { -            mCecController.setOption(OptionKey.WAKEUP, tv().getAutoWakeup()); +            mCecController.enableWakeupByOtp(tv().getAutoWakeup());          }          int reason = -1;          switch (initiatedBy) { @@ -988,7 +988,7 @@ public class HdmiControlService extends SystemService {          mCecVersion = Math.max(HdmiControlManager.HDMI_CEC_VERSION_1_4_B,                  Math.min(settingsCecVersion, supportedCecVersion)); -        mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true); +        mCecController.enableSystemCecControl(true);          mCecController.setLanguage(mMenuLanguage);          initializeLocalDevices(initiatedBy);      } @@ -3424,7 +3424,7 @@ public class HdmiControlService extends SystemService {                  device.onStandby(mStandbyMessageReceived, standbyAction);              }              if (!isAudioSystemDevice()) { -                mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false); +                mCecController.enableSystemCecControl(false);                  mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);              }          } @@ -3573,12 +3573,6 @@ public class HdmiControlService extends SystemService {      }      @ServiceThreadOnly -    void setCecOption(int key, boolean value) { -        assertRunOnServiceThread(); -        mCecController.setOption(key, value); -    } - -    @ServiceThreadOnly      void setControlEnabled(@HdmiControlManager.HdmiCecControl int enabled) {          assertRunOnServiceThread(); @@ -3612,8 +3606,8 @@ public class HdmiControlService extends SystemService {      @ServiceThreadOnly      private void enableHdmiControlService() { -        mCecController.setOption(OptionKey.ENABLE_CEC, true); -        mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true); +        mCecController.enableCec(true); +        mCecController.enableSystemCecControl(true);          mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);          initializeCec(INITIATED_BY_ENABLE_CEC); @@ -3621,21 +3615,23 @@ public class HdmiControlService extends SystemService {      @ServiceThreadOnly      private void disableHdmiControlService() { -        disableDevices(new PendingActionClearedCallback() { -            @Override -            public void onCleared(HdmiCecLocalDevice device) { -                assertRunOnServiceThread(); -                mCecController.flush(new Runnable() { +        disableDevices( +                new PendingActionClearedCallback() {                      @Override -                    public void run() { -                        mCecController.setOption(OptionKey.ENABLE_CEC, false); -                        mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false); -                        mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED); -                        clearLocalDevices(); +                    public void onCleared(HdmiCecLocalDevice device) { +                        assertRunOnServiceThread(); +                        mCecController.flush( +                                new Runnable() { +                                    @Override +                                    public void run() { +                                        mCecController.enableCec(false); +                                        mCecController.enableSystemCecControl(false); +                                        mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED); +                                        clearLocalDevices(); +                                    } +                                });                      }                  }); -            } -        });      }      @ServiceThreadOnly diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index fc7be7ff8d1c..364f6db28f03 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -25,6 +25,7 @@ import android.annotation.UserIdInt;  import android.app.ActivityManager;  import android.app.ActivityManagerInternal;  import android.app.ILocaleManager; +import android.content.ComponentName;  import android.content.Context;  import android.content.Intent;  import android.content.pm.PackageManager; @@ -38,6 +39,8 @@ import android.os.RemoteException;  import android.os.ResultReceiver;  import android.os.ShellCallback;  import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils;  import android.util.Slog;  import com.android.internal.annotations.VisibleForTesting; @@ -357,17 +360,20 @@ public class LocaleManagerService extends SystemService {                  false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,                  "getApplicationLocales", /* callerPackage= */ null); -        // This function handles three types of query operations: +        // This function handles four types of query operations:          // 1.) A normal, non-privileged app querying its own locale. -        // 2.) The installer of the given app querying locales of a package installed -        // by said installer. -        // 3.) A privileged system service querying locales of another package. -        // The least privileged case is a normal app performing a query, so check that first and -        // get locales if the package name is owned by the app. Next check if the calling app -        // is the installer of the given app and get locales. If neither conditions matched, -        // check if the caller has the necessary permission and fetch locales. +        // 2.) The installer of the given app querying locales of a package installed by said +        // installer. +        // 3.) The current input method querying locales of another package. +        // 4.) A privileged system service querying locales of another package. +        // The least privileged case is a normal app performing a query, so check that first and get +        // locales if the package name is owned by the app. Next check if the calling app is the +        // installer of the given app and get locales. Finally check if the calling app is the +        // current input method. If neither conditions matched, check if the caller has the +        // necessary permission and fetch locales.          if (!isPackageOwnedByCaller(appPackageName, userId) -                && !isCallerInstaller(appPackageName, userId)) { +                && !isCallerInstaller(appPackageName, userId) +                && !isCallerFromCurrentInputMethod(userId)) {              enforceReadAppSpecificLocalesPermission();          }          final long token = Binder.clearCallingIdentity(); @@ -412,6 +418,26 @@ public class LocaleManagerService extends SystemService {          return false;      } +    /** +     * Checks if the calling app is the current input method. +     */ +    private boolean isCallerFromCurrentInputMethod(int userId) { +        String currentInputMethod = Settings.Secure.getStringForUser( +                mContext.getContentResolver(), +                Settings.Secure.DEFAULT_INPUT_METHOD, +                userId); +        if (!TextUtils.isEmpty(currentInputMethod)) { +            String inputMethodPkgName = ComponentName +                    .unflattenFromString(currentInputMethod) +                    .getPackageName(); +            int inputMethodUid = getPackageUid(inputMethodPkgName, userId); +            return inputMethodUid >= 0 && UserHandle.isSameApp(Binder.getCallingUid(), +                    inputMethodUid); +        } + +        return false; +    } +      private void enforceReadAppSpecificLocalesPermission() {          mContext.enforceCallingOrSelfPermission(                  android.Manifest.permission.READ_APP_SPECIFIC_LOCALES, diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index e653f0466863..6f637b83a694 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -1447,7 +1447,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements       * @return the cell ID or -1 if invalid       */      private static long getCidFromCellIdentity(CellIdentity id) { -        if (id == null) return -1; +        if (id == null) { +            return -1; +        }          long cid = -1;          switch(id.getType()) {              case CellInfo.TYPE_GSM: cid = ((CellIdentityGsm) id).getCid(); break; @@ -1522,7 +1524,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements                  for (CellInfo ci : cil) {                      int status = ci.getCellConnectionStatus(); -                    if (status == CellInfo.CONNECTION_PRIMARY_SERVING +                    if (ci.isRegistered() +                            || status == CellInfo.CONNECTION_PRIMARY_SERVING                              || status == CellInfo.CONNECTION_SECONDARY_SERVING) {                          CellIdentity c = ci.getCellIdentity();                          int t = getCellType(ci); diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java index 07e9fe6ad541..6c4c829b051d 100644 --- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java @@ -115,6 +115,16 @@ public final class GnssMeasurementsProvider extends          if (request.getIntervalMillis() == GnssMeasurementRequest.PASSIVE_INTERVAL) {              return true;          } +        // The HAL doc does not specify if consecutive start() calls will be allowed. +        // Some vendors may ignore the 2nd start() call if stop() is not called. +        // Thus, here we always call stop() before calling start() to avoid being ignored. +        if (mGnssNative.stopMeasurementCollection()) { +            if (D) { +                Log.d(TAG, "stopping gnss measurements"); +            } +        } else { +            Log.e(TAG, "error stopping gnss measurements"); +        }          if (mGnssNative.startMeasurementCollection(request.isFullTracking(),                  request.isCorrelationVectorOutputsEnabled(),                  request.getIntervalMillis())) { diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 5f06ca945eae..77dbde13889b 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -151,10 +151,7 @@ class MediaRouter2ServiceImpl {          mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);      } -    //////////////////////////////////////////////////////////////// -    ////  Calls from MediaRouter2 -    ////   - Should not have @NonNull/@Nullable on any arguments -    //////////////////////////////////////////////////////////////// +    // Methods that implement MediaRouter2 operations.      @NonNull      public void enforceMediaContentControlPermission() { @@ -242,7 +239,7 @@ class MediaRouter2ServiceImpl {          }      } -    public void registerRouter2(IMediaRouter2 router, String packageName) { +    public void registerRouter2(@NonNull IMediaRouter2 router, @NonNull String packageName) {          Objects.requireNonNull(router, "router must not be null");          if (TextUtils.isEmpty(packageName)) {              throw new IllegalArgumentException("packageName must not be empty"); @@ -269,7 +266,7 @@ class MediaRouter2ServiceImpl {          }      } -    public void unregisterRouter2(IMediaRouter2 router) { +    public void unregisterRouter2(@NonNull IMediaRouter2 router) {          Objects.requireNonNull(router, "router must not be null");          final long token = Binder.clearCallingIdentity(); @@ -282,8 +279,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void setDiscoveryRequestWithRouter2(IMediaRouter2 router, -            RouteDiscoveryPreference preference) { +    public void setDiscoveryRequestWithRouter2(@NonNull IMediaRouter2 router, +            @NonNull RouteDiscoveryPreference preference) {          Objects.requireNonNull(router, "router must not be null");          Objects.requireNonNull(preference, "preference must not be null"); @@ -302,8 +299,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void setRouteVolumeWithRouter2(IMediaRouter2 router, -            MediaRoute2Info route, int volume) { +    public void setRouteVolumeWithRouter2(@NonNull IMediaRouter2 router, +            @NonNull MediaRoute2Info route, int volume) {          Objects.requireNonNull(router, "router must not be null");          Objects.requireNonNull(route, "route must not be null"); @@ -317,9 +314,9 @@ class MediaRouter2ServiceImpl {          }      } -    public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, -            long managerRequestId, RoutingSessionInfo oldSession, -            MediaRoute2Info route, Bundle sessionHints) { +    public void requestCreateSessionWithRouter2(@NonNull IMediaRouter2 router, int requestId, +            long managerRequestId, @NonNull RoutingSessionInfo oldSession, +            @NonNull MediaRoute2Info route, Bundle sessionHints) {          Objects.requireNonNull(router, "router must not be null");          Objects.requireNonNull(oldSession, "oldSession must not be null");          Objects.requireNonNull(route, "route must not be null"); @@ -335,8 +332,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void selectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId, -            MediaRoute2Info route) { +    public void selectRouteWithRouter2(@NonNull IMediaRouter2 router, +            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {          Objects.requireNonNull(router, "router must not be null");          Objects.requireNonNull(route, "route must not be null");          if (TextUtils.isEmpty(uniqueSessionId)) { @@ -353,8 +350,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void deselectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId, -            MediaRoute2Info route) { +    public void deselectRouteWithRouter2(@NonNull IMediaRouter2 router, +            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {          Objects.requireNonNull(router, "router must not be null");          Objects.requireNonNull(route, "route must not be null");          if (TextUtils.isEmpty(uniqueSessionId)) { @@ -371,8 +368,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void transferToRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId, -            MediaRoute2Info route) { +    public void transferToRouteWithRouter2(@NonNull IMediaRouter2 router, +            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {          Objects.requireNonNull(router, "router must not be null");          Objects.requireNonNull(route, "route must not be null");          if (TextUtils.isEmpty(uniqueSessionId)) { @@ -389,8 +386,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void setSessionVolumeWithRouter2(IMediaRouter2 router, String uniqueSessionId, -            int volume) { +    public void setSessionVolumeWithRouter2(@NonNull IMediaRouter2 router, +            @NonNull String uniqueSessionId, int volume) {          Objects.requireNonNull(router, "router must not be null");          Objects.requireNonNull(uniqueSessionId, "uniqueSessionId must not be null"); @@ -404,7 +401,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void releaseSessionWithRouter2(IMediaRouter2 router, String uniqueSessionId) { +    public void releaseSessionWithRouter2(@NonNull IMediaRouter2 router, +            @NonNull String uniqueSessionId) {          Objects.requireNonNull(router, "router must not be null");          if (TextUtils.isEmpty(uniqueSessionId)) {              throw new IllegalArgumentException("uniqueSessionId must not be empty"); @@ -420,13 +418,10 @@ class MediaRouter2ServiceImpl {          }      } -    //////////////////////////////////////////////////////////////// -    ////  Calls from MediaRouter2Manager -    ////   - Should not have @NonNull/@Nullable on any arguments -    //////////////////////////////////////////////////////////////// +    // Methods that implement MediaRouter2Manager operations.      @NonNull -    public List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager) { +    public List<RoutingSessionInfo> getRemoteSessions(@NonNull IMediaRouter2Manager manager) {          Objects.requireNonNull(manager, "manager must not be null");          final long token = Binder.clearCallingIdentity();          try { @@ -438,7 +433,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void registerManager(IMediaRouter2Manager manager, String packageName) { +    public void registerManager(@NonNull IMediaRouter2Manager manager, +            @NonNull String packageName) {          Objects.requireNonNull(manager, "manager must not be null");          if (TextUtils.isEmpty(packageName)) {              throw new IllegalArgumentException("packageName must not be empty"); @@ -458,7 +454,7 @@ class MediaRouter2ServiceImpl {          }      } -    public void unregisterManager(IMediaRouter2Manager manager) { +    public void unregisterManager(@NonNull IMediaRouter2Manager manager) {          Objects.requireNonNull(manager, "manager must not be null");          final long token = Binder.clearCallingIdentity(); @@ -471,7 +467,7 @@ class MediaRouter2ServiceImpl {          }      } -    public void startScan(IMediaRouter2Manager manager) { +    public void startScan(@NonNull IMediaRouter2Manager manager) {          Objects.requireNonNull(manager, "manager must not be null");          final long token = Binder.clearCallingIdentity();          try { @@ -483,7 +479,7 @@ class MediaRouter2ServiceImpl {          }      } -    public void stopScan(IMediaRouter2Manager manager) { +    public void stopScan(@NonNull IMediaRouter2Manager manager) {          Objects.requireNonNull(manager, "manager must not be null");          final long token = Binder.clearCallingIdentity();          try { @@ -495,8 +491,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId, -            MediaRoute2Info route, int volume) { +    public void setRouteVolumeWithManager(@NonNull IMediaRouter2Manager manager, int requestId, +            @NonNull MediaRoute2Info route, int volume) {          Objects.requireNonNull(manager, "manager must not be null");          Objects.requireNonNull(route, "route must not be null"); @@ -510,10 +506,11 @@ class MediaRouter2ServiceImpl {          }      } -    public void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId, -            RoutingSessionInfo oldSession, MediaRoute2Info route) { +    public void requestCreateSessionWithManager(@NonNull IMediaRouter2Manager manager, +            int requestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {          Objects.requireNonNull(manager, "manager must not be null");          Objects.requireNonNull(oldSession, "oldSession must not be null"); +        Objects.requireNonNull(route, "route must not be null");          final long token = Binder.clearCallingIdentity();          try { @@ -525,8 +522,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void selectRouteWithManager(IMediaRouter2Manager manager, int requestId, -            String uniqueSessionId, MediaRoute2Info route) { +    public void selectRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId, +            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {          Objects.requireNonNull(manager, "manager must not be null");          if (TextUtils.isEmpty(uniqueSessionId)) {              throw new IllegalArgumentException("uniqueSessionId must not be empty"); @@ -543,8 +540,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId, -            String uniqueSessionId, MediaRoute2Info route) { +    public void deselectRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId, +            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {          Objects.requireNonNull(manager, "manager must not be null");          if (TextUtils.isEmpty(uniqueSessionId)) {              throw new IllegalArgumentException("uniqueSessionId must not be empty"); @@ -561,8 +558,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId, -            String uniqueSessionId, MediaRoute2Info route) { +    public void transferToRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId, +            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {          Objects.requireNonNull(manager, "manager must not be null");          if (TextUtils.isEmpty(uniqueSessionId)) {              throw new IllegalArgumentException("uniqueSessionId must not be empty"); @@ -579,8 +576,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId, -            String uniqueSessionId, int volume) { +    public void setSessionVolumeWithManager(@NonNull IMediaRouter2Manager manager, int requestId, +            @NonNull String uniqueSessionId, int volume) {          Objects.requireNonNull(manager, "manager must not be null");          if (TextUtils.isEmpty(uniqueSessionId)) {              throw new IllegalArgumentException("uniqueSessionId must not be empty"); @@ -596,8 +593,8 @@ class MediaRouter2ServiceImpl {          }      } -    public void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, -            String uniqueSessionId) { +    public void releaseSessionWithManager(@NonNull IMediaRouter2Manager manager, int requestId, +            @NonNull String uniqueSessionId) {          Objects.requireNonNull(manager, "manager must not be null");          if (TextUtils.isEmpty(uniqueSessionId)) {              throw new IllegalArgumentException("uniqueSessionId must not be empty"); @@ -681,11 +678,6 @@ class MediaRouter2ServiceImpl {          return mUserManagerInternal.getProfileParentId(userId) == mCurrentActiveUserId;      } -    //////////////////////////////////////////////////////////////// -    ////  ***Locked methods related to MediaRouter2 -    ////   - Should have @NonNull/@Nullable on all arguments -    //////////////////////////////////////////////////////////////// -      @GuardedBy("mLock")      private void registerRouter2Locked(@NonNull IMediaRouter2 router, int uid, int pid,              @NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission, @@ -960,11 +952,6 @@ class MediaRouter2ServiceImpl {                          DUMMY_REQUEST_ID, routerRecord, uniqueSessionId));      } -    //////////////////////////////////////////////////////////// -    ////  ***Locked methods related to MediaRouter2Manager -    ////   - Should have @NonNull/@Nullable on all arguments -    //////////////////////////////////////////////////////////// -      private List<RoutingSessionInfo> getRemoteSessionsLocked(              @NonNull IMediaRouter2Manager manager) {          final IBinder binder = manager.asBinder(); @@ -1102,8 +1089,8 @@ class MediaRouter2ServiceImpl {      }      private void requestCreateSessionWithManagerLocked(int requestId, -            @NonNull IMediaRouter2Manager manager, -            @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) { +            @NonNull IMediaRouter2Manager manager, @NonNull RoutingSessionInfo oldSession, +            @NonNull MediaRoute2Info route) {          ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());          if (managerRecord == null) {              return; @@ -1250,8 +1237,7 @@ class MediaRouter2ServiceImpl {      }      private void releaseSessionWithManagerLocked(int requestId, -            @NonNull IMediaRouter2Manager manager, -            @NonNull String uniqueSessionId) { +            @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId) {          final IBinder binder = manager.asBinder();          ManagerRecord managerRecord = mAllManagerRecords.get(binder); @@ -1274,11 +1260,6 @@ class MediaRouter2ServiceImpl {                          uniqueRequestId, routerRecord, uniqueSessionId));      } -    //////////////////////////////////////////////////////////// -    ////  ***Locked methods used by both router2 and manager -    ////   - Should have @NonNull/@Nullable on all arguments -    //////////////////////////////////////////////////////////// -      @GuardedBy("mLock")      private UserRecord getOrCreateUserRecordLocked(int userId) {          UserRecord userRecord = mUserRecords.get(userId); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 77fea09b5ecc..f459c0e5eeb4 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -61,6 +61,7 @@ import static android.content.pm.PackageManager.FEATURE_LEANBACK;  import static android.content.pm.PackageManager.FEATURE_TELECOM;  import static android.content.pm.PackageManager.FEATURE_TELEVISION;  import static android.content.pm.PackageManager.MATCH_ALL; +import static android.content.pm.PackageManager.MATCH_ANY_USER;  import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;  import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;  import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -10700,10 +10701,18 @@ public class NotificationManagerService extends SystemService {          private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();          ArrayMap<Pair<ComponentName, Integer>, NotificationListenerFilter>                  mRequestedNotificationListeners = new ArrayMap<>(); +        private final boolean mIsHeadlessSystemUserMode;          public NotificationListeners(Context context, Object lock, UserProfiles userProfiles,                  IPackageManager pm) { +            this(context, lock, userProfiles, pm, UserManager.isHeadlessSystemUserMode()); +        } + +        @VisibleForTesting +        public NotificationListeners(Context context, Object lock, UserProfiles userProfiles, +                IPackageManager pm, boolean isHeadlessSystemUserMode) {              super(context, lock, userProfiles, pm); +            this.mIsHeadlessSystemUserMode = isHeadlessSystemUserMode;          }          @Override @@ -10728,10 +10737,16 @@ public class NotificationManagerService extends SystemService {                      if (TextUtils.isEmpty(listeners[i])) {                          continue;                      } +                    int packageQueryFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; +                    // In the headless system user mode, packages might not be installed for the +                    // system user. Match packages for any user since apps can be installed only for +                    // non-system users and would be considering uninstalled for the system user. +                    if (mIsHeadlessSystemUserMode) { +                        packageQueryFlags += MATCH_ANY_USER; +                    }                      ArraySet<ComponentName> approvedListeners = -                            this.queryPackageForServices(listeners[i], -                                    MATCH_DIRECT_BOOT_AWARE -                                            | MATCH_DIRECT_BOOT_UNAWARE, USER_SYSTEM); +                            this.queryPackageForServices(listeners[i], packageQueryFlags, +                                    USER_SYSTEM);                      for (int k = 0; k < approvedListeners.size(); k++) {                          ComponentName cn = approvedListeners.valueAt(k);                          addDefaultComponentOrPackage(cn.flattenToString()); diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 8e672c3b32c5..17bb39c945bd 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -166,6 +166,14 @@ final class OverlayManagerServiceImpl {          CollectionUtils.addAll(updatedTargets, removeOverlaysForUser(                  (info) -> !userPackages.containsKey(info.packageName), newUserId)); +        final ArraySet<String> overlaidByOthers = new ArraySet<>(); +        for (AndroidPackage androidPackage : userPackages.values()) { +            final String overlayTarget = androidPackage.getOverlayTarget(); +            if (!TextUtils.isEmpty(overlayTarget)) { +                overlaidByOthers.add(overlayTarget); +            } +        } +          // Update the state of all installed packages containing overlays, and initialize new          // overlays that are not currently in the settings.          for (int i = 0, n = userPackages.size(); i < n; i++) { @@ -175,8 +183,10 @@ final class OverlayManagerServiceImpl {                          updatePackageOverlays(pkg, newUserId, 0 /* flags */));                  // When a new user is switched to for the first time, package manager must be -                // informed of the overlay paths for all packages installed in the user. -                updatedTargets.add(new PackageAndUser(pkg.getPackageName(), newUserId)); +                // informed of the overlay paths for all overlaid packages installed in the user. +                if (overlaidByOthers.contains(pkg.getPackageName())) { +                    updatedTargets.add(new PackageAndUser(pkg.getPackageName(), newUserId)); +                }              } catch (OperationFailedException e) {                  Slog.e(TAG, "failed to initialize overlays of '" + pkg.getPackageName()                          + "' for user " + newUserId + "", e); diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index 3f04264714e4..c4f6836eba7b 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -18,6 +18,7 @@ package com.android.server.pm;  import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; +import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;  import static com.android.server.pm.ApexManager.ActiveApexInfo;  import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;  import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; @@ -34,6 +35,7 @@ import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PK  import android.Manifest;  import android.annotation.NonNull; +import android.annotation.Nullable;  import android.annotation.RequiresPermission;  import android.app.ActivityManager;  import android.app.AppGlobals; @@ -56,9 +58,16 @@ import android.util.Slog;  import com.android.internal.R;  import com.android.internal.annotations.GuardedBy;  import com.android.internal.logging.MetricsLogger; +import com.android.server.LocalManagerRegistry; +import com.android.server.art.ArtManagerLocal; +import com.android.server.art.model.ArtFlags; +import com.android.server.art.model.OptimizeParams; +import com.android.server.art.model.OptimizeResult; +import com.android.server.pm.PackageDexOptimizer.DexOptResult;  import com.android.server.pm.dex.DexManager;  import com.android.server.pm.dex.DexoptOptions;  import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState;  import com.android.server.pm.pkg.PackageStateInternal;  import dalvik.system.DexFile; @@ -72,11 +81,15 @@ import java.util.Collections;  import java.util.Comparator;  import java.util.HashSet;  import java.util.List; +import java.util.Optional;  import java.util.Set;  import java.util.concurrent.TimeUnit;  import java.util.function.Predicate; -final class DexOptHelper { +/** + * Helper class for dex optimization operations in PackageManagerService. + */ +public final class DexOptHelper {      private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;      private final PackageManagerService mPm; @@ -405,11 +418,12 @@ final class DexOptHelper {       * {@link PackageDexOptimizer#DEX_OPT_CANCELLED}       * {@link PackageDexOptimizer#DEX_OPT_FAILED}       */ -    @PackageDexOptimizer.DexOptResult +    @DexOptResult      /* package */ int performDexOptWithStatus(DexoptOptions options) {          return performDexOptTraced(options);      } +    @DexOptResult      private int performDexOptTraced(DexoptOptions options) {          Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");          try { @@ -421,7 +435,13 @@ final class DexOptHelper {      // Run dexopt on a given package. Returns true if dexopt did not fail, i.e.      // if the package can now be considered up to date for the given filter. +    @DexOptResult      private int performDexOptInternal(DexoptOptions options) { +        Optional<Integer> artSrvRes = performDexOptWithArtService(options); +        if (artSrvRes.isPresent()) { +            return artSrvRes.get(); +        } +          AndroidPackage p;          PackageSetting pkgSetting;          synchronized (mPm.mLock) { @@ -446,8 +466,74 @@ final class DexOptHelper {          }      } -    private int performDexOptInternalWithDependenciesLI(AndroidPackage p, -            @NonNull PackageStateInternal pkgSetting, DexoptOptions options) { +    /** +     * Performs dexopt on the given package using ART Service. +     * +     * @return a {@link DexOptResult}, or empty if the request isn't supported so that it is +     *     necessary to fall back to the legacy code paths. +     */ +    private Optional<Integer> performDexOptWithArtService(DexoptOptions options) { +        ArtManagerLocal artManager = getArtManagerLocal(); +        if (artManager == null) { +            return Optional.empty(); +        } + +        try (PackageManagerLocal.FilteredSnapshot snapshot = +                        getPackageManagerLocal().withFilteredSnapshot()) { +            PackageState ops = snapshot.getPackageState(options.getPackageName()); +            if (ops == null) { +                return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED); +            } +            AndroidPackage oap = ops.getAndroidPackage(); +            if (oap == null) { +                return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED); +            } +            if (oap.isApex()) { +                return Optional.of(PackageDexOptimizer.DEX_OPT_SKIPPED); +            } + +            // TODO(b/245301593): Delete the conditional when ART Service supports +            // FLAG_SHOULD_INCLUDE_DEPENDENCIES and we can just set it unconditionally. +            /*@OptimizeFlags*/ int extraFlags = ops.getUsesLibraries().isEmpty() +                    ? 0 +                    : ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES; + +            OptimizeParams params = options.convertToOptimizeParams(extraFlags); +            if (params == null) { +                return Optional.empty(); +            } + +            // TODO(b/251903639): Either remove controlDexOptBlocking, or don't ignore it here. +            OptimizeResult result; +            try { +                result = artManager.optimizePackage(snapshot, options.getPackageName(), params); +            } catch (UnsupportedOperationException e) { +                reportArtManagerFallback(options.getPackageName(), e.toString()); +                return Optional.empty(); +            } + +            // TODO(b/251903639): Move this to ArtManagerLocal.addOptimizePackageDoneCallback when +            // it is implemented. +            for (OptimizeResult.PackageOptimizeResult pkgRes : result.getPackageOptimizeResults()) { +                PackageState ps = snapshot.getPackageState(pkgRes.getPackageName()); +                AndroidPackage ap = ps != null ? ps.getAndroidPackage() : null; +                if (ap != null) { +                    CompilerStats.PackageStats stats = mPm.getOrCreateCompilerPackageStats(ap); +                    for (OptimizeResult.DexContainerFileOptimizeResult dexRes : +                            pkgRes.getDexContainerFileOptimizeResults()) { +                        stats.setCompileTime( +                                dexRes.getDexContainerFile(), dexRes.getDex2oatWallTimeMillis()); +                    } +                } +            } + +            return Optional.of(convertToDexOptResult(result)); +        } +    } + +    @DexOptResult +    private int performDexOptInternalWithDependenciesLI( +            AndroidPackage p, @NonNull PackageStateInternal pkgSetting, DexoptOptions options) {          // System server gets a special path.          if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) {              return mPm.getDexManager().dexoptSystemServer(options); @@ -514,10 +600,20 @@ final class DexOptHelper {          // Whoever is calling forceDexOpt wants a compiled package.          // Don't use profiles since that may cause compilation to be skipped. -        final int res = performDexOptInternalWithDependenciesLI(pkg, packageState, -                new DexoptOptions(packageName, REASON_CMDLINE, -                        getDefaultCompilerFilter(), null /* splitName */, -                        DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE)); +        DexoptOptions options = new DexoptOptions(packageName, REASON_CMDLINE, +                getDefaultCompilerFilter(), null /* splitName */, +                DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE); + +        // performDexOptWithArtService ignores the snapshot and takes its own, so it can race with +        // the package checks above, but at worst the effect is only a bit less friendly error +        // below. +        Optional<Integer> artSrvRes = performDexOptWithArtService(options); +        int res; +        if (artSrvRes.isPresent()) { +            res = artSrvRes.get(); +        } else { +            res = performDexOptInternalWithDependenciesLI(pkg, packageState, options); +        }          Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);          if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) { @@ -800,4 +896,59 @@ final class DexOptHelper {          }          return false;      } + +    private @NonNull PackageManagerLocal getPackageManagerLocal() { +        try { +            return LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal.class); +        } catch (ManagerNotFoundException e) { +            throw new RuntimeException(e); +        } +    } + +    /** +     * Called whenever we need to fall back from ART Service to the legacy dexopt code. +     */ +    public static void reportArtManagerFallback(String packageName, String reason) { +        // STOPSHIP(b/251903639): Minimize these calls to avoid platform getting shipped with code +        // paths that will always bypass ART Service. +        Slog.i(TAG, "Falling back to old PackageManager dexopt for " + packageName + ": " + reason); +    } + +    /** +     * Returns {@link ArtManagerLocal} if one is found and should be used for package optimization. +     */ +    private @Nullable ArtManagerLocal getArtManagerLocal() { +        if (!"true".equals(SystemProperties.get("dalvik.vm.useartservice", ""))) { +            return null; +        } +        try { +            return LocalManagerRegistry.getManagerOrThrow(ArtManagerLocal.class); +        } catch (ManagerNotFoundException e) { +            throw new RuntimeException(e); +        } +    } + +    /** +     * Converts an ART Service {@link OptimizeResult} to {@link DexOptResult}. +     * +     * For interfacing {@link ArtManagerLocal} with legacy dex optimization code in PackageManager. +     */ +    @DexOptResult +    private static int convertToDexOptResult(OptimizeResult result) { +        /*@OptimizeStatus*/ int status = result.getFinalStatus(); +        switch (status) { +            case OptimizeResult.OPTIMIZE_SKIPPED: +                return PackageDexOptimizer.DEX_OPT_SKIPPED; +            case OptimizeResult.OPTIMIZE_FAILED: +                return PackageDexOptimizer.DEX_OPT_FAILED; +            case OptimizeResult.OPTIMIZE_PERFORMED: +                return PackageDexOptimizer.DEX_OPT_PERFORMED; +            case OptimizeResult.OPTIMIZE_CANCELLED: +                return PackageDexOptimizer.DEX_OPT_CANCELLED; +            default: +                throw new IllegalArgumentException("OptimizeResult for " +                        + result.getPackageOptimizeResults().get(0).getPackageName() +                        + " has unsupported status " + status); +        } +    }  } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index d25bca76245b..2a2410fd1767 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -652,12 +652,6 @@ public class PackageDexOptimizer {      @DexOptResult      private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path,              PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) { -        if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) { -            // We are asked to optimize only the dex files used by other apps and this is not -            // on of them: skip it. -            return DEX_OPT_SKIPPED; -        } -          String compilerFilter = getRealCompilerFilter(info, options.getCompilerFilter(),                  dexUseInfo.isUsedByOtherApps());          // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags. diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 60f247843bb7..21191919c8e0 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2633,6 +2633,9 @@ public class UserManagerService extends IUserManager.Stub {      /** @return a specific user restriction that's in effect currently. */      @Override      public boolean hasUserRestriction(String restrictionKey, @UserIdInt int userId) { +        if (!userExists(userId)) { +            return false; +        }          checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "hasUserRestriction");          return mLocalService.hasUserRestriction(restrictionKey, userId);      } @@ -5516,6 +5519,13 @@ public class UserManagerService extends IUserManager.Stub {      private void removeUserState(final @UserIdInt int userId) {          Slog.i(LOG_TAG, "Removing user state of user " + userId); + +        // Cleanup lock settings.  This must happen before destroyUserKey(), since the user's DE +        // storage must still be accessible for the lock settings state to be properly cleaned up. +        mLockPatternUtils.removeUser(userId); + +        // Evict and destroy the user's CE and DE encryption keys.  At this point, the user's CE and +        // DE storage is made inaccessible, except to delete its contents.          try {              mContext.getSystemService(StorageManager.class).destroyUserKey(userId);          } catch (IllegalStateException e) { @@ -5523,9 +5533,6 @@ public class UserManagerService extends IUserManager.Stub {              Slog.i(LOG_TAG, "Destroying key for user " + userId + " failed, continuing anyway", e);          } -        // Cleanup lock settings -        mLockPatternUtils.removeUser(userId); -          // Cleanup package manager settings          mPm.cleanUpUser(this, userId); diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java index ea233161b4af..f5557c417f1b 100644 --- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java +++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java @@ -18,6 +18,16 @@ package com.android.server.pm.dex;  import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason; +import android.annotation.Nullable; + +import com.android.server.art.ReasonMapping; +import com.android.server.art.model.ArtFlags; +import com.android.server.art.model.OptimizeParams; +import com.android.server.pm.DexOptHelper; +import com.android.server.pm.PackageManagerService; + +import dalvik.system.DexFile; +  /**   * Options used for dexopt invocations.   */ @@ -40,10 +50,6 @@ public final class DexoptOptions {      // will only consider the primary apk.      public static final int DEXOPT_ONLY_SECONDARY_DEX = 1 << 3; -    // When set, dexopt will optimize only dex files that are used by other apps. -    // Currently, this flag is ignored for primary apks. -    public static final int DEXOPT_ONLY_SHARED_DEX = 1 << 4; -      // When set, dexopt will attempt to scale down the optimizations previously applied in order      // save disk space.      public static final int DEXOPT_DOWNGRADE = 1 << 5; @@ -105,7 +111,6 @@ public final class DexoptOptions {                  DEXOPT_FORCE |                  DEXOPT_BOOT_COMPLETE |                  DEXOPT_ONLY_SECONDARY_DEX | -                DEXOPT_ONLY_SHARED_DEX |                  DEXOPT_DOWNGRADE |                  DEXOPT_AS_SHARED_LIBRARY |                  DEXOPT_IDLE_BACKGROUND_JOB | @@ -146,10 +151,6 @@ public final class DexoptOptions {          return (mFlags & DEXOPT_ONLY_SECONDARY_DEX) != 0;      } -    public boolean isDexoptOnlySharedDex() { -        return (mFlags & DEXOPT_ONLY_SHARED_DEX) != 0; -    } -      public boolean isDowngrade() {          return (mFlags & DEXOPT_DOWNGRADE) != 0;      } @@ -198,4 +199,133 @@ public final class DexoptOptions {                  mSplitName,                  mFlags);      } + +    /** +     * Returns an {@link OptimizeParams} instance corresponding to this object, for use with +     * {@link com.android.server.art.ArtManagerLocal}. +     * +     * @param extraFlags extra {@link ArtFlags#OptimizeFlags} to set in the returned +     *     {@code OptimizeParams} beyond those converted from this object +     * @return null if the settings cannot be accurately represented, and hence the old +     *     PackageManager/installd code paths need to be used. +     */ +    public @Nullable OptimizeParams convertToOptimizeParams(/*@OptimizeFlags*/ int extraFlags) { +        if (mSplitName != null) { +            DexOptHelper.reportArtManagerFallback( +                    mPackageName, "Request to optimize only split " + mSplitName); +            return null; +        } + +        /*@OptimizeFlags*/ int flags = extraFlags; +        if ((mFlags & DEXOPT_CHECK_FOR_PROFILES_UPDATES) == 0 +                && DexFile.isProfileGuidedCompilerFilter(mCompilerFilter)) { +            // ART Service doesn't support bypassing this, so not setting this flag is not +            // supported. +            DexOptHelper.reportArtManagerFallback(mPackageName, +                    "DEXOPT_CHECK_FOR_PROFILES_UPDATES not set with profile compiler filter"); +            return null; +        } +        if ((mFlags & DEXOPT_FORCE) != 0) { +            flags |= ArtFlags.FLAG_FORCE; +        } +        if ((mFlags & DEXOPT_ONLY_SECONDARY_DEX) != 0) { +            flags |= ArtFlags.FLAG_FOR_SECONDARY_DEX; +        } else { +            flags |= ArtFlags.FLAG_FOR_PRIMARY_DEX; +        } +        if ((mFlags & DEXOPT_DOWNGRADE) != 0) { +            flags |= ArtFlags.FLAG_SHOULD_DOWNGRADE; +        } +        if ((mFlags & DEXOPT_INSTALL_WITH_DEX_METADATA_FILE) == 0) { +            // ART Service cannot be instructed to ignore a DM file if present, so not setting this +            // flag is not supported. +            DexOptHelper.reportArtManagerFallback( +                    mPackageName, "DEXOPT_INSTALL_WITH_DEX_METADATA_FILE not set"); +            return null; +        } + +        /*@PriorityClassApi*/ int priority; +        // Replicates logic in RunDex2Oat::PrepareCompilerRuntimeAndPerfConfigFlags in installd. +        if ((mFlags & DEXOPT_BOOT_COMPLETE) != 0) { +            if ((mFlags & DEXOPT_FOR_RESTORE) != 0) { +                priority = ArtFlags.PRIORITY_INTERACTIVE_FAST; +            } else { +                // TODO(b/251903639): Repurpose DEXOPT_IDLE_BACKGROUND_JOB to choose new +                // dalvik.vm.background-dex2oat-* properties. +                priority = ArtFlags.PRIORITY_INTERACTIVE; +            } +        } else { +            priority = ArtFlags.PRIORITY_BOOT; +        } + +        // The following flags in mFlags are ignored: +        // +        // -  DEXOPT_AS_SHARED_LIBRARY: It's implicit with ART Service since it always looks at +        //    <uses-library> rather than actual dependencies. +        // +        //    We don't require it to be set either. It's safe when switching between old and new +        //    code paths since the only effect is that some packages may be unnecessarily compiled +        //    without user profiles. +        // +        // -  DEXOPT_IDLE_BACKGROUND_JOB: Its only effect is to allow the debug variant dex2oatd to +        //    be used, but ART Service never uses that (cf. Artd::GetDex2Oat in artd.cc). + +        String reason; +        switch (mCompilationReason) { +            case PackageManagerService.REASON_FIRST_BOOT: +                reason = ReasonMapping.REASON_FIRST_BOOT; +                break; +            case PackageManagerService.REASON_BOOT_AFTER_OTA: +                reason = ReasonMapping.REASON_BOOT_AFTER_OTA; +                break; +            case PackageManagerService.REASON_POST_BOOT: +                // This reason will go away with the legacy dexopt code. +                DexOptHelper.reportArtManagerFallback( +                        mPackageName, "Unsupported compilation reason REASON_POST_BOOT"); +                return null; +            case PackageManagerService.REASON_INSTALL: +                reason = ReasonMapping.REASON_INSTALL; +                break; +            case PackageManagerService.REASON_INSTALL_FAST: +                reason = ReasonMapping.REASON_INSTALL_FAST; +                break; +            case PackageManagerService.REASON_INSTALL_BULK: +                reason = ReasonMapping.REASON_INSTALL_BULK; +                break; +            case PackageManagerService.REASON_INSTALL_BULK_SECONDARY: +                reason = ReasonMapping.REASON_INSTALL_BULK_SECONDARY; +                break; +            case PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED: +                reason = ReasonMapping.REASON_INSTALL_BULK_DOWNGRADED; +                break; +            case PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED: +                reason = ReasonMapping.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED; +                break; +            case PackageManagerService.REASON_BACKGROUND_DEXOPT: +                reason = ReasonMapping.REASON_BG_DEXOPT; +                break; +            case PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE: +                reason = ReasonMapping.REASON_INACTIVE; +                break; +            case PackageManagerService.REASON_CMDLINE: +                reason = ReasonMapping.REASON_CMDLINE; +                break; +            case PackageManagerService.REASON_SHARED: +            case PackageManagerService.REASON_AB_OTA: +                // REASON_SHARED shouldn't go into this code path - it's only used at lower levels +                // in PackageDexOptimizer. +                // TODO(b/251921228): OTA isn't supported, so REASON_AB_OTA shouldn't come this way +                // either. +                throw new UnsupportedOperationException( +                        "ART Service unsupported compilation reason " + mCompilationReason); +            default: +                throw new IllegalArgumentException( +                        "Invalid compilation reason " + mCompilationReason); +        } + +        return new OptimizeParams.Builder(reason, flags) +                .setCompilerFilter(mCompilerFilter) +                .setPriorityClass(priority) +                .build(); +    }  } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 5abc875a697f..4784723b7735 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -6768,6 +6768,11 @@ public final class PowerManagerService extends SystemService          public void nap(long eventTime, boolean allowWake) {              napInternal(eventTime, Process.SYSTEM_UID, allowWake);          } + +        @Override +        public boolean isAmbientDisplaySuppressed() { +            return mAmbientDisplaySuppressionController.isSuppressed(); +        }      }      /** diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index edd1ef36deda..ad1ff72f653c 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -946,8 +946,12 @@ public class TunerResourceManagerService extends SystemService implements IBinde          int inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;          // Priority max value is 1000          int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; +        // If the desired frontend id was specified, we only need to check the frontend. +        boolean hasDesiredFrontend = request.desiredId != TunerFrontendRequest.DEFAULT_DESIRED_ID;          for (FrontendResource fr : getFrontendResources().values()) { -            if (fr.getType() == request.frontendType) { +            int frontendId = getResourceIdFromHandle(fr.getHandle()); +            if (fr.getType() == request.frontendType +                    && (!hasDesiredFrontend || frontendId == request.desiredId)) {                  if (!fr.isInUse()) {                      // Unused resource cannot be acquired if the max is already reached, but                      // TRM still has to look for the reclaim candidate diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java index 8ac4fd4860bb..141be702ee13 100644 --- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java +++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java @@ -65,6 +65,9 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {      public final DeviceVibrationEffectAdapter deviceEffectAdapter;      public final VibrationThread.VibratorManagerHooks vibratorManagerHooks; +    // Not guarded by lock because they're not modified by this conductor, it's used here only to +    // check immutable attributes. The status and other mutable states are changed by the service or +    // by the vibrator steps.      private final Vibration mVibration;      private final SparseArray<VibratorController> mVibrators = new SparseArray<>(); @@ -412,6 +415,16 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {          }      } +    /** Returns true if a cancellation signal was sent via {@link #notifyCancelled}. */ +    public boolean wasNotifiedToCancel() { +        if (Build.IS_DEBUGGABLE) { +            expectIsVibrationThread(false); +        } +        synchronized (mLock) { +            return mSignalCancel != null; +        } +    } +      @GuardedBy("mLock")      private boolean hasPendingNotifySignalLocked() {          if (Build.IS_DEBUGGABLE) { diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 8514e272e250..8613b5027d57 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -864,8 +864,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {          }          Vibration currentVibration = mCurrentVibration.getVibration(); -        if (currentVibration.hasEnded()) { -            // Current vibration is finishing up, it should not block incoming vibrations. +        if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) { +            // Current vibration has ended or is cancelling, should not block incoming vibrations.              return null;          } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 214a2c197a5c..3c457e1cc277 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2578,6 +2578,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {                          // activity lifecycle transaction to make sure the override pending app                          // transition will be applied immediately.                          targetActivity.applyOptionsAnimation(); +                        if (activityOptions != null && activityOptions.getLaunchCookie() != null) { +                            targetActivity.mLaunchCookie = activityOptions.getLaunchCookie(); +                        }                      } finally {                          mActivityMetricsLogger.notifyActivityLaunched(launchingState,                                  START_TASK_TO_FRONT, false /* newActivityCreated */, diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index e9774479233d..30399ed15f7e 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -588,6 +588,7 @@ class BackNavigationController {          ProtoLog.d(WM_DEBUG_BACK_PREVIEW,                  "Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity); +        activity.mTaskSupervisor.mStoppingActivities.remove(activity);          activity.getDisplayContent().ensureActivitiesVisible(null /* starting */,                  0 /* configChanges */, false /* preserveWindows */, true);      } diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp index 424ffd463713..34e4976dcca0 100644 --- a/services/core/jni/gnss/AGnssRil.cpp +++ b/services/core/jni/gnss/AGnssRil.cpp @@ -55,13 +55,13 @@ jboolean AGnssRil::setRefLocation(jint type, jint mcc, jint mnc, jint lac, jlong          case IAGnssRil::AGnssRefLocationType::UMTS_CELLID:          case IAGnssRil::AGnssRefLocationType::LTE_CELLID:          case IAGnssRil::AGnssRefLocationType::NR_CELLID: -            location.cellID.mcc = mcc; -            location.cellID.mnc = mnc; -            location.cellID.lac = lac; -            location.cellID.cid = cid; -            location.cellID.tac = tac; -            location.cellID.pcid = pcid; -            location.cellID.arfcn = arfcn; +            location.cellID.mcc = static_cast<int>(mcc); +            location.cellID.mnc = static_cast<int>(mnc); +            location.cellID.lac = static_cast<int>(lac); +            location.cellID.cid = static_cast<long>(cid); +            location.cellID.tac = static_cast<int>(tac); +            location.cellID.pcid = static_cast<int>(pcid); +            location.cellID.arfcn = static_cast<int>(arfcn);              break;          default:              ALOGE("Unknown cellid (%s:%d).", __FUNCTION__, __LINE__); @@ -106,20 +106,24 @@ jboolean AGnssRil_V1_0::setSetId(jint type, const jstring& setid_string) {      return checkHidlReturn(result, "IAGnssRil_V1_0 setSetId() failed.");  } -jboolean AGnssRil_V1_0::setRefLocation(jint type, jint mcc, jint mnc, jint lac, jlong cid, jint, -                                       jint, jint) { +jboolean AGnssRil_V1_0::setRefLocation(jint type, jint mcc, jint mnc, jint lac, jlong cid, jint tac, +                                       jint pcid, jint) {      IAGnssRil_V1_0::AGnssRefLocation location; -    switch (static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type)) { +    location.type = static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type); + +    switch (location.type) {          case IAGnssRil_V1_0::AGnssRefLocationType::GSM_CELLID:          case IAGnssRil_V1_0::AGnssRefLocationType::UMTS_CELLID: -            location.type = static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type); -            location.cellID.mcc = mcc; -            location.cellID.mnc = mnc; -            location.cellID.lac = lac; -            location.cellID.cid = cid; +        case IAGnssRil_V1_0::AGnssRefLocationType::LTE_CELLID: +            location.cellID.mcc = static_cast<uint16_t>(mcc); +            location.cellID.mnc = static_cast<uint16_t>(mnc); +            location.cellID.lac = static_cast<uint16_t>(lac); +            location.cellID.cid = static_cast<uint32_t>(cid); +            location.cellID.tac = static_cast<uint16_t>(tac); +            location.cellID.pcid = static_cast<uint16_t>(pcid);              break;          default: -            ALOGE("Neither a GSM nor a UMTS cellid (%s:%d).", __FUNCTION__, __LINE__); +            ALOGE("Unknown cellid (%s:%d).", __FUNCTION__, __LINE__);              return JNI_FALSE;              break;      } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index 90b1f4ecdcb3..abc32c9339ae 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -39,6 +39,8 @@ import android.app.AppOpsManager;  import android.app.BroadcastOptions;  import android.content.Intent;  import android.content.IntentFilter; +import android.media.AudioManager; +import android.os.Bundle;  import android.os.HandlerThread;  import android.os.UserHandle;  import android.provider.Settings; @@ -386,4 +388,86 @@ public class BroadcastQueueModernImplTest {          assertEquals(Intent.ACTION_SCREEN_OFF, queue.getActive().intent.getAction());          assertTrue(queue.isEmpty());      } + +    /** +     * Verify that sending a broadcast with DELIVERY_GROUP_POLICY_MOST_RECENT works as expected. +     */ +    @Test +    public void testDeliveryGroupPolicy_mostRecent() { +        final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); +        final BroadcastOptions optionsTimeTick = BroadcastOptions.makeBasic(); +        optionsTimeTick.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); + +        final Intent musicVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION); +        musicVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, +                AudioManager.STREAM_MUSIC); +        final BroadcastOptions optionsMusicVolumeChanged = BroadcastOptions.makeBasic(); +        optionsMusicVolumeChanged.setDeliveryGroupPolicy( +                BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); +        optionsMusicVolumeChanged.setDeliveryGroupKey("audio", +                String.valueOf(AudioManager.STREAM_MUSIC)); + +        final Intent alarmVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION); +        alarmVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, +                AudioManager.STREAM_ALARM); +        final BroadcastOptions optionsAlarmVolumeChanged = BroadcastOptions.makeBasic(); +        optionsAlarmVolumeChanged.setDeliveryGroupPolicy( +                BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); +        optionsAlarmVolumeChanged.setDeliveryGroupKey("audio", +                String.valueOf(AudioManager.STREAM_ALARM)); + +        // Halt all processing so that we get a consistent view +        mHandlerThread.getLooper().getQueue().postSyncBarrier(); + +        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); +        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, +                optionsMusicVolumeChanged)); +        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged, +                optionsAlarmVolumeChanged)); +        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, +                optionsMusicVolumeChanged)); + +        final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN, +                getUidForPackage(PACKAGE_GREEN)); +        // Verify that the older musicVolumeChanged has been removed. +        verifyPendingRecords(queue, +                List.of(timeTick, alarmVolumeChanged, musicVolumeChanged)); + +        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); +        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged, +                optionsAlarmVolumeChanged)); +        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, +                optionsMusicVolumeChanged)); +        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged, +                optionsAlarmVolumeChanged)); +        // Verify that the older alarmVolumeChanged has been removed. +        verifyPendingRecords(queue, +                List.of(timeTick, musicVolumeChanged, alarmVolumeChanged)); + +        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); +        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, +                optionsMusicVolumeChanged)); +        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged, +                optionsAlarmVolumeChanged)); +        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); +        // Verify that the older timeTick has been removed. +        verifyPendingRecords(queue, +                List.of(musicVolumeChanged, alarmVolumeChanged, timeTick)); +    } + +    private void verifyPendingRecords(BroadcastProcessQueue queue, +            List<Intent> intents) { +        for (int i = 0; i < intents.size(); i++) { +            queue.makeActiveNextPending(); +            final Intent actualIntent = queue.getActive().intent; +            final Intent expectedIntent = intents.get(i); +            final String errMsg = "actual=" + actualIntent + ", expected=" + expectedIntent +                    + ", actual_extras=" + actualIntent.getExtras() +                    + ", expected_extras=" + expectedIntent.getExtras(); +            assertTrue(errMsg, actualIntent.filterEquals(expectedIntent)); +            assertTrue(errMsg, Bundle.kindofEquals( +                    actualIntent.getExtras(), expectedIntent.getExtras())); +        } +        assertTrue(queue.isEmpty()); +    }  } diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java index 581a2a71a9b6..2d7d46f83c47 100644 --- a/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;  import static org.junit.Assert.fail;  import android.app.backup.BackupTransport; +import android.app.backup.IBackupManagerMonitor;  import android.app.backup.RestoreDescription;  import android.app.backup.RestoreSet;  import android.content.Intent; @@ -254,6 +255,9 @@ public class BackupTransportClientTest {              ITransportStatusCallback c) throws RemoteException {}          @Override public void abortFullRestore(ITransportStatusCallback c) throws RemoteException {}          @Override public void getTransportFlags(AndroidFuture<Integer> f) throws RemoteException {} +        @Override +        public void getBackupManagerMonitor(AndroidFuture<IBackupManagerMonitor> resultFuture) +                throws RemoteException {}          @Override public IBinder asBinder() {              return null;          } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 73548a3a1132..1b5db0a35449 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -41,6 +41,7 @@ import android.hardware.biometrics.common.OperationContext;  import android.hardware.biometrics.fingerprint.ISession;  import android.hardware.biometrics.fingerprint.PointerContext;  import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintManager;  import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;  import android.hardware.fingerprint.ISidefpsController;  import android.hardware.fingerprint.IUdfpsOverlayController; @@ -74,6 +75,7 @@ import org.mockito.Mock;  import org.mockito.junit.MockitoJUnit;  import org.mockito.junit.MockitoRule; +import java.time.Clock;  import java.util.ArrayList;  import java.util.List;  import java.util.function.Consumer; @@ -131,6 +133,8 @@ public class FingerprintAuthenticationClientTest {      private Probe mLuxProbe;      @Mock      private AuthSessionCoordinator mAuthSessionCoordinator; +    @Mock +    private Clock mClock;      @Captor      private ArgumentCaptor<OperationContext> mOperationContextCaptor;      @Captor @@ -451,6 +455,52 @@ public class FingerprintAuthenticationClientTest {      }      @Test +    public void sideFingerprintSkipsWindowIfVendorMessageMatch() throws Exception { +        when(mSensorProps.isAnySidefpsType()).thenReturn(true); +        final int vendorAcquireMessage = 1234; + +        mContext.getOrCreateTestableResources().addOverride( +                R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, +                FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR); +        mContext.getOrCreateTestableResources().addOverride( +                R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage, +                vendorAcquireMessage); + +        final FingerprintAuthenticationClient client = createClient(1); +        client.start(mCallback); +        mLooper.dispatchAll(); +        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), +                true /* authenticated */, new ArrayList<>()); +        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, vendorAcquireMessage); +        mLooper.dispatchAll(); + +        verify(mCallback).onClientFinished(any(), eq(true)); +    } + +    @Test +    public void sideFingerprintDoesNotSkipWindowOnVendorErrorMismatch() throws Exception { +        when(mSensorProps.isAnySidefpsType()).thenReturn(true); +        final int vendorAcquireMessage = 1234; + +        mContext.getOrCreateTestableResources().addOverride( +                R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, +                FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR); +        mContext.getOrCreateTestableResources().addOverride( +                R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage, +                vendorAcquireMessage); + +        final FingerprintAuthenticationClient client = createClient(1); +        client.start(mCallback); +        mLooper.dispatchAll(); +        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), +                true /* authenticated */, new ArrayList<>()); +        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 1); +        mLooper.dispatchAll(); + +        verify(mCallback, never()).onClientFinished(any(), anyBoolean()); +    } + +    @Test      public void sideFingerprintSendsAuthIfFingerUp() throws Exception {          when(mSensorProps.isAnySidefpsType()).thenReturn(true); @@ -497,6 +547,79 @@ public class FingerprintAuthenticationClientTest {          verify(mCallback).onClientFinished(any(), eq(true));      } +    @Test +    public void sideFingerprintPowerWindowStartsOnAcquireStart() throws Exception { +        final int powerWindow = 500; +        final long authStart = 300; + +        when(mSensorProps.isAnySidefpsType()).thenReturn(true); +        mContext.getOrCreateTestableResources().addOverride( +                R.integer.config_sidefpsBpPowerPressWindow, powerWindow); + +        final FingerprintAuthenticationClient client = createClient(1); +        client.start(mCallback); + +        // Acquire start occurs at time = 0ms +        when(mClock.millis()).thenReturn(0L); +        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */); + +        // Auth occurs at time = 300 +        when(mClock.millis()).thenReturn(authStart); +        // At this point the delay should be 500 - (300 - 0) == 200 milliseconds. +        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), +                true /* authenticated */, new ArrayList<>()); +        mLooper.dispatchAll(); +        verify(mCallback, never()).onClientFinished(any(), anyBoolean()); + +        // After waiting 200 milliseconds, auth should succeed. +        mLooper.moveTimeForward(powerWindow - authStart); +        mLooper.dispatchAll(); +        verify(mCallback).onClientFinished(any(), eq(true)); +    } + +    @Test +    public void sideFingerprintPowerWindowStartsOnLastAcquireStart() throws Exception { +        final int powerWindow = 500; + +        when(mSensorProps.isAnySidefpsType()).thenReturn(true); +        mContext.getOrCreateTestableResources().addOverride( +                R.integer.config_sidefpsBpPowerPressWindow, powerWindow); + +        final FingerprintAuthenticationClient client = createClient(1); +        client.start(mCallback); +        // Acquire start occurs at time = 0ms +        when(mClock.millis()).thenReturn(0L); +        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */); + +        // Auth reject occurs at time = 300ms +        when(mClock.millis()).thenReturn(300L); +        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), +                false /* authenticated */, new ArrayList<>()); +        mLooper.dispatchAll(); + +        mLooper.moveTimeForward(300); +        mLooper.dispatchAll(); +        verify(mCallback, never()).onClientFinished(any(), anyBoolean()); + +        when(mClock.millis()).thenReturn(1300L); +        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */); + +        // If code is correct, the new acquired start timestamp should be used +        // and the code should only have to wait 500 - (1500-1300)ms. +        when(mClock.millis()).thenReturn(1500L); +        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), +                true /* authenticated */, new ArrayList<>()); +        mLooper.dispatchAll(); + +        mLooper.moveTimeForward(299); +        mLooper.dispatchAll(); +        verify(mCallback, never()).onClientFinished(any(), anyBoolean()); + +        mLooper.moveTimeForward(1); +        mLooper.dispatchAll(); +        verify(mCallback).onClientFinished(any(), eq(true)); +    } +      private FingerprintAuthenticationClient createClient() throws RemoteException {          return createClient(100 /* version */, true /* allowBackgroundAuthentication */);      } @@ -524,7 +647,7 @@ public class FingerprintAuthenticationClientTest {                  null /* taskStackListener */, mLockoutCache,                  mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,                  mSensorProps, -                new Handler(mLooper.getLooper()), 0 /* biometricStrength */) { +                new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {              @Override              protected ActivityTaskManager getActivityTaskManager() {                  return mActivityTaskManager; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java index 559a2c0d6a09..29eccd46650e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java @@ -118,7 +118,13 @@ final class FakeNativeWrapper implements NativeWrapper {      }      @Override -    public void nativeSetOption(int flag, boolean enabled) {} +    public void enableWakeupByOtp(boolean enabled) {} + +    @Override +    public void enableCec(boolean enabled) {} + +    @Override +    public void enableSystemCecControl(boolean enabled) {}      @Override      public void nativeSetLanguage(String language) {} diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java index 1dcdbac8a7c2..dbcd38c35958 100644 --- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java @@ -16,6 +16,8 @@  package com.android.server.locales; +import static com.google.common.truth.Truth.assertThat; +  import static junit.framework.Assert.assertEquals;  import static junit.framework.Assert.assertNull;  import static junit.framework.Assert.fail; @@ -35,13 +37,16 @@ import static org.mockito.Mockito.verify;  import android.Manifest;  import android.app.ActivityManagerInternal; +import android.content.ComponentName;  import android.content.Context;  import android.content.pm.InstallSourceInfo;  import android.content.pm.PackageInstaller;  import android.content.pm.PackageManager;  import android.os.Binder;  import android.os.LocaleList; +import android.provider.Settings; +import androidx.test.InstrumentationRegistry;  import androidx.test.ext.junit.runners.AndroidJUnit4;  import com.android.internal.content.PackageMonitor; @@ -111,6 +116,8 @@ public class LocaleManagerServiceTest {          doReturn(DEFAULT_USER_ID).when(mMockActivityManager)                  .handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(),                          anyString(), anyString()); +        doReturn(InstrumentationRegistry.getContext().getContentResolver()) +                .when(mMockContext).getContentResolver();          mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);          mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager, @@ -299,6 +306,25 @@ public class LocaleManagerServiceTest {          assertEquals(DEFAULT_LOCALES, locales);      } +    @Test +    public void testGetApplicationLocales_callerIsCurrentInputMethod_returnsLocales() +            throws Exception { +        doReturn(DEFAULT_UID).when(mMockPackageManager) +                .getPackageUidAsUser(anyString(), any(), anyInt()); +        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES)) +                .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt()); +        String imPkgName = getCurrentInputMethodPackageName(); +        doReturn(Binder.getCallingUid()).when(mMockPackageManager) +                .getPackageUidAsUser(eq(imPkgName), any(), anyInt()); + +        LocaleList locales = +                mLocaleManagerService.getApplicationLocales( +                        DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + +        verify(mMockContext, never()).enforceCallingOrSelfPermission(any(), any()); +        assertEquals(DEFAULT_LOCALES, locales); +    } +      private static void assertNoLocalesStored(LocaleList locales) {          assertNull(locales);      } @@ -311,4 +337,13 @@ public class LocaleManagerServiceTest {      private void setUpPassingPermissionCheckFor(String permission) {          doNothing().when(mMockContext).enforceCallingOrSelfPermission(eq(permission), any());      } + +    private String getCurrentInputMethodPackageName() { +        String im = Settings.Secure.getString( +                InstrumentationRegistry.getContext().getContentResolver(), +                Settings.Secure.DEFAULT_INPUT_METHOD); +        ComponentName cn = ComponentName.unflattenFromString(im); +        assertThat(cn).isNotNull(); +        return cn.getPackageName(); +    }  } diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java index 808b74e31029..853eea133fbd 100644 --- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java @@ -47,6 +47,8 @@ import android.util.AtomicFile;  import android.util.TypedXmlPullParser;  import android.util.Xml; +import androidx.test.InstrumentationRegistry; +  import com.android.internal.content.PackageMonitor;  import com.android.internal.util.XmlUtils;  import com.android.server.wm.ActivityTaskManagerInternal; @@ -124,6 +126,8 @@ public class SystemAppUpdateTrackerTest {          doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)                  .getInstallSourceInfo(anyString());          doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); +        doReturn(InstrumentationRegistry.getContext().getContentResolver()) +                .when(mMockContext).getContentResolver();          mStoragefile = new AtomicFile(new File(                  Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml")); diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java index 94e67d16acab..3f55f1bb76a4 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java @@ -16,16 +16,16 @@  package com.android.server.om; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat;  import android.content.om.OverlayIdentifier;  import android.content.om.OverlayInfo;  import androidx.test.runner.AndroidJUnit4; +import com.google.common.truth.Expect; + +import org.junit.Rule;  import org.junit.Test;  import org.junit.runner.RunWith; @@ -43,6 +43,9 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI      private static final String OVERLAY2 = OVERLAY + "2";      private static final OverlayIdentifier IDENTIFIER2 = new OverlayIdentifier(OVERLAY2); +    @Rule +    public final Expect expect = Expect.create(); +      @Test      public void alwaysInitializeAllPackages() {          final OverlayManagerServiceImpl impl = getImpl(); @@ -51,13 +54,11 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI          addPackage(target(otherTarget), USER);          addPackage(overlay(OVERLAY, TARGET), USER); -        final Set<PackageAndUser> allPackages = -                Set.of(new PackageAndUser(TARGET, USER), -                        new PackageAndUser(otherTarget, USER), -                        new PackageAndUser(OVERLAY, USER)); +        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER)); -        assertEquals(allPackages, impl.updateOverlaysForUser(USER)); -        assertEquals(allPackages, impl.updateOverlaysForUser(USER)); +        // The result should be the same for every time +        assertThat(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages); +        assertThat(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);      }      @Test @@ -66,29 +67,31 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI          addPackage(target(TARGET), USER);          addPackage(overlay(OVERLAY, TARGET), USER); -        final Set<PackageAndUser> allPackages = -                Set.of(new PackageAndUser(TARGET, USER), new PackageAndUser(OVERLAY, USER)); +        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));          configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_DISABLED, 0 /* priority */); -        assertEquals(allPackages, impl.updateOverlaysForUser(USER)); +        expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);          final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER); -        assertNotNull(o1); -        assertFalse(o1.isEnabled()); -        assertFalse(o1.isMutable); +        expect.that(o1).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o1.isEnabled()).isFalse(); +        expect.that(o1.isMutable).isFalse();          configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_ENABLED, 0 /* priority */); -        assertEquals(allPackages, impl.updateOverlaysForUser(USER)); +        expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);          final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER, USER); -        assertNotNull(o2); -        assertTrue(o2.isEnabled()); -        assertFalse(o2.isMutable); +        expect.that(o2).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o2.isEnabled()).isTrue(); +        expect.that(o2.isMutable).isFalse();          configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_DISABLED, 0 /* priority */); -        assertEquals(allPackages, impl.updateOverlaysForUser(USER)); +        expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);          final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER); -        assertNotNull(o3); -        assertFalse(o3.isEnabled()); -        assertFalse(o3.isMutable); +        expect.that(o3).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o3.isEnabled()).isFalse(); +        expect.that(o3.isMutable).isFalse();      }      @Test @@ -98,28 +101,30 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI          addPackage(overlay(OVERLAY, TARGET), USER);          configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */); -        final Set<PackageAndUser> allPackages = -                Set.of(new PackageAndUser(TARGET, USER), new PackageAndUser(OVERLAY, USER)); +        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER)); -        assertEquals(allPackages, impl.updateOverlaysForUser(USER)); +        expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);          final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER); -        assertNotNull(o1); -        assertFalse(o1.isEnabled()); -        assertTrue(o1.isMutable); +        expect.that(o1).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o1.isEnabled()).isFalse(); +        expect.that(o1.isMutable).isTrue();          configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_ENABLED, 0 /* priority */); -        assertEquals(allPackages, impl.updateOverlaysForUser(USER)); +        expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);          final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER, USER); -        assertNotNull(o2); -        assertFalse(o2.isEnabled()); -        assertTrue(o2.isMutable); +        expect.that(o2).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o2.isEnabled()).isFalse(); +        expect.that(o2.isMutable).isTrue();          configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */); -        assertEquals(allPackages, impl.updateOverlaysForUser(USER)); +        expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);          final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER); -        assertNotNull(o3); -        assertFalse(o3.isEnabled()); -        assertTrue(o3.isMutable); +        expect.that(o3).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o3.isEnabled()).isFalse(); +        expect.that(o3.isMutable).isTrue();      }      @Test @@ -128,17 +133,17 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI          addPackage(target(TARGET), USER);          addPackage(overlay(OVERLAY, TARGET), USER); -        final Set<PackageAndUser> allPackages = -                Set.of(new PackageAndUser(TARGET, USER), new PackageAndUser(OVERLAY, USER)); +        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));          final Consumer<ConfigState> setOverlay = (state -> {              configureSystemOverlay(OVERLAY, state, 0 /* priority */); -            assertEquals(allPackages, impl.updateOverlaysForUser(USER)); +            expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);              final OverlayInfo o = impl.getOverlayInfo(IDENTIFIER, USER); -            assertNotNull(o); -            assertEquals(o.isEnabled(), state == ConfigState.IMMUTABLE_ENABLED +            expect.that(o).isNotNull(); +            assertThat(expect.hasFailures()).isFalse(); +            expect.that(o.isEnabled()).isEqualTo(state == ConfigState.IMMUTABLE_ENABLED                      || state == ConfigState.MUTABLE_ENABLED); -            assertEquals(o.isMutable, state == ConfigState.MUTABLE_DISABLED +            expect.that(o.isMutable).isEqualTo(state == ConfigState.MUTABLE_DISABLED                      || state == ConfigState.MUTABLE_ENABLED);          }); @@ -180,20 +185,20 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI          configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */);          configureSystemOverlay(OVERLAY2, ConfigState.MUTABLE_DISABLED, 1 /* priority */); -        final Set<PackageAndUser> allPackages = -                Set.of(new PackageAndUser(TARGET, USER), new PackageAndUser(OVERLAY, USER), -                        new PackageAndUser(OVERLAY2, USER)); +        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER)); -        assertEquals(allPackages, impl.updateOverlaysForUser(USER)); +        expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);          final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER); -        assertNotNull(o1); -        assertEquals(0, o1.priority); -        assertFalse(o1.isEnabled()); +        expect.that(o1).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o1.priority).isEqualTo(0); +        expect.that(o1.isEnabled()).isFalse();          final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER); -        assertNotNull(o2); -        assertEquals(1, o2.priority); -        assertFalse(o2.isEnabled()); +        expect.that(o2).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o2.priority).isEqualTo(1); +        expect.that(o2.isEnabled()).isFalse();          // Overlay priority changing between reboots should not affect enable state of mutable          // overlays. @@ -202,16 +207,18 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI          // Reorder the overlays          configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 1 /* priority */);          configureSystemOverlay(OVERLAY2, ConfigState.MUTABLE_DISABLED, 0 /* priority */); -        assertEquals(allPackages, impl.updateOverlaysForUser(USER)); +        expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);          final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER); -        assertNotNull(o3); -        assertEquals(1, o3.priority); -        assertTrue(o3.isEnabled()); +        expect.that(o3).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o3.priority).isEqualTo(1); +        expect.that(o3.isEnabled()).isTrue();          final OverlayInfo o4 = impl.getOverlayInfo(IDENTIFIER2, USER); -        assertNotNull(o4); -        assertEquals(0, o4.priority); -        assertFalse(o4.isEnabled()); +        expect.that(o4).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o4.priority).isEqualTo(0); +        expect.that(o4.isEnabled()).isFalse();      }      @Test @@ -223,33 +230,35 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI          configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_ENABLED, 0 /* priority */);          configureSystemOverlay(OVERLAY2, ConfigState.IMMUTABLE_ENABLED, 1 /* priority */); -        final Set<PackageAndUser> allPackages = -                Set.of(new PackageAndUser(TARGET, USER), new PackageAndUser(OVERLAY, USER), -                        new PackageAndUser(OVERLAY2, USER)); +        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER)); -        assertEquals(allPackages, impl.updateOverlaysForUser(USER)); +        expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);          final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER); -        assertNotNull(o1); -        assertEquals(0, o1.priority); -        assertTrue(o1.isEnabled()); +        expect.that(o1).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o1.priority).isEqualTo(0); +        expect.that(o1.isEnabled()).isTrue();          final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER); -        assertNotNull(o2); -        assertEquals(1, o2.priority); -        assertTrue(o2.isEnabled()); +        expect.that(o2).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o2.priority).isEqualTo(1); +        expect.that(o2.isEnabled()).isTrue();          // Reorder the overlays          configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_ENABLED, 1 /* priority */);          configureSystemOverlay(OVERLAY2, ConfigState.IMMUTABLE_ENABLED, 0 /* priority */); -        assertEquals(allPackages, impl.updateOverlaysForUser(USER)); +        expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);          final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER); -        assertNotNull(o3); -        assertEquals(1, o3.priority); -        assertTrue(o3.isEnabled()); +        expect.that(o3).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o3.priority).isEqualTo(1); +        expect.that(o3.isEnabled()).isTrue();          final OverlayInfo o4 = impl.getOverlayInfo(IDENTIFIER2, USER); -        assertNotNull(o4); -        assertEquals(0, o4.priority); -        assertTrue(o4.isEnabled()); +        expect.that(o4).isNotNull(); +        assertThat(expect.hasFailures()).isFalse(); +        expect.that(o4.priority).isEqualTo(0); +        expect.that(o4.isEnabled()).isTrue();      }  } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java index 96707fde8edb..00aa52012e59 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -176,6 +176,13 @@ public class UserManagerServiceTest {      }      @Test +    public void testHasUserRestriction_NonExistentUserReturnsFalse() { +        int nonExistentUserId = UserHandle.USER_NULL; +        assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, nonExistentUserId)) +                .isFalse(); +    } + +    @Test      public void testSetUserRestrictionWithIncorrectID() throws Exception {          int incorrectId = 1;          while (mUserManagerService.userExists(incorrectId)) { diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java index d5893c8d0b9f..77d542a2e43d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java @@ -52,7 +52,6 @@ public class DexoptOptionsTests {          assertFalse(opt.isBootComplete());          assertFalse(opt.isCheckForProfileUpdates());          assertFalse(opt.isDexoptOnlySecondaryDex()); -        assertFalse(opt.isDexoptOnlySharedDex());          assertFalse(opt.isDowngrade());          assertFalse(opt.isForce());          assertFalse(opt.isDexoptIdleBackgroundJob()); @@ -67,7 +66,6 @@ public class DexoptOptionsTests {                  DexoptOptions.DEXOPT_BOOT_COMPLETE |                  DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |                  DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX | -                DexoptOptions.DEXOPT_ONLY_SHARED_DEX |                  DexoptOptions.DEXOPT_DOWNGRADE  |                  DexoptOptions.DEXOPT_AS_SHARED_LIBRARY |                  DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB | @@ -81,7 +79,6 @@ public class DexoptOptionsTests {          assertTrue(opt.isBootComplete());          assertTrue(opt.isCheckForProfileUpdates());          assertTrue(opt.isDexoptOnlySecondaryDex()); -        assertTrue(opt.isDexoptOnlySharedDex());          assertTrue(opt.isDowngrade());          assertTrue(opt.isForce());          assertTrue(opt.isDexoptAsSharedLibrary()); @@ -113,7 +110,6 @@ public class DexoptOptionsTests {              assertTrue(opt.isBootComplete());              assertTrue(opt.isCheckForProfileUpdates());              assertFalse(opt.isDexoptOnlySecondaryDex()); -            assertFalse(opt.isDexoptOnlySharedDex());              assertFalse(opt.isDowngrade());              assertTrue(opt.isForce());              assertFalse(opt.isDexoptAsSharedLibrary()); @@ -131,7 +127,6 @@ public class DexoptOptionsTests {          assertTrue(opt.isBootComplete());          assertFalse(opt.isCheckForProfileUpdates());          assertFalse(opt.isDexoptOnlySecondaryDex()); -        assertFalse(opt.isDexoptOnlySharedDex());          assertFalse(opt.isDowngrade());          assertTrue(opt.isForce());          assertFalse(opt.isDexoptAsSharedLibrary()); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java index 235849c1cd8b..c484f457faea 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -53,7 +53,8 @@ final class FakeVibratorControllerProvider {      private boolean mIsAvailable = true;      private boolean mIsInfoLoadSuccessful = true; -    private long mLatency; +    private long mOnLatency; +    private long mOffLatency;      private int mOffCount;      private int mCapabilities; @@ -97,7 +98,7 @@ final class FakeVibratorControllerProvider {          public long on(long milliseconds, long vibrationId) {              recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,                      /* frequencyHz= */ 0, (int) milliseconds)); -            applyLatency(); +            applyLatency(mOnLatency);              scheduleListener(milliseconds, vibrationId);              return milliseconds;          } @@ -105,12 +106,13 @@ final class FakeVibratorControllerProvider {          @Override          public void off() {              mOffCount++; +            applyLatency(mOffLatency);          }          @Override          public void setAmplitude(float amplitude) {              mAmplitudes.add(amplitude); -            applyLatency(); +            applyLatency(mOnLatency);          }          @Override @@ -121,7 +123,7 @@ final class FakeVibratorControllerProvider {              }              recordEffectSegment(vibrationId,                      new PrebakedSegment((int) effect, false, (int) strength)); -            applyLatency(); +            applyLatency(mOnLatency);              scheduleListener(EFFECT_DURATION, vibrationId);              return EFFECT_DURATION;          } @@ -141,7 +143,7 @@ final class FakeVibratorControllerProvider {                  duration += EFFECT_DURATION + primitive.getDelay();                  recordEffectSegment(vibrationId, primitive);              } -            applyLatency(); +            applyLatency(mOnLatency);              scheduleListener(duration, vibrationId);              return duration;          } @@ -154,7 +156,7 @@ final class FakeVibratorControllerProvider {                  recordEffectSegment(vibrationId, primitive);              }              recordBraking(vibrationId, braking); -            applyLatency(); +            applyLatency(mOnLatency);              scheduleListener(duration, vibrationId);              return duration;          } @@ -193,10 +195,10 @@ final class FakeVibratorControllerProvider {              return mIsInfoLoadSuccessful;          } -        private void applyLatency() { +        private void applyLatency(long latencyMillis) {              try { -                if (mLatency > 0) { -                    Thread.sleep(mLatency); +                if (latencyMillis > 0) { +                    Thread.sleep(latencyMillis);                  }              } catch (InterruptedException e) {              } @@ -240,10 +242,15 @@ final class FakeVibratorControllerProvider {      /**       * Sets the latency this controller should fake for turning the vibrator hardware on or setting -     * it's vibration amplitude. +     * the vibration amplitude.       */ -    public void setLatency(long millis) { -        mLatency = millis; +    public void setOnLatency(long millis) { +        mOnLatency = millis; +    } + +    /** Sets the latency this controller should fake for turning the vibrator off. */ +    public void setOffLatency(long millis) { +        mOffLatency = millis;      }      /** Set the capabilities of the fake vibrator hardware. */ diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index a15e4b0c74a0..fc830a9f61ad 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -1159,7 +1159,7 @@ public class VibrationThreadTest {          // 25% of the first waveform step will be spent on the native on() call.          // 25% of each waveform step will be spent on the native setAmplitude() call.. -        mVibratorProviders.get(VIBRATOR_ID).setLatency(stepDuration / 4); +        mVibratorProviders.get(VIBRATOR_ID).setOnLatency(stepDuration / 4);          mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);          int stepCount = totalDuration / stepDuration; @@ -1190,7 +1190,7 @@ public class VibrationThreadTest {          fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);          long latency = 5_000; // 5s -        fakeVibrator.setLatency(latency); +        fakeVibrator.setOnLatency(latency);          long vibrationId = 1;          VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); @@ -1204,8 +1204,7 @@ public class VibrationThreadTest {          // fail at waitForCompletion(cancellingThread).          Thread cancellingThread = new Thread(                  () -> conductor.notifyCancelled( -                        new Vibration.EndInfo( -                                Vibration.Status.CANCELLED_BY_USER), +                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),                          /* immediate= */ false));          cancellingThread.start(); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index c46fecd1a55e..c83afb74ccba 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -826,13 +826,40 @@ public class VibratorManagerServiceTest {          // The second vibration shouldn't have recorded that the vibrators were turned on.          verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());          // No segment played is the prebaked CLICK from the second vibration. -        assertFalse( -                mVibratorProviders.get(1).getAllEffectSegments().stream() -                        .anyMatch(segment -> segment instanceof PrebakedSegment)); +        assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream() +                .anyMatch(PrebakedSegment.class::isInstance));          cancelVibrate(service);  // Clean up repeating effect.      }      @Test +    public void vibrate_withOngoingRepeatingVibrationBeingCancelled_playsAfterPreviousIsCancelled() +            throws Exception { +        mockVibrators(1); +        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); +        fakeVibrator.setOffLatency(50); // Add latency so cancellation is slow. +        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); +        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); +        VibratorManagerService service = createSystemReadyService(); + +        VibrationEffect repeatingEffect = VibrationEffect.createWaveform( +                new long[]{10, 10_000}, new int[]{255, 0}, 1); +        vibrate(service, repeatingEffect, ALARM_ATTRS); + +        // VibrationThread will start this vibration async, wait until the off waveform step. +        assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS)); + +        // Cancel vibration right before requesting a new one. +        // This should trigger slow IVibrator.off before setting the vibration status to cancelled. +        cancelVibrate(service); +        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), +                ALARM_ATTRS); + +        // Check that second vibration was played. +        assertTrue(fakeVibrator.getAllEffectSegments().stream() +                .anyMatch(PrebakedSegment.class::isInstance)); +    } + +    @Test      public void vibrate_withNewRepeatingVibration_cancelsOngoingEffect() throws Exception {          mockVibrators(1);          mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); @@ -880,10 +907,8 @@ public class VibratorManagerServiceTest {          // The second vibration shouldn't have recorded that the vibrators were turned on.          verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());          // The second vibration shouldn't have played any prebaked segment. -        assertFalse( -                mVibratorProviders.get(1).getAllEffectSegments().stream() -                        .anyMatch(segment -> segment instanceof PrebakedSegment)); - +        assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream() +                .anyMatch(PrebakedSegment.class::isInstance));          cancelVibrate(service);  // Clean up long effect.      } diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index 91c2fe0eb262..8e81e2d8997c 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -1371,6 +1371,39 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {          verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);      } +    @Test +    public void dreamWhenDocked_ambientModeSuppressed_suppressionEnabled() { +        mUiManagerService.setStartDreamImmediatelyOnDock(true); +        mUiManagerService.setDreamsDisabledByAmbientModeSuppression(true); + +        when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(true); +        triggerDockIntent(); +        verifyAndSendResultBroadcast(); +        verify(mInjector, never()).startDreamWhenDockedIfAppropriate(mContext); +    } + +    @Test +    public void dreamWhenDocked_ambientModeSuppressed_suppressionDisabled() { +        mUiManagerService.setStartDreamImmediatelyOnDock(true); +        mUiManagerService.setDreamsDisabledByAmbientModeSuppression(false); + +        when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(true); +        triggerDockIntent(); +        verifyAndSendResultBroadcast(); +        verify(mInjector).startDreamWhenDockedIfAppropriate(mContext); +    } + +    @Test +    public void dreamWhenDocked_ambientModeNotSuppressed_suppressionEnabled() { +        mUiManagerService.setStartDreamImmediatelyOnDock(true); +        mUiManagerService.setDreamsDisabledByAmbientModeSuppression(true); + +        when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(false); +        triggerDockIntent(); +        verifyAndSendResultBroadcast(); +        verify(mInjector).startDreamWhenDockedIfAppropriate(mContext); +    } +      private void triggerDockIntent() {          final Intent dockedIntent =                  new Intent(Intent.ACTION_DOCK_EVENT) diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java index 1e945776cf40..248a3fc8b3b1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -15,6 +15,7 @@   */  package com.android.server.notification; +import static android.content.pm.PackageManager.MATCH_ANY_USER;  import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;  import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;  import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; @@ -30,9 +31,11 @@ import static junit.framework.Assert.assertTrue;  import static org.mockito.ArgumentMatchers.any;  import static org.mockito.ArgumentMatchers.anyInt;  import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.intThat;  import static org.mockito.ArgumentMatchers.nullable;  import static org.mockito.Mockito.atLeast;  import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn;  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.never;  import static org.mockito.Mockito.reset; @@ -49,6 +52,7 @@ import android.content.pm.IPackageManager;  import android.content.pm.PackageManager;  import android.content.pm.ServiceInfo;  import android.content.pm.VersionedPackage; +import android.content.res.Resources;  import android.os.Bundle;  import android.os.UserHandle;  import android.service.notification.NotificationListenerFilter; @@ -69,6 +73,7 @@ import com.google.common.collect.ImmutableList;  import org.junit.Before;  import org.junit.Test; +import org.mockito.ArgumentMatcher;  import org.mockito.Mock;  import org.mockito.MockitoAnnotations;  import org.mockito.internal.util.reflection.FieldSetter; @@ -77,6 +82,7 @@ import java.io.BufferedInputStream;  import java.io.BufferedOutputStream;  import java.io.ByteArrayInputStream;  import java.io.ByteArrayOutputStream; +import java.util.Arrays;  import java.util.List;  public class NotificationListenersTest extends UiServiceTestCase { @@ -85,6 +91,8 @@ public class NotificationListenersTest extends UiServiceTestCase {      private PackageManager mPm;      @Mock      private IPackageManager miPm; +    @Mock +    private Resources mResources;      @Mock      NotificationManagerService mNm; @@ -96,7 +104,8 @@ public class NotificationListenersTest extends UiServiceTestCase {      private ComponentName mCn1 = new ComponentName("pkg", "pkg.cmp");      private ComponentName mCn2 = new ComponentName("pkg2", "pkg2.cmp2"); - +    private ComponentName mUninstalledComponent = new ComponentName("pkg3", +            "pkg3.NotificationListenerService");      @Before      public void setUp() throws Exception { @@ -111,7 +120,7 @@ public class NotificationListenersTest extends UiServiceTestCase {      @Test      public void testReadExtraTag() throws Exception { -        String xml = "<" + TAG_REQUESTED_LISTENERS+ ">" +        String xml = "<" + TAG_REQUESTED_LISTENERS + ">"                  + "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">"                  + "<allowed types=\"7\" />"                  + "</listener>" @@ -131,11 +140,55 @@ public class NotificationListenersTest extends UiServiceTestCase {      }      @Test +    public void loadDefaultsFromConfig_forHeadlessSystemUser_loadUninstalled() throws Exception { +        // setup with headless system user mode +        mListeners = spy(mNm.new NotificationListeners( +                mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm, +                /* isHeadlessSystemUserMode= */ true)); +        mockDefaultListenerConfigForUninstalledComponent(mUninstalledComponent); + +        mListeners.loadDefaultsFromConfig(); + +        assertThat(mListeners.getDefaultComponents()).contains(mUninstalledComponent); +    } + +    @Test +    public void loadDefaultsFromConfig_forNonHeadlessSystemUser_ignoreUninstalled() +            throws Exception { +        // setup without headless system user mode +        mListeners = spy(mNm.new NotificationListeners( +                mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm, +                /* isHeadlessSystemUserMode= */ false)); +        mockDefaultListenerConfigForUninstalledComponent(mUninstalledComponent); + +        mListeners.loadDefaultsFromConfig(); + +        assertThat(mListeners.getDefaultComponents()).doesNotContain(mUninstalledComponent); +    } + +    private void mockDefaultListenerConfigForUninstalledComponent(ComponentName componentName) { +        ArraySet<ComponentName> components = new ArraySet<>(Arrays.asList(componentName)); +        when(mResources +                .getString( +                        com.android.internal.R.string.config_defaultListenerAccessPackages)) +                .thenReturn(componentName.getPackageName()); +        when(mContext.getResources()).thenReturn(mResources); +        doReturn(components).when(mListeners).queryPackageForServices( +                eq(componentName.getPackageName()), +                intThat(hasIntBitFlag(MATCH_ANY_USER)), +                anyInt()); +    } + +    public static ArgumentMatcher<Integer> hasIntBitFlag(int flag) { +        return arg -> arg != null && ((arg & flag) == flag); +    } + +    @Test      public void testWriteExtraTag() throws Exception {          NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());          VersionedPackage a1 = new VersionedPackage("pkg1", 243);          NotificationListenerFilter nlf2 = -                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); +                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[]{a1}));          mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);          mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java deleted file mode 100644 index d7650420788c..000000000000 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java +++ /dev/null @@ -1,551 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - *      http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.notification; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.app.ActivityManager; -import android.app.Notification; -import android.app.PendingIntent; -import android.app.Person; -import android.app.RemoteInput; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Typeface; -import android.graphics.drawable.Icon; -import android.net.Uri; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.style.StyleSpan; -import android.util.Pair; -import android.widget.RemoteViews; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.server.UiServiceTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class NotificationTest extends UiServiceTestCase { - -    @Mock -    ActivityManager mAm; - -    @Mock -    Resources mResources; - -    @Before -    public void setUp() { -        MockitoAnnotations.initMocks(this); -    } - -    @Test -    public void testDoesNotStripsExtenders() { -        Notification.Builder nb = new Notification.Builder(mContext, "channel"); -        nb.extend(new Notification.CarExtender().setColor(Color.RED)); -        nb.extend(new Notification.TvExtender().setChannelId("different channel")); -        nb.extend(new Notification.WearableExtender().setDismissalId("dismiss")); -        Notification before = nb.build(); -        Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before); - -        assertTrue(before == after); - -        assertEquals("different channel", new Notification.TvExtender(before).getChannelId()); -        assertEquals(Color.RED, new Notification.CarExtender(before).getColor()); -        assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId()); -    } - -    @Test -    public void testStyleChangeVisiblyDifferent_noStyles() { -        Notification.Builder n1 = new Notification.Builder(mContext, "test"); -        Notification.Builder n2 = new Notification.Builder(mContext, "test"); - -        assertFalse(Notification.areStyledNotificationsVisiblyDifferent(n1, n2)); -    } - -    @Test -    public void testStyleChangeVisiblyDifferent_noStyleToStyle() { -        Notification.Builder n1 = new Notification.Builder(mContext, "test"); -        Notification.Builder n2 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.BigTextStyle()); - -        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2)); -    } - -    @Test -    public void testStyleChangeVisiblyDifferent_styleToNoStyle() { -        Notification.Builder n2 = new Notification.Builder(mContext, "test"); -        Notification.Builder n1 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.BigTextStyle()); - -        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2)); -    } - -    @Test -    public void testStyleChangeVisiblyDifferent_changeStyle() { -        Notification.Builder n1 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.InboxStyle()); -        Notification.Builder n2 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.BigTextStyle()); - -        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2)); -    } - -    @Test -    public void testInboxTextChange() { -        Notification.Builder nInbox1 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.InboxStyle().addLine("a").addLine("b")); -        Notification.Builder nInbox2 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.InboxStyle().addLine("b").addLine("c")); - -        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nInbox1, nInbox2)); -    } - -    @Test -    public void testBigTextTextChange() { -        Notification.Builder nBigText1 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.BigTextStyle().bigText("something")); -        Notification.Builder nBigText2 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.BigTextStyle().bigText("else")); - -        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigText1, nBigText2)); -    } - -    @Test -    public void testBigPictureChange() { -        Bitmap bitA = mock(Bitmap.class); -        when(bitA.getGenerationId()).thenReturn(100); -        Bitmap bitB = mock(Bitmap.class); -        when(bitB.getGenerationId()).thenReturn(200); - -        Notification.Builder nBigPic1 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.BigPictureStyle().bigPicture(bitA)); -        Notification.Builder nBigPic2 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.BigPictureStyle().bigPicture(bitB)); - -        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2)); -    } - -    @Test -    public void testMessagingChange_text() { -        Notification.Builder nM1 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.MessagingStyle("") -                        .addMessage(new Notification.MessagingStyle.Message( -                                "a", 100, mock(Person.class)))); -        Notification.Builder nM2 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.MessagingStyle("") -                        .addMessage(new Notification.MessagingStyle.Message( -                                "a", 100, mock(Person.class))) -                        .addMessage(new Notification.MessagingStyle.Message( -                                "b", 100, mock(Person.class))) -                ); - -        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); -    } - -    @Test -    public void testMessagingChange_data() { -        Notification.Builder nM1 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.MessagingStyle("") -                        .addMessage(new Notification.MessagingStyle.Message( -                                "a", 100, mock(Person.class)) -                                .setData("text", mock(Uri.class)))); -        Notification.Builder nM2 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.MessagingStyle("") -                        .addMessage(new Notification.MessagingStyle.Message( -                                "a", 100, mock(Person.class)))); - -        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); -    } - -    @Test -    public void testMessagingChange_sender() { -        Person a = mock(Person.class); -        when(a.getName()).thenReturn("A"); -        Person b = mock(Person.class); -        when(b.getName()).thenReturn("b"); -        Notification.Builder nM1 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.MessagingStyle("") -                        .addMessage(new Notification.MessagingStyle.Message("a", 100, b))); -        Notification.Builder nM2 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.MessagingStyle("") -                        .addMessage(new Notification.MessagingStyle.Message("a", 100, a))); - -        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); -    } - -    @Test -    public void testMessagingChange_key() { -        Person a = mock(Person.class); -        when(a.getKey()).thenReturn("A"); -        Person b = mock(Person.class); -        when(b.getKey()).thenReturn("b"); -        Notification.Builder nM1 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.MessagingStyle("") -                        .addMessage(new Notification.MessagingStyle.Message("a", 100, a))); -        Notification.Builder nM2 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.MessagingStyle("") -                        .addMessage(new Notification.MessagingStyle.Message("a", 100, b))); - -        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); -    } - -    @Test -    public void testMessagingChange_ignoreTimeChange() { -        Notification.Builder nM1 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.MessagingStyle("") -                        .addMessage(new Notification.MessagingStyle.Message( -                                "a", 100, mock(Person.class)))); -        Notification.Builder nM2 = new Notification.Builder(mContext, "test") -                .setStyle(new Notification.MessagingStyle("") -                        .addMessage(new Notification.MessagingStyle.Message( -                                "a", 1000, mock(Person.class))) -                ); - -        assertFalse(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2)); -    } - -    @Test -    public void testRemoteViews_nullChange() { -        Notification.Builder n1 = new Notification.Builder(mContext, "test") -                .setContent(mock(RemoteViews.class)); -        Notification.Builder n2 = new Notification.Builder(mContext, "test"); -        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); - -        n1 = new Notification.Builder(mContext, "test"); -        n2 = new Notification.Builder(mContext, "test") -                .setContent(mock(RemoteViews.class)); -        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); - -        n1 = new Notification.Builder(mContext, "test") -                .setCustomBigContentView(mock(RemoteViews.class)); -        n2 = new Notification.Builder(mContext, "test"); -        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); - -        n1 = new Notification.Builder(mContext, "test"); -        n2 = new Notification.Builder(mContext, "test") -                .setCustomBigContentView(mock(RemoteViews.class)); -        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); - -        n1 = new Notification.Builder(mContext, "test"); -        n2 = new Notification.Builder(mContext, "test"); -        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); -    } - -    @Test -    public void testRemoteViews_layoutChange() { -        RemoteViews a = mock(RemoteViews.class); -        when(a.getLayoutId()).thenReturn(234); -        RemoteViews b = mock(RemoteViews.class); -        when(b.getLayoutId()).thenReturn(189); - -        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a); -        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b); -        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); - -        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a); -        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b); -        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); - -        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a); -        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b); -        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); -    } - -    @Test -    public void testRemoteViews_layoutSame() { -        RemoteViews a = mock(RemoteViews.class); -        when(a.getLayoutId()).thenReturn(234); -        RemoteViews b = mock(RemoteViews.class); -        when(b.getLayoutId()).thenReturn(234); - -        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a); -        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b); -        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); - -        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a); -        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b); -        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); - -        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a); -        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b); -        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); -    } - -    @Test -    public void testRemoteViews_sequenceChange() { -        RemoteViews a = mock(RemoteViews.class); -        when(a.getLayoutId()).thenReturn(234); -        when(a.getSequenceNumber()).thenReturn(1); -        RemoteViews b = mock(RemoteViews.class); -        when(b.getLayoutId()).thenReturn(234); -        when(b.getSequenceNumber()).thenReturn(2); - -        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a); -        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b); -        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); - -        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a); -        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b); -        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); - -        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a); -        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b); -        assertTrue(Notification.areRemoteViewsChanged(n1, n2)); -    } - -    @Test -    public void testRemoteViews_sequenceSame() { -        RemoteViews a = mock(RemoteViews.class); -        when(a.getLayoutId()).thenReturn(234); -        when(a.getSequenceNumber()).thenReturn(1); -        RemoteViews b = mock(RemoteViews.class); -        when(b.getLayoutId()).thenReturn(234); -        when(b.getSequenceNumber()).thenReturn(1); - -        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a); -        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b); -        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); - -        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a); -        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b); -        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); - -        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a); -        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b); -        assertFalse(Notification.areRemoteViewsChanged(n1, n2)); -    } - -    @Test -    public void testActionsDifferent_null() { -        Notification n1 = new Notification.Builder(mContext, "test") -                .build(); -        Notification n2 = new Notification.Builder(mContext, "test") -                .build(); - -        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2)); -    } - -    @Test -    public void testActionsDifferentSame() { -        PendingIntent intent = mock(PendingIntent.class); -        Icon icon = mock(Icon.class); - -        Notification n1 = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) -                .build(); -        Notification n2 = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) -                .build(); - -        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2)); -    } - -    @Test -    public void testActionsDifferentText() { -        PendingIntent intent = mock(PendingIntent.class); -        Icon icon = mock(Icon.class); - -        Notification n1 = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) -                .build(); -        Notification n2 = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build()) -                .build(); - -        assertTrue(Notification.areActionsVisiblyDifferent(n1, n2)); -    } - -    @Test -    public void testActionsDifferentSpannables() { -        PendingIntent intent = mock(PendingIntent.class); -        Icon icon = mock(Icon.class); - -        Notification n1 = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, -                        new SpannableStringBuilder().append("test1", -                                new StyleSpan(Typeface.BOLD), -                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE), -                        intent).build()) -                .build(); -        Notification n2 = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, "test1", intent).build()) -                .build(); - -        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2)); -    } - -    @Test -    public void testActionsDifferentNumber() { -        PendingIntent intent = mock(PendingIntent.class); -        Icon icon = mock(Icon.class); - -        Notification n1 = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) -                .build(); -        Notification n2 = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build()) -                .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build()) -                .build(); - -        assertTrue(Notification.areActionsVisiblyDifferent(n1, n2)); -    } - -    @Test -    public void testActionsDifferentIntent() { -        PendingIntent intent1 = mock(PendingIntent.class); -        PendingIntent intent2 = mock(PendingIntent.class); -        Icon icon = mock(Icon.class); - -        Notification n1 = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent1).build()) -                .build(); -        Notification n2 = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent2).build()) -                .build(); - -        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2)); -    } - -    @Test -    public void testActionsIgnoresRemoteInputs() { -        PendingIntent intent = mock(PendingIntent.class); -        Icon icon = mock(Icon.class); - -        Notification n1 = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent) -                        .addRemoteInput(new RemoteInput.Builder("a") -                                .setChoices(new CharSequence[] {"i", "m"}) -                                .build()) -                        .build()) -                .build(); -        Notification n2 = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent) -                        .addRemoteInput(new RemoteInput.Builder("a") -                                .setChoices(new CharSequence[] {"t", "m"}) -                                .build()) -                        .build()) -                .build(); - -        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2)); -    } - -    @Test -    public void testFreeformRemoteInputActionPair_noRemoteInput() { -        PendingIntent intent = mock(PendingIntent.class); -        Icon icon = mock(Icon.class); -        Notification notification = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent) -                        .build()) -                .build(); -        assertNull(notification.findRemoteInputActionPair(false)); -    } - -    @Test -    public void testFreeformRemoteInputActionPair_hasRemoteInput() { -        PendingIntent intent = mock(PendingIntent.class); -        Icon icon = mock(Icon.class); - -        RemoteInput remoteInput = new RemoteInput.Builder("a").build(); - -        Notification.Action actionWithRemoteInput = -                new Notification.Action.Builder(icon, "TEXT 1", intent) -                        .addRemoteInput(remoteInput) -                        .addRemoteInput(remoteInput) -                        .build(); - -        Notification.Action actionWithoutRemoteInput = -                new Notification.Action.Builder(icon, "TEXT 2", intent) -                        .build(); - -        Notification notification = new Notification.Builder(mContext, "test") -                .addAction(actionWithoutRemoteInput) -                .addAction(actionWithRemoteInput) -                .build(); - -        Pair<RemoteInput, Notification.Action> remoteInputActionPair = -                notification.findRemoteInputActionPair(false); - -        assertNotNull(remoteInputActionPair); -        assertEquals(remoteInput, remoteInputActionPair.first); -        assertEquals(actionWithRemoteInput, remoteInputActionPair.second); -    } - -    @Test -    public void testFreeformRemoteInputActionPair_requestFreeform_noFreeformRemoteInput() { -        PendingIntent intent = mock(PendingIntent.class); -        Icon icon = mock(Icon.class); -        Notification notification = new Notification.Builder(mContext, "test") -                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent) -                        .addRemoteInput( -                                new RemoteInput.Builder("a") -                                        .setAllowFreeFormInput(false).build()) -                        .build()) -                .build(); -        assertNull(notification.findRemoteInputActionPair(true)); -    } - -    @Test -    public void testFreeformRemoteInputActionPair_requestFreeform_hasFreeformRemoteInput() { -        PendingIntent intent = mock(PendingIntent.class); -        Icon icon = mock(Icon.class); - -        RemoteInput remoteInput = -                new RemoteInput.Builder("a").setAllowFreeFormInput(false).build(); -        RemoteInput freeformRemoteInput = -                new RemoteInput.Builder("b").setAllowFreeFormInput(true).build(); - -        Notification.Action actionWithFreeformRemoteInput = -                new Notification.Action.Builder(icon, "TEXT 1", intent) -                        .addRemoteInput(remoteInput) -                        .addRemoteInput(freeformRemoteInput) -                        .build(); - -        Notification.Action actionWithoutFreeformRemoteInput = -                new Notification.Action.Builder(icon, "TEXT 2", intent) -                        .addRemoteInput(remoteInput) -                        .build(); - -        Notification notification = new Notification.Builder(mContext, "test") -                .addAction(actionWithoutFreeformRemoteInput) -                .addAction(actionWithFreeformRemoteInput) -                .build(); - -        Pair<RemoteInput, Notification.Action> remoteInputActionPair = -                notification.findRemoteInputActionPair(true); - -        assertNotNull(remoteInputActionPair); -        assertEquals(freeformRemoteInput, remoteInputActionPair.first); -        assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second); -    } -} - diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java index d5e336b1cf2f..eed32d7d815c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java @@ -40,14 +40,18 @@ import static org.mockito.ArgumentMatchers.anyInt;  import static org.mockito.ArgumentMatchers.anyString;  import static org.mockito.ArgumentMatchers.eq;  import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doNothing;  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.timeout; +import android.app.ActivityOptions;  import android.app.WaitResult;  import android.content.ComponentName;  import android.content.Intent;  import android.content.pm.ActivityInfo; +import android.os.Binder;  import android.os.ConditionVariable; +import android.os.IBinder;  import android.os.RemoteException;  import android.platform.test.annotations.Presubmit;  import android.view.Display; @@ -308,4 +312,40 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {          waitHandlerIdle(mAtm.mH);          verify(mRootWindowContainer, timeout(TIMEOUT_MS)).startHomeOnEmptyDisplays("userUnlocked");      } + +    /** Verifies that launch from recents sets the launch cookie on the activity. */ +    @Test +    public void testStartActivityFromRecents_withLaunchCookie() { +        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); + +        IBinder launchCookie = new Binder("test_launch_cookie"); +        ActivityOptions options = ActivityOptions.makeBasic(); +        options.setLaunchCookie(launchCookie); +        SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(options.toBundle()); + +        doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(), +                anyInt(), any()); + +        mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions); + +        assertThat(activity.mLaunchCookie).isEqualTo(launchCookie); +        verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions)); +    } + +    /** Verifies that launch from recents doesn't set the launch cookie on the activity. */ +    @Test +    public void testStartActivityFromRecents_withoutLaunchCookie() { +        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); + +        SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle( +                ActivityOptions.makeBasic().toBundle()); + +        doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(), +                anyInt(), any()); + +        mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions); + +        assertThat(activity.mLaunchCookie).isNull(); +        verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions)); +    }  } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java index 7abe3698a99c..d5356774ffdb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java @@ -88,7 +88,7 @@ public class TaskPositionerTests extends WindowTestsBase {      @After      public void tearDown() { -        mPositioner = null; +        TaskPositioner.setFactory(null);      }      @Test diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index f43f0a53650b..fcb76b3920fe 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -8658,11 +8658,12 @@ public class CarrierConfigManager {      /**       * Boolean indicating if the VoNR setting is visible in the Call Settings menu. -     * If true, the VoNR setting menu will be visible. If false, the menu will be gone. +     * If this flag is set and VoNR is enabled for this carrier (see {@link #KEY_VONR_ENABLED_BOOL}) +     * the VoNR setting menu will be visible. If {@link #KEY_VONR_ENABLED_BOOL} or +     * this setting is false, the menu will be gone.       * -     * Disabled by default. +     * Enabled by default.       * -     * @hide       */      public static final String KEY_VONR_SETTING_VISIBILITY_BOOL = "vonr_setting_visibility_bool"; @@ -8672,7 +8673,6 @@ public class CarrierConfigManager {       *       * Disabled by default.       * -     * @hide       */      public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool"; diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java index 35859fe1472e..29d87a25c939 100644 --- a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java +++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java @@ -18,6 +18,7 @@ package com.android.tests.rollback.host;  import static org.junit.Assert.assertTrue;  import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue;  import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;  import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; @@ -35,6 +36,7 @@ import java.util.concurrent.TimeUnit;   */  @RunWith(DeviceJUnit4ClassRunner.class)  public class MultiUserRollbackTest extends BaseHostJUnit4Test { +    private boolean mSupportMultiUsers;      // The user that was running originally when the test starts.      private int mOriginalUserId;      private int mSecondaryUserId = -1; @@ -46,14 +48,20 @@ public class MultiUserRollbackTest extends BaseHostJUnit4Test {      @After      public void tearDown() throws Exception { -        removeSecondaryUserIfNecessary(); -        runPhaseForUsers("cleanUp", mOriginalUserId); -        uninstallPackage("com.android.cts.install.lib.testapp.A"); -        uninstallPackage("com.android.cts.install.lib.testapp.B"); +        if (mSupportMultiUsers) { +            removeSecondaryUserIfNecessary(); +            runPhaseForUsers("cleanUp", mOriginalUserId); +            uninstallPackage("com.android.cts.install.lib.testapp.A"); +            uninstallPackage("com.android.cts.install.lib.testapp.B"); +        }      }      @Before      public void setup() throws Exception { +        assumeTrue("Device does not support multiple users", +                getDevice().isMultiUserSupported()); + +        mSupportMultiUsers = true;          mOriginalUserId = getDevice().getCurrentUser();          createAndStartSecondaryUser();          installPackage("RollbackTest.apk", "--user all"); diff --git a/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml b/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml index 5a135c978343..7fe4bae2a3fe 100644 --- a/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml +++ b/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml @@ -16,7 +16,7 @@    -->  <manifest xmlns:android="http://schemas.android.com/apk/res/android"            package="com.android.sample.rollbackapp" > -    <uses-permission android:name="android.permission.TEST_MANAGE_ROLLBACKS" /> +    <uses-permission android:name="android.permission.MANAGE_ROLLBACKS" />      <application          android:label="@string/title_activity_main">          <activity @@ -28,4 +28,4 @@              </intent-filter>          </activity>      </application> -</manifest>
\ No newline at end of file +</manifest> diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt index 68a450d956a8..1b0f03564c3b 100644 --- a/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt +++ b/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt @@ -26,6 +26,7 @@ import com.android.tools.lint.detector.api.Scope  import com.android.tools.lint.detector.api.Severity  import com.android.tools.lint.detector.api.SourceCodeScanner  import com.android.tools.lint.detector.api.getUMethod +import com.google.android.lint.aidl.hasPermissionMethodAnnotation  import com.intellij.psi.PsiType  import org.jetbrains.uast.UAnnotation  import org.jetbrains.uast.UBlockExpression @@ -149,11 +150,6 @@ class PermissionMethodDetector : Detector(), SourceCodeScanner {              enabledByDefault = false          ) -        private fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations -            .any { -                it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD) -            } -          private fun isPermissionMethodReturnType(method: UMethod): Boolean =              listOf(PsiType.VOID, PsiType.INT, PsiType.BOOLEAN).contains(method.returnType) diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt index 510611161ea8..d120e1d41c99 100644 --- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt +++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt @@ -18,37 +18,54 @@ package com.google.android.lint.aidl  import com.android.tools.lint.detector.api.JavaContext  import com.android.tools.lint.detector.api.Location -import com.intellij.psi.PsiVariable +import com.android.tools.lint.detector.api.getUMethod +import org.jetbrains.kotlin.psi.psiUtil.parameterIndex  import org.jetbrains.uast.UCallExpression -import org.jetbrains.uast.ULiteralExpression -import org.jetbrains.uast.UQualifiedReferenceExpression -import org.jetbrains.uast.USimpleNameReferenceExpression -import org.jetbrains.uast.asRecursiveLogString +import org.jetbrains.uast.evaluateString +import org.jetbrains.uast.visitor.AbstractUastVisitor  /** - * Helper ADT class that facilitates the creation of lint auto fixes + * Helper class that facilitates the creation of lint auto fixes   *   * Handles "Single" permission checks that should be migrated to @EnforcePermission(...), as well as consecutive checks   * that should be migrated to @EnforcePermission(allOf={...})   *   * TODO: handle anyOf style annotations   */ -sealed class EnforcePermissionFix { -    abstract fun locations(): List<Location> -    abstract fun javaAnnotationParameter(): String - -    fun javaAnnotation(): String = "@$ANNOTATION_ENFORCE_PERMISSION(${javaAnnotationParameter()})" +data class EnforcePermissionFix( +    val locations: List<Location>, +    val permissionNames: List<String> +) { +    val annotation: String +        get() { +            val quotedPermissions = permissionNames.joinToString(", ") { """"$it"""" } +            val annotationParameter = +                if (permissionNames.size > 1) "allOf={$quotedPermissions}" else quotedPermissions +            return "@$ANNOTATION_ENFORCE_PERMISSION($annotationParameter)" +        }      companion object { -        fun fromCallExpression(callExpression: UCallExpression, context: JavaContext): SingleFix = -            SingleFix( -                getPermissionCheckLocation(context, callExpression), -                getPermissionCheckArgumentValue(callExpression) -            ) +        /** +         * conditionally constructs EnforcePermissionFix from a UCallExpression +         * @return EnforcePermissionFix if the called method is annotated with @PermissionMethod, else null +         */ +        fun fromCallExpression( +            context: JavaContext, +            callExpression: UCallExpression +        ): EnforcePermissionFix? = +            if (isPermissionMethodCall(callExpression)) { +                EnforcePermissionFix( +                    listOf(getPermissionCheckLocation(context, callExpression)), +                    getPermissionCheckValues(callExpression) +                ) +            } else null -        fun maybeAddManifestPrefix(permissionName: String): String = -            if (permissionName.contains(".")) permissionName -            else "android.Manifest.permission.$permissionName" + +        fun compose(individuals: List<EnforcePermissionFix>): EnforcePermissionFix = +            EnforcePermissionFix( +                individuals.flatMap { it.locations }, +                individuals.flatMap { it.permissionNames } +            )          /**           * Given a permission check, get its proper location @@ -70,49 +87,51 @@ sealed class EnforcePermissionFix {          }          /** -         * Given a permission check and an argument, -         * pull out the permission value that is being used +         * Given a @PermissionMethod, find arguments annotated with @PermissionName +         * and pull out the permission value(s) being used.  Also evaluates nested calls +         * to @PermissionMethod(s) in the given method's body.           */ -        private fun getPermissionCheckArgumentValue( -            callExpression: UCallExpression, -            argumentPosition: Int = 0 -        ): String { +        private fun getPermissionCheckValues( +            callExpression: UCallExpression +        ): List<String> { +            if (!isPermissionMethodCall(callExpression)) return emptyList() -            val identifier = when ( -                val argument = callExpression.valueArguments.getOrNull(argumentPosition) -            ) { -                is UQualifiedReferenceExpression -> when (val selector = argument.selector) { -                    is USimpleNameReferenceExpression -> -                        ((selector.resolve() as PsiVariable).computeConstantValue() as String) +            val result = mutableSetOf<String>() // protect against duplicate permission values +            val visitedCalls = mutableSetOf<UCallExpression>() // don't visit the same call twice +            val bfsQueue = ArrayDeque(listOf(callExpression)) -                    else -> throw RuntimeException( -                        "Couldn't resolve argument: ${selector.asRecursiveLogString()}" -                    ) -                } +            // Breadth First Search - evalutaing nested @PermissionMethod(s) in the available +            // source code for @PermissionName(s). +            while (bfsQueue.isNotEmpty()) { +                val current = bfsQueue.removeFirst() +                visitedCalls.add(current) +                result.addAll(findPermissions(current)) -                is USimpleNameReferenceExpression -> ( -                        (argument.resolve() as PsiVariable).computeConstantValue() as String) +                current.resolve()?.getUMethod()?.accept(object : AbstractUastVisitor() { +                    override fun visitCallExpression(node: UCallExpression): Boolean { +                        if (isPermissionMethodCall(node) && node !in visitedCalls) { +                            bfsQueue.add(node) +                        } +                        return false +                    } +                }) +            } -                is ULiteralExpression -> argument.value as String +            return result.toList() +        } -                else -> throw RuntimeException( -                    "Couldn't resolve argument: ${argument?.asRecursiveLogString()}" -                ) -            } +        private fun findPermissions( +            callExpression: UCallExpression, +        ): List<String> { +            val indices = callExpression.resolve()?.getUMethod() +                ?.uastParameters +                ?.filter(::hasPermissionNameAnnotation) +                ?.mapNotNull { it.sourcePsi?.parameterIndex() } +                ?: emptyList() -            return identifier.substringAfterLast(".") +            return indices.mapNotNull { +                callExpression.getArgumentForParameter(it)?.evaluateString() +            }          }      }  } - -data class SingleFix(val location: Location, val permissionName: String) : EnforcePermissionFix() { -    override fun locations(): List<Location> = listOf(this.location) -    override fun javaAnnotationParameter(): String = maybeAddManifestPrefix(this.permissionName) -} -data class AllOfFix(val checks: List<SingleFix>) : EnforcePermissionFix() { -    override fun locations(): List<Location> = this.checks.map { it.location } -    override fun javaAnnotationParameter(): String = -        "allOf={${ -            this.checks.joinToString(", ") { maybeAddManifestPrefix(it.permissionName) } -        }}" -} diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt new file mode 100644 index 000000000000..edbdd8d2adf3 --- /dev/null +++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.lint.aidl + +import com.android.tools.lint.detector.api.getUMethod +import com.google.android.lint.ANNOTATION_PERMISSION_METHOD +import com.google.android.lint.ANNOTATION_PERMISSION_NAME +import com.google.android.lint.CLASS_STUB +import com.intellij.psi.PsiAnonymousClass +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UMethod +import org.jetbrains.uast.UParameter + +/** + * Given a UMethod, determine if this method is + * an entrypoint to an interface generated by AIDL, + * returning the interface name if so + */ +fun getContainingAidlInterface(node: UMethod): String? { +    if (!isInClassCalledStub(node)) return null +    for (superMethod in node.findSuperMethods()) { +        for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements +            ?: continue) { +            if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) { +                return superMethod.containingClass?.name +            } +        } +    } +    return null +} + +private fun isInClassCalledStub(node: UMethod): Boolean { +    (node.containingClass as? PsiAnonymousClass)?.let { +        return it.baseClassReference.referenceName == CLASS_STUB +    } +    return node.containingClass?.extendsList?.referenceElements?.any { +        it.referenceName == CLASS_STUB +    } ?: false +} + +fun isPermissionMethodCall(callExpression: UCallExpression): Boolean { +    val method = callExpression.resolve()?.getUMethod() ?: return false +    return hasPermissionMethodAnnotation(method) +} + +fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations +    .any { +        it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD) +    } + +fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any { +    it.hasQualifiedName(ANNOTATION_PERMISSION_NAME) +} diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt index 2cea39423f1d..2c53f390128c 100644 --- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt +++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt @@ -25,9 +25,6 @@ import com.android.tools.lint.detector.api.JavaContext  import com.android.tools.lint.detector.api.Scope  import com.android.tools.lint.detector.api.Severity  import com.android.tools.lint.detector.api.SourceCodeScanner -import com.google.android.lint.CLASS_STUB -import com.google.android.lint.ENFORCE_PERMISSION_METHODS -import com.intellij.psi.PsiAnonymousClass  import org.jetbrains.uast.UBlockExpression  import org.jetbrains.uast.UCallExpression  import org.jetbrains.uast.UElement @@ -56,7 +53,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {              val body = (node.uastBody as? UBlockExpression) ?: return              val fix = accumulateSimplePermissionCheckFixes(body) ?: return -            val javaRemoveFixes = fix.locations().map { +            val javaRemoveFixes = fix.locations.map {                  fix()                      .replace()                      .reformat(true) @@ -67,7 +64,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {              }              val javaAnnotateFix = fix() -                .annotate(fix.javaAnnotation()) +                .annotate(fix.annotation)                  .range(context.getLocation(node))                  .autoFix()                  .build() @@ -77,7 +74,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {              context.report(                  ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION, -                fix.locations().last(), +                fix.locations.last(),                  message,                  fix().composite(*javaRemoveFixes.toTypedArray(), javaAnnotateFix)              ) @@ -97,14 +94,14 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {           */          private fun accumulateSimplePermissionCheckFixes(methodBody: UBlockExpression):                  EnforcePermissionFix? { -            val singleFixes = mutableListOf<SingleFix>() +            val singleFixes = mutableListOf<EnforcePermissionFix>()              for (expression in methodBody.expressions) {                  singleFixes.add(getPermissionCheckFix(expression) ?: break)              }              return when (singleFixes.size) {                  0 -> null                  1 -> singleFixes[0] -                else -> AllOfFix(singleFixes) +                else -> EnforcePermissionFix.compose(singleFixes)              }          } @@ -113,7 +110,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {           * the helper for creating a lint auto fix to @EnforcePermission           */          private fun getPermissionCheckFix(startingExpression: UElement?): -                SingleFix? { +                EnforcePermissionFix? {              return when (startingExpression) {                  is UQualifiedReferenceExpression -> getPermissionCheckFix(                      startingExpression.selector @@ -121,11 +118,8 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {                  is UIfExpression -> getPermissionCheckFix(startingExpression.condition) -                is UCallExpression -> { -                    return if (isPermissionCheck(startingExpression)) -                        EnforcePermissionFix.fromCallExpression(startingExpression, context) -                    else null -                } +                is UCallExpression -> return EnforcePermissionFix +                            .fromCallExpression(context, startingExpression)                  else -> null              } @@ -160,40 +154,5 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {              ),              enabledByDefault = false, // TODO: enable once b/241171714 is resolved          ) - -        private fun isPermissionCheck(callExpression: UCallExpression): Boolean { -            val method = callExpression.resolve() ?: return false -            val className = method.containingClass?.qualifiedName -            return ENFORCE_PERMISSION_METHODS.any { -                it.clazz == className && it.name == method.name -            } -        } - -        /** -         * given a UMethod, determine if this method is -         * an entrypoint to an interface generated by AIDL, -         * returning the interface name if so -         */ -        fun getContainingAidlInterface(node: UMethod): String? { -            if (!isInClassCalledStub(node)) return null -            for (superMethod in node.findSuperMethods()) { -                for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements -                    ?: continue) { -                    if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) { -                        return superMethod.containingClass?.name -                    } -                } -            } -            return null -        } - -        private fun isInClassCalledStub(node: UMethod): Boolean { -            (node.containingClass as? PsiAnonymousClass)?.let { -                return it.baseClassReference.referenceName == CLASS_STUB -            } -            return node.containingClass?.extendsList?.referenceElements?.any { -                it.referenceName == CLASS_STUB -            } ?: false -        }      }  } diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt index 1a1c6bc77785..a968f5e6cb1c 100644 --- a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt +++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt @@ -19,6 +19,7 @@ package com.google.android.lint.aidl  import com.android.tools.lint.checks.infrastructure.LintDetectorTest  import com.android.tools.lint.checks.infrastructure.TestFile  import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.checks.infrastructure.TestMode  import com.android.tools.lint.detector.api.Detector  import com.android.tools.lint.detector.api.Issue @@ -42,7 +43,7 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {                          private Context mContext;                          @Override                          public void test() throws android.os.RemoteException { -                            mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo"); +                            mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");                          }                      }                  """ @@ -53,8 +54,8 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {              .expect(                  """                  src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation] -                        mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo"); -                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); +                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                  0 errors, 1 warnings                  """              ) @@ -62,9 +63,9 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {                  """                  Fix for src/Foo.java line 7: Annotate with @EnforcePermission:                  @@ -5 +5 -                +     @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS) +                +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")                  @@ -7 +8 -                -         mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo"); +                -         mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");                  """              )      } @@ -81,7 +82,7 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {                              @Override                              public void test() throws android.os.RemoteException {                                  mContext.enforceCallingOrSelfPermission( -                                    "android.Manifest.permission.READ_CONTACTS", "foo"); +                                    "android.permission.READ_CONTACTS", "foo");                              }                          };                      } @@ -102,10 +103,49 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {                  """                  Fix for src/Foo.java line 8: Annotate with @EnforcePermission:                  @@ -6 +6 -                +         @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS) +                +         @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")                  @@ -8 +9                  -             mContext.enforceCallingOrSelfPermission( -                -                 "android.Manifest.permission.READ_CONTACTS", "foo"); +                -                 "android.permission.READ_CONTACTS", "foo"); +                """ +            ) +    } + +    fun testConstantEvaluation() { +        lint().files( +            java( +                """ +                    import android.content.Context; +                    import android.test.ITest; + +                    public class Foo extends ITest.Stub { +                        private Context mContext; +                        @Override +                        public void test() throws android.os.RemoteException { +                            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo"); +                        } +                    } +                """ +            ).indented(), +            *stubs, +            manifestStub +        ) +            .run() +            .expect( +                """ +                src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation] +                        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo"); +                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +                0 errors, 1 warnings +                """ +            ) +            .expectFixDiffs( +                """ +                Fix for src/Foo.java line 7: Annotate with @EnforcePermission: +                @@ -6 +6 +                +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS") +                @@ -8 +9 +                -         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");                  """              )      } @@ -122,9 +162,9 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {                              @Override                              public void test() throws android.os.RemoteException {                                  mContext.enforceCallingOrSelfPermission( -                                    "android.Manifest.permission.READ_CONTACTS", "foo"); +                                    "android.permission.READ_CONTACTS", "foo");                                  mContext.enforceCallingOrSelfPermission( -                                    "android.Manifest.permission.WRITE_CONTACTS", "foo"); +                                    "android.permission.WRITE_CONTACTS", "foo");                              }                          };                      } @@ -144,13 +184,13 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {              .expectFixDiffs(                  """                  Fix for src/Foo.java line 10: Annotate with @EnforcePermission: -                @@ -6 +6                                                                                                                                                                                                        -                +         @android.annotation.EnforcePermission(allOf={android.Manifest.permission.READ_CONTACTS, android.Manifest.permission.WRITE_CONTACTS}) +                @@ -6 +6 +                +         @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"})                  @@ -8 +9                  -             mContext.enforceCallingOrSelfPermission( -                -                 "android.Manifest.permission.READ_CONTACTS", "foo"); +                -                 "android.permission.READ_CONTACTS", "foo");                  -             mContext.enforceCallingOrSelfPermission( -                -                 "android.Manifest.permission.WRITE_CONTACTS", "foo"); +                -                 "android.permission.WRITE_CONTACTS", "foo");                  """              )      } @@ -166,7 +206,7 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {                          @Override                          public void test() throws android.os.RemoteException {                              long uid = Binder.getCallingUid(); -                            mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo"); +                            mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");                          }                      }                  """ @@ -177,6 +217,149 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {              .expectClean()      } +    fun testPermissionHelper() { +        lint().skipTestModes(TestMode.PARENTHESIZED).files( +            java( +                """ +                    import android.content.Context; +                    import android.test.ITest; + +                    public class Foo extends ITest.Stub { +                        private Context mContext; + +                        @android.content.pm.PermissionMethod +                        private void helper() { +                            mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); +                        } + +                        @Override +                        public void test() throws android.os.RemoteException { +                            helper(); +                        } +                    } +                """ +            ).indented(), +            *stubs +        ) +            .run() +            .expect( +                """ +                src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation] +                        helper(); +                        ~~~~~~~~~ +                0 errors, 1 warnings +                """ +            ) +            .expectFixDiffs( +                """ +                Fix for src/Foo.java line 14: Annotate with @EnforcePermission: +                @@ -12 +12 +                +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS") +                @@ -14 +15 +                -         helper(); +                """ +            ) +    } + +    fun testPermissionHelperAllOf() { +        lint().skipTestModes(TestMode.PARENTHESIZED).files( +            java( +                """ +                import android.content.Context; +                import android.test.ITest; + +                public class Foo extends ITest.Stub { +                    private Context mContext; + +                    @android.content.pm.PermissionMethod +                    private void helper() { +                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); +                        mContext.enforceCallingOrSelfPermission("android.permission.WRITE_CONTACTS", "foo"); +                    } + +                    @Override +                    public void test() throws android.os.RemoteException { +                        helper(); +                        mContext.enforceCallingOrSelfPermission("FOO", "foo"); +                    } +                } +                """ +            ).indented(), +            *stubs +        ) +            .run() +            .expect( +                """ +                src/Foo.java:16: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation] +                        mContext.enforceCallingOrSelfPermission("FOO", "foo"); +                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +                0 errors, 1 warnings +                """ +            ) +            .expectFixDiffs( +                """ +                Fix for src/Foo.java line 16: Annotate with @EnforcePermission: +                @@ -13 +13 +                +     @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS", "FOO"}) +                @@ -15 +16 +                -         helper(); +                -         mContext.enforceCallingOrSelfPermission("FOO", "foo"); +                """ +            ) +    } + + +    fun testPermissionHelperNested() { +        lint().skipTestModes(TestMode.PARENTHESIZED).files( +            java( +                """ +                import android.content.Context; +                import android.test.ITest; + +                public class Foo extends ITest.Stub { +                    private Context mContext; + +                    @android.content.pm.PermissionMethod +                    private void helperHelper() { +                        helper("android.permission.WRITE_CONTACTS"); +                    } + +                    @android.content.pm.PermissionMethod +                    private void helper(@android.content.pm.PermissionName String extraPermission) { +                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); +                    } + +                    @Override +                    public void test() throws android.os.RemoteException { +                        helperHelper(); +                    } +                } +                """ +            ).indented(), +            *stubs +        ) +            .run() +            .expect( +                """ +                src/Foo.java:19: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation] +                        helperHelper(); +                        ~~~~~~~~~~~~~~~ +                0 errors, 1 warnings +                """ +            ) +            .expectFixDiffs( +                """ +                Fix for src/Foo.java line 19: Annotate with @EnforcePermission: +                @@ -17 +17 +                +     @android.annotation.EnforcePermission(allOf={"android.permission.WRITE_CONTACTS", "android.permission.READ_CONTACTS"}) +                @@ -19 +20 +                -         helperHelper(); +                """ +            ) +    } + + +      companion object {          private val aidlStub: TestFile = java(              """ @@ -192,7 +375,8 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {              """                  package android.content;                  public class Context { -                    public void enforceCallingOrSelfPermission(String permission, String message) {} +                    @android.content.pm.PermissionMethod +                    public void enforceCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}                  }              """          ).indented() @@ -206,6 +390,59 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {              """          ).indented() -        val stubs = arrayOf(aidlStub, contextStub, binderStub) +        private val permissionMethodStub: TestFile = java( +            """ +                package android.content.pm; + +                import static java.lang.annotation.ElementType.METHOD; +                import static java.lang.annotation.RetentionPolicy.CLASS; + +                import java.lang.annotation.Retention; +                import java.lang.annotation.Target; + +                @Retention(CLASS) +                @Target({METHOD}) +                public @interface PermissionMethod {} +            """ +        ).indented() + +        private val permissionNameStub: TestFile = java( +            """ +                package android.content.pm; + +                import static java.lang.annotation.ElementType.FIELD; +                import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +                import static java.lang.annotation.ElementType.METHOD; +                import static java.lang.annotation.ElementType.PARAMETER; +                import static java.lang.annotation.RetentionPolicy.CLASS; + +                import java.lang.annotation.Retention; +                import java.lang.annotation.Target; + +                @Retention(CLASS) +                @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD}) +                public @interface PermissionName {} +            """ +        ).indented() + +        private val manifestStub: TestFile = java( +            """ +                package android; + +                public final class Manifest { +                    public static final class permission { +                        public static final String READ_CONTACTS="android.permission.READ_CONTACTS"; +                    } +                } +            """.trimIndent() +        ) + +        val stubs = arrayOf( +            aidlStub, +            contextStub, +            binderStub, +            permissionMethodStub, +            permissionNameStub +        )      }  }  |