diff options
60 files changed, 2215 insertions, 694 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 901796be9064..bedfa7f99d99 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -87,6 +87,7 @@ import android.content.pm.PackageManagerInternal; import android.database.ContentObserver; import android.net.Uri; import android.os.BatteryManager; +import android.os.BatteryStatsInternal; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -116,6 +117,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; import android.util.IndentingPrintWriter; +import android.util.IntArray; import android.util.Log; import android.util.LongArrayQueue; import android.util.Pair; @@ -249,6 +251,7 @@ public class AlarmManagerService extends SystemService { private ActivityManagerInternal mActivityManagerInternal; private final EconomyManagerInternal mEconomyManagerInternal; private PackageManagerInternal mPackageManagerInternal; + private BatteryStatsInternal mBatteryStatsInternal; private RoleManager mRoleManager; private volatile PermissionManagerServiceInternal mLocalPermissionManager; @@ -2113,6 +2116,8 @@ public class AlarmManagerService extends SystemService { LocalServices.getService(AppStandbyInternal.class); appStandbyInternal.addListener(new AppStandbyTracker()); + mBatteryStatsInternal = LocalServices.getService(BatteryStatsInternal.class); + mRoleManager = getContext().getSystemService(RoleManager.class); mMetricsHelper.registerPuller(() -> mAlarmStore); @@ -4783,8 +4788,12 @@ public class AlarmManagerService extends SystemService { } final ArraySet<Pair<String, Integer>> triggerPackages = new ArraySet<>(); + final IntArray wakeupUids = new IntArray(); for (int i = 0; i < triggerList.size(); i++) { final Alarm a = triggerList.get(i); + if (a.wakeup) { + wakeupUids.add(a.uid); + } if (mConstants.USE_TARE_POLICY) { if (!isExemptFromTare(a)) { triggerPackages.add(Pair.create( @@ -4796,6 +4805,11 @@ public class AlarmManagerService extends SystemService { a.sourcePackage, UserHandle.getUserId(a.creatorUid))); } } + if (wakeupUids.size() > 0 && mBatteryStatsInternal != null) { + mBatteryStatsInternal.noteCpuWakingActivity( + BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM, nowELAPSED, + wakeupUids.toArray()); + } deliverAlarmsLocked(triggerList, nowELAPSED); mTemporaryQuotaReserve.cleanUpExpiredQuotas(nowELAPSED); if (mConstants.USE_TARE_POLICY) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java index a1a541f92b38..b2ca3a051e36 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java @@ -337,7 +337,6 @@ public class TareController extends StateController { removeJobFromBillList(jobStatus, billToJobMap.keyAt(i)); } } - addJobToBillList(jobStatus, getRunningBill(jobStatus)); final int uid = jobStatus.getSourceUid(); if (mService.getUidBias(uid) == JobInfo.BIAS_TOP_APP) { @@ -347,6 +346,7 @@ public class TareController extends StateController { mTopStartedJobs.add(jobStatus); // Top jobs won't count towards quota so there's no need to involve the EconomyManager. } else { + addJobToBillList(jobStatus, getRunningBill(jobStatus)); mEconomyManagerInternal.noteOngoingEventStarted(userId, pkgName, getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId())); } @@ -357,9 +357,14 @@ public class TareController extends StateController { public void unprepareFromExecutionLocked(JobStatus jobStatus) { final int userId = jobStatus.getSourceUserId(); final String pkgName = jobStatus.getSourcePackageName(); - mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName, - getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId())); - mTopStartedJobs.remove(jobStatus); + // If this method is called, then jobStatus.madeActive was never updated, so don't use it + // to determine if the EconomyManager was notified. + if (!mTopStartedJobs.remove(jobStatus)) { + // If the job was started while the app was top, then the EconomyManager wasn't notified + // of the job start. + mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName, + getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId())); + } final ArraySet<ActionBill> bills = getPossibleStartBills(jobStatus); ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap = @@ -382,9 +387,13 @@ public class TareController extends StateController { boolean forUpdate) { final int userId = jobStatus.getSourceUserId(); final String pkgName = jobStatus.getSourcePackageName(); - mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName, - getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId())); - mTopStartedJobs.remove(jobStatus); + if (!mTopStartedJobs.remove(jobStatus) && jobStatus.madeActive > 0) { + // Only note the job stop if we previously told the EconomyManager that the job started. + // If the job was started while the app was top, then the EconomyManager wasn't notified + // of the job start. + mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName, + getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId())); + } ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap = mRegisteredBillsAndJobs.get(userId, pkgName); if (billToJobMap != null) { diff --git a/core/api/current.txt b/core/api/current.txt index 815ccfe0b1c6..967645158d23 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8947,6 +8947,7 @@ package android.companion { public final class AssociationInfo implements android.os.Parcelable { method public int describeContents(); + method @Nullable public android.os.Parcelable getAssociatedDevice(); method @Nullable public android.net.MacAddress getDeviceMacAddress(); method @Nullable public String getDeviceProfile(); method @Nullable public CharSequence getDisplayName(); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index ddfbc6847ed0..302d1469e1fb 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -159,6 +159,7 @@ interface INotificationManager void clearRequestedListenerHints(in INotificationListener token); void requestHintsFromListener(in INotificationListener token, int hints); int getHintsFromListener(in INotificationListener token); + int getHintsFromListenerNoToken(); void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter); int getInterruptionFilterFromListener(in INotificationListener token); void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 392f52a08fb5..f6d27ad08b00 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -66,9 +66,9 @@ import java.util.Objects; /** * Class to notify the user of events that happen. This is how you tell - * the user that something has happened in the background. {@more} + * the user that something has happened in the background. * - * Notifications can take different forms: + * <p>Notifications can take different forms: * <ul> * <li>A persistent icon that goes in the status bar and is accessible * through the launcher, (when the user selects it, a designated Intent diff --git a/core/java/android/companion/AssociatedDevice.java b/core/java/android/companion/AssociatedDevice.java new file mode 100644 index 000000000000..3758cdb680b1 --- /dev/null +++ b/core/java/android/companion/AssociatedDevice.java @@ -0,0 +1,133 @@ +/* + * 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.companion; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Loose wrapper around device parcelable. Device can be one of three types: + * + * <ul> + * <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li> + * <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li> + * <li>for WiFi - {@link android.net.wifi.ScanResult}</li> + * </ul> + * + * This class serves as temporary wrapper to deliver a loosely-typed parcelable object from + * {@link com.android.companiondevicemanager.CompanionDeviceActivity} to the Companion app, + * and should only be used internally. + * + * @hide + */ +public final class AssociatedDevice implements Parcelable { + private static final int CLASSIC_BLUETOOTH = 0; + private static final int BLUETOOTH_LE = 1; + private static final int WIFI = 2; + + @NonNull private final Parcelable mDevice; + + public AssociatedDevice(@NonNull Parcelable device) { + mDevice = device; + } + + private AssociatedDevice(Parcel in) { + Creator<? extends Parcelable> creator = getDeviceCreator(in.readInt()); + mDevice = creator.createFromParcel(in); + } + + /** + * Return device info. Cast to expected device type. + */ + @NonNull + public Parcelable getDevice() { + return mDevice; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + // Parcel device type with int for efficiency + dest.writeInt(getDeviceType()); + mDevice.writeToParcel(dest, flags); + } + + @Override + public int describeContents() { + return 0; + } + + private int getDeviceType() { + if (mDevice instanceof android.bluetooth.BluetoothDevice) return CLASSIC_BLUETOOTH; + if (mDevice instanceof android.bluetooth.le.ScanResult) return BLUETOOTH_LE; + if (mDevice instanceof android.net.wifi.ScanResult) return WIFI; + throw new UnsupportedOperationException("Unsupported device type."); + } + + private static Creator<? extends Parcelable> getDeviceCreator(int deviceType) { + switch (deviceType) { + case CLASSIC_BLUETOOTH: return android.bluetooth.BluetoothDevice.CREATOR; + case BLUETOOTH_LE: return android.bluetooth.le.ScanResult.CREATOR; + case WIFI: return android.net.wifi.ScanResult.CREATOR; + default: throw new UnsupportedOperationException("Unsupported device type."); + } + } + + @NonNull + public static final Parcelable.Creator<AssociatedDevice> CREATOR = + new Parcelable.Creator<AssociatedDevice>() { + @Override + public AssociatedDevice[] newArray(int size) { + return new AssociatedDevice[size]; + } + + @Override + public AssociatedDevice createFromParcel(@NonNull Parcel in) { + return new AssociatedDevice(in); + } + }; + + @Override + public String toString() { + return "AssociatedDevice { " + + "device = " + mDevice + + " }"; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AssociatedDevice that = (AssociatedDevice) o; + if (getDeviceType() != that.getDeviceType()) return false; + + // TODO(b/31972115): Take out this whole part ¯\_(ツ)_/¯ + if (mDevice instanceof android.bluetooth.le.ScanResult + || mDevice instanceof android.net.wifi.ScanResult) { + return mDevice.toString().equals(that.mDevice.toString()); + } + + return java.util.Objects.equals(mDevice, that.mDevice); + } + + @Override + public int hashCode() { + return java.util.Objects.hash(mDevice); + } +} diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 93748f81ffa1..93964b3f4180 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -52,6 +52,7 @@ public final class AssociationInfo implements Parcelable { private final @Nullable MacAddress mDeviceMacAddress; private final @Nullable CharSequence mDisplayName; private final @Nullable String mDeviceProfile; + private final @Nullable AssociatedDevice mAssociatedDevice; private final boolean mSelfManaged; private final boolean mNotifyOnDeviceNearby; @@ -78,8 +79,9 @@ public final class AssociationInfo implements Parcelable { */ public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName, @Nullable MacAddress macAddress, @Nullable CharSequence displayName, - @Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby, - boolean revoked, long timeApprovedMs, long lastTimeConnectedMs) { + @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice, + boolean selfManaged, boolean notifyOnDeviceNearby, boolean revoked, + long timeApprovedMs, long lastTimeConnectedMs) { if (id <= 0) { throw new IllegalArgumentException("Association ID should be greater than 0"); } @@ -96,6 +98,7 @@ public final class AssociationInfo implements Parcelable { mDeviceMacAddress = macAddress; mDisplayName = displayName; mDeviceProfile = deviceProfile; + mAssociatedDevice = associatedDevice; mSelfManaged = selfManaged; mNotifyOnDeviceNearby = notifyOnDeviceNearby; @@ -160,6 +163,24 @@ public final class AssociationInfo implements Parcelable { } /** + * Companion device that was associated. Note that this field is not persisted across sessions. + * + * Cast to expected device type before use: + * + * <ul> + * <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li> + * <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li> + * <li>for WiFi - {@link android.net.wifi.ScanResult}</li> + * </ul> + * + * @return the companion device that was associated, or {@code null} if the device is + * self-managed. + */ + public @Nullable Parcelable getAssociatedDevice() { + return mAssociatedDevice == null ? null : mAssociatedDevice.getDevice(); + } + + /** * @return whether the association is managed by the companion application it belongs to. * @see AssociationRequest.Builder#setSelfManaged(boolean) * @hide @@ -260,6 +281,7 @@ public final class AssociationInfo implements Parcelable { + ", mDisplayName='" + mDisplayName + '\'' + ", mDeviceProfile='" + mDeviceProfile + '\'' + ", mSelfManaged=" + mSelfManaged + + ", mAssociatedDevice=" + mAssociatedDevice + ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby + ", mRevoked=" + mRevoked + ", mTimeApprovedMs=" + new Date(mTimeApprovedMs) @@ -284,14 +306,15 @@ public final class AssociationInfo implements Parcelable { && Objects.equals(mPackageName, that.mPackageName) && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress) && Objects.equals(mDisplayName, that.mDisplayName) - && Objects.equals(mDeviceProfile, that.mDeviceProfile); + && Objects.equals(mDeviceProfile, that.mDeviceProfile) + && Objects.equals(mAssociatedDevice, that.mAssociatedDevice); } @Override public int hashCode() { return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName, - mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, mTimeApprovedMs, - mLastTimeConnectedMs); + mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, + mTimeApprovedMs, mLastTimeConnectedMs); } @Override @@ -309,6 +332,7 @@ public final class AssociationInfo implements Parcelable { dest.writeTypedObject(mDeviceMacAddress, 0); dest.writeCharSequence(mDisplayName); dest.writeString(mDeviceProfile); + dest.writeTypedObject(mAssociatedDevice, 0); dest.writeBoolean(mSelfManaged); dest.writeBoolean(mNotifyOnDeviceNearby); @@ -326,6 +350,7 @@ public final class AssociationInfo implements Parcelable { mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR); mDisplayName = in.readCharSequence(); mDeviceProfile = in.readString(); + mAssociatedDevice = in.readTypedObject(AssociatedDevice.CREATOR); mSelfManaged = in.readBoolean(); mNotifyOnDeviceNearby = in.readBoolean(); @@ -433,6 +458,7 @@ public final class AssociationInfo implements Parcelable { mOriginalInfo.mDeviceMacAddress, mOriginalInfo.mDisplayName, mOriginalInfo.mDeviceProfile, + mOriginalInfo.mAssociatedDevice, mOriginalInfo.mSelfManaged, mNotifyOnDeviceNearby, mRevoked, diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java index 75ab11531595..a2277e97c043 100644 --- a/core/java/android/companion/AssociationRequest.java +++ b/core/java/android/companion/AssociationRequest.java @@ -154,6 +154,11 @@ public final class AssociationRequest implements Parcelable { private @Nullable CharSequence mDisplayName; /** + * The device that was associated. Will be null for "self-managed" association. + */ + private @Nullable AssociatedDevice mAssociatedDevice; + + /** * Whether the association is to be managed by the companion application. */ private final boolean mSelfManaged; @@ -307,6 +312,11 @@ public final class AssociationRequest implements Parcelable { } /** @hide */ + public void setAssociatedDevice(AssociatedDevice associatedDevice) { + mAssociatedDevice = associatedDevice; + } + + /** @hide */ @NonNull @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public List<DeviceFilter<?>> getDeviceFilters() { @@ -439,6 +449,16 @@ public final class AssociationRequest implements Parcelable { /** + * The device that was associated. Will be null for "self-managed" association. + * + * @hide + */ + @DataClass.Generated.Member + public @Nullable AssociatedDevice getAssociatedDevice() { + return mAssociatedDevice; + } + + /** * The app package name of the application the association will belong to. * Populated by the system. * @@ -503,6 +523,7 @@ public final class AssociationRequest implements Parcelable { "deviceFilters = " + mDeviceFilters + ", " + "deviceProfile = " + mDeviceProfile + ", " + "displayName = " + mDisplayName + ", " + + "associatedDevice = " + mAssociatedDevice + ", " + "selfManaged = " + mSelfManaged + ", " + "forceConfirmation = " + mForceConfirmation + ", " + "packageName = " + mPackageName + ", " + @@ -530,6 +551,7 @@ public final class AssociationRequest implements Parcelable { && Objects.equals(mDeviceFilters, that.mDeviceFilters) && Objects.equals(mDeviceProfile, that.mDeviceProfile) && Objects.equals(mDisplayName, that.mDisplayName) + && Objects.equals(mAssociatedDevice, that.mAssociatedDevice) && mSelfManaged == that.mSelfManaged && mForceConfirmation == that.mForceConfirmation && Objects.equals(mPackageName, that.mPackageName) @@ -550,6 +572,7 @@ public final class AssociationRequest implements Parcelable { _hash = 31 * _hash + Objects.hashCode(mDeviceFilters); _hash = 31 * _hash + Objects.hashCode(mDeviceProfile); _hash = 31 * _hash + Objects.hashCode(mDisplayName); + _hash = 31 * _hash + Objects.hashCode(mAssociatedDevice); _hash = 31 * _hash + Boolean.hashCode(mSelfManaged); _hash = 31 * _hash + Boolean.hashCode(mForceConfirmation); _hash = 31 * _hash + Objects.hashCode(mPackageName); @@ -568,17 +591,19 @@ public final class AssociationRequest implements Parcelable { int flg = 0; if (mSingleDevice) flg |= 0x1; - if (mSelfManaged) flg |= 0x10; - if (mForceConfirmation) flg |= 0x20; - if (mSkipPrompt) flg |= 0x400; + if (mSelfManaged) flg |= 0x20; + if (mForceConfirmation) flg |= 0x40; + if (mSkipPrompt) flg |= 0x800; if (mDeviceProfile != null) flg |= 0x4; if (mDisplayName != null) flg |= 0x8; - if (mPackageName != null) flg |= 0x40; - if (mDeviceProfilePrivilegesDescription != null) flg |= 0x100; + if (mAssociatedDevice != null) flg |= 0x10; + if (mPackageName != null) flg |= 0x80; + if (mDeviceProfilePrivilegesDescription != null) flg |= 0x200; dest.writeInt(flg); dest.writeParcelableList(mDeviceFilters, flags); if (mDeviceProfile != null) dest.writeString(mDeviceProfile); if (mDisplayName != null) dest.writeCharSequence(mDisplayName); + if (mAssociatedDevice != null) dest.writeTypedObject(mAssociatedDevice, flags); if (mPackageName != null) dest.writeString(mPackageName); dest.writeInt(mUserId); if (mDeviceProfilePrivilegesDescription != null) dest.writeString(mDeviceProfilePrivilegesDescription); @@ -598,18 +623,20 @@ public final class AssociationRequest implements Parcelable { int flg = in.readInt(); boolean singleDevice = (flg & 0x1) != 0; - boolean selfManaged = (flg & 0x10) != 0; - boolean forceConfirmation = (flg & 0x20) != 0; - boolean skipPrompt = (flg & 0x400) != 0; + boolean selfManaged = (flg & 0x20) != 0; + boolean forceConfirmation = (flg & 0x40) != 0; + boolean skipPrompt = (flg & 0x800) != 0; List<DeviceFilter<?>> deviceFilters = new ArrayList<>(); in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(), (Class<android.companion.DeviceFilter<?>>) (Class<?>) android.companion.DeviceFilter.class); String deviceProfile = (flg & 0x4) == 0 ? null : in.readString(); CharSequence displayName = (flg & 0x8) == 0 ? null : (CharSequence) in.readCharSequence(); - String packageName = (flg & 0x40) == 0 ? null : in.readString(); + AssociatedDevice associatedDevice = (flg & 0x10) == 0 + ? null : (AssociatedDevice) in.readTypedObject(AssociatedDevice.CREATOR); + String packageName = (flg & 0x80) == 0 ? null : in.readString(); int userId = in.readInt(); - String deviceProfilePrivilegesDescription = (flg & 0x100) == 0 ? null : in.readString(); + String deviceProfilePrivilegesDescription = (flg & 0x200) == 0 ? null : in.readString(); long creationTime = in.readLong(); this.mSingleDevice = singleDevice; @@ -620,6 +647,7 @@ public final class AssociationRequest implements Parcelable { com.android.internal.util.AnnotationValidations.validate( DeviceProfile.class, null, mDeviceProfile); this.mDisplayName = displayName; + this.mAssociatedDevice = associatedDevice; this.mSelfManaged = selfManaged; this.mForceConfirmation = forceConfirmation; this.mPackageName = packageName; @@ -648,10 +676,10 @@ public final class AssociationRequest implements Parcelable { }; @DataClass.Generated( - time = 1649179640045L, + time = 1663088980513L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java", - inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_COMPUTER\nprivate final boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final boolean mSelfManaged\nprivate final boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.UserIdInt int mUserId\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate final long mCreationTime\nprivate boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic boolean isSelfManaged()\npublic boolean isForceConfirmation()\npublic boolean isSingleDevice()\npublic void setPackageName(java.lang.String)\npublic void setUserId(int)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic void setDisplayName(java.lang.CharSequence)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate boolean mSelfManaged\nprivate boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genConstDefs=false)") + inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_COMPUTER\nprivate final boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate @android.annotation.Nullable android.companion.AssociatedDevice mAssociatedDevice\nprivate final boolean mSelfManaged\nprivate final boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.UserIdInt int mUserId\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate final long mCreationTime\nprivate boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic boolean isSelfManaged()\npublic boolean isForceConfirmation()\npublic boolean isSingleDevice()\npublic void setPackageName(java.lang.String)\npublic void setUserId(int)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic void setDisplayName(java.lang.CharSequence)\npublic void setAssociatedDevice(android.companion.AssociatedDevice)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate boolean mSelfManaged\nprivate boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genConstDefs=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 4142bceb2c45..90973c307a20 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -145,7 +145,7 @@ public final class CompanionDeviceManager { * <li>for WiFi - {@link android.net.wifi.ScanResult}</li> * </ul> * - * @deprecated use {@link #EXTRA_ASSOCIATION} instead. + * @deprecated use {@link AssociationInfo#getAssociatedDevice()} instead. */ @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE"; diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java new file mode 100644 index 000000000000..122c54ad8144 --- /dev/null +++ b/core/java/android/credentials/ui/Entry.java @@ -0,0 +1,116 @@ +/* + * 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.annotation.NonNull; +import android.app.slice.Slice; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.AnnotationValidations; + +/** + * A credential, save, or action entry to be rendered. + * + * @hide + */ +public class Entry implements Parcelable { + // TODO: move to jetpack. + public static final String VERSION = "v1"; + public static final Uri CREDENTIAL_MANAGER_ENTRY_URI = Uri.parse("credentialmanager.slice"); + public static final String HINT_TITLE = "hint_title"; + public static final String HINT_SUBTITLE = "hint_subtitle"; + public static final String HINT_ICON = "hint_icon"; + + /** + * The intent extra key for the action chip {@code Entry} list when launching the UX activities. + */ + public static final String EXTRA_ENTRY_LIST_ACTION_CHIP = + "android.credentials.ui.extra.ENTRY_LIST_ACTION_CHIP"; + /** + * The intent extra key for the credential / save {@code Entry} list when launching the UX + * activities. + */ + public static final String EXTRA_ENTRY_LIST_CREDENTIAL = + "android.credentials.ui.extra.ENTRY_LIST_CREDENTIAL"; + /** + * The intent extra key for the authentication action {@code Entry} when launching the UX + * activities. + */ + public static final String EXTRA_ENTRY_AUTHENTICATION_ACTION = + "android.credentials.ui.extra.ENTRY_AUTHENTICATION_ACTION"; + + // TODO: may be changed to other type depending on the service implementation. + private final int mId; + + @NonNull + private final Slice mSlice; + + protected Entry(@NonNull Parcel in) { + int entryId = in.readInt(); + Slice slice = Slice.CREATOR.createFromParcel(in); + + mId = entryId; + mSlice = slice; + AnnotationValidations.validate(NonNull.class, null, mSlice); + } + + public Entry(int id, @NonNull Slice slice) { + mId = id; + mSlice = slice; + } + + /** + * Returns the id of this entry that's unique within the context of the CredentialManager + * request. + */ + public int getEntryId() { + return mId; + } + + /** + * Returns the Slice to be rendered. + */ + @NonNull + public Slice getSlice() { + return mSlice; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mId); + mSlice.writeToParcel(dest, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @NonNull Creator<Entry> CREATOR = new Creator<Entry>() { + @Override + public Entry createFromParcel(@NonNull Parcel in) { + return new Entry(in); + } + + @Override + public Entry[] newArray(int size) { + return new Entry[size]; + } + }; +} diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java index 49e5e49c2450..18e6ba430589 100644 --- a/core/java/android/credentials/ui/ProviderData.java +++ b/core/java/android/credentials/ui/ProviderData.java @@ -17,11 +17,15 @@ package android.credentials.ui; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.util.AnnotationValidations; +import java.util.ArrayList; +import java.util.List; + /** * Holds metadata and credential entries for a single provider. * @@ -36,13 +40,24 @@ public class ProviderData implements Parcelable { public static final String EXTRA_PROVIDER_DATA_LIST = "android.credentials.ui.extra.PROVIDER_DATA_LIST"; - // TODO: add entry data. - @NonNull private final String mPackageName; + @NonNull + private final List<Entry> mCredentialEntries; + @NonNull + private final List<Entry> mActionChips; + @Nullable + private final Entry mAuthenticationEntry; - public ProviderData(@NonNull String packageName) { + public ProviderData( + @NonNull String packageName, + @NonNull List<Entry> credentialEntries, + @NonNull List<Entry> actionChips, + @Nullable Entry authenticationEntry) { mPackageName = packageName; + mCredentialEntries = credentialEntries; + mActionChips = actionChips; + mAuthenticationEntry = authenticationEntry; } /** Returns the provider package name. */ @@ -51,15 +66,46 @@ public class ProviderData implements Parcelable { return mPackageName; } + @NonNull + public List<Entry> getCredentialEntries() { + return mCredentialEntries; + } + + @NonNull + public List<Entry> getActionChips() { + return mActionChips; + } + + @Nullable + public Entry getAuthenticationEntry() { + return mAuthenticationEntry; + } + protected ProviderData(@NonNull Parcel in) { String packageName = in.readString8(); mPackageName = packageName; AnnotationValidations.validate(NonNull.class, null, mPackageName); + + List<Entry> credentialEntries = new ArrayList<>(); + in.readTypedList(credentialEntries, Entry.CREATOR); + mCredentialEntries = credentialEntries; + AnnotationValidations.validate(NonNull.class, null, mCredentialEntries); + + List<Entry> actionChips = new ArrayList<>(); + in.readTypedList(actionChips, Entry.CREATOR); + mActionChips = actionChips; + AnnotationValidations.validate(NonNull.class, null, mActionChips); + + Entry authenticationEntry = in.readTypedObject(Entry.CREATOR); + mAuthenticationEntry = authenticationEntry; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString8(mPackageName); + dest.writeTypedList(mCredentialEntries); + dest.writeTypedList(mActionChips); + dest.writeTypedObject(mAuthenticationEntry, flags); } @Override diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java index 830584314039..bc6368639baa 100644 --- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java +++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java @@ -299,7 +299,7 @@ public final class SQLiteDatabaseConfiguration { if (isLegacyCompatibilityWalEnabled()) { return SQLiteCompatibilityWalFlags.getWALSyncMode(); } else { - return SQLiteGlobal.getDefaultSyncMode(); + return SQLiteGlobal.getWALSyncMode(); } } else { return SQLiteGlobal.getDefaultSyncMode(); diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index adeb722833fa..7a55a5c4ef72 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -2017,7 +2017,7 @@ public abstract class BatteryStats { public static final int EVENT_PACKAGE_INSTALLED = 0x000b; // Event for a package being uninstalled. public static final int EVENT_PACKAGE_UNINSTALLED = 0x000c; - // Event for a package being uninstalled. + // Event for an alarm being sent out to an app. public static final int EVENT_ALARM = 0x000d; // Record that we have decided we need to collect new stats data. public static final int EVENT_COLLECT_EXTERNAL_STATS = 0x000e; diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index 2c0be870836a..3bf9ca044141 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -115,6 +115,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba private final int mMaxStreamVolume; private boolean mAffectedByRingerMode; private boolean mNotificationOrRing; + private final boolean mNotifAliasRing; private final Receiver mReceiver = new Receiver(); private Handler mHandler; @@ -179,6 +180,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba if (mNotificationOrRing) { mRingerMode = mAudioManager.getRingerModeInternal(); } + mNotifAliasRing = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_alias_ring_notif_stream_types); mZenMode = mNotificationManager.getZenMode(); if (hasAudioProductStrategies()) { @@ -280,7 +283,15 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba if (zenMuted) { mSeekBar.setProgress(mLastAudibleStreamVolume, true); } else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { - mSeekBar.setProgress(0, true); + /** + * the first variable above is preserved and the conditions below are made explicit + * so that when user attempts to slide the notification seekbar out of vibrate the + * seekbar doesn't wrongly snap back to 0 when the streams aren't aliased + */ + if (mNotifAliasRing || mStreamType == AudioManager.STREAM_RING + || (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) { + mSeekBar.setProgress(0, true); + } } else if (mMuted) { mSeekBar.setProgress(0, true); } else { @@ -354,6 +365,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba // set the time of stop volume if ((mStreamType == AudioManager.STREAM_VOICE_CALL || mStreamType == AudioManager.STREAM_RING + || (!mNotifAliasRing && mStreamType == AudioManager.STREAM_NOTIFICATION) || mStreamType == AudioManager.STREAM_ALARM)) { sStopVolumeTime = java.lang.System.currentTimeMillis(); } @@ -632,8 +644,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } private void updateVolumeSlider(int streamType, int streamValue) { - final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType) - : (streamType == mStreamType); + final boolean streamMatch = mNotifAliasRing && mNotificationOrRing + ? isNotificationOrRing(streamType) : streamType == mStreamType; if (mSeekBar != null && streamMatch && streamValue != -1) { final boolean muted = mAudioManager.isStreamMute(mStreamType) || streamValue == 0; diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java index af9c5a5cc0d5..52ffc984c41e 100644 --- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java +++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java @@ -17,6 +17,9 @@ package com.android.internal.widget; +import static android.content.res.Resources.ID_NULL; + +import android.annotation.IdRes; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; @@ -96,6 +99,8 @@ public class ResolverDrawerLayout extends ViewGroup { private int mTopOffset; private boolean mShowAtTop; + @IdRes + private int mIgnoreOffsetTopLimitViewId = ID_NULL; private boolean mIsDragging; private boolean mOpenOnClick; @@ -156,6 +161,10 @@ public class ResolverDrawerLayout extends ViewGroup { mIsMaxCollapsedHeightSmallExplicit = a.hasValue(R.styleable.ResolverDrawerLayout_maxCollapsedHeightSmall); mShowAtTop = a.getBoolean(R.styleable.ResolverDrawerLayout_showAtTop, false); + if (a.hasValue(R.styleable.ResolverDrawerLayout_ignoreOffsetTopLimit)) { + mIgnoreOffsetTopLimitViewId = a.getResourceId( + R.styleable.ResolverDrawerLayout_ignoreOffsetTopLimit, ID_NULL); + } a.recycle(); mScrollIndicatorDrawable = mContext.getDrawable(R.drawable.scroll_indicator_material); @@ -577,12 +586,32 @@ public class ResolverDrawerLayout extends ViewGroup { dy -= 1.0f; } + boolean isIgnoreOffsetLimitSet = false; + int ignoreOffsetLimit = 0; + View ignoreOffsetLimitView = findIgnoreOffsetLimitView(); + if (ignoreOffsetLimitView != null) { + LayoutParams lp = (LayoutParams) ignoreOffsetLimitView.getLayoutParams(); + ignoreOffsetLimit = ignoreOffsetLimitView.getBottom() + lp.bottomMargin; + isIgnoreOffsetLimitSet = true; + } final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); + if (child.getVisibility() == View.GONE) { + continue; + } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.ignoreOffset) { child.offsetTopAndBottom((int) dy); + } else if (isIgnoreOffsetLimitSet) { + int top = child.getTop(); + int targetTop = Math.max( + (int) (ignoreOffsetLimit + lp.topMargin + dy), + lp.mFixedTop); + if (top != targetTop) { + child.offsetTopAndBottom(targetTop - top); + } + ignoreOffsetLimit = child.getBottom() + lp.bottomMargin; } } final boolean isCollapsedOld = mCollapseOffset != 0; @@ -1024,6 +1053,8 @@ public class ResolverDrawerLayout extends ViewGroup { final int rightEdge = width - getPaddingRight(); final int widthAvailable = rightEdge - leftEdge; + boolean isIgnoreOffsetLimitSet = false; + int ignoreOffsetLimit = 0; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); @@ -1036,9 +1067,24 @@ public class ResolverDrawerLayout extends ViewGroup { continue; } + if (mIgnoreOffsetTopLimitViewId != ID_NULL && !isIgnoreOffsetLimitSet) { + if (mIgnoreOffsetTopLimitViewId == child.getId()) { + ignoreOffsetLimit = child.getBottom() + lp.bottomMargin; + isIgnoreOffsetLimitSet = true; + } + } + int top = ypos + lp.topMargin; if (lp.ignoreOffset) { - top -= mCollapseOffset; + if (!isDragging()) { + lp.mFixedTop = (int) (top - mCollapseOffset); + } + if (isIgnoreOffsetLimitSet) { + top = Math.max(ignoreOffsetLimit + lp.topMargin, (int) (top - mCollapseOffset)); + ignoreOffsetLimit = top + child.getMeasuredHeight() + lp.bottomMargin; + } else { + top -= mCollapseOffset; + } } final int bottom = top + child.getMeasuredHeight(); @@ -1102,11 +1148,23 @@ public class ResolverDrawerLayout extends ViewGroup { mCollapsibleHeightReserved = ss.mCollapsibleHeightReserved; } + private View findIgnoreOffsetLimitView() { + if (mIgnoreOffsetTopLimitViewId == ID_NULL) { + return null; + } + View v = findViewById(mIgnoreOffsetTopLimitViewId); + if (v != null && v != this && v.getParent() == this && v.getVisibility() != View.GONE) { + return v; + } + return null; + } + public static class LayoutParams extends MarginLayoutParams { public boolean alwaysShow; public boolean ignoreOffset; public boolean hasNestedScrollIndicator; public int maxHeight; + int mFixedTop; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp index bb4ab39a59d1..1f64df49cb56 100644 --- a/core/jni/android_os_Parcel.cpp +++ b/core/jni/android_os_Parcel.cpp @@ -101,9 +101,18 @@ static void android_os_Parcel_markSensitive(jlong nativePtr) static void android_os_Parcel_markForBinder(JNIEnv* env, jclass clazz, jlong nativePtr, jobject binder) { + LOG_ALWAYS_FATAL_IF(binder == nullptr, "Null binder specified for markForBinder"); + Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); if (parcel) { - parcel->markForBinder(ibinderForJavaObject(env, binder)); + sp<IBinder> nBinder = ibinderForJavaObject(env, binder); + + if (nBinder == nullptr) { + ALOGE("Native binder in markForBinder is null for non-null jobject"); + return; + } + + parcel->markForBinder(nBinder); } } diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index f28e2f636dac..9f88f3369ae8 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -798,6 +798,12 @@ sp<IBinder> ibinderForJavaObject(JNIEnv* env, jobject obj) if (env->IsInstanceOf(obj, gBinderOffsets.mClass)) { JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject); + + if (jbh == nullptr) { + ALOGE("JavaBBinderHolder null on binder"); + return nullptr; + } + return jbh->get(env, obj); } diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml index 6a200d05c2d7..a06e8a4ea4a0 100644 --- a/core/res/res/layout/resolver_list.xml +++ b/core/res/res/layout/resolver_list.xml @@ -23,6 +23,7 @@ android:maxWidth="@dimen/resolver_max_width" android:maxCollapsedHeight="@dimen/resolver_max_collapsed_height" android:maxCollapsedHeightSmall="56dp" + android:ignoreOffsetTopLimit="@id/title_container" android:id="@id/contentPanel"> <RelativeLayout diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 69c504364544..2d832bc71684 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -9620,6 +9620,12 @@ <attr name="maxCollapsedHeightSmall" format="dimension" /> <!-- Whether the Drawer should be positioned at the top rather than at the bottom. --> <attr name="showAtTop" format="boolean" /> + <!-- By default `ResolverDrawerLayout`’s children views with `layout_ignoreOffset` property + set to true have a fixed position in the layout that won’t be affected by the drawer’s + movements. This property alternates that behavior. It specifies a child view’s id that + will push all ignoreOffset siblings below it when the drawer is moved i.e. setting the + top limit the ignoreOffset elements. --> + <attr name="ignoreOffsetTopLimit" format="reference" /> </declare-styleable> <declare-styleable name="MessagingLinearLayout"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2fbf8034c27e..530891f18933 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1626,6 +1626,7 @@ <java-symbol type="xml" name="password_kbd_symbols_shift" /> <java-symbol type="xml" name="power_profile" /> <java-symbol type="xml" name="power_profile_test" /> + <java-symbol type="xml" name="irq_device_map" /> <java-symbol type="xml" name="sms_short_codes" /> <java-symbol type="xml" name="audio_assets" /> <java-symbol type="xml" name="global_keys" /> diff --git a/core/res/res/xml/irq_device_map.xml b/core/res/res/xml/irq_device_map.xml new file mode 100644 index 000000000000..86a44d6a9fe0 --- /dev/null +++ b/core/res/res/xml/irq_device_map.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** 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. +*/ +--> +<irq-device-map> + <!-- This file maps devices (chips) that can send IRQs to the CPU (and bring it out of sleep) to + logical subsystems in userspace code. Since each Android device has its own uniquely + designed chipset, this mapping is expected to be empty by default and should be overridden + by device specific configs. + This mapping helps the system to meaningfully attribute CPU wakeups to logical work that + happened on the device. The devices are referred to by their names as defined in the kernel. + Currently defined subsystems are: + - Alarm: Use this to denote wakeup alarms requested by apps via the AlarmManager API. + + The overlay should use tags <device> and <subsystem> to describe this mapping in the + following way: + + <irq-device-map> + <device name="device_name_1"> + <subsystem>Subsystem1</subsystem> + <subsystem>Subsystem2</subsystem> + : + : + </device> + <device name="device_name_2"> + : + </device> + : + </irq-device-map> + + The tag <device> should have a "name" attribute specifying the kernel name of the device. + Each <device> tag can then enclose multiple <subsystem> tags. Each <subsystem> tag should + enclose the name of the logical subsystems (one of the ones defined above) as text. + Undefined subsystem names will be ignored by the framework. + --> +</irq-device-map>
\ No newline at end of file diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 1174b685e92c..31a9592968c5 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -94,7 +94,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen ActivityEmbeddingComponent { static final String TAG = "SplitController"; static final boolean ENABLE_SHELL_TRANSITIONS = - SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); + SystemProperties.getBoolean("persist.wm.debug.shell_transit", true); @VisibleForTesting @GuardedBy("mLock") diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index aaaccd837298..1372f4d64678 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -81,7 +81,7 @@ public class Transitions implements RemoteCallable<Transitions> { /** Set to {@code true} to enable shell transitions. */ public static final boolean ENABLE_SHELL_TRANSITIONS = - SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); + SystemProperties.getBoolean("persist.wm.debug.shell_transit", true); public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS && SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index 5ee8bf3006a3..1a1bebd28aef 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java @@ -61,7 +61,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public final class StageTaskListenerTests extends ShellTestCase { private static final boolean ENABLE_SHELL_TRANSITIONS = - SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); + SystemProperties.getBoolean("persist.wm.debug.shell_transit", true); @Mock private ShellTaskOrganizer mTaskOrganizer; diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 21d4d8023e54..891379977a6c 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -139,8 +139,18 @@ void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) { } ASurfaceControl* ASurfaceControl_fromSurfaceControl(JNIEnv* env, jobject surfaceControlObj) { - return reinterpret_cast<ASurfaceControl*>( - android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj)); + LOG_ALWAYS_FATAL_IF(!env, + "nullptr passed to ASurfaceControl_fromSurfaceControl as env argument"); + LOG_ALWAYS_FATAL_IF(!surfaceControlObj, + "nullptr passed to ASurfaceControl_fromSurfaceControl as surfaceControlObj " + "argument"); + SurfaceControl* surfaceControl = + android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj); + LOG_ALWAYS_FATAL_IF(!surfaceControl, + "surfaceControlObj passed to ASurfaceControl_fromSurfaceControl is not " + "valid"); + SurfaceControl_acquire(surfaceControl); + return reinterpret_cast<ASurfaceControl*>(surfaceControl); } struct ASurfaceControlStats { @@ -200,8 +210,17 @@ void ASurfaceTransaction_delete(ASurfaceTransaction* aSurfaceTransaction) { } ASurfaceTransaction* ASurfaceTransaction_fromTransaction(JNIEnv* env, jobject transactionObj) { - return reinterpret_cast<ASurfaceTransaction*>( - android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj)); + LOG_ALWAYS_FATAL_IF(!env, + "nullptr passed to ASurfaceTransaction_fromTransaction as env argument"); + LOG_ALWAYS_FATAL_IF(!transactionObj, + "nullptr passed to ASurfaceTransaction_fromTransaction as transactionObj " + "argument"); + Transaction* transaction = + android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj); + LOG_ALWAYS_FATAL_IF(!transaction, + "surfaceControlObj passed to ASurfaceTransaction_fromTransaction is not " + "valid"); + return reinterpret_cast<ASurfaceTransaction*>(transaction); } void ASurfaceTransaction_apply(ASurfaceTransaction* aSurfaceTransaction) { diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 9818ee78b2b1..56715b422069 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -46,6 +46,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; +import android.companion.AssociatedDevice; import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; @@ -331,6 +332,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements private void onUserSelectedDevice(@NonNull DeviceFilterPair<?> selectedDevice) { final MacAddress macAddress = selectedDevice.getMacAddress(); mRequest.setDisplayName(selectedDevice.getDisplayName()); + mRequest.setAssociatedDevice(new AssociatedDevice(selectedDevice.getDevice())); onAssociationApproved(macAddress); } diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java index c2034f89e18a..0c0f5a8a808e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java @@ -18,12 +18,9 @@ package com.android.settingslib.enterprise; import static java.util.Objects.requireNonNull; -import android.app.admin.DevicePolicyManager; import android.content.Context; -import android.content.pm.PackageManager; import android.os.UserHandle; import android.os.UserManager; -import android.text.TextUtils; import androidx.annotation.Nullable; @@ -69,50 +66,11 @@ final class ManagedDeviceActionDisabledByAdminController mResolveActivityChecker = requireNonNull(resolveActivityChecker); } - @Override - public void setupLearnMoreButton(Context context) { - assertInitialized(); - - String url = mStringProvider.getLearnMoreHelpPageUrl(); - - if (!TextUtils.isEmpty(url) - && canLaunchHelpPageInPreferredOrCurrentUser(context, url, mPreferredUserHandle)) { - setupLearnMoreButtonToLaunchHelpPage(context, url, mPreferredUserHandle); - } else { - mLauncher.setupLearnMoreButtonToShowAdminPolicies(context, mEnforcementAdminUserId, - mEnforcedAdmin); - } - } - - private boolean canLaunchHelpPageInPreferredOrCurrentUser( - Context context, String url, UserHandle preferredUserHandle) { - PackageManager packageManager = context.getPackageManager(); - if (mLauncher.canLaunchHelpPage( - packageManager, url, preferredUserHandle, mResolveActivityChecker) - && mForegroundUserChecker.isUserForeground(context, preferredUserHandle)) { - return true; - } - return mLauncher.canLaunchHelpPage( - packageManager, url, context.getUser(), mResolveActivityChecker); - } - /** - * Sets up the "Learn more" button to launch the web help page in the {@code - * preferredUserHandle} user. If not possible to launch it there, it sets up the button to - * launch it in the current user instead. + * We don't show Learn More button in Admin-Support Dialog anymore. */ - private void setupLearnMoreButtonToLaunchHelpPage( - Context context, String url, UserHandle preferredUserHandle) { - PackageManager packageManager = context.getPackageManager(); - if (mLauncher.canLaunchHelpPage( - packageManager, url, preferredUserHandle, mResolveActivityChecker) - && mForegroundUserChecker.isUserForeground(context, preferredUserHandle)) { - mLauncher.setupLearnMoreButtonToLaunchHelpPage(context, url, preferredUserHandle); - } - if (mLauncher.canLaunchHelpPage( - packageManager, url, context.getUser(), mResolveActivityChecker)) { - mLauncher.setupLearnMoreButtonToLaunchHelpPage(context, url, context.getUser()); - } + @Override + public void setupLearnMoreButton(Context context) { } private static boolean isUserForeground(Context context, UserHandle userHandle) { @@ -122,25 +80,7 @@ final class ManagedDeviceActionDisabledByAdminController @Override public String getAdminSupportTitle(@Nullable String restriction) { - if (restriction == null) { - return mStringProvider.getDefaultDisabledByPolicyTitle(); - } - switch (restriction) { - case UserManager.DISALLOW_ADJUST_VOLUME: - return mStringProvider.getDisallowAdjustVolumeTitle(); - case UserManager.DISALLOW_OUTGOING_CALLS: - return mStringProvider.getDisallowOutgoingCallsTitle(); - case UserManager.DISALLOW_SMS: - return mStringProvider.getDisallowSmsTitle(); - case DevicePolicyManager.POLICY_DISABLE_CAMERA: - return mStringProvider.getDisableCameraTitle(); - case DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE: - return mStringProvider.getDisableScreenCaptureTitle(); - case DevicePolicyManager.POLICY_SUSPEND_PACKAGES: - return mStringProvider.getSuspendPackagesTitle(); - default: - return mStringProvider.getDefaultDisabledByPolicyTitle(); - } + return mStringProvider.getDefaultDisabledByPolicyTitle(); } @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java index bc9bdecb904d..7b885660ccea 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java @@ -18,10 +18,7 @@ package com.android.settingslib.enterprise; import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN; import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER_ID; -import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.LEARN_MORE_ACTION_LAUNCH_HELP_PAGE; -import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES; import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.SUPPORT_MESSAGE; -import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.URL; import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DISABLED_BY_POLICY_CONTENT; import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DISABLED_BY_POLICY_TITLE; import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DISALLOW_ADJUST_VOLUME_TITLE; @@ -64,67 +61,6 @@ public class ManagedDeviceActionDisabledByAdminControllerTest { } @Test - public void setupLearnMoreButton_noUrl_negativeButtonSet() { - ManagedDeviceActionDisabledByAdminController controller = createController(EMPTY_URL); - - controller.setupLearnMoreButton(mContext); - - mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES); - } - - @Test - public void setupLearnMoreButton_validUrl_foregroundUser_launchesHelpPage() { - ManagedDeviceActionDisabledByAdminController controller = createController( - URL, - /* isUserForeground= */ true, - /* preferredUserHandle= */ MANAGED_USER, - /* userContainingBrowser= */ MANAGED_USER); - - controller.setupLearnMoreButton(mContext); - - mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_LAUNCH_HELP_PAGE); - } - - @Test - public void setupLearnMoreButton_validUrl_browserInPreferredUser_notForeground_showsAdminPolicies() { - ManagedDeviceActionDisabledByAdminController controller = createController( - URL, - /* isUserForeground= */ false, - /* preferredUserHandle= */ MANAGED_USER, - /* userContainingBrowser= */ MANAGED_USER); - - controller.setupLearnMoreButton(mContext); - - mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES); - } - - @Test - public void setupLearnMoreButton_validUrl_browserInCurrentUser_launchesHelpPage() { - ManagedDeviceActionDisabledByAdminController controller = createController( - URL, - /* isUserForeground= */ false, - /* preferredUserHandle= */ MANAGED_USER, - /* userContainingBrowser= */ mContext.getUser()); - - controller.setupLearnMoreButton(mContext); - - mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_LAUNCH_HELP_PAGE); - } - - @Test - public void setupLearnMoreButton_validUrl_browserNotOnAnyUser_showsAdminPolicies() { - ManagedDeviceActionDisabledByAdminController controller = createController( - URL, - /* isUserForeground= */ false, - /* preferredUserHandle= */ MANAGED_USER, - /* userContainingBrowser= */ null); - - controller.setupLearnMoreButton(mContext); - - mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES); - } - - @Test public void getAdminSupportTitleResource_noRestriction_works() { ManagedDeviceActionDisabledByAdminController controller = createController(); @@ -137,7 +73,7 @@ public class ManagedDeviceActionDisabledByAdminControllerTest { ManagedDeviceActionDisabledByAdminController controller = createController(); assertThat(controller.getAdminSupportTitle(RESTRICTION)) - .isEqualTo(SUPPORT_TITLE_FOR_RESTRICTION); + .isEqualTo(DEFAULT_DISABLED_BY_POLICY_TITLE); } @Test diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 3325eec0c451..6d61fd86e39d 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -5,20 +5,32 @@ set noparent dsandler@android.com aaliomer@google.com +aaronjli@google.com +acul@google.com adamcohen@google.com +aioana@google.com alexflo@google.com +andonian@google.com +aroederer@google.com arteiro@google.com asc@google.com awickham@google.com +ayepin@google.com +bbade@google.com beverlyt@google.com -brzezinski@google.com +bhinegardner@google.com +bhnm@google.com brycelee@google.com +brzezinski@google.com caitlinshk@google.com +chandruis@google.com chrisgollner@google.com +cinek@google.com dupin@google.com ethibodeau@google.com evanlaird@google.com florenceyang@google.com +gallmann@google.com gwasserman@google.com hwwang@google.com hyunyoungs@google.com @@ -35,31 +47,41 @@ joshtrask@google.com juliacr@google.com juliatuttle@google.com justinkoh@google.com +justinweir@google.com kozynski@google.com kprevas@google.com +lusilva@google.com lynhan@google.com madym@google.com mankoff@google.com +mateuszc@google.com +michaelmikhil@google.com +michschn@google.com mkephart@google.com mpietal@google.com mrcasey@google.com mrenouf@google.com nickchameyev@google.com nicomazz@google.com +nijamkin@google.com ogunwale@google.com +omarmt@google.com +patmanning@google.com peanutbutter@google.com peskal@google.com pinyaoting@google.com pixel@google.com pomini@google.com rahulbanerjee@google.com +rasheedlewis@google.com roosa@google.com +saff@google.com santie@google.com shanh@google.com snoeberger@google.com steell@google.com -sfufa@google.com stwu@google.com +syeonlee@google.com sunnygoyal@google.com susikp@google.com thiruram@google.com @@ -70,10 +92,13 @@ vadimt@google.com victortulias@google.com winsonc@google.com wleshner@google.com +xilei@google.com xuqiu@google.com +yeinj@google.com yuandizhou@google.com yurilin@google.com zakcohen@google.com +zoepage@google.com #Android TV rgl@google.com diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml index 5486adbd2b8e..6ec65cebf840 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml @@ -24,7 +24,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" androidprv:layout_maxWidth="@dimen/keyguard_security_width" - androidprv:layout_maxHeight="@dimen/keyguard_security_height" android:layout_gravity="center_horizontal|bottom" android:gravity="bottom" > diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java index 00f1c0108d0b..207f3440d38b 100644 --- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java +++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java @@ -15,6 +15,12 @@ */ package com.android.keyguard; +import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM; +import static androidx.constraintlayout.widget.ConstraintSet.END; +import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; +import static androidx.constraintlayout.widget.ConstraintSet.START; +import static androidx.constraintlayout.widget.ConstraintSet.TOP; + import android.annotation.Nullable; import android.app.admin.IKeyguardCallback; import android.app.admin.IKeyguardClient; @@ -30,7 +36,10 @@ import android.util.Log; import android.view.SurfaceControlViewHost; import android.view.SurfaceHolder; import android.view.SurfaceView; -import android.view.ViewGroup; +import android.view.View; + +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.constraintlayout.widget.ConstraintSet; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; @@ -49,7 +58,7 @@ public class AdminSecondaryLockScreenController { private static final int REMOTE_CONTENT_READY_TIMEOUT_MILLIS = 500; private final KeyguardUpdateMonitor mUpdateMonitor; private final Context mContext; - private final ViewGroup mParent; + private final ConstraintLayout mParent; private AdminSecurityView mView; private Handler mHandler; private IKeyguardClient mClient; @@ -156,6 +165,7 @@ public class AdminSecondaryLockScreenController { mUpdateMonitor = updateMonitor; mKeyguardCallback = callback; mView = new AdminSecurityView(mContext, mSurfaceHolderCallback); + mView.setId(View.generateViewId()); } /** @@ -167,6 +177,15 @@ public class AdminSecondaryLockScreenController { } if (!mView.isAttachedToWindow()) { mParent.addView(mView); + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(mParent); + constraintSet.connect(mView.getId(), TOP, PARENT_ID, TOP); + constraintSet.connect(mView.getId(), START, PARENT_ID, START); + constraintSet.connect(mView.getId(), END, PARENT_ID, END); + constraintSet.connect(mView.getId(), BOTTOM, PARENT_ID, BOTTOM); + constraintSet.constrainHeight(mView.getId(), ConstraintSet.MATCH_CONSTRAINT); + constraintSet.constrainWidth(mView.getId(), ConstraintSet.MATCH_CONSTRAINT); + constraintSet.applyTo(mParent); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 353c3698dce4..c34db1532d6c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -21,15 +21,23 @@ import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; +import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM; +import static androidx.constraintlayout.widget.ConstraintSet.CHAIN_SPREAD; +import static androidx.constraintlayout.widget.ConstraintSet.END; +import static androidx.constraintlayout.widget.ConstraintSet.LEFT; +import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT; +import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; +import static androidx.constraintlayout.widget.ConstraintSet.RIGHT; +import static androidx.constraintlayout.widget.ConstraintSet.START; +import static androidx.constraintlayout.widget.ConstraintSet.TOP; +import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT; + import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; import static java.lang.Integer.max; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; import android.app.Activity; import android.app.AlertDialog; import android.app.admin.DevicePolicyManager; @@ -44,12 +52,12 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.UserManager; import android.provider.Settings; +import android.transition.TransitionManager; import android.util.AttributeSet; import android.util.Log; import android.util.MathUtils; import android.util.TypedValue; import android.view.GestureDetector; -import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -59,8 +67,6 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; import android.view.WindowManager; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.ImageView; @@ -68,8 +74,9 @@ import android.widget.TextView; import androidx.annotation.IntDef; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.constraintlayout.widget.ConstraintSet; import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.SpringAnimation; @@ -92,9 +99,9 @@ import com.android.systemui.util.settings.GlobalSettings; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; -public class KeyguardSecurityContainer extends FrameLayout { +/** Determines how the bouncer is displayed to the user. */ +public class KeyguardSecurityContainer extends ConstraintLayout { static final int USER_TYPE_PRIMARY = 1; static final int USER_TYPE_WORK_PROFILE = 2; static final int USER_TYPE_SECONDARY_USER = 3; @@ -125,15 +132,6 @@ public class KeyguardSecurityContainer extends FrameLayout { // How much to scale the default slop by, to avoid accidental drags. private static final float SLOP_SCALE = 4f; - private static final long IME_DISAPPEAR_DURATION_MS = 125; - - // The duration of the animation to switch security sides. - private static final long SECURITY_SHIFT_ANIMATION_DURATION_MS = 500; - - // How much of the switch sides animation should be dedicated to fading the security out. The - // remainder will fade it back in again. - private static final float SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f; - @VisibleForTesting KeyguardSecurityViewFlipper mSecurityViewFlipper; private GlobalSettings mGlobalSettings; @@ -649,47 +647,8 @@ public class KeyguardSecurityContainer extends FrameLayout { } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int maxHeight = 0; - int maxWidth = 0; - int childState = 0; - - for (int i = 0; i < getChildCount(); i++) { - final View view = getChildAt(i); - if (view.getVisibility() != GONE) { - int updatedWidthMeasureSpec = mViewMode.getChildWidthMeasureSpec(widthMeasureSpec); - final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - - // When using EXACTLY spec, measure will use the layout width if > 0. Set before - // measuring the child - lp.width = MeasureSpec.getSize(updatedWidthMeasureSpec); - measureChildWithMargins(view, updatedWidthMeasureSpec, 0, - heightMeasureSpec, 0); - - maxWidth = Math.max(maxWidth, - view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); - maxHeight = Math.max(maxHeight, - view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); - childState = combineMeasuredStates(childState, view.getMeasuredState()); - } - } - - maxWidth += getPaddingLeft() + getPaddingRight(); - maxHeight += getPaddingTop() + getPaddingBottom(); - - // Check against our minimum height and width - maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); - maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); - - setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), - resolveSizeAndState(maxHeight, heightMeasureSpec, - childState << MEASURED_HEIGHT_STATE_SHIFT)); - } - - @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - int width = right - left; if (changed && mWidth != width) { mWidth = width; @@ -761,7 +720,7 @@ public class KeyguardSecurityContainer extends FrameLayout { * Enscapsulates the differences between bouncer modes for the container. */ interface ViewMode { - default void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings, + default void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, @NonNull UserSwitcherController userSwitcherController) {}; @@ -787,11 +746,6 @@ public class KeyguardSecurityContainer extends FrameLayout { /** On notif tap, this animation will run */ default void startAppearAnimation(SecurityMode securityMode) {}; - /** Override to alter the width measure spec to perhaps limit the ViewFlipper size */ - default int getChildWidthMeasureSpec(int parentWidthMeasureSpec) { - return parentWidthMeasureSpec; - } - /** Called when we are setting a new ViewMode */ default void onDestroy() {}; } @@ -801,13 +755,12 @@ public class KeyguardSecurityContainer extends FrameLayout { * screen devices */ abstract static class SidedSecurityMode implements ViewMode { - @Nullable private ValueAnimator mRunningSecurityShiftAnimator; private KeyguardSecurityViewFlipper mViewFlipper; - private ViewGroup mView; + private ConstraintLayout mView; private GlobalSettings mGlobalSettings; private int mDefaultSideSetting; - public void init(ViewGroup v, KeyguardSecurityViewFlipper viewFlipper, + public void init(ConstraintLayout v, KeyguardSecurityViewFlipper viewFlipper, GlobalSettings globalSettings, boolean leftAlignedByDefault) { mView = v; mViewFlipper = viewFlipper; @@ -850,127 +803,6 @@ public class KeyguardSecurityContainer extends FrameLayout { protected abstract void updateSecurityViewLocation(boolean leftAlign, boolean animate); - protected void translateSecurityViewLocation(boolean leftAlign, boolean animate) { - translateSecurityViewLocation(leftAlign, animate, i -> {}); - } - - /** - * Moves the inner security view to the correct location with animation. This is triggered - * when the user double taps on the side of the screen that is not currently occupied by - * the security view. - */ - protected void translateSecurityViewLocation(boolean leftAlign, boolean animate, - Consumer<Float> securityAlphaListener) { - if (mRunningSecurityShiftAnimator != null) { - mRunningSecurityShiftAnimator.cancel(); - mRunningSecurityShiftAnimator = null; - } - - int targetTranslation = leftAlign - ? 0 : mView.getMeasuredWidth() - mViewFlipper.getWidth(); - - if (animate) { - // This animation is a bit fun to implement. The bouncer needs to move, and fade - // in/out at the same time. The issue is, the bouncer should only move a short - // amount (120dp or so), but obviously needs to go from one side of the screen to - // the other. This needs a pretty custom animation. - // - // This works as follows. It uses a ValueAnimation to simply drive the animation - // progress. This animator is responsible for both the translation of the bouncer, - // and the current fade. It will fade the bouncer out while also moving it along the - // 120dp path. Once the bouncer is fully faded out though, it will "snap" the - // bouncer closer to its destination, then fade it back in again. The effect is that - // the bouncer will move from 0 -> X while fading out, then - // (destination - X) -> destination while fading back in again. - // TODO(b/208250221): Make this animation properly abortable. - Interpolator positionInterpolator = AnimationUtils.loadInterpolator( - mView.getContext(), android.R.interpolator.fast_out_extra_slow_in); - Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN; - Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN; - - mRunningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); - mRunningSecurityShiftAnimator.setDuration(SECURITY_SHIFT_ANIMATION_DURATION_MS); - mRunningSecurityShiftAnimator.setInterpolator(Interpolators.LINEAR); - - int initialTranslation = (int) mViewFlipper.getTranslationX(); - int totalTranslation = (int) mView.getResources().getDimension( - R.dimen.security_shift_animation_translation); - - final boolean shouldRestoreLayerType = mViewFlipper.hasOverlappingRendering() - && mViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE; - if (shouldRestoreLayerType) { - mViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null); - } - - float initialAlpha = mViewFlipper.getAlpha(); - - mRunningSecurityShiftAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mRunningSecurityShiftAnimator = null; - } - }); - mRunningSecurityShiftAnimator.addUpdateListener(animation -> { - float switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION; - boolean isFadingOut = animation.getAnimatedFraction() < switchPoint; - - int currentTranslation = (int) (positionInterpolator.getInterpolation( - animation.getAnimatedFraction()) * totalTranslation); - int translationRemaining = totalTranslation - currentTranslation; - - // Flip the sign if we're going from right to left. - if (leftAlign) { - currentTranslation = -currentTranslation; - translationRemaining = -translationRemaining; - } - - float opacity; - if (isFadingOut) { - // The bouncer fades out over the first X%. - float fadeOutFraction = MathUtils.constrainedMap( - /* rangeMin= */1.0f, - /* rangeMax= */0.0f, - /* valueMin= */0.0f, - /* valueMax= */switchPoint, - animation.getAnimatedFraction()); - opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction); - - // When fading out, the alpha needs to start from the initial opacity of the - // view flipper, otherwise we get a weird bit of jank as it ramps back to - // 100%. - mViewFlipper.setAlpha(opacity * initialAlpha); - - // Animate away from the source. - mViewFlipper.setTranslationX(initialTranslation + currentTranslation); - } else { - // And in again over the remaining (100-X)%. - float fadeInFraction = MathUtils.constrainedMap( - /* rangeMin= */0.0f, - /* rangeMax= */1.0f, - /* valueMin= */switchPoint, - /* valueMax= */1.0f, - animation.getAnimatedFraction()); - - opacity = fadeInInterpolator.getInterpolation(fadeInFraction); - mViewFlipper.setAlpha(opacity); - - // Fading back in, animate towards the destination. - mViewFlipper.setTranslationX(targetTranslation - translationRemaining); - } - securityAlphaListener.accept(opacity); - - if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) { - mViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null); - } - }); - - mRunningSecurityShiftAnimator.start(); - } else { - mViewFlipper.setTranslationX(targetTranslation); - } - } - - boolean isLeftAligned() { return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE, mDefaultSideSetting) @@ -989,11 +821,11 @@ public class KeyguardSecurityContainer extends FrameLayout { * Default bouncer is centered within the space */ static class DefaultViewMode implements ViewMode { - private ViewGroup mView; + private ConstraintLayout mView; private KeyguardSecurityViewFlipper mViewFlipper; @Override - public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings, + public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, @NonNull UserSwitcherController userSwitcherController) { @@ -1005,11 +837,14 @@ public class KeyguardSecurityContainer extends FrameLayout { } private void updateSecurityViewGroup() { - FrameLayout.LayoutParams lp = - (FrameLayout.LayoutParams) mViewFlipper.getLayoutParams(); - lp.gravity = Gravity.CENTER_HORIZONTAL; - mViewFlipper.setLayoutParams(lp); - mViewFlipper.setTranslationX(0); + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.connect(mViewFlipper.getId(), START, PARENT_ID, START); + constraintSet.connect(mViewFlipper.getId(), END, PARENT_ID, END); + constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM); + constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP); + constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT); + constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT); + constraintSet.applyTo(mView); } } @@ -1018,7 +853,7 @@ public class KeyguardSecurityContainer extends FrameLayout { * a user switcher, in both portrait and landscape modes. */ static class UserSwitcherViewMode extends SidedSecurityMode { - private ViewGroup mView; + private ConstraintLayout mView; private ViewGroup mUserSwitcherViewGroup; private KeyguardSecurityViewFlipper mViewFlipper; private TextView mUserSwitcher; @@ -1029,11 +864,8 @@ public class KeyguardSecurityContainer extends FrameLayout { private UserSwitcherController.UserSwitchCallback mUserSwitchCallback = this::setupUserSwitcher; - private float mAnimationLastAlpha = 1f; - private boolean mAnimationWaitsToShift = true; - @Override - public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings, + public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, @NonNull UserSwitcherController userSwitcherController) { @@ -1240,89 +1072,56 @@ public class KeyguardSecurityContainer extends FrameLayout { }); } - /** - * Each view will get half the width. Yes, it would be easier to use something other than - * FrameLayout but it was too disruptive to downstream projects to change. - */ - @Override - public int getChildWidthMeasureSpec(int parentWidthMeasureSpec) { - return MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parentWidthMeasureSpec) / 2, - MeasureSpec.EXACTLY); - } - @Override public void updateSecurityViewLocation() { updateSecurityViewLocation(isLeftAligned(), /* animate= */false); } public void updateSecurityViewLocation(boolean leftAlign, boolean animate) { - setYTranslation(); - setGravity(); - setXTranslation(leftAlign, animate); - } - - private void setXTranslation(boolean leftAlign, boolean animate) { - if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { - mUserSwitcherViewGroup.setTranslationX(0); - mViewFlipper.setTranslationX(0); - } else { - int switcherTargetTranslation = leftAlign - ? mView.getMeasuredWidth() - mViewFlipper.getWidth() : 0; - if (animate) { - mAnimationWaitsToShift = true; - mAnimationLastAlpha = 1f; - translateSecurityViewLocation(leftAlign, animate, securityAlpha -> { - // During the animation security view fades out - alpha goes from 1 to - // (almost) 0 - and then fades in - alpha grows back to 1. - // If new alpha is bigger than previous one it means we're at inflection - // point and alpha is zero or almost zero. That's when we want to do - // translation of user switcher, so that it's not visible to the user. - boolean fullyFadeOut = securityAlpha == 0.0f - || securityAlpha > mAnimationLastAlpha; - if (fullyFadeOut && mAnimationWaitsToShift) { - mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation); - mAnimationWaitsToShift = false; - } - mUserSwitcherViewGroup.setAlpha(securityAlpha); - mAnimationLastAlpha = securityAlpha; - }); - } else { - translateSecurityViewLocation(leftAlign, animate); - mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation); - } - } - - } - - private void setGravity() { - if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { - updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL); - updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL); - } else { - // horizontal gravity is the same because we translate these views anyway - updateViewGravity(mViewFlipper, Gravity.LEFT | Gravity.BOTTOM); - updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL); + if (animate) { + TransitionManager.beginDelayedTransition(mView, + new KeyguardSecurityViewTransition()); } - } - - private void setYTranslation() { int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans); if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { - mUserSwitcherViewGroup.setTranslationY(yTrans); + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP, yTrans); + constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP); + constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM); + constraintSet.centerHorizontally(mViewFlipper.getId(), PARENT_ID); + constraintSet.centerHorizontally(mUserSwitcherViewGroup.getId(), PARENT_ID); + constraintSet.setVerticalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD); + constraintSet.setVerticalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD); + constraintSet.constrainHeight(mUserSwitcherViewGroup.getId(), WRAP_CONTENT); + constraintSet.constrainWidth(mUserSwitcherViewGroup.getId(), WRAP_CONTENT); + constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT); + constraintSet.applyTo(mView); } else { - // Attempt to reposition a bit higher to make up for this frame being a bit lower - // on the device - mUserSwitcherViewGroup.setTranslationY(-yTrans); - mViewFlipper.setTranslationY(0); + int leftElement = leftAlign ? mViewFlipper.getId() : mUserSwitcherViewGroup.getId(); + int rightElement = + leftAlign ? mUserSwitcherViewGroup.getId() : mViewFlipper.getId(); + + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.connect(leftElement, LEFT, PARENT_ID, LEFT); + constraintSet.connect(leftElement, RIGHT, rightElement, LEFT); + constraintSet.connect(rightElement, LEFT, leftElement, RIGHT); + constraintSet.connect(rightElement, RIGHT, PARENT_ID, RIGHT); + constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP); + constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM, + yTrans); + constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP); + constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM); + constraintSet.setHorizontalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD); + constraintSet.setHorizontalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD); + constraintSet.constrainHeight(mUserSwitcherViewGroup.getId(), + MATCH_CONSTRAINT); + constraintSet.constrainWidth(mUserSwitcherViewGroup.getId(), + MATCH_CONSTRAINT); + constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT); + constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT); + constraintSet.applyTo(mView); } } - - private void updateViewGravity(View v, int gravity) { - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams(); - lp.gravity = gravity; - v.setLayoutParams(lp); - } } /** @@ -1330,11 +1129,11 @@ public class KeyguardSecurityContainer extends FrameLayout { * between alternate sides of the display. */ static class OneHandedViewMode extends SidedSecurityMode { - private ViewGroup mView; + private ConstraintLayout mView; private KeyguardSecurityViewFlipper mViewFlipper; @Override - public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings, + public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, @NonNull UserSwitcherController userSwitcherController) { @@ -1342,28 +1141,10 @@ public class KeyguardSecurityContainer extends FrameLayout { mView = v; mViewFlipper = viewFlipper; - updateSecurityViewGravity(); updateSecurityViewLocation(isLeftAligned(), /* animate= */false); } /** - * One-handed mode contains the child to half of the available space. - */ - @Override - public int getChildWidthMeasureSpec(int parentWidthMeasureSpec) { - return MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parentWidthMeasureSpec) / 2, - MeasureSpec.EXACTLY); - } - - private void updateSecurityViewGravity() { - FrameLayout.LayoutParams lp = - (FrameLayout.LayoutParams) mViewFlipper.getLayoutParams(); - lp.gravity = Gravity.LEFT | Gravity.BOTTOM; - mViewFlipper.setLayoutParams(lp); - } - - /** * Moves the bouncer to align with a tap (most likely in the shade), so the bouncer * appears on the same side as a touch. */ @@ -1380,7 +1161,20 @@ public class KeyguardSecurityContainer extends FrameLayout { } protected void updateSecurityViewLocation(boolean leftAlign, boolean animate) { - translateSecurityViewLocation(leftAlign, animate); + if (animate) { + TransitionManager.beginDelayedTransition(mView, + new KeyguardSecurityViewTransition()); + } + ConstraintSet constraintSet = new ConstraintSet(); + if (leftAlign) { + constraintSet.connect(mViewFlipper.getId(), LEFT, PARENT_ID, LEFT); + } else { + constraintSet.connect(mViewFlipper.getId(), RIGHT, PARENT_ID, RIGHT); + } + constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP); + constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM); + constraintSet.constrainPercentWidth(mViewFlipper.getId(), 0.5f); + constraintSet.applyTo(mView); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt new file mode 100644 index 000000000000..9eb2c118abac --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.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.keyguard + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.graphics.Rect +import android.transition.Transition +import android.transition.TransitionValues +import android.util.MathUtils +import android.view.View +import android.view.ViewGroup +import android.view.animation.AnimationUtils +import com.android.internal.R.interpolator.fast_out_extra_slow_in +import com.android.systemui.R +import com.android.systemui.animation.Interpolators + +/** Animates constraint layout changes for the security view. */ +class KeyguardSecurityViewTransition : Transition() { + + companion object { + const val PROP_BOUNDS = "securityViewLocation:bounds" + + // The duration of the animation to switch security sides. + const val SECURITY_SHIFT_ANIMATION_DURATION_MS = 500L + + // How much of the switch sides animation should be dedicated to fading the security out. + // The remainder will fade it back in again. + const val SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f + } + + private fun captureValues(values: TransitionValues) { + val boundsRect = Rect() + boundsRect.left = values.view.left + boundsRect.top = values.view.top + boundsRect.right = values.view.right + boundsRect.bottom = values.view.bottom + values.values[PROP_BOUNDS] = boundsRect + } + + override fun getTransitionProperties(): Array<String>? { + return arrayOf(PROP_BOUNDS) + } + + override fun captureEndValues(transitionValues: TransitionValues?) { + transitionValues?.let { captureValues(it) } + } + + override fun captureStartValues(transitionValues: TransitionValues?) { + transitionValues?.let { captureValues(it) } + } + + override fun createAnimator( + sceneRoot: ViewGroup?, + startValues: TransitionValues?, + endValues: TransitionValues? + ): Animator? { + if (sceneRoot == null || startValues == null || endValues == null) { + return null + } + + // This animation is a bit fun to implement. The bouncer needs to move, and fade + // in/out at the same time. The issue is, the bouncer should only move a short + // amount (120dp or so), but obviously needs to go from one side of the screen to + // the other. This needs a pretty custom animation. + // + // This works as follows. It uses a ValueAnimation to simply drive the animation + // progress. This animator is responsible for both the translation of the bouncer, + // and the current fade. It will fade the bouncer out while also moving it along the + // 120dp path. Once the bouncer is fully faded out though, it will "snap" the + // bouncer closer to its destination, then fade it back in again. The effect is that + // the bouncer will move from 0 -> X while fading out, then + // (destination - X) -> destination while fading back in again. + // TODO(b/208250221): Make this animation properly abortable. + val positionInterpolator = + AnimationUtils.loadInterpolator(sceneRoot.context, fast_out_extra_slow_in) + val fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN + val fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN + var runningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f) + runningSecurityShiftAnimator.duration = SECURITY_SHIFT_ANIMATION_DURATION_MS + runningSecurityShiftAnimator.interpolator = Interpolators.LINEAR + val startRect = startValues.values[PROP_BOUNDS] as Rect + val endRect = endValues.values[PROP_BOUNDS] as Rect + val v = startValues.view + val totalTranslation: Int = + sceneRoot.resources.getDimension(R.dimen.security_shift_animation_translation).toInt() + val shouldRestoreLayerType = + (v.hasOverlappingRendering() && v.layerType != View.LAYER_TYPE_HARDWARE) + if (shouldRestoreLayerType) { + v.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */ null) + } + val initialAlpha: Float = v.alpha + runningSecurityShiftAnimator.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + runningSecurityShiftAnimator = null + } + } + ) + + var finishedFadingOutNonSecurityView = false + + runningSecurityShiftAnimator.addUpdateListener { animation: ValueAnimator -> + val switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION + val isFadingOut = animation.animatedFraction < switchPoint + val opacity: Float + var currentTranslation = + (positionInterpolator.getInterpolation(animation.animatedFraction) * + totalTranslation) + .toInt() + var translationRemaining = totalTranslation - currentTranslation + val leftAlign = endRect.left < startRect.left + if (leftAlign) { + currentTranslation = -currentTranslation + translationRemaining = -translationRemaining + } + + if (isFadingOut) { + // The bouncer fades out over the first X%. + val fadeOutFraction = + MathUtils.constrainedMap( + /* rangeMin= */ 1.0f, + /* rangeMax= */ 0.0f, + /* valueMin= */ 0.0f, + /* valueMax= */ switchPoint, + animation.animatedFraction + ) + opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction) + + // When fading out, the alpha needs to start from the initial opacity of the + // view flipper, otherwise we get a weird bit of jank as it ramps back to + // 100%. + v.alpha = opacity * initialAlpha + if (v is KeyguardSecurityViewFlipper) { + v.setLeftTopRightBottom( + startRect.left + currentTranslation, + startRect.top, + startRect.right + currentTranslation, + startRect.bottom + ) + } + } else { + // And in again over the remaining (100-X)%. + val fadeInFraction = + MathUtils.constrainedMap( + /* rangeMin= */ 0.0f, + /* rangeMax= */ 1.0f, + /* valueMin= */ switchPoint, + /* valueMax= */ 1.0f, + animation.animatedFraction + ) + opacity = fadeInInterpolator.getInterpolation(fadeInFraction) + v.alpha = opacity + + // Fading back in, animate towards the destination. + if (v is KeyguardSecurityViewFlipper) { + v.setLeftTopRightBottom( + endRect.left - translationRemaining, + endRect.top, + endRect.right - translationRemaining, + endRect.bottom + ) + } + } + if (animation.animatedFraction == 1.0f && shouldRestoreLayerType) { + v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null) + } + + // For views that are not the security view flipper, we do not want to apply + // an x translation animation. Instead, we want to fade out, move to final position and + // then fade in. + if (v !is KeyguardSecurityViewFlipper) { + // Opacity goes close to 0 but does not fully get to 0. + if (opacity - 0.001f < 0f) { + v.setLeftTopRightBottom( + endRect.left, + endRect.top, + endRect.right, + endRect.bottom + ) + finishedFadingOutNonSecurityView = true + } else if (!finishedFadingOutNonSecurityView) { + v.setLeftTopRightBottom( + startRect.left, + startRect.top, + startRect.right, + startRect.bottom + ) + } + } + } + runningSecurityShiftAnimator.start() + return runningSecurityShiftAnimator + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt index 109be40ce10f..37829f25d179 100644 --- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt +++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt @@ -3,6 +3,7 @@ package com.android.systemui import android.content.ComponentName import android.content.Context import android.content.pm.PackageManager +import android.util.Log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -55,7 +56,11 @@ class ChooserSelector @Inject constructor( } else { PackageManager.COMPONENT_ENABLED_STATE_DISABLED } - packageManager.setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0) + try { + packageManager.setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0) + } catch (e: IllegalArgumentException) { + Log.w("ChooserSelector", "Unable to set IntentResolver enabled=" + enabled, e) + } } suspend inline fun awaitCancellation(): Nothing = suspendCancellableCoroutine { } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 8aaadeffae12..8e2ac064b4f1 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -116,7 +116,7 @@ public class Flags { * the framework APIs. */ public static final UnreleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER = - new UnreleasedFlag(210, true); + new UnreleasedFlag(210); /** * Whether `UserSwitcherController` should use the user interactor. @@ -127,8 +127,7 @@ 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, false); + public static final ReleasedFlag USER_CONTROLLER_USES_INTERACTOR = new ReleasedFlag(211); /***************************************/ // 300 - power menu @@ -236,7 +235,7 @@ public class Flags { // 1100 - windowing @Keep public static final SysPropBooleanFlag WM_ENABLE_SHELL_TRANSITIONS = - new SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false); + new SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", true); /** * b/170163464: animate bubbles expanded view collapse with home gesture diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS index 7dc9dc7efeb7..d71fbf656e5f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS @@ -1,3 +1,6 @@ +justinweir@google.com +syeonlee@google.com + per-file *Notification* = set noparent per-file *Notification* = file:../statusbar/notification/OWNERS @@ -11,4 +14,4 @@ per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, per-file NotificationPanelUnfoldAnimationController.kt = alexflo@google.com, jeffdq@google.com, juliacr@google.com per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com -per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
\ No newline at end of file +per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java index a7719d3d82a4..e71d80c130da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.collection.inflation; import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; +import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; + import android.os.SystemClock; import android.service.notification.NotificationStats; @@ -70,6 +72,8 @@ public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback dismissalSurface = NotificationStats.DISMISSAL_PEEK; } else if (mStatusBarStateController.isDozing()) { dismissalSurface = NotificationStats.DISMISSAL_AOD; + } else if (mStatusBarStateController.getState() == KEYGUARD) { + dismissalSurface = NotificationStats.DISMISSAL_LOCKSCREEN; } return new DismissedByUserStats( dismissalSurface, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java index 3292a8fcdb50..6cf4bf318c99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java @@ -37,6 +37,19 @@ public interface NotificationInterruptStateProvider { boolean shouldHeadsUp(NotificationEntry entry); /** + * Returns the value of whether this entry should peek (from shouldHeadsUp(entry)), but only + * optionally logs the status. + * + * This method should be used in cases where the caller needs to check whether a notification + * qualifies for a heads up, but is not necessarily guaranteed to make the heads-up happen. + * + * @param entry the entry to check + * @param log whether or not to log the results of this check + * @return true if the entry should heads up, false otherwise + */ + boolean checkHeadsUp(NotificationEntry entry, boolean log); + + /** * Whether the notification should appear as a bubble with a fly-out on top of the screen. * * @param entry the entry to check diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 558fd62c78bf..c5a69217a1ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -137,11 +137,11 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter public boolean shouldBubbleUp(NotificationEntry entry) { final StatusBarNotification sbn = entry.getSbn(); - if (!canAlertCommon(entry)) { + if (!canAlertCommon(entry, true)) { return false; } - if (!canAlertAwakeCommon(entry)) { + if (!canAlertAwakeCommon(entry, true)) { return false; } @@ -163,10 +163,15 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter @Override public boolean shouldHeadsUp(NotificationEntry entry) { + return checkHeadsUp(entry, true); + } + + @Override + public boolean checkHeadsUp(NotificationEntry entry, boolean log) { if (mStatusBarStateController.isDozing()) { - return shouldHeadsUpWhenDozing(entry); + return shouldHeadsUpWhenDozing(entry, log); } else { - return shouldHeadsUpWhenAwake(entry); + return shouldHeadsUpWhenAwake(entry, log); } } @@ -263,61 +268,61 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } } - private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) { + private boolean shouldHeadsUpWhenAwake(NotificationEntry entry, boolean log) { StatusBarNotification sbn = entry.getSbn(); if (!mUseHeadsUp) { - mLogger.logNoHeadsUpFeatureDisabled(); + if (log) mLogger.logNoHeadsUpFeatureDisabled(); return false; } - if (!canAlertCommon(entry)) { + if (!canAlertCommon(entry, log)) { return false; } - if (!canAlertHeadsUpCommon(entry)) { + if (!canAlertHeadsUpCommon(entry, log)) { return false; } - if (!canAlertAwakeCommon(entry)) { + if (!canAlertAwakeCommon(entry, log)) { return false; } if (isSnoozedPackage(sbn)) { - mLogger.logNoHeadsUpPackageSnoozed(entry); + if (log) mLogger.logNoHeadsUpPackageSnoozed(entry); return false; } boolean inShade = mStatusBarStateController.getState() == SHADE; if (entry.isBubble() && inShade) { - mLogger.logNoHeadsUpAlreadyBubbled(entry); + if (log) mLogger.logNoHeadsUpAlreadyBubbled(entry); return false; } if (entry.shouldSuppressPeek()) { - mLogger.logNoHeadsUpSuppressedByDnd(entry); + if (log) mLogger.logNoHeadsUpSuppressedByDnd(entry); return false; } if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) { - mLogger.logNoHeadsUpNotImportant(entry); + if (log) mLogger.logNoHeadsUpNotImportant(entry); return false; } boolean inUse = mPowerManager.isScreenOn() && !isDreaming(); if (!inUse) { - mLogger.logNoHeadsUpNotInUse(entry); + if (log) mLogger.logNoHeadsUpNotInUse(entry); return false; } for (int i = 0; i < mSuppressors.size(); i++) { if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) { - mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i)); + if (log) mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i)); return false; } } - mLogger.logHeadsUp(entry); + if (log) mLogger.logHeadsUp(entry); return true; } @@ -328,37 +333,37 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter * @param entry the entry to check * @return true if the entry should ambient pulse, false otherwise */ - private boolean shouldHeadsUpWhenDozing(NotificationEntry entry) { + private boolean shouldHeadsUpWhenDozing(NotificationEntry entry, boolean log) { if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) { - mLogger.logNoPulsingSettingDisabled(entry); + if (log) mLogger.logNoPulsingSettingDisabled(entry); return false; } if (mBatteryController.isAodPowerSave()) { - mLogger.logNoPulsingBatteryDisabled(entry); + if (log) mLogger.logNoPulsingBatteryDisabled(entry); return false; } - if (!canAlertCommon(entry)) { - mLogger.logNoPulsingNoAlert(entry); + if (!canAlertCommon(entry, log)) { + if (log) mLogger.logNoPulsingNoAlert(entry); return false; } - if (!canAlertHeadsUpCommon(entry)) { - mLogger.logNoPulsingNoAlert(entry); + if (!canAlertHeadsUpCommon(entry, log)) { + if (log) mLogger.logNoPulsingNoAlert(entry); return false; } if (entry.shouldSuppressAmbient()) { - mLogger.logNoPulsingNoAmbientEffect(entry); + if (log) mLogger.logNoPulsingNoAmbientEffect(entry); return false; } if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) { - mLogger.logNoPulsingNotImportant(entry); + if (log) mLogger.logNoPulsingNotImportant(entry); return false; } - mLogger.logPulsing(entry); + if (log) mLogger.logPulsing(entry); return true; } @@ -366,18 +371,22 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter * Common checks between regular & AOD heads up and bubbles. * * @param entry the entry to check + * @param log whether or not to log the results of these checks * @return true if these checks pass, false if the notification should not alert */ - private boolean canAlertCommon(NotificationEntry entry) { + private boolean canAlertCommon(NotificationEntry entry, boolean log) { for (int i = 0; i < mSuppressors.size(); i++) { if (mSuppressors.get(i).suppressInterruptions(entry)) { - mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ false); + if (log) { + mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), + /* awake */ false); + } return false; } } if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) { - mLogger.keyguardHideNotification(entry); + if (log) mLogger.keyguardHideNotification(entry); return false; } @@ -388,19 +397,20 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter * Common checks for heads up notifications on regular and AOD displays. * * @param entry the entry to check + * @param log whether or not to log the results of these checks * @return true if these checks pass, false if the notification should not alert */ - private boolean canAlertHeadsUpCommon(NotificationEntry entry) { + private boolean canAlertHeadsUpCommon(NotificationEntry entry, boolean log) { StatusBarNotification sbn = entry.getSbn(); // Don't alert notifications that are suppressed due to group alert behavior if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { - mLogger.logNoAlertingGroupAlertBehavior(entry); + if (log) mLogger.logNoAlertingGroupAlertBehavior(entry); return false; } if (entry.hasJustLaunchedFullScreenIntent()) { - mLogger.logNoAlertingRecentFullscreen(entry); + if (log) mLogger.logNoAlertingRecentFullscreen(entry); return false; } @@ -413,12 +423,14 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter * @param entry the entry to check * @return true if these checks pass, false if the notification should not alert */ - private boolean canAlertAwakeCommon(NotificationEntry entry) { + private boolean canAlertAwakeCommon(NotificationEntry entry, boolean log) { StatusBarNotification sbn = entry.getSbn(); for (int i = 0; i < mSuppressors.size(); i++) { if (mSuppressors.get(i).suppressAwakeInterruptions(entry)) { - mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ true); + if (log) { + mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ true); + } return false; } } 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 ce465bcfb42d..2719dd88b7be 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 @@ -44,7 +44,8 @@ import java.io.PrintWriter; import javax.inject.Inject; /** - * A global state to track all input states for the algorithm. + * Global state to track all input states for + * {@link com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm}. */ @SysUISingleton public class AmbientState implements Dumpable { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java index 80385e69cfa4..f5cd0ca7ab3b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java @@ -41,6 +41,7 @@ import android.testing.TestableLooper.RunWithLooper; import android.testing.ViewUtils; import android.view.SurfaceControlViewHost; import android.view.SurfaceView; +import android.view.View; import androidx.test.filters.SmallTest; @@ -84,6 +85,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext)); + mKeyguardSecurityContainer.setId(View.generateViewId()); ViewUtils.attachView(mKeyguardSecurityContainer); mTestableLooper = TestableLooper.get(this); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index c1036e356cfa..52f8825c724b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -21,10 +21,14 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE; import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT; import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; +import static androidx.constraintlayout.widget.ConstraintSet.CHAIN_SPREAD; +import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT; +import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; +import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT; + import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT; import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED; @@ -32,9 +36,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -43,19 +44,17 @@ import android.content.res.Configuration; import android.graphics.Insets; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; -import android.widget.FrameLayout; +import androidx.constraintlayout.widget.ConstraintSet; import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.user.data.source.UserRecord; import com.android.systemui.util.settings.GlobalSettings; @@ -64,8 +63,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -78,14 +75,11 @@ import java.util.ArrayList; public class KeyguardSecurityContainerTest extends SysuiTestCase { private static final int VIEW_WIDTH = 1600; - - private int mScreenWidth; - private int mFakeMeasureSpec; + private static final int VIEW_HEIGHT = 900; @Rule public MockitoRule mRule = MockitoJUnit.rule(); - @Mock private KeyguardSecurityViewFlipper mSecurityViewFlipper; @Mock private GlobalSettings mGlobalSettings; @@ -93,59 +87,32 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { private FalsingManager mFalsingManager; @Mock private UserSwitcherController mUserSwitcherController; - @Mock - private KeyguardStateController mKeyguardStateController; - @Captor - private ArgumentCaptor<FrameLayout.LayoutParams> mLayoutCaptor; private KeyguardSecurityContainer mKeyguardSecurityContainer; - private FrameLayout.LayoutParams mSecurityViewFlipperLayoutParams; @Before public void setup() { // Needed here, otherwise when mKeyguardSecurityContainer is created below, it'll cache // the real references (rather than the TestableResources that this call creates). mContext.ensureTestableResources(); - mSecurityViewFlipperLayoutParams = new FrameLayout.LayoutParams( - MATCH_PARENT, MATCH_PARENT); - when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams); + mSecurityViewFlipper = new KeyguardSecurityViewFlipper(getContext()); + mSecurityViewFlipper.setId(View.generateViewId()); mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext()); + mKeyguardSecurityContainer.setRight(VIEW_WIDTH); + mKeyguardSecurityContainer.setLeft(0); + mKeyguardSecurityContainer.setTop(0); + mKeyguardSecurityContainer.setBottom(VIEW_HEIGHT); + mKeyguardSecurityContainer.setId(View.generateViewId()); mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper; mKeyguardSecurityContainer.addView(mSecurityViewFlipper, new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User"); when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true); - - mScreenWidth = getUiDevice().getDisplayWidth(); - mFakeMeasureSpec = View - .MeasureSpec.makeMeasureSpec(mScreenWidth, View.MeasureSpec.EXACTLY); - } - - @Test - public void onMeasure_usesHalfWidthWithOneHandedModeEnabled() { - mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager, - mUserSwitcherController); - - int halfWidthMeasureSpec = - View.MeasureSpec.makeMeasureSpec(mScreenWidth / 2, View.MeasureSpec.EXACTLY); - mKeyguardSecurityContainer.onMeasure(mFakeMeasureSpec, mFakeMeasureSpec); - - verify(mSecurityViewFlipper).measure(halfWidthMeasureSpec, mFakeMeasureSpec); } - - @Test - public void onMeasure_usesFullWidthWithOneHandedModeDisabled() { - mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); - - mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec); - verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, mFakeMeasureSpec); - } - @Test - public void onMeasure_respectsViewInsets() { + public void testOnApplyWindowInsets() { int paddingBottom = getContext().getResources() .getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin); int imeInsetAmount = paddingBottom + 1; @@ -162,17 +129,12 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { .setInsetsIgnoringVisibility(systemBars(), systemBarInset) .build(); - // It's reduced by the max of the systembar and IME, so just subtract IME inset. - int expectedHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec( - mScreenWidth - imeInsetAmount, View.MeasureSpec.EXACTLY); - mKeyguardSecurityContainer.onApplyWindowInsets(insets); - mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec); - verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, expectedHeightMeasureSpec); + assertThat(mKeyguardSecurityContainer.getPaddingBottom()).isEqualTo(imeInsetAmount); } @Test - public void onMeasure_respectsViewInsets_largerSystembar() { + public void testOnApplyWindowInsets_largerSystembar() { int imeInsetAmount = 0; int paddingBottom = getContext().getResources() .getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin); @@ -189,25 +151,22 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { .setInsetsIgnoringVisibility(systemBars(), systemBarInset) .build(); - int expectedHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec( - mScreenWidth - systemBarInsetAmount, View.MeasureSpec.EXACTLY); - mKeyguardSecurityContainer.onApplyWindowInsets(insets); - mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec); - verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, expectedHeightMeasureSpec); + assertThat(mKeyguardSecurityContainer.getPaddingBottom()).isEqualTo(systemBarInsetAmount); } - private void setupForUpdateKeyguardPosition(boolean oneHandedMode) { - int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT; - mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager, + @Test + public void testDefaultViewMode() { + mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager, mUserSwitcherController); - - mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec); - mKeyguardSecurityContainer.layout(0, 0, mScreenWidth, mScreenWidth); - - // Clear any interactions with the mock so we know the interactions definitely come from the - // below testing. - reset(mSecurityViewFlipper); + mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, + mUserSwitcherController); + ConstraintSet.Constraint viewFlipperConstraint = + getViewConstraint(mSecurityViewFlipper.getId()); + assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.startToStart).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.endToEnd).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID); } @Test @@ -217,13 +176,22 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { mKeyguardSecurityContainer.getWidth() - 1f); verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_RIGHT); - assertSecurityTranslationX( - mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth()); + ConstraintSet.Constraint viewFlipperConstraint = + getViewConstraint(mSecurityViewFlipper.getId()); + assertThat(viewFlipperConstraint.layout.widthPercent).isEqualTo(0.5f); + assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1); mKeyguardSecurityContainer.updatePositionByTouchX(1f); verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_LEFT); - verify(mSecurityViewFlipper).setTranslationX(0.0f); + viewFlipperConstraint = + getViewConstraint(mSecurityViewFlipper.getId()); + assertThat(viewFlipperConstraint.layout.widthPercent).isEqualTo(0.5f); + assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1); } @Test @@ -232,10 +200,16 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { mKeyguardSecurityContainer.updatePositionByTouchX( mKeyguardSecurityContainer.getWidth() - 1f); - verify(mSecurityViewFlipper, never()).setTranslationX(anyInt()); + ConstraintSet.Constraint viewFlipperConstraint = + getViewConstraint(mSecurityViewFlipper.getId()); + assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1); + assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1); mKeyguardSecurityContainer.updatePositionByTouchX(1f); - verify(mSecurityViewFlipper, never()).setTranslationX(anyInt()); + viewFlipperConstraint = + getViewConstraint(mSecurityViewFlipper.getId()); + assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1); + assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1); } @Test @@ -249,17 +223,31 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { setupUserSwitcher(); mKeyguardSecurityContainer.onConfigurationChanged(landscapeConfig); - // THEN views are oriented side by side - assertSecurityGravity(Gravity.LEFT | Gravity.BOTTOM); - assertUserSwitcherGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); - assertSecurityTranslationX( - mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth()); - assertUserSwitcherTranslationX(0f); - + ConstraintSet.Constraint viewFlipperConstraint = + getViewConstraint(mSecurityViewFlipper.getId()); + ConstraintSet.Constraint userSwitcherConstraint = + getViewConstraint(R.id.keyguard_bouncer_user_switcher); + assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.leftToRight).isEqualTo( + R.id.keyguard_bouncer_user_switcher); + assertThat(userSwitcherConstraint.layout.leftToLeft).isEqualTo(PARENT_ID); + assertThat(userSwitcherConstraint.layout.rightToLeft).isEqualTo( + mSecurityViewFlipper.getId()); + assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID); + assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID); + assertThat(userSwitcherConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID); + assertThat(userSwitcherConstraint.layout.bottomMargin).isEqualTo( + getContext().getResources().getDimensionPixelSize( + R.dimen.bouncer_user_switcher_y_trans)); + assertThat(viewFlipperConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD); + assertThat(userSwitcherConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD); + assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT); + assertThat(userSwitcherConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT); } @Test - public void testUserSwitcherModeViewGravityPortrait() { + public void testUserSwitcherModeViewPositionPortrait() { // GIVEN one user has been setup and in landscape when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1)); Configuration portraitConfig = configuration(ORIENTATION_PORTRAIT); @@ -267,15 +255,28 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { // WHEN UserSwitcherViewMode is initialized and config has changed setupUserSwitcher(); - reset(mSecurityViewFlipper); - when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams); mKeyguardSecurityContainer.onConfigurationChanged(portraitConfig); - // THEN views are both centered horizontally - assertSecurityGravity(Gravity.CENTER_HORIZONTAL); - assertUserSwitcherGravity(Gravity.CENTER_HORIZONTAL); - assertSecurityTranslationX(0); - assertUserSwitcherTranslationX(0); + ConstraintSet.Constraint viewFlipperConstraint = + getViewConstraint(mSecurityViewFlipper.getId()); + ConstraintSet.Constraint userSwitcherConstraint = + getViewConstraint(R.id.keyguard_bouncer_user_switcher); + + assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID); + assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID); + assertThat(userSwitcherConstraint.layout.topMargin).isEqualTo( + getContext().getResources().getDimensionPixelSize( + R.dimen.bouncer_user_switcher_y_trans)); + assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID); + assertThat(userSwitcherConstraint.layout.leftToLeft).isEqualTo(PARENT_ID); + assertThat(userSwitcherConstraint.layout.rightToRight).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.verticalChainStyle).isEqualTo(CHAIN_SPREAD); + assertThat(userSwitcherConstraint.layout.verticalChainStyle).isEqualTo(CHAIN_SPREAD); + assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT); + assertThat(userSwitcherConstraint.layout.mHeight).isEqualTo(WRAP_CONTENT); + assertThat(userSwitcherConstraint.layout.mWidth).isEqualTo(WRAP_CONTENT); } @Test @@ -337,9 +338,9 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_LEFT); mKeyguardSecurityContainer.onConfigurationChanged(new Configuration()); - assertSecurityTranslationX(0); - assertUserSwitcherTranslationX( - mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth()); + ConstraintSet.Constraint viewFlipperConstraint = getViewConstraint( + mSecurityViewFlipper.getId()); + assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID); } private Configuration configuration(@Configuration.Orientation int orientation) { @@ -348,28 +349,6 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { return config; } - private void assertSecurityTranslationX(float translation) { - verify(mSecurityViewFlipper).setTranslationX(translation); - } - - private void assertUserSwitcherTranslationX(float translation) { - ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById( - R.id.keyguard_bouncer_user_switcher); - assertThat(userSwitcher.getTranslationX()).isEqualTo(translation); - } - - private void assertUserSwitcherGravity(@Gravity.GravityFlags int gravity) { - ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById( - R.id.keyguard_bouncer_user_switcher); - assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity) - .isEqualTo(gravity); - } - - private void assertSecurityGravity(@Gravity.GravityFlags int gravity) { - verify(mSecurityViewFlipper, atLeastOnce()).setLayoutParams(mLayoutCaptor.capture()); - assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(gravity); - } - private void setViewWidth(int width) { mKeyguardSecurityContainer.setRight(width); mKeyguardSecurityContainer.setLeft(0); @@ -399,9 +378,6 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT); mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER, mGlobalSettings, mFalsingManager, mUserSwitcherController); - // reset mSecurityViewFlipper so setup doesn't influence test verifications - reset(mSecurityViewFlipper); - when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams); } private ArrayList<UserRecord> buildUserRecords(int count) { @@ -415,4 +391,17 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { } return users; } + + private void setupForUpdateKeyguardPosition(boolean oneHandedMode) { + int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT; + mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager, + mUserSwitcherController); + } + + /** Get the ConstraintLayout constraint of the view. */ + private ConstraintSet.Constraint getViewConstraint(int viewId) { + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(mKeyguardSecurityContainer); + return constraintSet.getConstraint(viewId); + } } diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java index 0ab5a8afe907..80d9d972ed0a 100644 --- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java @@ -270,7 +270,8 @@ class AssociationRequestsProcessor { final long callingIdentity = Binder.clearCallingIdentity(); try { association = mService.createAssociation(userId, packageName, macAddress, - request.getDisplayName(), request.getDeviceProfile(), request.isSelfManaged()); + request.getDisplayName(), request.getDeviceProfile(), + request.getAssociatedDevice(), request.isSelfManaged()); } finally { Binder.restoreCallingIdentity(callingIdentity); } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 0426c79358ba..f2cb6028e854 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -55,6 +55,7 @@ import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.PendingIntent; +import android.companion.AssociatedDevice; import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.DeviceNotAssociatedException; @@ -872,23 +873,25 @@ public class CompanionDeviceManagerService extends SystemService { /** * @deprecated use - * {@link #createAssociation(int, String, MacAddress, CharSequence, String, boolean)} + * {@link #createAssociation(int, String, MacAddress, CharSequence, String, AssociatedDevice, + * boolean)} */ @Deprecated void legacyCreateAssociation(@UserIdInt int userId, @NonNull String deviceMacAddress, @NonNull String packageName, @Nullable String deviceProfile) { final MacAddress macAddress = MacAddress.fromString(deviceMacAddress); - createAssociation(userId, packageName, macAddress, null, deviceProfile, false); + createAssociation(userId, packageName, macAddress, null, deviceProfile, null, false); } AssociationInfo createAssociation(@UserIdInt int userId, @NonNull String packageName, @Nullable MacAddress macAddress, @Nullable CharSequence displayName, - @Nullable String deviceProfile, boolean selfManaged) { + @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice, + boolean selfManaged) { final int id = getNewAssociationIdForPackage(userId, packageName); final long timestamp = System.currentTimeMillis(); final AssociationInfo association = new AssociationInfo(id, userId, packageName, - macAddress, displayName, deviceProfile, selfManaged, + macAddress, displayName, deviceProfile, associatedDevice, selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE); Slog.i(TAG, "New CDM association created=" + association); mAssociationStore.addAssociation(association); diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java index 4b56c1b28036..c4f576647058 100644 --- a/services/companion/java/com/android/server/companion/PersistentDataStore.java +++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java @@ -229,6 +229,11 @@ final class PersistentDataStore { /** * Reads previously persisted data for the given user "into" the provided containers. * + * Note that {@link AssociationInfo#getAssociatedDevice()} will always be {@code null} after + * retrieval from this datastore because it is not persisted (by design). This means that + * persisted data is not guaranteed to be identical to the initial data that was stored at the + * time of association. + * * @param userId Android UserID * @param associationsOut a container to read the {@link AssociationInfo}s "into". * @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into". @@ -293,6 +298,9 @@ final class PersistentDataStore { /** * Persisted data to the disk. * + * Note that associatedDevice field in {@link AssociationInfo} is not persisted by this + * datastore implementation. + * * @param userId Android UserID * @param associations a set of user's associations. * @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user. @@ -419,7 +427,7 @@ final class PersistentDataStore { final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L); out.add(new AssociationInfo(associationId, userId, appPackage, - MacAddress.fromString(deviceAddress), null, profile, + MacAddress.fromString(deviceAddress), null, profile, null, /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved, Long.MAX_VALUE)); } @@ -556,9 +564,11 @@ final class PersistentDataStore { boolean notify, boolean revoked, long timeApproved, long lastTimeConnected) { AssociationInfo associationInfo = null; try { + // We do not persist AssociatedDevice, which means that AssociationInfo retrieved from + // datastore is not guaranteed to be identical to the one from initial association. associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress, - displayName, profile, selfManaged, notify, revoked, timeApproved, - lastTimeConnected); + displayName, profile, null, selfManaged, notify, revoked, + timeApproved, lastTimeConnected); } catch (Exception e) { if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e); } diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java index 285c4068d84b..41044bfc70ca 100644 --- a/services/core/java/android/os/BatteryStatsInternal.java +++ b/services/core/java/android/os/BatteryStatsInternal.java @@ -16,12 +16,18 @@ package android.os; +import android.annotation.IntDef; +import android.annotation.NonNull; + import com.android.internal.os.BinderCallsStats; import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Collection; import java.util.List; + /** * Battery stats local system service interface. This is used to pass internal data out of * BatteryStatsImpl, as well as make unchecked calls into BatteryStatsImpl. @@ -29,6 +35,19 @@ import java.util.List; * @hide Only for use within Android OS. */ public abstract class BatteryStatsInternal { + + public static final int CPU_WAKEUP_SUBSYSTEM_UNKNOWN = -1; + public static final int CPU_WAKEUP_SUBSYSTEM_ALARM = 1; + + /** @hide */ + @IntDef(prefix = {"CPU_WAKEUP_SUBSYSTEM_"}, value = { + CPU_WAKEUP_SUBSYSTEM_UNKNOWN, + CPU_WAKEUP_SUBSYSTEM_ALARM, + }) + @Retention(RetentionPolicy.SOURCE) + @interface CpuWakeupSubsystem { + } + /** * Returns the wifi interfaces. */ @@ -56,9 +75,10 @@ public abstract class BatteryStatsInternal { /** * Inform battery stats how many deferred jobs existed when the app got launched and how * long ago was the last job execution for the app. - * @param uid the uid of the app. + * + * @param uid the uid of the app. * @param numDeferred number of deferred jobs. - * @param sinceLast how long in millis has it been since a job was run + * @param sinceLast how long in millis has it been since a job was run */ public abstract void noteJobsDeferred(int uid, int numDeferred, long sinceLast); @@ -72,4 +92,11 @@ public abstract class BatteryStatsInternal { * Informs battery stats of native thread IDs of threads taking incoming binder calls. */ public abstract void noteBinderThreadNativeIds(int[] binderThreadNativeTids); + + /** + * Reports any activity that could potentially have caused the CPU to wake up. + * Accepts a timestamp to allow the reporter to report it before or after the event. + */ + public abstract void noteCpuWakingActivity(@CpuWakeupSubsystem int subsystem, + long elapsedMillis, @NonNull int... uids); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b34fe69f5a3e..ee13118f3e5c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5285,7 +5285,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Tell anyone interested that we are done booting! SystemProperties.set("sys.boot_completed", "1"); SystemProperties.set("dev.bootcomplete", "1"); - mUserController.sendBootCompleted( + mUserController.onBootComplete( new IIntentReceiver.Stub() { @Override public void performReceive(Intent intent, int resultCode, diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index d9d29d650f03..75d1f6897111 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -81,9 +81,11 @@ import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; +import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.StatsEvent; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BackgroundThread; @@ -104,6 +106,7 @@ import com.android.server.power.stats.BatteryExternalStatsWorker; import com.android.server.power.stats.BatteryStatsImpl; import com.android.server.power.stats.BatteryUsageStatsProvider; import com.android.server.power.stats.BatteryUsageStatsStore; +import com.android.server.power.stats.CpuWakeupStats; import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes; import java.io.File; @@ -120,6 +123,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -142,6 +146,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub private final PowerProfile mPowerProfile; final BatteryStatsImpl mStats; + @GuardedBy("mWakeupStats") + final CpuWakeupStats mCpuWakeupStats; private final BatteryUsageStatsStore mBatteryUsageStatsStore; private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider; private final Context mContext; @@ -373,6 +379,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub } mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats, mBatteryUsageStatsStore); + mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map); } public void publish() { @@ -464,6 +471,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub mStats.noteBinderThreadNativeIds(binderThreadNativeTids); } } + + @Override + public void noteCpuWakingActivity(int subsystem, long elapsedMillis, int... uids) { + Objects.requireNonNull(uids); + mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids); + } } @Override @@ -2251,12 +2264,13 @@ public final class BatteryStatsService extends IBatteryStats.Stub try { String reason; while ((reason = waitWakeup()) != null) { + final long nowElapsed = SystemClock.elapsedRealtime(); + final long nowUptime = SystemClock.uptimeMillis(); // Wait for the completion of pending works if there is any awaitCompletion(); - + mCpuWakeupStats.noteWakeupTimeAndReason(nowElapsed, nowUptime, reason); synchronized (mStats) { - mStats.noteWakeupReasonLocked(reason, - SystemClock.elapsedRealtime(), SystemClock.uptimeMillis()); + mStats.noteWakeupReasonLocked(reason, nowElapsed, nowUptime); } } } catch (RuntimeException e) { @@ -2312,6 +2326,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub pw.println(" --read-daily: read-load last written daily stats."); pw.println(" --settings: dump the settings key/values related to batterystats"); pw.println(" --cpu: dump cpu stats for debugging purpose"); + pw.println(" --wakeups: dump CPU wakeup history and attribution."); pw.println(" --power-profile: dump the power profile constants"); pw.println(" --usage: write battery usage stats. Optional arguments:"); pw.println(" --proto: output as a binary protobuffer"); @@ -2567,6 +2582,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub } dumpUsageStatsToProto(fd, pw, model, proto); return; + } else if ("--wakeups".equals(arg)) { + mCpuWakeupStats.dump(new IndentingPrintWriter(pw, " "), + SystemClock.elapsedRealtime()); + return; } else if ("-a".equals(arg)) { flags |= BatteryStats.DUMP_VERBOSE; } else if (arg.length() > 0 && arg.charAt(0) == '-'){ @@ -2697,12 +2716,16 @@ public final class BatteryStatsService extends IBatteryStats.Stub } else { if (DBG) Slog.d(TAG, "begin dumpLocked from UID " + Binder.getCallingUid()); awaitCompletion(); + synchronized (mStats) { mStats.dumpLocked(mContext, pw, flags, reqUid, historyStart); if (writeData) { mStats.writeAsyncLocked(); } } + pw.println(); + mCpuWakeupStats.dump(new IndentingPrintWriter(pw, " "), SystemClock.elapsedRealtime()); + if (DBG) Slog.d(TAG, "end dumpLocked"); } } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 226c63862226..216a48ec699c 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -439,11 +439,6 @@ class UserController implements Handler.Callback { mUserLru.add(UserHandle.USER_SYSTEM); mLockPatternUtils = mInjector.getLockPatternUtils(); updateStartedUserArrayLU(); - - // TODO(b/232452368): currently mAllowUserUnlocking is only used on devices with HSUM - // (Headless System User Mode), but on master it will be used by all devices (and hence this - // initial assignment should be removed). - mAllowUserUnlocking = !UserManager.isHeadlessSystemUserMode(); } void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers, @@ -602,8 +597,11 @@ class UserController implements Handler.Callback { if (!mInjector.getUserManager().isPreCreated(userId)) { mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG, userId, 0)); - // In case of headless system user mode, do not send boot complete broadcast for - // system user as it is sent by sendBootCompleted call. + // The "locked boot complete" broadcast for the system user is supposed be sent when + // the device has finished booting. Normally, that is the same time that the system + // user transitions to RUNNING_LOCKED. However, in "headless system user mode", the + // system user is explicitly started before the device has finished booting. In + // that case, we need to wait until onBootComplete() to send the broadcast. if (!(UserManager.isHeadlessSystemUserMode() && uss.mHandle.isSystem())) { // ACTION_LOCKED_BOOT_COMPLETED sendLockedBootCompletedBroadcast(resultTo, userId); @@ -1808,15 +1806,13 @@ class UserController implements Handler.Callback { */ private boolean maybeUnlockUser(@UserIdInt int userId, @Nullable IProgressListener listener) { - // Delay user unlocking for headless system user mode until the system boot - // completes. When the system boot completes, the {@link #onBootCompleted()} - // method unlocks all started users for headless system user mode. This is done - // to prevent unlocking the users too early during the system boot up. - // Otherwise, emulated volumes are mounted too early during the system - // boot up. When vold is reset on boot complete, vold kills all apps/services - // (that use these emulated volumes) before unmounting the volumes(b/241929666). - // In the past, these killings have caused the system to become too unstable on - // some occasions. + // We cannot allow users to be unlocked before PHASE_BOOT_COMPLETED, for two reasons. + // First, emulated volumes aren't supposed to be used until then; StorageManagerService + // assumes it can reset everything upon reaching PHASE_BOOT_COMPLETED. Second, on some + // devices the Weaver HAL needed to unlock the user's storage isn't available until sometime + // shortly before PHASE_BOOT_COMPLETED. The below logic enforces a consistent flow across + // all devices, regardless of their Weaver implementation. + // // Any unlocks that get delayed by this will be done by onBootComplete() instead. if (!mAllowUserUnlocking) { Slogf.i(TAG, "Not unlocking user %d yet because boot hasn't completed", userId); @@ -2424,11 +2420,8 @@ class UserController implements Handler.Callback { } } - /** - * @deprecated TODO(b/232452368): this logic will be merged into sendBootCompleted - */ - @Deprecated - private void onBootCompletedOnHeadlessSystemUserModeDevices() { + void onBootComplete(IIntentReceiver resultTo) { + // Now that PHASE_BOOT_COMPLETED has been reached, user unlocking is allowed. setAllowUserUnlocking(true); // Get a copy of mStartedUsers to use outside of lock. @@ -2436,37 +2429,30 @@ class UserController implements Handler.Callback { synchronized (mLock) { startedUsers = mStartedUsers.clone(); } + // In non-headless system user mode, call finishUserBoot() to transition the system user + // from the BOOTING state to RUNNING_LOCKED, then to RUNNING_UNLOCKED if possible. + // + // In headless system user mode, additional users may have been started, and all users + // (including the system user) that ever get started are started explicitly. In this case, + // we should *not* transition users out of the BOOTING state using finishUserBoot(), as that + // doesn't handle issuing the needed onUserStarting() call, and it would just race with an + // explicit start anyway. We do, however, need to send the "locked boot complete" broadcast + // for the system user, as that got skipped earlier due to the *device* boot not being + // complete yet. We also need to try to unlock all started users, since until now explicit + // user starts didn't proceed to unlocking, due to it being too early in the device boot. + // // USER_SYSTEM must be processed first. It will be first in the array, as its ID is lowest. Preconditions.checkArgument(startedUsers.keyAt(0) == UserHandle.USER_SYSTEM); for (int i = 0; i < startedUsers.size(); i++) { - UserState uss = startedUsers.valueAt(i); - int userId = uss.mHandle.getIdentifier(); - Slogf.i(TAG, "Attempting to unlock user %d on boot complete", userId); - maybeUnlockUser(userId); - } - } - - void sendBootCompleted(IIntentReceiver resultTo) { - if (UserManager.isHeadlessSystemUserMode()) { - // Unlocking users is delayed until boot complete for headless system user mode. - onBootCompletedOnHeadlessSystemUserModeDevices(); - } - - // Get a copy of mStartedUsers to use outside of lock - SparseArray<UserState> startedUsers; - synchronized (mLock) { - startedUsers = mStartedUsers.clone(); - } - for (int i = 0; i < startedUsers.size(); i++) { + int userId = startedUsers.keyAt(i); UserState uss = startedUsers.valueAt(i); if (!UserManager.isHeadlessSystemUserMode()) { finishUserBoot(uss, resultTo); - } else if (uss.mHandle.isSystem()) { - // In case of headless system user mode, send only locked boot complete broadcast - // for system user since finishUserBoot call will be made using other code path; - // for non-system user, do nothing since finishUserBoot will be called elsewhere. - sendLockedBootCompletedBroadcast(resultTo, uss.mHandle.getIdentifier()); - return; + } else { + if (userId == UserHandle.USER_SYSTEM) { + sendLockedBootCompletedBroadcast(resultTo, userId); + } + maybeUnlockUser(userId); } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 4b18add39999..77fea09b5ecc 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4872,6 +4872,13 @@ public class NotificationManagerService extends SystemService { } @Override + public int getHintsFromListenerNoToken() { + synchronized (mNotificationLock) { + return mListenerHints; + } + } + + @Override public void requestInterruptionFilterFromListener(INotificationListener token, int interruptionFilter) throws RemoteException { final long identity = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java new file mode 100644 index 000000000000..5f76fbc0ec36 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java @@ -0,0 +1,455 @@ +/* + * 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.power.stats; + +import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM; +import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN; + +import android.content.Context; +import android.os.UserHandle; +import android.util.IndentingPrintWriter; +import android.util.LongSparseArray; +import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseLongArray; +import android.util.TimeSparseArray; +import android.util.TimeUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.IntPair; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Stores stats about CPU wakeups and tries to attribute them to subsystems and uids. + */ +public class CpuWakeupStats { + private static final String SUBSYSTEM_ALARM_STRING = "Alarm"; + @VisibleForTesting + static final long WAKEUP_RETENTION_MS = 3 * 24 * 60 * 60_000; // 3 days. + @VisibleForTesting + static final long WAKEUP_REASON_HALF_WINDOW_MS = 500; + + private final IrqDeviceMap mIrqDeviceMap; + private final WakingActivityHistory mRecentWakingActivity = new WakingActivityHistory(); + + @VisibleForTesting + final TimeSparseArray<Wakeup> mWakeupEvents = new TimeSparseArray<>(); + @VisibleForTesting + final TimeSparseArray<SparseArray<SparseBooleanArray>> mWakeupAttribution = + new TimeSparseArray<>(); + + public CpuWakeupStats(Context context, int mapRes) { + mIrqDeviceMap = IrqDeviceMap.getInstance(context, mapRes); + } + + /** Notes a wakeup reason as reported by SuspendControlService to battery stats. */ + public synchronized void noteWakeupTimeAndReason(long elapsedRealtime, long uptime, + String rawReason) { + final Wakeup parsedWakeup = new Wakeup(rawReason, elapsedRealtime, uptime); + mWakeupEvents.put(elapsedRealtime, parsedWakeup); + attemptAttributionFor(parsedWakeup); + // Assuming that wakeups always arrive in monotonically increasing elapsedRealtime order, + // we can delete all history that will not be useful in attributing future wakeups. + mRecentWakingActivity.clearAllBefore(elapsedRealtime - WAKEUP_REASON_HALF_WINDOW_MS); + + // Limit history of wakeups and their attribution to the last WAKEUP_RETENTION_MS. Note that + // the last wakeup and its attribution (if computed) is always stored, even if that wakeup + // had occurred before WAKEUP_RETENTION_MS. + int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS); + for (int i = lastIdx; i >= 0; i--) { + mWakeupEvents.removeAt(i); + } + lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS); + for (int i = lastIdx; i >= 0; i--) { + mWakeupAttribution.removeAt(i); + } + } + + /** Notes a waking activity that could have potentially woken up the CPU. */ + public synchronized void noteWakingActivity(int subsystem, long elapsedRealtime, int... uids) { + if (!attemptAttributionWith(subsystem, elapsedRealtime, uids)) { + mRecentWakingActivity.recordActivity(subsystem, elapsedRealtime, uids); + } + } + + private synchronized void attemptAttributionFor(Wakeup wakeup) { + final SparseBooleanArray subsystems = getResponsibleSubsystemsForWakeup(wakeup); + if (subsystems == null) { + // We don't support attribution for this kind of wakeup yet. + return; + } + + SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get(wakeup.mElapsedMillis); + if (attribution == null) { + attribution = new SparseArray<>(); + mWakeupAttribution.put(wakeup.mElapsedMillis, attribution); + } + + for (int subsystemIdx = 0; subsystemIdx < subsystems.size(); subsystemIdx++) { + final int subsystem = subsystems.keyAt(subsystemIdx); + + // Blame all activity that happened WAKEUP_REASON_HALF_WINDOW_MS before or after + // the wakeup from each responsible subsystem. + final long startTime = wakeup.mElapsedMillis - WAKEUP_REASON_HALF_WINDOW_MS; + final long endTime = wakeup.mElapsedMillis + WAKEUP_REASON_HALF_WINDOW_MS; + + final SparseBooleanArray uidsToBlame = mRecentWakingActivity.removeBetween(subsystem, + startTime, endTime); + attribution.put(subsystem, uidsToBlame); + } + } + + private synchronized boolean attemptAttributionWith(int subsystem, long activityElapsed, + int... uids) { + final int startIdx = mWakeupEvents.closestIndexOnOrAfter( + activityElapsed - WAKEUP_REASON_HALF_WINDOW_MS); + final int endIdx = mWakeupEvents.closestIndexOnOrBefore( + activityElapsed + WAKEUP_REASON_HALF_WINDOW_MS); + + for (int wakeupIdx = startIdx; wakeupIdx <= endIdx; wakeupIdx++) { + final Wakeup wakeup = mWakeupEvents.valueAt(wakeupIdx); + final SparseBooleanArray subsystems = getResponsibleSubsystemsForWakeup(wakeup); + if (subsystems == null) { + // Unsupported for attribution + continue; + } + if (subsystems.get(subsystem)) { + // We don't expect more than one wakeup to be found within such a short window, so + // just attribute this one and exit + SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get( + wakeup.mElapsedMillis); + if (attribution == null) { + attribution = new SparseArray<>(); + mWakeupAttribution.put(wakeup.mElapsedMillis, attribution); + } + SparseBooleanArray uidsToBlame = attribution.get(subsystem); + if (uidsToBlame == null) { + uidsToBlame = new SparseBooleanArray(uids.length); + attribution.put(subsystem, uidsToBlame); + } + for (final int uid : uids) { + uidsToBlame.put(uid, true); + } + return true; + } + } + return false; + } + + /** Dumps the relevant stats for cpu wakeups and their attribution to subsystem and uids */ + public synchronized void dump(IndentingPrintWriter pw, long nowElapsed) { + pw.println("CPU wakeup stats:"); + pw.increaseIndent(); + + mIrqDeviceMap.dump(pw); + pw.println(); + + mRecentWakingActivity.dump(pw, nowElapsed); + pw.println(); + + final SparseLongArray attributionStats = new SparseLongArray(); + pw.println("Wakeup events:"); + pw.increaseIndent(); + for (int i = mWakeupEvents.size() - 1; i >= 0; i--) { + TimeUtils.formatDuration(mWakeupEvents.keyAt(i), nowElapsed, pw); + pw.println(":"); + + pw.increaseIndent(); + final Wakeup wakeup = mWakeupEvents.valueAt(i); + pw.println(wakeup); + pw.print("Attribution: "); + final SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get( + wakeup.mElapsedMillis); + if (attribution == null) { + pw.println("N/A"); + } else { + for (int subsystemIdx = 0; subsystemIdx < attribution.size(); subsystemIdx++) { + if (subsystemIdx > 0) { + pw.print(", "); + } + final long counters = attributionStats.get(attribution.keyAt(subsystemIdx), + IntPair.of(0, 0)); + int attributed = IntPair.first(counters); + final int total = IntPair.second(counters) + 1; + + pw.print("subsystem: " + subsystemToString(attribution.keyAt(subsystemIdx))); + pw.print(", uids: ["); + final SparseBooleanArray uids = attribution.valueAt(subsystemIdx); + if (uids != null) { + for (int uidIdx = 0; uidIdx < uids.size(); uidIdx++) { + if (uidIdx > 0) { + pw.print(", "); + } + UserHandle.formatUid(pw, uids.keyAt(uidIdx)); + } + attributed++; + } + pw.print("]"); + + attributionStats.put(attribution.keyAt(subsystemIdx), + IntPair.of(attributed, total)); + } + pw.println(); + } + pw.decreaseIndent(); + } + pw.decreaseIndent(); + + pw.println("Attribution stats:"); + pw.increaseIndent(); + for (int i = 0; i < attributionStats.size(); i++) { + pw.print("Subsystem " + subsystemToString(attributionStats.keyAt(i))); + pw.print(": "); + final long ratio = attributionStats.valueAt(i); + pw.println(IntPair.first(ratio) + "/" + IntPair.second(ratio)); + } + pw.println("Total: " + mWakeupEvents.size()); + pw.decreaseIndent(); + + pw.decreaseIndent(); + pw.println(); + } + + private static final class WakingActivityHistory { + private static final long WAKING_ACTIVITY_RETENTION_MS = 3 * 60 * 60_000; // 3 hours. + private SparseArray<TimeSparseArray<SparseBooleanArray>> mWakingActivity = + new SparseArray<>(); + + void recordActivity(int subsystem, long elapsedRealtime, int... uids) { + if (uids == null) { + return; + } + TimeSparseArray<SparseBooleanArray> wakingActivity = mWakingActivity.get(subsystem); + if (wakingActivity == null) { + wakingActivity = new TimeSparseArray<>(); + mWakingActivity.put(subsystem, wakingActivity); + } + SparseBooleanArray uidsToBlame = wakingActivity.get(elapsedRealtime); + if (uidsToBlame == null) { + uidsToBlame = new SparseBooleanArray(uids.length); + wakingActivity.put(elapsedRealtime, uidsToBlame); + } + for (int i = 0; i < uids.length; i++) { + uidsToBlame.put(uids[i], true); + } + // Limit activity history per subsystem to the last WAKING_ACTIVITY_RETENTION_MS. + // Note that the last activity is always present, even if it occurred before + // WAKING_ACTIVITY_RETENTION_MS. + final int endIdx = wakingActivity.closestIndexOnOrBefore( + elapsedRealtime - WAKING_ACTIVITY_RETENTION_MS); + for (int i = endIdx; i >= 0; i--) { + wakingActivity.removeAt(endIdx); + } + } + + void clearAllBefore(long elapsedRealtime) { + for (int subsystemIdx = mWakingActivity.size() - 1; subsystemIdx >= 0; subsystemIdx--) { + final TimeSparseArray<SparseBooleanArray> activityPerSubsystem = + mWakingActivity.valueAt(subsystemIdx); + final int endIdx = activityPerSubsystem.closestIndexOnOrBefore(elapsedRealtime); + for (int removeIdx = endIdx; removeIdx >= 0; removeIdx--) { + activityPerSubsystem.removeAt(removeIdx); + } + // Generally waking activity is a high frequency occurrence for any subsystem, so we + // don't delete the TimeSparseArray even if it is now empty, to avoid object churn. + // This will leave one TimeSparseArray per subsystem, which are few right now. + } + } + + SparseBooleanArray removeBetween(int subsystem, long startElapsed, long endElapsed) { + final SparseBooleanArray uidsToReturn = new SparseBooleanArray(); + + final TimeSparseArray<SparseBooleanArray> activityForSubsystem = + mWakingActivity.get(subsystem); + if (activityForSubsystem != null) { + final int startIdx = activityForSubsystem.closestIndexOnOrAfter(startElapsed); + final int endIdx = activityForSubsystem.closestIndexOnOrBefore(endElapsed); + for (int i = endIdx; i >= startIdx; i--) { + final SparseBooleanArray uidsForTime = activityForSubsystem.valueAt(i); + for (int j = 0; j < uidsForTime.size(); j++) { + if (uidsForTime.valueAt(j)) { + uidsToReturn.put(uidsForTime.keyAt(j), true); + } + } + } + // More efficient to remove in a separate loop as it avoids repeatedly calling gc(). + for (int i = endIdx; i >= startIdx; i--) { + activityForSubsystem.removeAt(i); + } + // Generally waking activity is a high frequency occurrence for any subsystem, so we + // don't delete the TimeSparseArray even if it is now empty, to avoid object churn. + // This will leave one TimeSparseArray per subsystem, which are few right now. + } + return uidsToReturn.size() > 0 ? uidsToReturn : null; + } + + void dump(IndentingPrintWriter pw, long nowElapsed) { + pw.println("Recent waking activity:"); + pw.increaseIndent(); + for (int i = 0; i < mWakingActivity.size(); i++) { + pw.println("Subsystem " + subsystemToString(mWakingActivity.keyAt(i)) + ":"); + final LongSparseArray<SparseBooleanArray> wakingActivity = + mWakingActivity.valueAt(i); + if (wakingActivity == null) { + continue; + } + pw.increaseIndent(); + for (int j = wakingActivity.size() - 1; j >= 0; j--) { + TimeUtils.formatDuration(wakingActivity.keyAt(j), nowElapsed, pw); + final SparseBooleanArray uidsToBlame = wakingActivity.valueAt(j); + if (uidsToBlame == null) { + pw.println(); + continue; + } + pw.print(": "); + for (int k = 0; k < uidsToBlame.size(); k++) { + if (uidsToBlame.valueAt(k)) { + UserHandle.formatUid(pw, uidsToBlame.keyAt(k)); + pw.print(", "); + } + } + pw.println(); + } + pw.decreaseIndent(); + } + pw.decreaseIndent(); + } + } + + private SparseBooleanArray getResponsibleSubsystemsForWakeup(Wakeup wakeup) { + if (ArrayUtils.isEmpty(wakeup.mDevices)) { + return null; + } + final SparseBooleanArray result = new SparseBooleanArray(); + for (final Wakeup.IrqDevice device : wakeup.mDevices) { + final List<String> rawSubsystems = mIrqDeviceMap.getSubsystemsForDevice(device.mDevice); + + boolean anyKnownSubsystem = false; + if (rawSubsystems != null) { + for (int i = 0; i < rawSubsystems.size(); i++) { + final int subsystem = stringToKnownSubsystem(rawSubsystems.get(i)); + if (subsystem != CPU_WAKEUP_SUBSYSTEM_UNKNOWN) { + // Just in case the xml had arbitrary subsystem names, we want to make sure + // that we only put the known ones into our attribution map. + result.put(subsystem, true); + anyKnownSubsystem = true; + } + } + } + if (!anyKnownSubsystem) { + result.put(CPU_WAKEUP_SUBSYSTEM_UNKNOWN, true); + } + } + return result; + } + + static int stringToKnownSubsystem(String rawSubsystem) { + switch (rawSubsystem) { + case SUBSYSTEM_ALARM_STRING: + return CPU_WAKEUP_SUBSYSTEM_ALARM; + } + return CPU_WAKEUP_SUBSYSTEM_UNKNOWN; + } + + static String subsystemToString(int subsystem) { + switch (subsystem) { + case CPU_WAKEUP_SUBSYSTEM_ALARM: + return SUBSYSTEM_ALARM_STRING; + case CPU_WAKEUP_SUBSYSTEM_UNKNOWN: + return "Unknown"; + } + return "N/A"; + } + + private static final class Wakeup { + private static final String PARSER_TAG = "CpuWakeupStats.Wakeup"; + private static final String ABORT_REASON_PREFIX = "Abort"; + private static final Pattern sIrqPattern = Pattern.compile("(\\d+)\\s+(\\S+)"); + + String mRawReason; + long mElapsedMillis; + long mUptimeMillis; + IrqDevice[] mDevices; + + Wakeup(String rawReason, long elapsedMillis, long uptimeMillis) { + mRawReason = rawReason; + mElapsedMillis = elapsedMillis; + mUptimeMillis = uptimeMillis; + mDevices = parseIrqDevices(rawReason); + } + + private static IrqDevice[] parseIrqDevices(String rawReason) { + final String[] components = rawReason.split(":"); + if (ArrayUtils.isEmpty(components) || components[0].startsWith(ABORT_REASON_PREFIX)) { + // We don't support parsing aborts yet. + return null; + } + + int parsedDeviceCount = 0; + IrqDevice[] parsedDevices = new IrqDevice[components.length]; + + for (String component : components) { + final Matcher matcher = sIrqPattern.matcher(component); + if (matcher.find()) { + final int line; + final String device; + try { + line = Integer.parseInt(matcher.group(1)); + device = matcher.group(2); + } catch (NumberFormatException e) { + Slog.e(PARSER_TAG, + "Exception while parsing device names from part: " + component, e); + continue; + } + parsedDevices[parsedDeviceCount++] = new IrqDevice(line, device); + } + } + return (parsedDeviceCount > 0) ? Arrays.copyOf(parsedDevices, parsedDeviceCount) : null; + } + + @Override + public String toString() { + return "Wakeup{" + + "mRawReason='" + mRawReason + '\'' + + ", mElapsedMillis=" + mElapsedMillis + + ", mUptimeMillis=" + TimeUtils.formatDuration(mUptimeMillis) + + ", mDevices=" + Arrays.toString(mDevices) + + '}'; + } + + static final class IrqDevice { + int mLine; + String mDevice; + + IrqDevice(int line, String device) { + mLine = line; + mDevice = device; + } + + @Override + public String toString() { + return "IrqDevice{" + "mLine=" + mLine + ", mDevice='" + mDevice + '\'' + '}'; + } + } + } +} diff --git a/services/core/java/com/android/server/power/stats/IrqDeviceMap.java b/services/core/java/com/android/server/power/stats/IrqDeviceMap.java new file mode 100644 index 000000000000..091d18e30ccc --- /dev/null +++ b/services/core/java/com/android/server/power/stats/IrqDeviceMap.java @@ -0,0 +1,137 @@ +/* + * 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.power.stats; + +import android.annotation.XmlRes; +import android.content.Context; +import android.content.res.XmlResourceParser; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.IndentingPrintWriter; +import android.util.LongSparseArray; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Parses irq_device_map.xml to store a mapping of devices that can send IRQs to the CPU to + * subsystems that represent some logical work happening on the device that could need an IRQ. + */ +public class IrqDeviceMap { + private static final String TAG_IRQ_DEVICE_MAP = "irq-device-map"; + private static final String TAG_DEVICE = "device"; + private static final String TAG_SUBSYSTEM = "subsystem"; + private static final String ATTR_NAME = "name"; + + private static LongSparseArray<IrqDeviceMap> sInstanceMap = new LongSparseArray<>(1); + + private final ArrayMap<String, List<String>> mSubsystemsForDevice = new ArrayMap(); + + private IrqDeviceMap(XmlResourceParser parser) { + try { + XmlUtils.beginDocument(parser, TAG_IRQ_DEVICE_MAP); + + int type; + String currentDevice = null; + final ArraySet<String> subsystems = new ArraySet<>(); + + while ((type = parser.getEventType()) != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_DEVICE)) { + currentDevice = parser.getAttributeValue(null, ATTR_NAME); + } + if (currentDevice != null && type == XmlPullParser.END_TAG + && parser.getName().equals(TAG_DEVICE)) { + final int n = subsystems.size(); + if (n > 0) { + mSubsystemsForDevice.put(currentDevice, + Collections.unmodifiableList(new ArrayList<>(subsystems))); + } + subsystems.clear(); + currentDevice = null; + } + if (currentDevice != null && type == XmlPullParser.START_TAG + && parser.getName().equals(TAG_SUBSYSTEM)) { + parser.next(); + if (parser.getEventType() == XmlPullParser.TEXT) { + subsystems.add(parser.getText()); + } + } + parser.next(); + } + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + parser.close(); + } + } + + /** + * Returns an instance of IrqDeviceMap initialzed with the given context and xml resource. + * The xml resource should describe the mapping in a way similar to + * core/res/res/xml/irq_device_map.xml. + */ + public static IrqDeviceMap getInstance(Context context, @XmlRes int resId) { + synchronized (IrqDeviceMap.class) { + final int idx = sInstanceMap.indexOfKey(resId); + if (idx >= 0) { + return sInstanceMap.valueAt(idx); + } + } + final XmlResourceParser parser = context.getResources().getXml(resId); + final IrqDeviceMap irqDeviceMap = new IrqDeviceMap(parser); + synchronized (IrqDeviceMap.class) { + sInstanceMap.put(resId, irqDeviceMap); + } + return irqDeviceMap; + } + + List<String> getSubsystemsForDevice(String device) { + return mSubsystemsForDevice.get(device); + } + + void dump(IndentingPrintWriter pw) { + pw.println("Irq device map:"); + pw.increaseIndent(); + + final LongSparseArray<IrqDeviceMap> instanceMap; + synchronized (IrqDeviceMap.class) { + instanceMap = sInstanceMap; + } + final int idx = instanceMap.indexOfValue(this); + final String res = (idx >= 0) ? ("0x" + Long.toHexString(instanceMap.keyAt(idx))) : null; + pw.println("Loaded from xml resource: " + res); + + pw.println("Map:"); + pw.increaseIndent(); + for (int i = 0; i < mSubsystemsForDevice.size(); i++) { + pw.print(mSubsystemsForDevice.keyAt(i) + ": "); + pw.println(mSubsystemsForDevice.valueAt(i)); + } + pw.decreaseIndent(); + + pw.decreaseIndent(); + } +} diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 961c320dabe6..ad3f045987ea 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -423,7 +423,7 @@ public class WindowManagerService extends IWindowManager.Stub * @see #ENABLE_SHELL_TRANSITIONS */ public static final boolean sEnableShellTransitions = - SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, false); + SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, true); /** * Allows a fullscreen windowing mode activity to launch in its desired orientation directly diff --git a/services/tests/servicestests/res/xml/irq_device_map_1.xml b/services/tests/servicestests/res/xml/irq_device_map_1.xml new file mode 100644 index 000000000000..1f1a77b437ab --- /dev/null +++ b/services/tests/servicestests/res/xml/irq_device_map_1.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** 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. +*/ +--> +<irq-device-map> + <device name="test.device.1"> + <subsystem>test.subsystem.1</subsystem> + <subsystem>test.subsystem.2</subsystem> + </device> + <device name="test.device.2"> + <subsystem>test.subsystem.3</subsystem> + <subsystem>test.subsystem.2</subsystem> + </device> +</irq-device-map>
\ No newline at end of file diff --git a/services/tests/servicestests/res/xml/irq_device_map_2.xml b/services/tests/servicestests/res/xml/irq_device_map_2.xml new file mode 100644 index 000000000000..508c98d871da --- /dev/null +++ b/services/tests/servicestests/res/xml/irq_device_map_2.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** 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. +*/ +--> + +<!-- Long comment describing anything that may be needed +for this file and its maintenance --> +<irq-device-map> + <!-- Small comment specific to this device --> + <invalid name="test.device.1"> + <!-- These valid subsystem definitions should be ignored because of invalid parent tag --> + <subsystem>test.subsystem.1</subsystem> + <subsystem>test.subsystem.2</subsystem> + </invalid> + <device name="test.device.2"> + <!-- Multiline comment to describe nuances + about why these subsystems + rely on this hardware device + to wake the CPU up from sleep + --> + <subsystem>test.subsystem.3</subsystem> + <!-- Small comment specific to test.subsystem.4 --> + <subsystem>test.subsystem.4</subsystem> + <subsystem>test.subsystem.5</subsystem> + <!-- Duplicates should be ignored --> + <subsystem>test.subsystem.4</subsystem> + <subsystem>test.subsystem.3</subsystem> + <subsystem>test.subsystem.5</subsystem> + </device> + + <device name="test.device.3"> + <!-- All child tags are invalid, mapping should be empty / non-existent for this device --> + <subsys>ignored</subsys> + <system>redundant</system> + </device> + + <device name="test.device.4"> + <!-- Invalid child tags should be skipped but others should be mapped --> + <invalid>unused</invalid> + <random>skipped</random> + <subsystem>test.subsystem.1</subsystem> + <subsystem>test.subsystem.4</subsystem> + </device> + +</irq-device-map>
\ No newline at end of file diff --git a/services/tests/servicestests/res/xml/irq_device_map_3.xml b/services/tests/servicestests/res/xml/irq_device_map_3.xml new file mode 100644 index 000000000000..498b676dd1dc --- /dev/null +++ b/services/tests/servicestests/res/xml/irq_device_map_3.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** 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. +*/ +--> +<irq-device-map> + <device name="test.alarm.device"> + <subsystem>Alarm</subsystem> + </device> + <device name="test.wifi.device"> + <subsystem>undefined</subsystem> + </device> +</irq-device-map>
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 2d2c76c40b10..0b776a3e6642 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -181,11 +181,8 @@ public class UserControllerTest { doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt()); mockIsUsersOnSecondaryDisplaysEnabled(false); // All UserController params are set to default. - mUserController = new UserController(mInjector); - // TODO(b/232452368): need to explicitly call setAllowUserUnlocking(), otherwise most - // tests would fail. But we might need to disable it for the onBootComplete() test (i.e, - // to make sure the users are unlocked at the right time) + mUserController = new UserController(mInjector); mUserController.setAllowUserUnlocking(true); setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS); setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated= */ true, null); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 6388c7d52f10..9c5d1a5b0610 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -226,7 +226,7 @@ public class VirtualDeviceManagerServiceTest { mContext.getSystemService(WindowManager.class), threadVerifier); mAssociationInfo = new AssociationInfo(1, 0, null, - MacAddress.BROADCAST_ADDRESS, "", null, true, false, false, 0, 0); + MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0); mVdms = new VirtualDeviceManagerService(mContext); mLocalService = mVdms.getLocalServiceInstance(); diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java new file mode 100644 index 000000000000..7731a326cf61 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java @@ -0,0 +1,175 @@ +/* + * 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.power.stats; + +import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM; +import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN; + +import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS; +import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_RETENTION_MS; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.util.SparseArray; +import android.util.SparseBooleanArray; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.frameworks.servicestests.R; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + +@RunWith(AndroidJUnit4.class) +public class CpuWakeupStatsTest { + private static final String KERNEL_REASON_ALARM_IRQ = "120 test.alarm.device"; + private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device"; + private static final String KERNEL_REASON_UNKNOWN = "unsupported-free-form-reason"; + + private static final int TEST_UID_1 = 13239823; + private static final int TEST_UID_2 = 25268423; + private static final int TEST_UID_3 = 92261423; + private static final int TEST_UID_4 = 56926423; + private static final int TEST_UID_5 = 76421423; + + private static final Context sContext = InstrumentationRegistry.getTargetContext(); + private final ThreadLocalRandom mRandom = ThreadLocalRandom.current(); + + @Test + public void removesOldWakeups() { + final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1); + + final Set<Long> timestamps = new HashSet<>(); + final long firstWakeup = 453192; + + obj.noteWakeupTimeAndReason(firstWakeup, 32, "unused"); + timestamps.add(firstWakeup); + for (int i = 1; i < 1000; i++) { + final long delta = mRandom.nextLong(WAKEUP_RETENTION_MS); + if (timestamps.add(firstWakeup + delta)) { + obj.noteWakeupTimeAndReason(firstWakeup + delta, i, "unused"); + } + } + assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size()); + + obj.noteWakeupTimeAndReason(firstWakeup + WAKEUP_RETENTION_MS + 1242, 231, "unused"); + assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size()); + + for (int i = 0; i < 100; i++) { + final long now = mRandom.nextLong(WAKEUP_RETENTION_MS + 1, 100 * WAKEUP_RETENTION_MS); + obj.noteWakeupTimeAndReason(now, i, "unused"); + assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - WAKEUP_RETENTION_MS)) + .isLessThan(0); + } + } + + @Test + public void alarmIrqAttributionSolo() { + final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3); + final long wakeupTime = 12423121; + + obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_ALARM_IRQ); + + // Outside the window, so should be ignored. + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, + wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1); + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, + wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2); + // Should be attributed + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3, TEST_UID_5); + + final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime); + assertThat(attribution).isNotNull(); + assertThat(attribution.size()).isEqualTo(1); + assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue(); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_1)).isEqualTo(false); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_2)).isEqualTo(false); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(true); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(true); + } + + @Test + public void alarmIrqAttributionCombined() { + final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3); + final long wakeupTime = 92123210; + + obj.noteWakeupTimeAndReason(wakeupTime, 4, + KERNEL_REASON_UNKNOWN_IRQ + ":" + KERNEL_REASON_ALARM_IRQ); + + // Outside the window, so should be ignored. + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, + wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1); + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, + wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2); + // Should be attributed + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3); + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4, + TEST_UID_5); + + final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime); + assertThat(attribution).isNotNull(); + assertThat(attribution.size()).isEqualTo(2); + assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue(); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_1)).isEqualTo(false); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_2)).isEqualTo(false); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(true); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_4)).isEqualTo(true); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(true); + assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue(); + } + + @Test + public void unknownIrqAttribution() { + final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3); + final long wakeupTime = 92123410; + + obj.noteWakeupTimeAndReason(wakeupTime, 24, KERNEL_REASON_UNKNOWN_IRQ); + + // Unrelated subsystems, should not be attributed + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3); + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4, + TEST_UID_5); + + final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime); + assertThat(attribution).isNotNull(); + assertThat(attribution.size()).isEqualTo(1); + assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue(); + final SparseBooleanArray uids = attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN); + assertThat(uids == null || uids.size() == 0).isTrue(); + } + + @Test + public void unknownAttribution() { + final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3); + final long wakeupTime = 72123210; + + obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNKNOWN); + + // Unrelated subsystems, should be ignored. + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3); + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4); + + // There should be nothing in the attribution map. + assertThat(obj.mWakeupAttribution.size()).isEqualTo(0); + } +} diff --git a/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java b/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java new file mode 100644 index 000000000000..43d9e60c4a2b --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java @@ -0,0 +1,98 @@ +/* + * 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.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.frameworks.servicestests.R; +import com.android.internal.util.CollectionUtils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class IrqDeviceMapTest { + private static final String TEST_DEVICE_1 = "test.device.1"; + private static final String TEST_DEVICE_2 = "test.device.2"; + private static final String TEST_DEVICE_3 = "test.device.3"; + private static final String TEST_DEVICE_4 = "test.device.4"; + + private static final String TEST_SUBSYSTEM_1 = "test.subsystem.1"; + private static final String TEST_SUBSYSTEM_2 = "test.subsystem.2"; + private static final String TEST_SUBSYSTEM_3 = "test.subsystem.3"; + private static final String TEST_SUBSYSTEM_4 = "test.subsystem.4"; + private static final String TEST_SUBSYSTEM_5 = "test.subsystem.5"; + + private static final Context sContext = InstrumentationRegistry.getTargetContext(); + + @Test + public void cachesInstancesPerXml() { + IrqDeviceMap irqDeviceMap1 = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_1); + IrqDeviceMap irqDeviceMap2 = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_1); + assertThat(irqDeviceMap1).isSameInstanceAs(irqDeviceMap2); + + irqDeviceMap2 = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_2); + assertThat(irqDeviceMap1).isNotSameInstanceAs(irqDeviceMap2); + + irqDeviceMap1 = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_2); + assertThat(irqDeviceMap1).isSameInstanceAs(irqDeviceMap2); + } + + @Test + public void simpleXml() { + IrqDeviceMap deviceMap = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_1); + + List<String> subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_1); + assertThat(subsystems).hasSize(2); + // No specific order is required. + assertThat(subsystems).containsExactly(TEST_SUBSYSTEM_2, TEST_SUBSYSTEM_1); + + subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_2); + assertThat(subsystems).hasSize(2); + // No specific order is required. + assertThat(subsystems).containsExactly(TEST_SUBSYSTEM_2, TEST_SUBSYSTEM_3); + } + + @Test + public void complexXml() { + IrqDeviceMap deviceMap = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_2); + + List<String> subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_1); + assertThat(CollectionUtils.isEmpty(subsystems)).isTrue(); + + subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_2); + assertThat(subsystems).hasSize(3); + // No specific order is required. + assertThat(subsystems).containsExactly(TEST_SUBSYSTEM_3, TEST_SUBSYSTEM_4, + TEST_SUBSYSTEM_5); + + subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_3); + assertThat(CollectionUtils.isEmpty(subsystems)).isTrue(); + + subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_4); + assertThat(subsystems).hasSize(2); + // No specific order is required. + assertThat(subsystems).containsExactly(TEST_SUBSYSTEM_4, TEST_SUBSYSTEM_1); + } +} |