diff options
68 files changed, 2045 insertions, 380 deletions
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index cd45f4df3d50..b4f4a7efad98 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -212,6 +212,11 @@ public final class AttributionSource implements Parcelable { } /** @hide */ + public AttributionSource withDefaultToken() { + return withToken(sDefaultToken); + } + + /** @hide */ public AttributionSource withPid(int pid) { return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag(), getToken(), mAttributionSourceState.renouncedPermissions, getNext()); @@ -520,16 +525,28 @@ public final class AttributionSource implements Parcelable { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AttributionSource that = (AttributionSource) o; - return mAttributionSourceState.uid == that.mAttributionSourceState.uid + return equalsExceptToken(that) && Objects.equals( + mAttributionSourceState.token, that.mAttributionSourceState.token); + } + + /** + * We store trusted attribution sources without their token (the token is the key to the map) + * to avoid having a strong reference to the token. This means, when checking the equality of a + * supplied AttributionSource in PermissionManagerService.isTrustedAttributionSource, we want to + * compare everything except the token. + * + * @hide + */ + public boolean equalsExceptToken(@Nullable AttributionSource o) { + if (o == null) return false; + return mAttributionSourceState.uid == o.mAttributionSourceState.uid && Objects.equals(mAttributionSourceState.packageName, - that.mAttributionSourceState.packageName) + o.mAttributionSourceState.packageName) && Objects.equals(mAttributionSourceState.attributionTag, - that.mAttributionSourceState.attributionTag) - && Objects.equals(mAttributionSourceState.token, - that.mAttributionSourceState.token) + o.mAttributionSourceState.attributionTag) && Arrays.equals(mAttributionSourceState.renouncedPermissions, - that.mAttributionSourceState.renouncedPermissions) - && Objects.equals(getNext(), that.getNext()); + o.mAttributionSourceState.renouncedPermissions) + && Objects.equals(getNext(), o.getNext()); } @Override diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java index 7a153ef9c6f3..c5f56144c29c 100644 --- a/core/java/android/os/AggregateBatteryConsumer.java +++ b/core/java/android/os/AggregateBatteryConsumer.java @@ -116,8 +116,9 @@ public final class AggregateBatteryConsumer extends BatteryConsumer { * Builder for DeviceBatteryConsumer. */ public static final class Builder extends BaseBuilder<AggregateBatteryConsumer.Builder> { - public Builder(BatteryConsumer.BatteryConsumerData data, int scope) { - super(data, CONSUMER_TYPE_AGGREGATE); + public Builder(BatteryConsumer.BatteryConsumerData data, int scope, + double minConsumedPowerThreshold) { + super(data, CONSUMER_TYPE_AGGREGATE, minConsumedPowerThreshold); data.putInt(COLUMN_INDEX_SCOPE, scope); } diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index 0ba8d51e820f..ca84b3563561 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -795,11 +795,12 @@ public abstract class BatteryConsumer { protected final BatteryConsumer.BatteryConsumerData mData; protected final PowerComponents.Builder mPowerComponentsBuilder; - public BaseBuilder(BatteryConsumer.BatteryConsumerData data, int consumerType) { + public BaseBuilder(BatteryConsumer.BatteryConsumerData data, int consumerType, + double minConsumedPowerThreshold) { mData = data; data.putLong(COLUMN_INDEX_BATTERY_CONSUMER_TYPE, consumerType); - mPowerComponentsBuilder = new PowerComponents.Builder(data); + mPowerComponentsBuilder = new PowerComponents.Builder(data, minConsumedPowerThreshold); } @Nullable diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index e2c52cecc2b1..cc37b5433b82 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -707,7 +707,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA, false); builder = new Builder(customComponentNames.toArray(new String[0]), true, - includesProcStateData); + includesProcStateData, 0); builder.setStatsStartTimestamp( parser.getAttributeLong(null, XML_ATTR_START_TIMESTAMP)); @@ -782,6 +782,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { private final String[] mCustomPowerComponentNames; private final boolean mIncludePowerModels; private final boolean mIncludesProcessStateData; + private final double mMinConsumedPowerThreshold; private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout; private long mStatsStartTimestampMs; private long mStatsEndTimestampMs; @@ -802,11 +803,11 @@ public final class BatteryUsageStats implements Parcelable, Closeable { private BatteryStatsHistory mBatteryStatsHistory; public Builder(@NonNull String[] customPowerComponentNames) { - this(customPowerComponentNames, false, false); + this(customPowerComponentNames, false, false, 0); } public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels, - boolean includeProcessStateData) { + boolean includeProcessStateData, double minConsumedPowerThreshold) { mBatteryConsumersCursorWindow = new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE); mBatteryConsumerDataLayout = @@ -817,12 +818,14 @@ public final class BatteryUsageStats implements Parcelable, Closeable { mCustomPowerComponentNames = customPowerComponentNames; mIncludePowerModels = includePowerModels; mIncludesProcessStateData = includeProcessStateData; + mMinConsumedPowerThreshold = minConsumedPowerThreshold; for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) { final BatteryConsumer.BatteryConsumerData data = BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow, mBatteryConsumerDataLayout); mAggregateBatteryConsumersBuilders[scope] = - new AggregateBatteryConsumer.Builder(data, scope); + new AggregateBatteryConsumer.Builder( + data, scope, mMinConsumedPowerThreshold); } } @@ -961,7 +964,8 @@ public final class BatteryUsageStats implements Parcelable, Closeable { final BatteryConsumer.BatteryConsumerData data = BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow, mBatteryConsumerDataLayout); - builder = new UidBatteryConsumer.Builder(data, batteryStatsUid); + builder = new UidBatteryConsumer.Builder(data, batteryStatsUid, + mMinConsumedPowerThreshold); mUidBatteryConsumerBuilders.put(uid, builder); } return builder; @@ -979,7 +983,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { final BatteryConsumer.BatteryConsumerData data = BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow, mBatteryConsumerDataLayout); - builder = new UidBatteryConsumer.Builder(data, uid); + builder = new UidBatteryConsumer.Builder(data, uid, mMinConsumedPowerThreshold); mUidBatteryConsumerBuilders.put(uid, builder); } return builder; @@ -996,7 +1000,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { final BatteryConsumer.BatteryConsumerData data = BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow, mBatteryConsumerDataLayout); - builder = new UserBatteryConsumer.Builder(data, userId); + builder = new UserBatteryConsumer.Builder(data, userId, mMinConsumedPowerThreshold); mUserBatteryConsumerBuilders.put(userId, builder); } return builder; diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java index b3f4d9874f4e..49d7e8bc5632 100644 --- a/core/java/android/os/BatteryUsageStatsQuery.java +++ b/core/java/android/os/BatteryUsageStatsQuery.java @@ -80,6 +80,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { private final long mMaxStatsAgeMs; private final long mFromTimestamp; private final long mToTimestamp; + private final double mMinConsumedPowerThreshold; private final @BatteryConsumer.PowerComponent int[] mPowerComponents; private BatteryUsageStatsQuery(@NonNull Builder builder) { @@ -87,6 +88,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray() : new int[]{UserHandle.USER_ALL}; mMaxStatsAgeMs = builder.mMaxStatsAgeMs; + mMinConsumedPowerThreshold = builder.mMinConsumedPowerThreshold; mFromTimestamp = builder.mFromTimestamp; mToTimestamp = builder.mToTimestamp; mPowerComponents = builder.mPowerComponents; @@ -137,6 +139,14 @@ public final class BatteryUsageStatsQuery implements Parcelable { } /** + * Returns the minimal power component consumed power threshold. The small power consuming + * components will be reported as zero. + */ + public double getMinConsumedPowerThreshold() { + return mMinConsumedPowerThreshold; + } + + /** * Returns the exclusive lower bound of the stored snapshot timestamps that should be included * in the aggregation. Ignored if {@link #getToTimestamp()} is zero. */ @@ -158,6 +168,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { mUserIds = new int[in.readInt()]; in.readIntArray(mUserIds); mMaxStatsAgeMs = in.readLong(); + mMinConsumedPowerThreshold = in.readDouble(); mFromTimestamp = in.readLong(); mToTimestamp = in.readLong(); mPowerComponents = in.createIntArray(); @@ -169,6 +180,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { dest.writeInt(mUserIds.length); dest.writeIntArray(mUserIds); dest.writeLong(mMaxStatsAgeMs); + dest.writeDouble(mMinConsumedPowerThreshold); dest.writeLong(mFromTimestamp); dest.writeLong(mToTimestamp); dest.writeIntArray(mPowerComponents); @@ -202,6 +214,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS; private long mFromTimestamp; private long mToTimestamp; + private double mMinConsumedPowerThreshold = 0; private @BatteryConsumer.PowerComponent int[] mPowerComponents; /** @@ -301,5 +314,14 @@ public final class BatteryUsageStatsQuery implements Parcelable { mMaxStatsAgeMs = maxStatsAgeMs; return this; } + + /** + * Set the minimal power component consumed power threshold. The small power consuming + * components will be reported as zero. + */ + public Builder setMinConsumedPowerThreshold(double minConsumedPowerThreshold) { + mMinConsumedPowerThreshold = minConsumedPowerThreshold; + return this; + } } } diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java index 5dffa0a0ac34..9e5f5399301c 100644 --- a/core/java/android/os/PowerComponents.java +++ b/core/java/android/os/PowerComponents.java @@ -461,9 +461,11 @@ class PowerComponents { private static final byte POWER_MODEL_UNINITIALIZED = -1; private final BatteryConsumer.BatteryConsumerData mData; + private final double mMinConsumedPowerThreshold; - Builder(BatteryConsumer.BatteryConsumerData data) { + Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold) { mData = data; + mMinConsumedPowerThreshold = minConsumedPowerThreshold; for (BatteryConsumer.Key[] keys : mData.layout.keys) { for (BatteryConsumer.Key key : keys) { if (key.mPowerModelColumnIndex != -1) { @@ -476,6 +478,9 @@ class PowerComponents { @NonNull public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower, int powerModel) { + if (Math.abs(componentPower) < mMinConsumedPowerThreshold) { + componentPower = 0; + } mData.putDouble(key.mPowerColumnIndex, componentPower); if (key.mPowerModelColumnIndex != -1) { mData.putInt(key.mPowerModelColumnIndex, powerModel); @@ -491,6 +496,9 @@ class PowerComponents { */ @NonNull public Builder setConsumedPowerForCustomComponent(int componentId, double componentPower) { + if (Math.abs(componentPower) < mMinConsumedPowerThreshold) { + componentPower = 0; + } final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; if (index < 0 || index >= mData.layout.customPowerComponentCount) { throw new IllegalArgumentException( diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java index 103452d255a7..03a1b6f7fe01 100644 --- a/core/java/android/os/UidBatteryConsumer.java +++ b/core/java/android/os/UidBatteryConsumer.java @@ -207,17 +207,18 @@ public final class UidBatteryConsumer extends BatteryConsumer { private String mPackageWithHighestDrain = PACKAGE_NAME_UNINITIALIZED; private boolean mExcludeFromBatteryUsageStats; - public Builder(BatteryConsumerData data, @NonNull BatteryStats.Uid batteryStatsUid) { - this(data, batteryStatsUid, batteryStatsUid.getUid()); + public Builder(BatteryConsumerData data, @NonNull BatteryStats.Uid batteryStatsUid, + double minConsumedPowerThreshold) { + this(data, batteryStatsUid, batteryStatsUid.getUid(), minConsumedPowerThreshold); } - public Builder(BatteryConsumerData data, int uid) { - this(data, null, uid); + public Builder(BatteryConsumerData data, int uid, double minConsumedPowerThreshold) { + this(data, null, uid, minConsumedPowerThreshold); } private Builder(BatteryConsumerData data, @Nullable BatteryStats.Uid batteryStatsUid, - int uid) { - super(data, CONSUMER_TYPE_UID); + int uid, double minConsumedPowerThreshold) { + super(data, CONSUMER_TYPE_UID, minConsumedPowerThreshold); mBatteryStatsUid = batteryStatsUid; mUid = uid; mIsVirtualUid = mUid == Process.SDK_SANDBOX_VIRTUAL_UID; diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java index 6b4a5cfc836f..a2ff078263ca 100644 --- a/core/java/android/os/UserBatteryConsumer.java +++ b/core/java/android/os/UserBatteryConsumer.java @@ -107,8 +107,8 @@ public class UserBatteryConsumer extends BatteryConsumer { public static final class Builder extends BaseBuilder<Builder> { private List<UidBatteryConsumer.Builder> mUidBatteryConsumers; - Builder(BatteryConsumerData data, int userId) { - super(data, CONSUMER_TYPE_USER); + Builder(BatteryConsumerData data, int userId, double minConsumedPowerThreshold) { + super(data, CONSUMER_TYPE_USER, minConsumedPowerThreshold); data.putLong(COLUMN_INDEX_USER_ID, userId); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1cf41cfaee4c..d66ffcebae5e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5164,7 +5164,6 @@ public final class Settings { public static final Uri DEFAULT_RINGTONE_URI = getUriFor(RINGTONE); /** {@hide} */ - @Readable public static final String RINGTONE_CACHE = "ringtone_cache"; /** {@hide} */ public static final Uri RINGTONE_CACHE_URI = getUriFor(RINGTONE_CACHE); diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 5127f05a03ab..dfa0000cca78 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -1155,9 +1155,15 @@ public final class Choreographer { } private void allocateFrameTimelines(int length) { - mFrameTimelines = new FrameTimeline[length]; - for (int i = 0; i < mFrameTimelines.length; i++) { - mFrameTimelines[i] = new FrameTimeline(); + // Maintain one default frame timeline for API (such as getFrameTimelines and + // getPreferredFrameTimeline) consistency. It should have default data when accessed. + length = Math.max(1, length); + + if (mFrameTimelines == null || mFrameTimelines.length != length) { + mFrameTimelines = new FrameTimeline[length]; + for (int i = 0; i < mFrameTimelines.length; i++) { + mFrameTimelines[i] = new FrameTimeline(); + } } } @@ -1167,12 +1173,7 @@ public final class Choreographer { */ FrameTimeline update( long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) { - // Even if the frame timelines length is 0, continue with allocation for API - // FrameData.getFrameTimelines consistency. The 0 length frame timelines code path - // should only occur when USE_VSYNC property is false. - if (mFrameTimelines.length != vsyncEventData.frameTimelinesLength) { - allocateFrameTimelines(vsyncEventData.frameTimelinesLength); - } + allocateFrameTimelines(vsyncEventData.frameTimelinesLength); mFrameTimeNanos = frameTimeNanos; mPreferredFrameTimelineIndex = vsyncEventData.preferredFrameTimelineIndex; for (int i = 0; i < mFrameTimelines.length; i++) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 963e806e2dad..d680d0432f25 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -50,6 +50,8 @@ import static android.view.ViewRootImplProto.VISIBLE_RECT; import static android.view.ViewRootImplProto.WIDTH; import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES; import static android.view.ViewRootImplProto.WIN_FRAME; +import static android.view.ViewRootRefreshRateController.RefreshRatePref.LOWER; +import static android.view.ViewRootRefreshRateController.RefreshRatePref.RESTORE; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; @@ -96,6 +98,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; import android.annotation.UiContext; +import android.annotation.UiThread; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.ICompatCameraControlCallback; @@ -240,6 +243,7 @@ import java.util.OptionalInt; import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; /** @@ -423,6 +427,74 @@ public final class ViewRootImpl implements ViewParent, } /** + * Used to notify if the user is typing or not. + * @hide + */ + public interface TypingHintNotifier { + /** + * Called when the typing hint is changed. This would be invoked by the + * {@link android.view.inputmethod.RemoteInputConnectionImpl} + * to hint if the user is typing when the it is {@link #isActive() active}. + * + * This can be only happened on the UI thread. The behavior won't be guaranteed if + * invoking this on a non-UI thread. + * + * @param isTyping {@code true} if the user is typing. + */ + @UiThread + void onTypingHintChanged(boolean isTyping); + + /** + * Indicates whether the notifier is currently in active state or not. + * + * @see #deactivate() + */ + boolean isActive(); + + /** + * Deactivate the notifier when no longer in use. Mostly invoked when finishing the typing. + */ + void deactivate(); + } + + /** + * The {@link TypingHintNotifier} implementation used to handle + * the refresh rate preference when the typing state is changed. + */ + private static class TypingHintNotifierImpl implements TypingHintNotifier { + + private final AtomicReference<TypingHintNotifier> mActiveNotifier; + + @NonNull + private final ViewRootRefreshRateController mController; + + TypingHintNotifierImpl(@NonNull AtomicReference<TypingHintNotifier> notifier, + @NonNull ViewRootRefreshRateController controller) { + mController = controller; + mActiveNotifier = notifier; + } + + @Override + public void onTypingHintChanged(boolean isTyping) { + if (!isActive()) { + // No-op when the listener was deactivated. + return; + } + mController.updateRefreshRatePreference(isTyping ? LOWER : RESTORE); + } + + @Override + public boolean isActive() { + return mActiveNotifier.get() == this; + } + + @Override + public void deactivate() { + mActiveNotifier.compareAndSet(this, null); + } + } + + /** * Callback used to notify corresponding activity about camera compat control changes, override * configuration change and make sure that all resources are set correctly before updating the * ViewRootImpl's internal state. @@ -430,6 +502,32 @@ public final class ViewRootImpl implements ViewParent, private ActivityConfigCallback mActivityConfigCallback; /** + * The current active {@link TypingHintNotifier} to handle + * typing hint change operations. + */ + private final AtomicReference<TypingHintNotifier> mActiveTypingHintNotifier = + new AtomicReference<>(null); + + /** + * Create a {@link TypingHintNotifier} if the client support variable + * refresh rate for typing. The {@link TypingHintNotifier} is created + * and mapped to a new active input connection each time. + * + * @hide + */ + @Nullable + public TypingHintNotifier createTypingHintNotifierIfSupported() { + if (mRefreshRateController == null) { + return null; + } + final TypingHintNotifier newNotifier = new TypingHintNotifierImpl(mActiveTypingHintNotifier, + mRefreshRateController); + mActiveTypingHintNotifier.set(newNotifier); + + return newNotifier; + } + + /** * Used when configuration change first updates the config of corresponding activity. * In that case we receive a call back from {@link ActivityThread} and this flag is used to * preserve the initial value. @@ -858,6 +956,8 @@ public final class ViewRootImpl implements ViewParent, private final InsetsController mInsetsController; private final ImeFocusController mImeFocusController; + private ViewRootRefreshRateController mRefreshRateController; + private boolean mIsSurfaceOpaque; private final BackgroundBlurDrawable.Aggregator mBlurRegionAggregator = @@ -1048,6 +1148,13 @@ public final class ViewRootImpl implements ViewParent, mViewConfiguration, mContext.getSystemService(InputMethodManager.class)); + // Whether the variable refresh rate for typing is supported. + boolean useVariableRefreshRateWhenTyping = context.getResources().getBoolean( + R.bool.config_variableRefreshRateTypingSupported); + if (useVariableRefreshRateWhenTyping) { + mRefreshRateController = new ViewRootRefreshRateController(this); + } + mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled(); mIsStylusPointerIconEnabled = InputSettings.isStylusPointerIconEnabled(mContext); @@ -2089,6 +2196,10 @@ public final class ViewRootImpl implements ViewParent, if (!mIsInTraversal) { scheduleTraversals(); } + + if (!mInsetsController.getState().isSourceOrDefaultVisible(ID_IME, Type.ime())) { + notifyLeaveTypingEvent(); + } } @Override @@ -6850,6 +6961,17 @@ public final class ViewRootImpl implements ViewParent, } /** + * Restores the refresh rate after leaving typing, the leaving typing cases like + * the IME insets is invisible or the user interacts the screen outside keyboard. + */ + @UiThread + private void notifyLeaveTypingEvent() { + if (mRefreshRateController != null && mActiveTypingHintNotifier.get() != null) { + mRefreshRateController.updateRefreshRatePreference(RESTORE); + } + } + + /** * Delivers post-ime input events to the view hierarchy. */ final class ViewPostImeInputStage extends InputStage { @@ -7066,6 +7188,10 @@ public final class ViewRootImpl implements ViewParent, mLastClickToolType = event.getToolType(event.getActionIndex()); } + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + notifyLeaveTypingEvent(); + } + mAttachInfo.mUnbufferedDispatchRequested = false; mAttachInfo.mHandlingPointerEvent = true; // If the event was fully handled by the handwriting initiator, then don't dispatch it diff --git a/core/java/android/view/ViewRootRefreshRateController.java b/core/java/android/view/ViewRootRefreshRateController.java new file mode 100644 index 000000000000..cb9a81c03479 --- /dev/null +++ b/core/java/android/view/ViewRootRefreshRateController.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static android.os.Trace.TRACE_TAG_VIEW; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Trace; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Controller to request refresh rate preference operations to the {@link ViewRootImpl}. + * + * @hide + */ +public class ViewRootRefreshRateController { + + private static final String TAG = "VRRefreshRateController"; + + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private static final float TARGET_REFRESH_RATE_UPPER_BOUND = 60f; + + @NonNull + private final ViewRootImpl mViewRootImpl; + + private final RefreshRateParams mRateParams; + + private final boolean mHasPreferredRefreshRate; + + private int mRefreshRatePref = RefreshRatePref.NONE; + + private boolean mMaxRefreshRateOverride = false; + + @IntDef(value = { + RefreshRatePref.NONE, + RefreshRatePref.LOWER, + RefreshRatePref.RESTORE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RefreshRatePref { + /** + * Indicates that no refresh rate preference. + */ + int NONE = 0; + + /** + * Indicates that apply the lower refresh rate. + */ + int LOWER = 1; + + /** + * Indicates that restore to previous refresh rate. + */ + int RESTORE = 2; + } + + public ViewRootRefreshRateController(@NonNull ViewRootImpl viewRoot) { + mViewRootImpl = viewRoot; + mRateParams = new RefreshRateParams(getLowerSupportedRefreshRate()); + mHasPreferredRefreshRate = hasPreferredRefreshRate(); + if (mHasPreferredRefreshRate && DEBUG) { + Log.d(TAG, "App has preferred refresh rate. name:" + viewRoot); + } + } + + /** + * Updates the preference to {@link ViewRootRefreshRateController#mRefreshRatePref}, + * and check if it's needed to update the preferred refresh rate on demand. Like if the + * user is typing, try to apply the {@link RefreshRateParams#mTargetRefreshRate}. + * + * @param refreshRatePref to indicate the refresh rate preference + */ + public void updateRefreshRatePreference(@RefreshRatePref int refreshRatePref) { + mRefreshRatePref = refreshRatePref; + doRefreshRateCheck(); + } + + private void doRefreshRateCheck() { + if (mRefreshRatePref == RefreshRatePref.NONE) { + return; + } + if (mHasPreferredRefreshRate) { + return; + } + if (DEBUG) { + Log.d(TAG, "mMaxRefreshRateOverride:" + mMaxRefreshRateOverride + + ", mRefreshRatePref:" + refreshRatePrefToString(mRefreshRatePref)); + } + + switch (mRefreshRatePref) { + case RefreshRatePref.LOWER : + if (!mMaxRefreshRateOverride) { + // Save previous preferred rate before update + mRateParams.savePreviousRefreshRateParams(mViewRootImpl.mWindowAttributes); + updateMaxRefreshRate(); + } else if (mViewRootImpl.mDisplay.getRefreshRate() + > mRateParams.mTargetRefreshRate) { + // Boosted, try to update again. + updateMaxRefreshRate(); + } + break; + case RefreshRatePref.RESTORE : + resetRefreshRate(); + break; + default : + throw new RuntimeException("Unexpected value: " + mRefreshRatePref); + } + } + + private void updateMaxRefreshRate() { + Trace.traceBegin(TRACE_TAG_VIEW, "VRRC.updateMaxRefreshRate"); + WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes; + params.preferredMaxDisplayRefreshRate = mRateParams.mTargetRefreshRate; + mViewRootImpl.setLayoutParams(params, false); + mMaxRefreshRateOverride = true; + Trace.instant(TRACE_TAG_VIEW, "VRRC update preferredMax=" + + mRateParams.mTargetRefreshRate); + Trace.traceEnd(TRACE_TAG_VIEW); + if (DEBUG) { + Log.d(TAG, "update max refresh rate to: " + params.preferredMaxDisplayRefreshRate); + } + } + + private void resetRefreshRate() { + if (!mMaxRefreshRateOverride) { + return; + } + Trace.traceBegin(TRACE_TAG_VIEW, "VRRC.resetRefreshRate"); + WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes; + params.preferredMaxDisplayRefreshRate = mRateParams.mPreviousPreferredMaxRefreshRate; + mViewRootImpl.setLayoutParams(params, false); + mMaxRefreshRateOverride = false; + Trace.instant(TRACE_TAG_VIEW, "VRRC restore previous=" + + mRateParams.mPreviousPreferredMaxRefreshRate); + Trace.traceEnd(TRACE_TAG_VIEW); + if (DEBUG) { + Log.d(TAG, "reset max refresh rate to: " + params.preferredMaxDisplayRefreshRate); + } + } + + private boolean hasPreferredRefreshRate() { + WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes; + return params.preferredRefreshRate > 0 + || params.preferredMaxDisplayRefreshRate > 0 + || params.preferredMinDisplayRefreshRate > 0 + || params.preferredDisplayModeId > 0; + } + + private float getLowerSupportedRefreshRate() { + final Display display = mViewRootImpl.mDisplay; + final Display.Mode defaultMode = display.getDefaultMode(); + float targetRefreshRate = defaultMode.getRefreshRate(); + for (Display.Mode mode : display.getSupportedModes()) { + if (mode.getRefreshRate() < targetRefreshRate) { + targetRefreshRate = mode.getRefreshRate(); + } + } + if (targetRefreshRate < TARGET_REFRESH_RATE_UPPER_BOUND) { + targetRefreshRate = TARGET_REFRESH_RATE_UPPER_BOUND; + } + return targetRefreshRate; + } + + private static String refreshRatePrefToString(@RefreshRatePref int pref) { + switch (pref) { + case RefreshRatePref.NONE: + return "NONE"; + case RefreshRatePref.LOWER: + return "LOWER"; + case RefreshRatePref.RESTORE: + return "RESTORE"; + default: + return "Unknown pref=" + pref; + } + } + + /** + * A class for recording refresh rate parameters of the target view, including the target + * refresh rate we want to apply when entering particular states, and the original preferred + * refresh rate for restoring when leaving the state. + */ + private static class RefreshRateParams { + float mTargetRefreshRate; + + float mPreviousPreferredMaxRefreshRate = 0; + + RefreshRateParams(float targetRefreshRate) { + mTargetRefreshRate = targetRefreshRate; + if (DEBUG) { + Log.d(TAG, "The target rate: " + targetRefreshRate); + } + } + void savePreviousRefreshRateParams(WindowManager.LayoutParams param) { + mPreviousPreferredMaxRefreshRate = param.preferredMaxDisplayRefreshRate; + if (DEBUG) { + Log.d(TAG, "Save previous params, preferred: " + param.preferredRefreshRate + + ", Max: " + param.preferredMaxDisplayRefreshRate); + } + } + } +} diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java index e9d7b9b25d91..364adc77f7d3 100644 --- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java @@ -28,6 +28,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UiThread; import android.graphics.RectF; import android.os.Bundle; import android.os.CancellationSignal; @@ -182,6 +183,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { private CancellationSignalBeamer.Receiver mBeamer; + private ViewRootImpl.TypingHintNotifier mTypingHintNotifier; + RemoteInputConnectionImpl(@NonNull Looper looper, @NonNull InputConnection inputConnection, @NonNull InputMethodManager inputMethodManager, @Nullable View servedView) { @@ -190,6 +193,12 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { mH = new Handler(mLooper); mParentInputMethodManager = inputMethodManager; mServedView = new WeakReference<>(servedView); + if (servedView != null) { + final ViewRootImpl viewRoot = servedView.getViewRootImpl(); + if (viewRoot != null) { + mTypingHintNotifier = viewRoot.createTypingHintNotifierIfSupported(); + } + } } /** @@ -364,6 +373,12 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; } dispatch(() -> { + notifyTypingHint(false /* isTyping */); + // Deactivate the notifier when finishing typing. + if (mTypingHintNotifier != null) { + mTypingHintNotifier.deactivate(); + } + // Note that we do not need to worry about race condition here, because 1) mFinished is // updated only inside this block, and 2) the code here is running on a Handler hence we // assume multiple closeConnection() tasks will not be handled at the same time. @@ -628,6 +643,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; } ic.commitText(text, newCursorPosition); + notifyTypingHint(true /* isTyping */); }); } @@ -783,6 +799,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; } ic.setComposingText(text, newCursorPosition); + notifyTypingHint(true /* isTyping */); }); } @@ -910,6 +927,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; } ic.deleteSurroundingText(beforeLength, afterLength); + notifyTypingHint(true /* isTyping */); }); } @@ -1473,4 +1491,16 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { private static boolean useImeTracing() { return ImeTracing.getInstance().isEnabled(); } + + /** + * Dispatch the typing hint to {@link ViewRootImpl.TypingHintNotifier}. + * The input connection indicates that the user is typing when {@link #commitText} or + * {@link #setComposingText)} and the user finish typing when {@link #deactivate()}. + */ + @UiThread + private void notifyTypingHint(boolean isTyping) { + if (mTypingHintNotifier != null) { + mTypingHintNotifier.onTypingHintChanged(isTyping); + } + } } diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml index d1a4935633cb..37d2fa0540f0 100644 --- a/core/res/res/layout/autofill_fill_dialog.xml +++ b/core/res/res/layout/autofill_fill_dialog.xml @@ -85,14 +85,18 @@ android:layout_marginStart="24dp" android:layout_marginEnd="24dp" android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton" - android:orientation="horizontal"> + android:orientation="horizontal" + android:gravity="center_vertical"> <Button android:id="@+id/autofill_dialog_no" android:layout_width="wrap_content" - android:layout_height="36dp" - android:layout_marginTop="6dp" - android:layout_marginBottom="6dp" + android:layout_height="40dp" + android:paddingStart="12dp" + android:paddingEnd="12dp" + android:paddingTop="0dp" + android:paddingBottom="0dp" + android:minWidth="0dp" style="?android:attr/borderlessButtonStyle" android:text="@string/autofill_save_no"> </Button> @@ -107,9 +111,8 @@ <Button android:id="@+id/autofill_dialog_yes" android:layout_width="wrap_content" - android:layout_height="36dp" - android:layout_marginTop="6dp" - android:layout_marginBottom="6dp" + android:layout_height="40dp" + android:minWidth="0dp" style="@style/AutofillHalfSheetTonalButton" android:text="@string/autofill_save_yes" android:visibility="gone" > diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml index 85529d6a4b3b..bed19a87eb16 100644 --- a/core/res/res/layout/autofill_save.xml +++ b/core/res/res/layout/autofill_save.xml @@ -72,14 +72,18 @@ android:layout_marginTop="32dp" android:layout_marginBottom="18dp" android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton" - android:orientation="horizontal"> + android:orientation="horizontal" + android:gravity="center_vertical"> <Button android:id="@+id/autofill_save_no" android:layout_width="wrap_content" - android:layout_height="36dp" - android:layout_marginTop="6dp" - android:layout_marginBottom="6dp" + android:layout_height="40dp" + android:paddingStart="12dp" + android:paddingEnd="12dp" + android:paddingTop="0dp" + android:paddingBottom="0dp" + android:minWidth="0dp" style="?android:attr/borderlessButtonStyle" android:text="@string/autofill_save_no"> </Button> @@ -94,9 +98,8 @@ <Button android:id="@+id/autofill_save_yes" android:layout_width="wrap_content" - android:layout_height="36dp" - android:layout_marginTop="6dp" - android:layout_marginBottom="6dp" + android:layout_height="40dp" + android:minWidth="0dp" style="@style/AutofillHalfSheetTonalButton" android:text="@string/autofill_save_yes"> </Button> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 4184e7914a0e..478b01cfa385 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5377,6 +5377,13 @@ of known compatibility issues. --> <string-array name="config_highRefreshRateBlacklist"></string-array> + <!-- The list of packages to automatically opt in to refresh rate suppressing by small area + detection. Format of this array should be packageName:threshold and threshold value should + be between 0 to 1--> + <string-array name="config_smallAreaDetectionAllowlist" translatable="false"> + <!-- Add packages:threshold here --> + </string-array> + <!-- The list of packages to force slowJpegMode for Apps using Camera API1 --> <string-array name="config_forceSlowJpegModeList" translatable="false"> <!-- Add packages here --> @@ -6089,6 +6096,8 @@ <!-- Default value for Settings.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED --> <bool name="config_searchPressHoldNavHandleEnabledDefault">true</bool> + <!-- Default value for Settings.ASSIST_LONG_PRESS_HOME_ENABLED for search overlay --> + <bool name="config_searchLongPressHomeEnabledDefault">true</bool> <!-- The maximum byte size of the information contained in the bundle of HotwordDetectedResult. --> @@ -6562,6 +6571,9 @@ device. --> <bool name="config_enableAppCloningBuildingBlocks">true</bool> + <!-- Whether the variable refresh rate when typing feature is enabled for the device. --> + <bool name="config_variableRefreshRateTypingSupported">false</bool> + <!-- Enables or disables support for repair mode. The feature creates a secure environment to protect the user's privacy when the device is being repaired. Off by default, since OEMs may have had a similar feature on their devices. --> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 79964b3d91f7..d238a130e821 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1500,12 +1500,8 @@ please see styles_device_defaults.xml. <item name="fontFamily">google-sans-text-medium</item> <item name="textStyle">normal</item> <item name="textAllCaps">false</item> - <item name="layout_marginTop">6dp</item> - <item name="layout_marginBottom">6dp</item> - <item name="paddingStart">16dp</item> - <item name="paddingEnd">16dp</item> - <item name="paddingTop">8dp</item> - <item name="paddingBottom">8dp</item> + <item name="paddingStart">24dp</item> + <item name="paddingEnd">24dp</item> </style> <!-- @hide Tonal button for Autofill half screen dialog --> <style name="AutofillHalfSheetTonalButton" parent="AutofillHalfSheetButton"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 81fbc38c493d..644597c190dc 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4239,6 +4239,8 @@ <java-symbol type="array" name="config_highRefreshRateBlacklist" /> <java-symbol type="array" name="config_forceSlowJpegModeList" /> + <java-symbol type="array" name="config_smallAreaDetectionAllowlist" /> + <java-symbol type="layout" name="chooser_dialog" /> <java-symbol type="layout" name="chooser_dialog_item" /> <java-symbol type="drawable" name="chooser_dialog_background" /> @@ -4866,6 +4868,7 @@ <java-symbol type="bool" name="config_assistTouchGestureEnabledDefault" /> <java-symbol type="bool" name="config_searchPressHoldNavHandleEnabledDefault" /> + <java-symbol type="bool" name="config_searchLongPressHomeEnabledDefault" /> <java-symbol type="integer" name="config_hotwordDetectedResultMaxBundleSize" /> @@ -4942,6 +4945,8 @@ <java-symbol type="bool" name="config_repairModeSupported" /> + <java-symbol type="bool" name="config_variableRefreshRateTypingSupported" /> + <java-symbol type="string" name="config_devicePolicyManagementUpdater" /> <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" /> diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java index 0676f899674d..aaaa3c7740c5 100644 --- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java +++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java @@ -241,7 +241,8 @@ public class BatteryUsageStatsPulledTest { final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(new String[]{"CustomConsumer1", "CustomConsumer2"}, /* includePowerModels */ true, - /* includeProcessStats */true) + /* includeProcessStats */ true, + /* minConsumedPowerThreshold */ 0) .setDischargePercentage(20) .setDischargedPowerRange(1000, 2000) .setDischargeDurationMs(1234) @@ -325,7 +326,7 @@ public class BatteryUsageStatsPulledTest { @Test public void testLargeAtomTruncated() { final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[0], true, false); + new BatteryUsageStats.Builder(new String[0], true, false, 0); // If not truncated, this BatteryUsageStats object would generate a proto buffer // significantly larger than 50 Kb for (int i = 0; i < 3000; i++) { diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index a358c4f6f7e9..57f092001383 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1389,6 +1389,15 @@ android:resource="@xml/accessibility_shortcut_test_activity"/> </activity> + <activity android:name="android.view.ViewRefreshRateTestActivity" + android:label="ViewRefreshRateTestActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" /> + </intent-filter> + </activity> + <!-- Activity-level metadata --> <meta-data android:name="com.android.frameworks.coretests.isApp" android:value="true" /> <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" /> diff --git a/core/tests/coretests/res/layout/activity_refresh_rate_test.xml b/core/tests/coretests/res/layout/activity_refresh_rate_test.xml new file mode 100644 index 000000000000..ad57fc1dd338 --- /dev/null +++ b/core/tests/coretests/res/layout/activity_refresh_rate_test.xml @@ -0,0 +1,22 @@ +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/layout" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> +</LinearLayout>
\ No newline at end of file diff --git a/core/tests/coretests/src/android/view/ViewRefreshRateTestActivity.java b/core/tests/coretests/src/android/view/ViewRefreshRateTestActivity.java new file mode 100644 index 000000000000..2b11851e8674 --- /dev/null +++ b/core/tests/coretests/src/android/view/ViewRefreshRateTestActivity.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.app.Activity; +import android.os.Bundle; + +import com.android.frameworks.coretests.R; + +public class ViewRefreshRateTestActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_refresh_rate_test); + } +} diff --git a/core/tests/coretests/src/android/view/ViewRootRefreshRateControllerTest.java b/core/tests/coretests/src/android/view/ViewRootRefreshRateControllerTest.java new file mode 100644 index 000000000000..d278bc3fffb5 --- /dev/null +++ b/core/tests/coretests/src/android/view/ViewRootRefreshRateControllerTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static android.view.ViewRootRefreshRateController.RefreshRatePref.LOWER; +import static android.view.ViewRootRefreshRateController.RefreshRatePref.RESTORE; + +import static junit.framework.Assert.assertEquals; + +import static org.junit.Assume.assumeTrue; + +import android.app.Instrumentation; +import android.platform.test.annotations.Presubmit; +import android.widget.EditText; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; + +import com.android.frameworks.coretests.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ViewRootRefreshRateControllerTest { + + private static final float TARGET_REFRESH_RATE_UPPER_BOUND = 60f; + + private boolean mUseVariableRefreshRateWhenTyping; + + private ViewRootRefreshRateController mRefreshRateController; + + @Rule + public ActivityTestRule<ViewRefreshRateTestActivity> mActivityRule = + new ActivityTestRule<>(ViewRefreshRateTestActivity.class); + + private ViewRefreshRateTestActivity mActivity; + + private float mLowestSupportRefreshRate; + + private Instrumentation mInstrumentation; + + @Before + public void setUp() throws Exception { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mActivity = mActivityRule.getActivity(); + mLowestSupportRefreshRate = getLowerSupportedRefreshRate(); + mUseVariableRefreshRateWhenTyping = mInstrumentation.getContext().getResources() + .getBoolean(com.android.internal.R.bool.config_variableRefreshRateTypingSupported); + } + + @Test + public void testUpdateRefreshRatePreference_shouldLowerThenRestore() throws Throwable { + // Ignored if the feature is not enabled. + assumeTrue(mUseVariableRefreshRateWhenTyping); + + final ViewGroup viewGroup = mActivity.findViewById(R.id.layout); + final EditText editText = new EditText(mActivity); + + mActivityRule.runOnUiThread(() -> viewGroup.addView(editText)); + mInstrumentation.waitForIdleSync(); + + final ViewRootImpl viewRootImpl = editText.getViewRootImpl(); + mRefreshRateController = new ViewRootRefreshRateController(viewRootImpl); + final float originalPreferredMaxDisplayRefreshRate = + viewRootImpl.mWindowAttributes.preferredMaxDisplayRefreshRate; + + mRefreshRateController.updateRefreshRatePreference(LOWER); + + // Update to lower rate. + assertEquals(viewRootImpl.mWindowAttributes.preferredMaxDisplayRefreshRate, + mLowestSupportRefreshRate); + + mRefreshRateController.updateRefreshRatePreference(RESTORE); + + // Restore to previous preferred rate. + assertEquals(viewRootImpl.mWindowAttributes.preferredMaxDisplayRefreshRate, + originalPreferredMaxDisplayRefreshRate); + } + + private float getLowerSupportedRefreshRate() { + final Display display = mActivity.getDisplay(); + final Display.Mode defaultMode = display.getDefaultMode(); + float targetRefreshRate = defaultMode.getRefreshRate(); + for (Display.Mode mode : display.getSupportedModes()) { + if (mode.getRefreshRate() < targetRefreshRate) { + targetRefreshRate = mode.getRefreshRate(); + } + } + if (targetRefreshRate < TARGET_REFRESH_RATE_UPPER_BOUND) { + targetRefreshRate = TARGET_REFRESH_RATE_UPPER_BOUND; + } + return targetRefreshRate; + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 896fe61b5611..d894487fafb6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -651,7 +651,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { if (minDimensionsPair == null) { return splitAttributes; } - final FoldingFeature foldingFeature = getFoldingFeature(taskProperties); + final FoldingFeature foldingFeature = getFoldingFeatureForHingeType( + taskProperties, splitAttributes); final Configuration taskConfiguration = taskProperties.getConfiguration(); final Rect primaryBounds = getPrimaryBounds(taskConfiguration, splitAttributes, foldingFeature); @@ -726,7 +727,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { Rect getRelBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties, @NonNull SplitAttributes splitAttributes) { final Configuration taskConfiguration = taskProperties.getConfiguration(); - final FoldingFeature foldingFeature = getFoldingFeature(taskProperties); + final FoldingFeature foldingFeature = getFoldingFeatureForHingeType( + taskProperties, splitAttributes); if (!shouldShowSplit(splitAttributes)) { return new Rect(); } @@ -933,6 +935,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } @Nullable + private FoldingFeature getFoldingFeatureForHingeType( + @NonNull TaskProperties taskProperties, + @NonNull SplitAttributes splitAttributes) { + SplitType splitType = splitAttributes.getSplitType(); + if (!(splitType instanceof HingeSplitType)) { + return null; + } + return getFoldingFeature(taskProperties); + } + + @Nullable @VisibleForTesting FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) { final int displayId = taskProperties.getDisplayId(); diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 43acdd51b07b..ff369c8a5eee 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -40,7 +40,6 @@ import android.os.Build; import android.os.Environment; import android.os.FileUtils; import android.os.IBinder; -import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; @@ -934,18 +933,6 @@ public class RingtoneManager { Settings.System.putStringForUser(resolver, setting, ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId()); - - // Stream selected ringtone into cache so it's available for playback - // when CE storage is still locked - if (ringtoneUri != null) { - final Uri cacheUri = getCacheForType(type, context.getUserId()); - try (InputStream in = openRingtone(context, ringtoneUri); - OutputStream out = resolver.openOutputStream(cacheUri, "wt")) { - FileUtils.copy(in, out); - } catch (IOException e) { - Log.w(TAG, "Failed to cache ringtone: " + e); - } - } } private static boolean isInternalRingtoneUri(Uri uri) { @@ -1041,28 +1028,6 @@ public class RingtoneManager { } } - /** - * Try opening the given ringtone locally first, but failover to - * {@link IRingtonePlayer} if we can't access it directly. Typically happens - * when process doesn't hold - * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}. - */ - private static InputStream openRingtone(Context context, Uri uri) throws IOException { - final ContentResolver resolver = context.getContentResolver(); - try { - return resolver.openInputStream(uri); - } catch (SecurityException | IOException e) { - Log.w(TAG, "Failed to open directly; attempting failover: " + e); - final IRingtonePlayer player = context.getSystemService(AudioManager.class) - .getRingtonePlayer(); - try { - return new ParcelFileDescriptor.AutoCloseInputStream(player.openRingtone(uri)); - } catch (Exception e2) { - throw new IOException(e2); - } - } - } - private static String getSettingForType(int type) { if ((type & TYPE_RINGTONE) != 0) { return Settings.System.RINGTONE; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java index 15446b6cc39b..3790490ab753 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @@ -34,7 +34,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Process; import android.os.UserManager; -import android.provider.Settings; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; @@ -53,11 +52,10 @@ public class InstallStart extends Activity { private static final String DOWNLOADS_AUTHORITY = "downloads"; - private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = 1; - private static final int DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER = 2; private PackageManager mPackageManager; private UserManager mUserManager; private boolean mAbortInstall = false; + private boolean mShouldFinish = true; private final boolean mLocalLOGV = false; @@ -130,7 +128,7 @@ public class InstallStart extends Activity { mAbortInstall = true; } - checkDevicePolicyRestriction(); + checkDevicePolicyRestrictions(); final String installerPackageNameFromIntent = getIntent().getStringExtra( Intent.EXTRA_INSTALLER_PACKAGE_NAME); @@ -149,7 +147,9 @@ public class InstallStart extends Activity { if (mAbortInstall) { setResult(RESULT_CANCELED); - finish(); + if (mShouldFinish) { + finish(); + } return; } @@ -291,58 +291,52 @@ public class InstallStart extends Activity { return originatingUid == installerUid; } - private void checkDevicePolicyRestriction() { - // Check for install apps user restriction first. - final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource( - UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle()); - if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) { - if (mLocalLOGV) Log.i(TAG, "install not allowed: " + UserManager.DISALLOW_INSTALL_APPS); - mAbortInstall = true; - showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER); - return; - } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) { - if (mLocalLOGV) { - Log.i(TAG, "install not allowed by admin; showing " - + Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS); + private void checkDevicePolicyRestrictions() { + final String[] restrictions = new String[] { + UserManager.DISALLOW_INSTALL_APPS, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY + }; + + final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class); + for (String restriction : restrictions) { + if (!mUserManager.hasUserRestrictionForUser(restriction, Process.myUserHandle())) { + continue; } - mAbortInstall = true; - startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS)); - return; - } - final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource( - UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle()); - final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource( - UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle()); - final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM - & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource); - if (systemRestriction != 0) { - if (mLocalLOGV) Log.i(TAG, "Showing DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER"); - mAbortInstall = true; - showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER); - } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) { - mAbortInstall = true; - startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); - } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) { mAbortInstall = true; - startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY); + + // If the given restriction is set by an admin, display information about the + // admin enforcing the restriction for the affected user. If not enforced by the admin, + // show the system dialog. + final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction); + if (showAdminSupportDetailsIntent != null) { + if (mLocalLOGV) Log.i(TAG, "starting " + showAdminSupportDetailsIntent); + startActivity(showAdminSupportDetailsIntent); + } else { + if (mLocalLOGV) Log.i(TAG, "Restriction set by system: " + restriction); + mShouldFinish = false; + showDialogInner(restriction); + } + break; } } /** - * Replace any dialog shown by the dialog with the one for the given {@link #createDialog(int)}. + * Replace any dialog shown by the dialog with the one for the given + * {@link #createDialog(String)}. * - * @param id The dialog type to add + * @param restriction The restriction to create the dialog for */ - private void showDialogInner(int id) { - if (mLocalLOGV) Log.i(TAG, "showDialogInner(" + id + ")"); + private void showDialogInner(String restriction) { + if (mLocalLOGV) Log.i(TAG, "showDialogInner(" + restriction + ")"); DialogFragment currentDialog = (DialogFragment) getFragmentManager().findFragmentByTag("dialog"); if (currentDialog != null) { currentDialog.dismissAllowingStateLoss(); } - DialogFragment newDialog = createDialog(id); + DialogFragment newDialog = createDialog(restriction); if (newDialog != null) { getFragmentManager().beginTransaction() .add(newDialog, "dialog").commitAllowingStateLoss(); @@ -352,35 +346,20 @@ public class InstallStart extends Activity { /** * Create a new dialog. * - * @param id The id of the dialog (determines dialog type) - * + * @param restriction The restriction to create the dialog for * @return The dialog */ - private DialogFragment createDialog(int id) { - if (mLocalLOGV) Log.i(TAG, "createDialog(" + id + ")"); - switch (id) { - case DLG_INSTALL_APPS_RESTRICTED_FOR_USER: + private DialogFragment createDialog(String restriction) { + if (mLocalLOGV) Log.i(TAG, "createDialog(" + restriction + ")"); + switch (restriction) { + case UserManager.DISALLOW_INSTALL_APPS: return PackageUtil.SimpleErrorDialog.newInstance( R.string.install_apps_user_restriction_dlg_text); - case DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER: + case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES: + case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY: return PackageUtil.SimpleErrorDialog.newInstance( R.string.unknown_apps_user_restriction_dlg_text); } return null; } - - private void startAdminSupportDetailsActivity(String restriction) { - if (mLocalLOGV) Log.i(TAG, "startAdminSupportDetailsActivity(): " + restriction); - - // If the given restriction is set by an admin, display information about the - // admin enforcing the restriction for the affected user. - final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class); - final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction); - if (showAdminSupportDetailsIntent != null) { - if (mLocalLOGV) Log.i(TAG, "starting " + showAdminSupportDetailsIntent); - startActivity(showAdminSupportDetailsIntent); - } else { - if (mLocalLOGV) Log.w(TAG, "not intent for " + restriction); - } - } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt index 67f4418b7e4c..0a33b9bc6bc7 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt @@ -18,6 +18,7 @@ package com.android.settingslib.spa.widget.scaffold import androidx.activity.compose.BackHandler import androidx.appcompat.R +import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.RowScope @@ -96,7 +97,8 @@ fun SearchScaffold( Modifier .padding(paddingValues.horizontalValues()) .padding(top = paddingValues.calculateTopPadding()) - .fillMaxSize(), + .focusable() + .fillMaxSize() ) { content( bottomPadding = paddingValues.calculateBottomPadding(), diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 765edd72cfbd..e45913c1eb21 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -58,6 +58,7 @@ import android.compat.annotation.EnabledSince; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; @@ -76,6 +77,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.hardware.camera2.utils.ArrayUtils; import android.media.AudioManager; +import android.media.IRingtonePlayer; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -110,6 +112,7 @@ import android.provider.settings.validators.Validator; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -129,7 +132,10 @@ import libcore.util.HexEncoding; import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.nio.ByteBuffer; import java.security.InvalidKeyException; @@ -841,29 +847,68 @@ public class SettingsProvider extends ContentProvider { uri = ContentProvider.getUriWithoutUserId(uri); final String cacheRingtoneSetting; - final String cacheName; if (Settings.System.RINGTONE_CACHE_URI.equals(uri)) { cacheRingtoneSetting = Settings.System.RINGTONE; - cacheName = Settings.System.RINGTONE_CACHE; } else if (Settings.System.NOTIFICATION_SOUND_CACHE_URI.equals(uri)) { cacheRingtoneSetting = Settings.System.NOTIFICATION_SOUND; - cacheName = Settings.System.NOTIFICATION_SOUND_CACHE; } else if (Settings.System.ALARM_ALERT_CACHE_URI.equals(uri)) { cacheRingtoneSetting = Settings.System.ALARM_ALERT; - cacheName = Settings.System.ALARM_ALERT_CACHE; } else { throw new FileNotFoundException("Direct file access no longer supported; " + "ringtone playback is available through android.media.Ringtone"); } + final File cacheFile = getCacheFile(cacheRingtoneSetting, userId); + return ParcelFileDescriptor.open(cacheFile, ParcelFileDescriptor.parseMode(mode)); + } + + @Nullable + private String getCacheName(String setting) { + if (Settings.System.RINGTONE.equals(setting)) { + return Settings.System.RINGTONE_CACHE; + } else if (Settings.System.NOTIFICATION_SOUND.equals(setting)) { + return Settings.System.NOTIFICATION_SOUND_CACHE; + } else if (Settings.System.ALARM_ALERT.equals(setting)) { + return Settings.System.ALARM_ALERT_CACHE; + } + return null; + } + + @Nullable + private File getCacheFile(String setting, int userId) { int actualCacheOwner; // Redirect cache to parent if ringtone setting is owned by profile parent synchronized (mLock) { - actualCacheOwner = resolveOwningUserIdForSystemSettingLocked(userId, - cacheRingtoneSetting); + actualCacheOwner = resolveOwningUserIdForSystemSettingLocked(userId, setting); + } + final String cacheName = getCacheName(setting); + if (cacheName == null) { + return null; } final File cacheFile = new File(getRingtoneCacheDir(actualCacheOwner), cacheName); - return ParcelFileDescriptor.open(cacheFile, ParcelFileDescriptor.parseMode(mode)); + return cacheFile; + } + + + /** + * Try opening the given ringtone locally first, but failover to + * {@link IRingtonePlayer} if we can't access it directly. Typically, happens + * when process doesn't hold {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}. + */ + private static InputStream openRingtone(Context context, Uri uri) throws IOException { + final ContentResolver resolver = context.getContentResolver(); + try { + return resolver.openInputStream(uri); + } catch (SecurityException | IOException e) { + Log.w(LOG_TAG, "Failed to open directly; attempting failover: " + e); + final IRingtonePlayer player = context.getSystemService(AudioManager.class) + .getRingtonePlayer(); + try { + return new ParcelFileDescriptor.AutoCloseInputStream(player.openRingtone(uri)); + } catch (Exception e2) { + throw new IOException(e2); + } + } } private File getRingtoneCacheDir(int userId) { @@ -1938,49 +1983,65 @@ public class SettingsProvider extends ContentProvider { return false; } - // Invalidate any relevant cache files - String cacheName = null; - if (Settings.System.RINGTONE.equals(name)) { - cacheName = Settings.System.RINGTONE_CACHE; - } else if (Settings.System.NOTIFICATION_SOUND.equals(name)) { - cacheName = Settings.System.NOTIFICATION_SOUND_CACHE; - } else if (Settings.System.ALARM_ALERT.equals(name)) { - cacheName = Settings.System.ALARM_ALERT_CACHE; - } - if (cacheName != null) { + File cacheFile = getCacheFile(name, callingUserId); + if (cacheFile != null) { if (!isValidAudioUri(name, value)) { return false; } - final File cacheFile = new File( - getRingtoneCacheDir(owningUserId), cacheName); + // Invalidate any relevant cache files cacheFile.delete(); } + final boolean success; // Mutate the value. synchronized (mLock) { switch (operation) { case MUTATION_OPERATION_INSERT: { validateSystemSettingValue(name, value); - return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM, + success = mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM, owningUserId, name, value, null, false, callingPackage, false, null, overrideableByRestore); + break; } case MUTATION_OPERATION_DELETE: { - return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SYSTEM, + success = mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SYSTEM, owningUserId, name, false, null); + break; } case MUTATION_OPERATION_UPDATE: { validateSystemSettingValue(name, value); - return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SYSTEM, + success = mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SYSTEM, owningUserId, name, value, null, false, callingPackage, false, null); + break; + } + + default: { + success = false; + Slog.e(LOG_TAG, "Unknown operation code: " + operation); } } - Slog.e(LOG_TAG, "Unknown operation code: " + operation); + } + + if (!success) { return false; } + + if ((operation == MUTATION_OPERATION_INSERT || operation == MUTATION_OPERATION_UPDATE) + && cacheFile != null && value != null) { + final Uri ringtoneUri = Uri.parse(value); + // Stream selected ringtone into cache, so it's available for playback + // when CE storage is still locked + try (InputStream in = openRingtone(getContext(), ringtoneUri); + OutputStream out = new FileOutputStream(cacheFile)) { + FileUtils.copy(in, out); + } catch (IOException e) { + Slog.w(LOG_TAG, "Failed to cache ringtone: " + e); + } + } + return true; } private boolean isValidAudioUri(String name, String uri) { @@ -3267,20 +3328,21 @@ public class SettingsProvider extends ContentProvider { return Global.SECURE_FRP_MODE.equals(setting.getName()); } - public void resetSettingsLocked(int type, int userId, String packageName, int mode, + public boolean resetSettingsLocked(int type, int userId, String packageName, int mode, String tag) { - resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/ + return resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/ null); } - public void resetSettingsLocked(int type, int userId, String packageName, int mode, + public boolean resetSettingsLocked(int type, int userId, String packageName, int mode, String tag, @Nullable String prefix) { final int key = makeKey(type, userId); SettingsState settingsState = peekSettingsStateLocked(key); if (settingsState == null) { - return; + return false; } + boolean success = false; banConfigurationIfNecessary(type, prefix, settingsState); switch (mode) { case Settings.RESET_MODE_PACKAGE_DEFAULTS: { @@ -3300,6 +3362,7 @@ public class SettingsProvider extends ContentProvider { } if (someSettingChanged) { settingsState.persistSyncLocked(); + success = true; } } } break; @@ -3321,6 +3384,7 @@ public class SettingsProvider extends ContentProvider { } if (someSettingChanged) { settingsState.persistSyncLocked(); + success = true; } } } break; @@ -3348,6 +3412,7 @@ public class SettingsProvider extends ContentProvider { } if (someSettingChanged) { settingsState.persistSyncLocked(); + success = true; } } } break; @@ -3372,10 +3437,12 @@ public class SettingsProvider extends ContentProvider { } if (someSettingChanged) { settingsState.persistSyncLocked(); + success = true; } } } break; } + return success; } public void removeSettingsForPackageLocked(String packageName, int userId) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index a9d2b30c2b6f..4be572f8c0f6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -55,7 +55,6 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.screenrecord.MediaProjectionPermissionDialog; import com.android.systemui.screenrecord.ScreenShareOption; import com.android.systemui.statusbar.phone.SystemUIDialog; @@ -73,7 +72,6 @@ public class MediaProjectionPermissionActivity extends Activity private final FeatureFlags mFeatureFlags; private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver; - private final ActivityStarter mActivityStarter; private String mPackageName; private int mUid; @@ -89,10 +87,8 @@ public class MediaProjectionPermissionActivity extends Activity @Inject public MediaProjectionPermissionActivity(FeatureFlags featureFlags, - Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver, - ActivityStarter activityStarter) { + Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) { mFeatureFlags = featureFlags; - mActivityStarter = activityStarter; mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver; } @@ -313,16 +309,8 @@ public class MediaProjectionPermissionActivity extends Activity // Start activity from the current foreground user to avoid creating a separate // SystemUI process without access to recent tasks because it won't have // WM Shell running inside. - // It is also important to make sure the shade is dismissed, otherwise users won't - // see the app selector. mUserSelectingTask = true; - mActivityStarter.startActivity( - intent, - /* dismissShade= */ true, - /* animationController= */ null, - /* showOverLockscreenWhenLocked= */ false, - UserHandle.of(ActivityManager.getCurrentUser()) - ); + startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser())); } } catch (RemoteException e) { Log.e(TAG, "Error granting projection permission", e); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 37f032b464b7..2c15e27b8148 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -125,6 +125,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( // FrameCallback used to delay starting the light reveal animation until the next frame private val startLightRevealCallback = TraceUtils.namedRunnable("startLightReveal") { + lightRevealAnimationPlaying = true lightRevealAnimator.start() } @@ -268,7 +269,6 @@ class UnlockedScreenOffAnimationController @Inject constructor( decidedToAnimateGoingToSleep = true shouldAnimateInKeyguard = true - lightRevealAnimationPlaying = true // Start the animation on the next frame. startAnimation() is called after // PhoneWindowManager makes a binder call to System UI on @@ -283,7 +283,8 @@ class UnlockedScreenOffAnimationController @Inject constructor( // dispatched, a race condition could make it possible for this callback to be run // as the device is waking up. That results in the AOD UI being shown while we wake // up, with unpredictable consequences. - if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY)) { + if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) && + shouldAnimateInKeyguard) { aodUiAnimationPlaying = true // Show AOD. That'll cause the KeyguardVisibilityHelper to call diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index e76f26d8128e..e6f8c4861a94 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -134,6 +134,22 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { verify(shadeViewController, times(1)).showAodUi() } + @Test + fun testAodUiShowNotInvokedIfWakingUp() { + `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true) + `when`(powerManager.isInteractive).thenReturn(false) + + val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java) + controller.startAnimation() + controller.onStartedWakingUp() + + verify(handler).postDelayed(callbackCaptor.capture(), anyLong()) + + callbackCaptor.value.run() + + verify(shadeViewController, never()).showAodUi() + } + /** * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal * animation to start. If the device is woken up during the screen off, we should *never* do diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index dc6f8584006e..2c745ae3bf55 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -17,7 +17,6 @@ package com.android.server.am; import static android.Manifest.permission.BATTERY_STATS; -import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.DEVICE_POWER; import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.POWER_SAVER; @@ -31,6 +30,7 @@ import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE; import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.RequiresNoPermission; +import android.annotation.SuppressLint; import android.app.StatsManager; import android.app.usage.NetworkStatsManager; import android.bluetooth.BluetoothActivityEnergyInfo; @@ -82,6 +82,7 @@ import android.os.health.HealthStatsParceler; import android.os.health.HealthStatsWriter; import android.os.health.UidHealthStats; import android.power.PowerStatsInternal; +import android.provider.DeviceConfig; import android.provider.Settings; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; @@ -169,6 +170,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub .replaceWith("?"); private static final int MAX_LOW_POWER_STATS_SIZE = 32768; private static final int POWER_STATS_QUERY_TIMEOUT_MILLIS = 2000; + private static final String MIN_CONSUMED_POWER_THRESHOLD_KEY = "min_consumed_power_threshold"; private static final String EMPTY = "Empty"; private final HandlerThread mHandlerThread; @@ -855,12 +857,17 @@ public final class BatteryStatsService extends IBatteryStats.Stub final BatteryUsageStats bus; switch (atomTag) { case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET: + @SuppressLint("MissingPermission") + final double minConsumedPowerThreshold = + DeviceConfig.getFloat(DeviceConfig.NAMESPACE_BATTERY_STATS, + MIN_CONSUMED_POWER_THRESHOLD_KEY, 0); final BatteryUsageStatsQuery querySinceReset = new BatteryUsageStatsQuery.Builder() .setMaxStatsAgeMs(0) .includeProcessStateData() .includeVirtualUids() .includePowerModels() + .setMinConsumedPowerThreshold(minConsumedPowerThreshold) .build(); bus = getBatteryUsageStats(List.of(querySinceReset)).get(0); break; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 898a3c416083..6546f6e533c0 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -468,6 +468,8 @@ public final class DisplayManagerService extends SystemService { private SensorManager mSensorManager; private BrightnessTracker mBrightnessTracker; + private SmallAreaDetectionController mSmallAreaDetectionController; + // Whether minimal post processing is allowed by the user. @GuardedBy("mSyncRoot") @@ -731,6 +733,8 @@ public final class DisplayManagerService extends SystemService { filter.addAction(Intent.ACTION_DOCK_EVENT); mContext.registerReceiver(mIdleModeReceiver, filter); + + mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext); } @VisibleForTesting @@ -3046,6 +3050,9 @@ public final class DisplayManagerService extends SystemService { pw.println(); mDisplayModeDirector.dump(pw); mBrightnessSynchronizer.dump(pw); + if (mSmallAreaDetectionController != null) { + mSmallAreaDetectionController.dump(pw); + } } private static float[] getFloatArray(TypedArray array) { diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java new file mode 100644 index 000000000000..adaa5390cb9b --- /dev/null +++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageManagerInternal; +import android.provider.DeviceConfig; +import android.provider.DeviceConfigInterface; +import android.util.ArrayMap; +import android.util.SparseArray; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Map; + +final class SmallAreaDetectionController { + private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds); + private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold); + + // TODO(b/281720315): Move this to DeviceConfig once server side ready. + private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST = + "small_area_detection_allowlist"; + + private final Object mLock = new Object(); + private final Context mContext; + private final PackageManagerInternal mPackageManager; + private final UserManagerInternal mUserManager; + @GuardedBy("mLock") + private final Map<String, Float> mAllowPkgMap = new ArrayMap<>(); + // TODO(b/298722189): Update allowlist when user changes + @GuardedBy("mLock") + private int[] mUserIds; + + static SmallAreaDetectionController create(@NonNull Context context) { + final SmallAreaDetectionController controller = + new SmallAreaDetectionController(context, DeviceConfigInterface.REAL); + final String property = DeviceConfigInterface.REAL.getProperty( + DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_SMALL_AREA_DETECTION_ALLOWLIST); + controller.updateAllowlist(property); + return controller; + } + + @VisibleForTesting + SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) { + mContext = context; + mPackageManager = LocalServices.getService(PackageManagerInternal.class); + mUserManager = LocalServices.getService(UserManagerInternal.class); + deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + BackgroundThread.getExecutor(), + new SmallAreaDetectionController.OnPropertiesChangedListener()); + mPackageManager.getPackageList(new PackageReceiver()); + } + + @VisibleForTesting + void updateAllowlist(@Nullable String property) { + synchronized (mLock) { + mAllowPkgMap.clear(); + if (property != null) { + final String[] mapStrings = property.split(","); + for (String mapString : mapStrings) putToAllowlist(mapString); + } else { + final String[] defaultMapStrings = mContext.getResources() + .getStringArray(R.array.config_smallAreaDetectionAllowlist); + for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString); + } + updateSmallAreaDetection(); + } + } + + @GuardedBy("mLock") + private void putToAllowlist(String rowData) { + // Data format: package:threshold - e.g. "com.abc.music:0.05" + final String[] items = rowData.split(":"); + if (items.length == 2) { + try { + final String pkg = items[0]; + final float threshold = Float.valueOf(items[1]); + mAllowPkgMap.put(pkg, threshold); + } catch (Exception e) { + // Just skip if items[1] - the threshold is not parsable number + } + } + } + + @GuardedBy("mLock") + private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) { + for (int i = 0; i < mUserIds.length; i++) { + final int userId = mUserIds[i]; + final int uid = mPackageManager.getPackageUid(pkg, 0, userId); + if (uid > 0) list.put(uid, threshold); + } + } + + @GuardedBy("mLock") + private void updateSmallAreaDetection() { + if (mAllowPkgMap.isEmpty()) return; + + mUserIds = mUserManager.getUserIds(); + + final SparseArray<Float> uidThresholdList = new SparseArray<>(); + for (String pkg : mAllowPkgMap.keySet()) { + final float threshold = mAllowPkgMap.get(pkg); + updateUidListForAllUsers(uidThresholdList, pkg, threshold); + } + + final int[] uids = new int[uidThresholdList.size()]; + final float[] thresholds = new float[uidThresholdList.size()]; + for (int i = 0; i < uidThresholdList.size(); i++) { + uids[i] = uidThresholdList.keyAt(i); + thresholds[i] = uidThresholdList.valueAt(i); + } + updateSmallAreaDetection(uids, thresholds); + } + + @VisibleForTesting + void updateSmallAreaDetection(int[] uids, float[] thresholds) { + nativeUpdateSmallAreaDetection(uids, thresholds); + } + + void setSmallAreaDetectionThreshold(int uid, float threshold) { + nativeSetSmallAreaDetectionThreshold(uid, threshold); + } + + void dump(PrintWriter pw) { + pw.println("Small area detection allowlist"); + pw.println(" Packages:"); + synchronized (mLock) { + for (String pkg : mAllowPkgMap.keySet()) { + pw.println(" " + pkg + " threshold = " + mAllowPkgMap.get(pkg)); + } + pw.println(" mUserIds=" + Arrays.toString(mUserIds)); + } + } + + private class OnPropertiesChangedListener implements DeviceConfig.OnPropertiesChangedListener { + public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { + if (properties.getKeyset().contains(KEY_SMALL_AREA_DETECTION_ALLOWLIST)) { + updateAllowlist( + properties.getString(KEY_SMALL_AREA_DETECTION_ALLOWLIST, null /*default*/)); + } + } + } + + private final class PackageReceiver implements PackageManagerInternal.PackageListObserver { + @Override + public void onPackageAdded(@NonNull String packageName, int uid) { + synchronized (mLock) { + if (mAllowPkgMap.containsKey(packageName)) { + setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName)); + } + } + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index d662aaedb774..204e358f25ff 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1365,11 +1365,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertCallerIsOwnerRootOrVerifier(); synchronized (mLock) { assertPreparedAndNotDestroyedLocked("getNames"); + String[] names; if (!isCommitted()) { - return getNamesLocked(); + names = getNamesLocked(); } else { - return getStageDirContentsLocked(); + names = getStageDirContentsLocked(); } + return ArrayUtils.removeString(names, APP_METADATA_FILE_NAME); } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 297ad73e054b..c24d5236f4f7 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1001,7 +1001,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { } synchronized (mLock) { - mAttributions.put(source.getToken(), source); + // Change the token for the AttributionSource we're storing, so that we don't store + // a strong reference to the original token inside the map itself. + mAttributions.put(source.getToken(), source.withDefaultToken()); } } @@ -1009,7 +1011,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { synchronized (mLock) { final AttributionSource cachedSource = mAttributions.get(source.getToken()); if (cachedSource != null) { - return cachedSource.equals(source); + return cachedSource.equalsExceptToken(source); } return false; } diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index ebd4aec3aef9..5e5f6f2bd644 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -166,10 +166,11 @@ public class BatteryUsageStatsProvider { && mStats.isProcessStateDataAvailable(); final boolean includeVirtualUids = ((query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS) != 0); + final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold(); final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder( mStats.getCustomEnergyConsumerNames(), includePowerModels, - includeProcessStateData); + includeProcessStateData, minConsumedPowerThreshold); // TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration // of stats sessions to wall-clock adjustments batteryUsageStatsBuilder.setStatsStartTimestamp(mStats.getStartClockTime()); @@ -303,10 +304,12 @@ public class BatteryUsageStatsProvider { final boolean includeProcessStateData = ((query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0) && mStats.isProcessStateDataAvailable(); + final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold(); final String[] customEnergyConsumerNames = mStats.getCustomEnergyConsumerNames(); final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( - customEnergyConsumerNames, includePowerModels, includeProcessStateData); + customEnergyConsumerNames, includePowerModels, includeProcessStateData, + minConsumedPowerThreshold); if (mBatteryUsageStatsStore == null) { Log.e(TAG, "BatteryUsageStatsStore is unavailable"); return builder.build(); diff --git a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java index 4f221b532b75..97181948352b 100644 --- a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java +++ b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java @@ -244,6 +244,8 @@ public final class ConfigurationInternal { && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting && mUserId == that.mUserId && mUserConfigAllowed == that.mUserConfigAllowed && mSystemClockUpdateThresholdMillis == that.mSystemClockUpdateThresholdMillis + && mSystemClockConfidenceThresholdMillis + == that.mSystemClockConfidenceThresholdMillis && mAutoSuggestionLowerBound.equals(that.mAutoSuggestionLowerBound) && mManualSuggestionLowerBound.equals(that.mManualSuggestionLowerBound) && mSuggestionUpperBound.equals(that.mSuggestionUpperBound) @@ -253,7 +255,8 @@ public final class ConfigurationInternal { @Override public int hashCode() { int result = Objects.hash(mAutoDetectionSupported, mAutoDetectionEnabledSetting, mUserId, - mUserConfigAllowed, mSystemClockUpdateThresholdMillis, mAutoSuggestionLowerBound, + mUserConfigAllowed, mSystemClockUpdateThresholdMillis, + mSystemClockConfidenceThresholdMillis, mAutoSuggestionLowerBound, mManualSuggestionLowerBound, mSuggestionUpperBound); result = 31 * result + Arrays.hashCode(mOriginPriorities); return result; diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java index fc960d83dc3b..c52f8f887463 100644 --- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java +++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java @@ -22,15 +22,16 @@ import android.content.Context; import android.os.Handler; import android.os.PowerManager; import android.os.SystemClock; +import android.util.IndentingPrintWriter; import android.util.Slog; import com.android.server.AlarmManagerInternal; import com.android.server.LocalServices; import com.android.server.SystemClockTime; import com.android.server.SystemClockTime.TimeConfidence; -import com.android.server.timezonedetector.StateChangeListener; -import java.io.PrintWriter; +import java.time.Duration; +import java.time.Instant; import java.util.Objects; /** @@ -41,14 +42,11 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { private static final String LOG_TAG = TimeDetectorService.TAG; @NonNull private final Handler mHandler; - @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final PowerManager.WakeLock mWakeLock; @NonNull private final AlarmManagerInternal mAlarmManagerInternal; - EnvironmentImpl(@NonNull Context context, @NonNull Handler handler, - @NonNull ServiceConfigAccessor serviceConfigAccessor) { + EnvironmentImpl(@NonNull Context context, @NonNull Handler handler) { mHandler = Objects.requireNonNull(handler); - mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); PowerManager powerManager = context.getSystemService(PowerManager.class); mWakeLock = Objects.requireNonNull( @@ -59,19 +57,6 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { } @Override - public void setConfigurationInternalChangeListener( - @NonNull StateChangeListener listener) { - StateChangeListener stateChangeListener = - () -> mHandler.post(listener::onChange); - mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener); - } - - @Override - public ConfigurationInternal getCurrentUserConfigurationInternal() { - return mServiceConfigAccessor.getCurrentUserConfigurationInternal(); - } - - @Override public void acquireWakeLock() { if (mWakeLock.isHeld()) { Slog.wtf(LOG_TAG, "WakeLock " + mWakeLock + " already held"); @@ -126,8 +111,19 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { } @Override - public void dumpDebugLog(@NonNull PrintWriter printWriter) { - SystemClockTime.dump(printWriter); + public void dumpDebugLog(@NonNull IndentingPrintWriter pw) { + long elapsedRealtimeMillis = elapsedRealtimeMillis(); + pw.printf("elapsedRealtimeMillis()=%s (%s)\n", + Duration.ofMillis(elapsedRealtimeMillis), elapsedRealtimeMillis); + long systemClockMillis = systemClockMillis(); + pw.printf("systemClockMillis()=%s (%s)\n", + Instant.ofEpochMilli(systemClockMillis), systemClockMillis); + pw.println("systemClockConfidence()=" + systemClockConfidence()); + + pw.println("SystemClockTime debug log:"); + pw.increaseIndent(); + SystemClockTime.dump(pw); + pw.decreaseIndent(); } @Override diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index 22f096b11f18..d88f4268434b 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -96,8 +96,8 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL; TimeDetectorService service = new TimeDetectorService( - context, handler, callerIdentityInjector, serviceConfigAccessor, - timeDetectorStrategy, NtpTrustedTime.getInstance(context)); + context, handler, callerIdentityInjector, timeDetectorStrategy, + NtpTrustedTime.getInstance(context)); // Publish the binder service so it can be accessed from other (appropriately // permissioned) processes. @@ -108,7 +108,6 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub @NonNull private final Handler mHandler; @NonNull private final Context mContext; @NonNull private final CallerIdentityInjector mCallerIdentityInjector; - @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy; @NonNull private final NtpTrustedTime mNtpTrustedTime; @@ -123,20 +122,18 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub @VisibleForTesting public TimeDetectorService(@NonNull Context context, @NonNull Handler handler, @NonNull CallerIdentityInjector callerIdentityInjector, - @NonNull ServiceConfigAccessor serviceConfigAccessor, @NonNull TimeDetectorStrategy timeDetectorStrategy, @NonNull NtpTrustedTime ntpTrustedTime) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector); - mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy); mNtpTrustedTime = Objects.requireNonNull(ntpTrustedTime); - // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when - // the configuration changes for any reason. - mServiceConfigAccessor.addConfigurationInternalChangeListener( - () -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread)); + // Wire up a change listener so that ITimeDetectorListeners can be notified when the + // detector state changes for any reason. + mTimeDetectorStrategy.addChangeListener( + () -> mHandler.post(this::handleChangeOnHandlerThread)); } @Override @@ -151,10 +148,8 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub final long token = mCallerIdentityInjector.clearCallingIdentity(); try { - ConfigurationInternal configurationInternal = - mServiceConfigAccessor.getConfigurationInternal(userId); - final boolean bypassUserPolicyCheck = false; - return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyCheck); + final boolean bypassUserPolicyChecks = false; + return mTimeDetectorStrategy.getCapabilitiesAndConfig(userId, bypassUserPolicyChecks); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); } @@ -180,9 +175,9 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub final long token = mCallerIdentityInjector.clearCallingIdentity(); try { - final boolean bypassUserPolicyCheck = false; - return mServiceConfigAccessor.updateConfiguration( - resolvedUserId, configuration, bypassUserPolicyCheck); + final boolean bypassUserPolicyChecks = false; + return mTimeDetectorStrategy.updateConfiguration( + resolvedUserId, configuration, bypassUserPolicyChecks); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); } @@ -262,7 +257,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub } } - private void handleConfigurationInternalChangedOnHandlerThread() { + private void handleChangeOnHandlerThread() { // Configuration has changed, but each user may have a different view of the configuration. // It's possible that this will cause unnecessary notifications but that shouldn't be a // problem. diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index 11cec6663a37..15c0a809cde8 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -21,6 +21,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; +import android.app.time.TimeCapabilitiesAndConfig; +import android.app.time.TimeConfiguration; import android.app.time.TimeState; import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; @@ -87,6 +89,48 @@ public interface TimeDetectorStrategy extends Dumpable { */ boolean confirmTime(@NonNull UnixEpochTime confirmationTime); + /** + * Adds a listener that will be triggered when something changes that could affect the result + * of the {@link #getCapabilitiesAndConfig} call for the <em>current user only</em>. This + * includes the current user changing. This is exposed so that (indirect) users like SettingsUI + * can monitor for changes to data derived from {@link TimeCapabilitiesAndConfig} and update + * the UI accordingly. + */ + void addChangeListener(@NonNull StateChangeListener listener); + + /** + * Returns a {@link TimeCapabilitiesAndConfig} object for the specified user. + * + * <p>The strategy is dependent on device state like current user, settings and device config. + * These updates are usually handled asynchronously, so callers should expect some delay between + * a change being made directly to services like settings and the strategy becoming aware of + * them. Changes made via {@link #updateConfiguration} will be visible immediately. + * + * @param userId the user ID to retrieve the information for + * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device + * policy restrictions that should apply to actual users can be ignored + */ + TimeCapabilitiesAndConfig getCapabilitiesAndConfig( + @UserIdInt int userId, boolean bypassUserPolicyChecks); + + /** + * Updates the configuration properties that control a device's time behavior. + * + * <p>This method returns {@code true} if the configuration was changed, {@code false} + * otherwise. + * + * <p>See {@link #getCapabilitiesAndConfig} for guarantees about visibility of updates to + * subsequent calls. + * + * @param userId the current user ID, supplied to make sure that the asynchronous process + * that happens when users switch is completed when the call is made + * @param configuration the configuration changes + * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device + * policy restrictions that should apply to actual users can be ignored + */ + boolean updateConfiguration(@UserIdInt int userId, + @NonNull TimeConfiguration configuration, boolean bypassUserPolicyChecks); + /** Processes the suggested time from telephony sources. */ void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion suggestion); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index b293bacfdc0d..fd35df61c952 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -30,6 +30,7 @@ import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; import android.app.time.TimeCapabilities; import android.app.time.TimeCapabilitiesAndConfig; +import android.app.time.TimeConfiguration; import android.app.time.TimeState; import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; @@ -48,10 +49,10 @@ import com.android.server.timezonedetector.ArrayMapWithHistory; import com.android.server.timezonedetector.ReferenceWithHistory; import com.android.server.timezonedetector.StateChangeListener; -import java.io.PrintWriter; -import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Objects; /** @@ -94,6 +95,12 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { @NonNull private final Environment mEnvironment; + @NonNull + private final ServiceConfigAccessor mServiceConfigAccessor; + + @GuardedBy("this") + @NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>(); + @GuardedBy("this") @NonNull private ConfigurationInternal mCurrentConfigurationInternal; @@ -139,16 +146,6 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { */ public interface Environment { - /** - * Sets a {@link StateChangeListener} that will be invoked when there are any changes that - * could affect the content of {@link ConfigurationInternal}. - * This is invoked during system server setup. - */ - void setConfigurationInternalChangeListener(@NonNull StateChangeListener listener); - - /** Returns the {@link ConfigurationInternal} for the current user. */ - @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); - /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */ void acquireWakeLock(); @@ -174,16 +171,15 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { /** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */ void releaseWakeLock(); - /** * Adds a standalone entry to the time debug log. */ void addDebugLogEntry(@NonNull String logMsg); /** - * Dumps the time debug log to the supplied {@link PrintWriter}. + * Dumps the time debug log to the supplied {@link IndentingPrintWriter}. */ - void dumpDebugLog(PrintWriter printWriter); + void dumpDebugLog(IndentingPrintWriter ipw); /** * Requests that the supplied runnable is invoked asynchronously. @@ -195,19 +191,23 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { @NonNull Context context, @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) { - TimeDetectorStrategyImpl.Environment environment = - new EnvironmentImpl(context, handler, serviceConfigAccessor); - return new TimeDetectorStrategyImpl(environment); + TimeDetectorStrategyImpl.Environment environment = new EnvironmentImpl(context, handler); + return new TimeDetectorStrategyImpl(environment, serviceConfigAccessor); } @VisibleForTesting - TimeDetectorStrategyImpl(@NonNull Environment environment) { + TimeDetectorStrategyImpl(@NonNull Environment environment, + @NonNull ServiceConfigAccessor serviceConfigAccessor) { mEnvironment = Objects.requireNonNull(environment); + mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); synchronized (this) { - mEnvironment.setConfigurationInternalChangeListener( - this::handleConfigurationInternalChanged); - mCurrentConfigurationInternal = mEnvironment.getCurrentUserConfigurationInternal(); + // Listen for config and user changes and get an initial snapshot of configuration. + StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged; + mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener); + + // Initialize mCurrentConfigurationInternal with a starting value. + updateCurrentConfigurationInternalIfRequired("TimeDetectorStrategyImpl:"); } } @@ -421,6 +421,57 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } } + @GuardedBy("this") + private void notifyStateChangeListenersAsynchronously() { + for (StateChangeListener listener : mStateChangeListeners) { + // This is queuing asynchronous notification, so no need to surrender the "this" lock. + mEnvironment.runAsync(listener::onChange); + } + } + + @Override + public synchronized void addChangeListener(@NonNull StateChangeListener listener) { + mStateChangeListeners.add(listener); + } + + @Override + public synchronized TimeCapabilitiesAndConfig getCapabilitiesAndConfig(@UserIdInt int userId, + boolean bypassUserPolicyChecks) { + ConfigurationInternal configurationInternal; + if (mCurrentConfigurationInternal.getUserId() == userId) { + // Use the cached snapshot we have. + configurationInternal = mCurrentConfigurationInternal; + } else { + // This is not a common case: It would be unusual to want the configuration for a user + // other than the "current" user, but it is supported because it is trivial to do so. + // Unlike the current user config, there's no cached copy to worry about so read it + // directly from mServiceConfigAccessor. + configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId); + } + return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks); + } + + @Override + public synchronized boolean updateConfiguration(@UserIdInt int userId, + @NonNull TimeConfiguration configuration, boolean bypassUserPolicyChecks) { + // Write-through + boolean updateSuccessful = mServiceConfigAccessor.updateConfiguration( + userId, configuration, bypassUserPolicyChecks); + + // The update above will trigger config update listeners asynchronously if they are needed, + // but that could mean an immediate call to getCapabilitiesAndConfig() for the current user + // wouldn't see the update. So, handle the cache update and notifications here. When the + // async update listener triggers it will find everything already up to date and do nothing. + if (updateSuccessful) { + String logMsg = "updateConfiguration:" + + " userId=" + userId + + ", configuration=" + configuration + + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks; + updateCurrentConfigurationInternalIfRequired(logMsg); + } + return updateSuccessful; + } + @Override public synchronized void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion suggestion) { // Empty time suggestion means that telephony network connectivity has been lost. @@ -448,26 +499,49 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { doAutoTimeDetection(reason); } - private synchronized void handleConfigurationInternalChanged() { - ConfigurationInternal currentUserConfig = - mEnvironment.getCurrentUserConfigurationInternal(); - String logMsg = "handleConfigurationInternalChanged:" - + " oldConfiguration=" + mCurrentConfigurationInternal - + ", newConfiguration=" + currentUserConfig; - addDebugLogEntry(logMsg); - mCurrentConfigurationInternal = currentUserConfig; - - boolean autoDetectionEnabled = - mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior(); - // When automatic time detection is enabled we update the system clock instantly if we can. - // Conversely, when automatic time detection is disabled we leave the clock as it is. - if (autoDetectionEnabled) { - String reason = "Auto time zone detection config changed."; - doAutoTimeDetection(reason); - } else { - // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what - // it should be in future. - mLastAutoSystemClockTimeSet = null; + /** + * Handles a configuration change notification. + */ + private synchronized void handleConfigurationInternalMaybeChanged() { + String logMsg = "handleConfigurationInternalMaybeChanged:"; + updateCurrentConfigurationInternalIfRequired(logMsg); + } + + @GuardedBy("this") + private void updateCurrentConfigurationInternalIfRequired(@NonNull String logMsg) { + ConfigurationInternal newCurrentConfigurationInternal = + mServiceConfigAccessor.getCurrentUserConfigurationInternal(); + // mCurrentConfigurationInternal is null the first time this method is called. + ConfigurationInternal oldCurrentConfigurationInternal = mCurrentConfigurationInternal; + + // If the configuration actually changed, update the cached copy synchronously and do + // other necessary house-keeping / (async) listener notifications. + if (!newCurrentConfigurationInternal.equals(oldCurrentConfigurationInternal)) { + mCurrentConfigurationInternal = newCurrentConfigurationInternal; + + logMsg = new StringBuilder(logMsg) + .append(" [oldConfiguration=").append(oldCurrentConfigurationInternal) + .append(", newConfiguration=").append(newCurrentConfigurationInternal) + .append("]") + .toString(); + addDebugLogEntry(logMsg); + + // The configuration and maybe the status changed so notify listeners. + notifyStateChangeListenersAsynchronously(); + + boolean autoDetectionEnabled = + mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior(); + // When automatic time detection is enabled we update the system clock instantly if we + // can. Conversely, when automatic time detection is disabled we leave the clock as it + // is. + if (autoDetectionEnabled) { + String reason = "Auto time zone detection config changed."; + doAutoTimeDetection(reason); + } else { + // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict + // what it should be in future. + mLastAutoSystemClockTimeSet = null; + } } } @@ -489,13 +563,11 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { ipw.println("[Capabilities=" + mCurrentConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks) + "]"); - long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis(); - ipw.printf("mEnvironment.elapsedRealtimeMillis()=%s (%s)\n", - Duration.ofMillis(elapsedRealtimeMillis), elapsedRealtimeMillis); - long systemClockMillis = mEnvironment.systemClockMillis(); - ipw.printf("mEnvironment.systemClockMillis()=%s (%s)\n", - Instant.ofEpochMilli(systemClockMillis), systemClockMillis); - ipw.println("mEnvironment.systemClockConfidence()=" + mEnvironment.systemClockConfidence()); + + ipw.println("mEnvironment:"); + ipw.increaseIndent(); + mEnvironment.dumpDebugLog(ipw); + ipw.decreaseIndent(); ipw.println("Time change log:"); ipw.increaseIndent(); // level 2 @@ -525,6 +597,11 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { ipw.decreaseIndent(); // level 1 } + @VisibleForTesting + public synchronized ConfigurationInternal getCachedCapabilitiesAndConfigForTests() { + return mCurrentConfigurationInternal; + } + @GuardedBy("this") private boolean storeTelephonySuggestion(@NonNull TelephonyTimeSuggestion suggestion) { UnixEpochTime newUnixEpochTime = suggestion.getUnixEpochTime(); diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 9e3a611c0e70..97b3e3280f2b 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -880,6 +880,58 @@ final class LetterboxConfiguration { false /* forTabletopMode */); } + /** + * Overrides persistent horizontal position of the letterboxed app window when horizontal + * reachability is enabled. + */ + void setPersistentLetterboxPositionForHorizontalReachability(boolean forBookMode, + @LetterboxHorizontalReachabilityPosition int position) { + mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability( + forBookMode, position); + } + + /** + * Overrides persistent vertical position of the letterboxed app window when vertical + * reachability is enabled. + */ + void setPersistentLetterboxPositionForVerticalReachability(boolean forTabletopMode, + @LetterboxVerticalReachabilityPosition int position) { + mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability( + forTabletopMode, position); + } + + /** + * Resets persistent horizontal position of the letterboxed app window when horizontal + * reachability + * is enabled to default position. + */ + void resetPersistentLetterboxPositionForHorizontalReachability() { + mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability( + false /* forBookMode */, + readLetterboxHorizontalReachabilityPositionFromConfig(mContext, + false /* forBookMode */)); + mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability( + true /* forBookMode */, + readLetterboxHorizontalReachabilityPositionFromConfig(mContext, + true /* forBookMode */)); + } + + /** + * Resets persistent vertical position of the letterboxed app window when vertical reachability + * is + * enabled to default position. + */ + void resetPersistentLetterboxPositionForVerticalReachability() { + mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability( + false /* forTabletopMode */, + readLetterboxVerticalReachabilityPositionFromConfig(mContext, + false /* forTabletopMode */)); + mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability( + true /* forTabletopMode */, + readLetterboxVerticalReachabilityPositionFromConfig(mContext, + true /* forTabletopMode */)); + } + @LetterboxHorizontalReachabilityPosition private static int readLetterboxHorizontalReachabilityPositionFromConfig(Context context, boolean forBookMode) { diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index ad46770432a1..bd3913063a3f 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -559,7 +559,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return false; } - final int sourceWindowingMode = source.getWindowingMode(); + final int sourceWindowingMode = source.getTask().getWindowingMode(); if (sourceWindowingMode != WINDOWING_MODE_FULLSCREEN && sourceWindowingMode != WINDOWING_MODE_FREEFORM) { return false; diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index ceebb27642ce..bfe055354b9c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -911,6 +911,70 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } + private int runSetPersistentLetterboxPositionForHorizontalReachability(PrintWriter pw) + throws RemoteException { + @LetterboxHorizontalReachabilityPosition final int position; + try { + String arg = getNextArgRequired(); + switch (arg) { + case "left": + position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; + break; + case "center": + position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER; + break; + case "right": + position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT; + break; + default: + getErrPrintWriter().println( + "Error: 'left', 'center' or 'right' are expected as an argument"); + return -1; + } + } catch (IllegalArgumentException e) { + getErrPrintWriter().println( + "Error: 'left', 'center' or 'right' are expected as an argument" + e); + return -1; + } + synchronized (mInternal.mGlobalLock) { + mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability( + false /* IsInBookMode */, position); + } + return 0; + } + + private int runSetPersistentLetterboxPositionForVerticalReachability(PrintWriter pw) + throws RemoteException { + @LetterboxVerticalReachabilityPosition final int position; + try { + String arg = getNextArgRequired(); + switch (arg) { + case "top": + position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; + break; + case "center": + position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER; + break; + case "bottom": + position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM; + break; + default: + getErrPrintWriter().println( + "Error: 'top', 'center' or 'bottom' are expected as an argument"); + return -1; + } + } catch (IllegalArgumentException e) { + getErrPrintWriter().println( + "Error: 'top', 'center' or 'bottom' are expected as an argument" + e); + return -1; + } + synchronized (mInternal.mGlobalLock) { + mLetterboxConfiguration.setPersistentLetterboxPositionForVerticalReachability( + false /* forTabletopMode */, position); + } + return 0; + } + private int runSetBooleanFlag(PrintWriter pw, Consumer<Boolean> setter) throws RemoteException { String arg = getNextArg(); @@ -994,6 +1058,12 @@ public class WindowManagerShellCommand extends ShellCommand { case "--defaultPositionForVerticalReachability": runSetLetterboxDefaultPositionForVerticalReachability(pw); break; + case "--persistentPositionForHorizontalReachability": + runSetPersistentLetterboxPositionForHorizontalReachability(pw); + break; + case "--persistentPositionForVerticalReachability": + runSetPersistentLetterboxPositionForVerticalReachability(pw); + break; case "--isEducationEnabled": runSetBooleanFlag(pw, mLetterboxConfiguration::setIsEducationEnabled); break; @@ -1080,6 +1150,14 @@ public class WindowManagerShellCommand extends ShellCommand { case "defaultPositionForVerticalReachability": mLetterboxConfiguration.resetDefaultPositionForVerticalReachability(); break; + case "persistentPositionForHorizontalReachability": + mLetterboxConfiguration + .resetPersistentLetterboxPositionForHorizontalReachability(); + break; + case "persistentPositionForVerticalReachability": + mLetterboxConfiguration + .resetPersistentLetterboxPositionForVerticalReachability(); + break; case "isEducationEnabled": mLetterboxConfiguration.resetIsEducationEnabled(); break; @@ -1206,6 +1284,8 @@ public class WindowManagerShellCommand extends ShellCommand { mLetterboxConfiguration.resetEnabledAutomaticReachabilityInBookMode(); mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability(); mLetterboxConfiguration.resetDefaultPositionForVerticalReachability(); + mLetterboxConfiguration.resetPersistentLetterboxPositionForHorizontalReachability(); + mLetterboxConfiguration.resetPersistentLetterboxPositionForVerticalReachability(); mLetterboxConfiguration.resetIsEducationEnabled(); mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled(); mLetterboxConfiguration.resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(); @@ -1233,6 +1313,12 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println("Vertical position multiplier (tabletop mode): " + mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier( true /* isInTabletopMode */)); + pw.println("Horizontal position multiplier for reachability: " + + mLetterboxConfiguration.getHorizontalMultiplierForReachability( + false /* isInBookMode */)); + pw.println("Vertical position multiplier for reachability: " + + mLetterboxConfiguration.getVerticalMultiplierForReachability( + false /* isInTabletopMode */)); pw.println("Aspect ratio: " + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio()); pw.println("Default min aspect ratio for unresizable apps: " @@ -1472,6 +1558,12 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" --defaultPositionForVerticalReachability [top|center|bottom]"); pw.println(" Default position of app window when vertical reachability is."); pw.println(" enabled."); + pw.println(" --persistentPositionForHorizontalReachability [left|center|right]"); + pw.println(" Persistent position of app window when horizontal reachability is."); + pw.println(" enabled."); + pw.println(" --persistentPositionForVerticalReachability [top|center|bottom]"); + pw.println(" Persistent position of app window when vertical reachability is."); + pw.println(" enabled."); pw.println(" --isEducationEnabled [true|1|false|0]"); pw.println(" Whether education is allowed for letterboxed fullscreen apps."); pw.println(" --isSplitScreenAspectRatioForUnresizableAppsEnabled [true|1|false|0]"); @@ -1493,8 +1585,10 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha"); pw.println(" |horizontalPositionMultiplier|verticalPositionMultiplier"); pw.println(" |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled"); - pw.println(" |isEducationEnabled||defaultPositionMultiplierForHorizontalReachability"); + pw.println(" |isEducationEnabled|defaultPositionMultiplierForHorizontalReachability"); pw.println(" |isTranslucentLetterboxingEnabled|isUserAppAspectRatioSettingsEnabled"); + pw.println(" |persistentPositionMultiplierForHorizontalReachability"); + pw.println(" |persistentPositionMultiplierForVerticalReachability"); pw.println(" |defaultPositionMultiplierForVerticalReachability]"); pw.println(" Resets overrides to default values for specified properties separated"); pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'."); diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index d5217c8295bd..77db96001e0c 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -41,6 +41,7 @@ cc_library_static { "com_android_server_companion_virtual_InputController.cpp", "com_android_server_devicepolicy_CryptoTestHelper.cpp", "com_android_server_display_DisplayControl.cpp", + "com_android_server_display_SmallAreaDetectionController.cpp", "com_android_server_connectivity_Vpn.cpp", "com_android_server_gpu_GpuService.cpp", "com_android_server_HardwarePropertiesManagerService.cpp", diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp new file mode 100644 index 000000000000..b256f168f2af --- /dev/null +++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "SmallAreaDetectionController" + +#include <gui/SurfaceComposerClient.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedPrimitiveArray.h> + +#include "jni.h" +#include "utils/Log.h" + +namespace android { +static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids, + jfloatArray jthresholds) { + if (juids == nullptr || jthresholds == nullptr) return; + + ScopedIntArrayRO uids(env, juids); + ScopedFloatArrayRO thresholds(env, jthresholds); + + if (uids.size() != thresholds.size()) { + ALOGE("uids size exceeds thresholds size!"); + return; + } + + std::vector<int32_t> uidVector; + std::vector<float> thresholdVector; + size_t size = uids.size(); + uidVector.reserve(size); + thresholdVector.reserve(size); + for (int i = 0; i < size; i++) { + uidVector.push_back(static_cast<int32_t>(uids[i])); + thresholdVector.push_back(static_cast<float>(thresholds[i])); + } + SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector); +} + +static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid, + jfloat threshold) { + SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold); +} + +static const JNINativeMethod gMethods[] = { + {"nativeUpdateSmallAreaDetection", "([I[F)V", (void*)nativeUpdateSmallAreaDetection}, + {"nativeSetSmallAreaDetectionThreshold", "(IF)V", + (void*)nativeSetSmallAreaDetectionThreshold}, +}; + +int register_android_server_display_smallAreaDetectionController(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/display/SmallAreaDetectionController", + gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 290ad8de9547..63c3b4d1834c 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -66,6 +66,7 @@ int register_android_server_app_GameManagerService(JNIEnv* env); int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env); int register_com_android_server_display_DisplayControl(JNIEnv* env); int register_com_android_server_SystemClockTime(JNIEnv* env); +int register_android_server_display_smallAreaDetectionController(JNIEnv* env); }; using namespace android; @@ -124,5 +125,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_com_android_server_wm_TaskFpsCallbackController(env); register_com_android_server_display_DisplayControl(env); register_com_android_server_SystemClockTime(env); + register_android_server_display_smallAreaDetectionController(env); return JNI_VERSION_1_4; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f3c2de6f7af1..b1041855a2dc 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -679,7 +679,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // to decide whether an existing policy in the {@link #DEVICE_POLICIES_XML} needs to // be upgraded. See {@link PolicyVersionUpgrader} on instructions how to add an upgrade // step. - static final int DPMS_VERSION = 5; + static final int DPMS_VERSION = 6; static { SECURE_SETTINGS_ALLOWLIST = new ArraySet<>(); @@ -875,8 +875,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = true; // TODO(b/265683382) remove the flag after rollout. - private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running"; - public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = true; + public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false; // TODO(b/261999445) remove the flag after rollout. private static final String HEADLESS_FLAG = "headless"; @@ -23839,10 +23838,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private static boolean isKeepProfilesRunningFlagEnabled() { - return DeviceConfig.getBoolean( - NAMESPACE_DEVICE_POLICY_MANAGER, - KEEP_PROFILES_RUNNING_FLAG, - DEFAULT_KEEP_PROFILES_RUNNING_FLAG); + return DEFAULT_KEEP_PROFILES_RUNNING_FLAG; } private boolean isUnicornFlagEnabled() { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java index 06f11be20245..913860c4c1a0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java @@ -116,6 +116,19 @@ public class PolicyVersionUpgrader { currentVersion = 5; } + if (currentVersion == 5) { + Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion)); + // No-op upgrade here: + // DevicePolicyData.mEffectiveKeepProfilesRunning is only stored in XML file when it is + // different from its default value, otherwise the tag is not written. When loading, if + // the tag is missing, the field retains the value previously assigned in the + // constructor, which is the default value. + // In version 5 the default value was 'true', in version 6 it is 'false', so when + // loading XML version 5 we need to initialize the field to 'true' for it to be restored + // correctly in case the tag is missing. This is done in loadDataForUser(). + currentVersion = 6; + } + writePoliciesAndVersion(allUsers, allUsersData, ownersData, currentVersion); } @@ -281,6 +294,10 @@ public class PolicyVersionUpgrader { private DevicePolicyData loadDataForUser( int userId, int loadVersion, ComponentName ownerComponent) { DevicePolicyData policy = new DevicePolicyData(userId); + // See version 5 -> 6 step in upgradePolicy() + if (loadVersion == 5 && userId == UserHandle.USER_SYSTEM) { + policy.mEffectiveKeepProfilesRunning = true; + } DevicePolicyData.load(policy, mProvider.makeDevicePoliciesJournaledFile(userId), mProvider.getAdminInfoSupplier(userId), diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp index a421db0bdf46..8eb1b0b570cc 100644 --- a/services/tests/displayservicetests/Android.bp +++ b/services/tests/displayservicetests/Android.bp @@ -27,6 +27,7 @@ android_test { "mockingservicestests-utils-mockito", "platform-compat-test-rules", "platform-test-annotations", + "service-permission.stubs.system_server", "services.core", "servicestests-utils", "testables", diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 50cf1696ba83..14586aef2c44 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -58,6 +58,7 @@ import android.compat.testing.PlatformCompatChangeRule; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Rect; @@ -106,6 +107,7 @@ import com.android.server.display.DisplayManagerService.DeviceStateListener; import com.android.server.display.DisplayManagerService.SyncRoot; import com.android.server.input.InputManagerInternal; import com.android.server.lights.LightsManager; +import com.android.server.pm.UserManagerInternal; import com.android.server.sensors.SensorManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -254,10 +256,11 @@ public class DisplayManagerServiceTest { @Mock LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy; @Mock IBinder mMockDisplayToken; @Mock SensorManagerInternal mMockSensorManagerInternal; - @Mock SensorManager mSensorManager; - @Mock DisplayDeviceConfig mMockDisplayDeviceConfig; + @Mock PackageManagerInternal mMockPackageManagerInternal; + @Mock UserManagerInternal mMockUserManagerInternal; + @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor; @@ -276,6 +279,10 @@ public class DisplayManagerServiceTest { LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); LocalServices.addService( VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); // TODO: b/287945043 mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java new file mode 100644 index 000000000000..1ce79a5b596b --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import static android.os.Process.INVALID_UID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContextWrapper; +import android.content.pm.PackageManagerInternal; +import android.provider.DeviceConfigInterface; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SmallAreaDetectionControllerTest { + + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); + + @Mock + private PackageManagerInternal mMockPackageManagerInternal; + @Mock + private UserManagerInternal mMockUserManagerInternal; + + private SmallAreaDetectionController mSmallAreaDetectionController; + + private static final String PKG_A = "com.a.b.c"; + private static final String PKG_B = "com.d.e.f"; + private static final String PKG_NOT_INSTALLED = "com.not.installed"; + private static final float THRESHOLD_A = 0.05f; + private static final float THRESHOLD_B = 0.07f; + private static final int USER_1 = 110; + private static final int USER_2 = 111; + private static final int UID_A_1 = 11011111; + private static final int UID_A_2 = 11111111; + private static final int UID_B_1 = 11022222; + private static final int UID_B_2 = 11122222; + + @Before + public void setup() { + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); + + when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2}); + when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1); + when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2); + when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1); + when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2); + when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn( + INVALID_UID); + when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn( + INVALID_UID); + + mSmallAreaDetectionController = spy(new SmallAreaDetectionController( + new ContextWrapper(ApplicationProvider.getApplicationContext()), + DeviceConfigInterface.REAL)); + doNothing().when(mSmallAreaDetectionController).updateSmallAreaDetection(any(), any()); + } + + @Test + public void testUpdateAllowlist_validProperty() { + final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B; + mSmallAreaDetectionController.updateAllowlist(property); + + final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2}; + final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + eq(resultThresholdArray)); + } + + @Test + public void testUpdateAllowlist_includeInvalidRow() { + final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B; + mSmallAreaDetectionController.updateAllowlist(property); + + final int[] resultUidArray = {UID_B_1, UID_B_2}; + final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + eq(resultThresholdArray)); + } + + @Test + public void testUpdateAllowlist_includeNotInstalledPkg() { + final String property = + PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B; + mSmallAreaDetectionController.updateAllowlist(property); + + final int[] resultUidArray = {UID_A_1, UID_A_2}; + final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + eq(resultThresholdArray)); + } + + @Test + public void testUpdateAllowlist_invalidProperty() { + final String property = PKG_A; + mSmallAreaDetectionController.updateAllowlist(property); + + verify(mSmallAreaDetectionController, never()).updateSmallAreaDetection(any(), any()); + } +} diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml new file mode 100644 index 000000000000..4785a881638f --- /dev/null +++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml @@ -0,0 +1,10 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<policies setup-complete="true" provisioning-state="3"> + <keep-profiles-running value="false" /> + <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"> + <policies flags="991" /> + <strong-auth-unlock-timeout value="0" /> + <organization-color value="-16738680" /> + <active-password value="0" /> + </admin> +</policies> diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml new file mode 100644 index 000000000000..07ec229fa267 --- /dev/null +++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml @@ -0,0 +1,10 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<policies setup-complete="true" provisioning-state="3"> + <keep-profiles-running value="true" /> + <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"> + <policies flags="991" /> + <strong-auth-unlock-timeout value="0" /> + <organization-color value="-16738680" /> + <active-password value="0" /> + </admin> +</policies> diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java index eb2ee35161ff..d2b921deb0d9 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java @@ -76,7 +76,7 @@ import javax.xml.parsers.DocumentBuilderFactory; public class PolicyVersionUpgraderTest extends DpmTestBase { // NOTE: Only change this value if the corresponding CL also adds a test to test the upgrade // to the new version. - private static final int LATEST_TESTED_VERSION = 5; + private static final int LATEST_TESTED_VERSION = 6; public static final String PERMISSIONS_TAG = "admin-can-grant-sensors-permissions"; public static final String DEVICE_OWNER_XML = "device_owner_2.xml"; private ComponentName mFakeAdmin; @@ -313,7 +313,7 @@ public class PolicyVersionUpgraderTest extends DpmTestBase { } @Test - public void testEffectiveKeepProfilesRunningSet() throws Exception { + public void testEffectiveKeepProfilesRunningSetToFalse4To5() throws Exception { writeVersionToXml(4); final int userId = UserHandle.USER_SYSTEM; @@ -327,10 +327,111 @@ public class PolicyVersionUpgraderTest extends DpmTestBase { Document policies = readPolicies(userId); Element keepProfilesRunning = (Element) policies.getDocumentElement() .getElementsByTagName("keep-profiles-running").item(0); - assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("false"); + + // Default value (false) is not serialized. + assertThat(keepProfilesRunning).isNull(); + } + @Test + public void testEffectiveKeepProfilesRunningIsToFalse4To6() throws Exception { + writeVersionToXml(4); + + final int userId = UserHandle.USER_SYSTEM; + mProvider.mUsers = new int[]{userId}; + preparePoliciesFile(userId, "device_policies.xml"); + + mUpgrader.upgradePolicy(6); + + assertThat(readVersionFromXml()).isAtLeast(6); + + Document policies = readPolicies(userId); + Element keepProfilesRunning = (Element) policies.getDocumentElement() + .getElementsByTagName("keep-profiles-running").item(0); + + // Default value (false) is not serialized. + assertThat(keepProfilesRunning).isNull(); + } + + /** + * Verify correct behaviour when upgrading from Android 13 + */ + @Test + public void testEffectiveKeepProfilesRunningIsToFalse3To6() throws Exception { + writeVersionToXml(3); + + final int userId = UserHandle.USER_SYSTEM; + mProvider.mUsers = new int[]{userId}; + preparePoliciesFile(userId, "device_policies.xml"); + + mUpgrader.upgradePolicy(6); + + assertThat(readVersionFromXml()).isAtLeast(6); + + Document policies = readPolicies(userId); + Element keepProfilesRunning = (Element) policies.getDocumentElement() + .getElementsByTagName("keep-profiles-running").item(0); + + // Default value (false) is not serialized. + assertThat(keepProfilesRunning).isNull(); } @Test + public void testEffectiveKeepProfilesRunningMissingInV5() throws Exception { + writeVersionToXml(5); + + final int userId = UserHandle.USER_SYSTEM; + mProvider.mUsers = new int[]{userId}; + preparePoliciesFile(userId, "device_policies.xml"); + + mUpgrader.upgradePolicy(6); + + assertThat(readVersionFromXml()).isAtLeast(6); + + Document policies = readPolicies(userId); + Element keepProfilesRunning = (Element) policies.getDocumentElement() + .getElementsByTagName("keep-profiles-running").item(0); + assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("true"); + } + + @Test + public void testEffectiveKeepProfilesRunningTrueInV5() throws Exception { + writeVersionToXml(5); + + final int userId = UserHandle.USER_SYSTEM; + mProvider.mUsers = new int[]{userId}; + preparePoliciesFile(userId, "device_policies_keep_profiles_running_true.xml"); + + mUpgrader.upgradePolicy(6); + + assertThat(readVersionFromXml()).isAtLeast(6); + + Document policies = readPolicies(userId); + Element keepProfilesRunning = (Element) policies.getDocumentElement() + .getElementsByTagName("keep-profiles-running").item(0); + assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("true"); + } + + @Test + public void testEffectiveKeepProfilesRunningFalseInV5() throws Exception { + writeVersionToXml(5); + + final int userId = UserHandle.USER_SYSTEM; + mProvider.mUsers = new int[]{userId}; + preparePoliciesFile(userId, "device_policies_keep_profiles_running_false.xml"); + + mUpgrader.upgradePolicy(6); + + assertThat(readVersionFromXml()).isAtLeast(6); + + Document policies = readPolicies(userId); + Element keepProfilesRunning = (Element) policies.getDocumentElement() + .getElementsByTagName("keep-profiles-running").item(0); + + // Default value (false) is not serialized. + assertThat(keepProfilesRunning).isNull(); + } + + + @Test public void isLatestVersionTested() { assertThat(DevicePolicyManagerService.DPMS_VERSION).isEqualTo(LATEST_TESTED_VERSION); } diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 3135215d65f7..a1e1da68bfcc 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java @@ -222,8 +222,10 @@ public class BatteryUsageStatsRule implements TestRule { & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0; final boolean includeProcessStateData = (query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0; + final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold(); BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( - customPowerComponentNames, includePowerModels, includeProcessStateData); + customPowerComponentNames, includePowerModels, includeProcessStateData, + minConsumedPowerThreshold); SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats(); for (int i = 0; i < uidStats.size(); i++) { builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i)); diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java index 266a22632a6d..07c486c6ce58 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java @@ -181,7 +181,7 @@ public class BatteryUsageStatsTest { final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[]{"FOO"}, true).build(); final BatteryUsageStats sum = - new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true) + new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0) .add(stats1) .add(stats2) .build(); @@ -222,7 +222,7 @@ public class BatteryUsageStatsTest { @Test public void testAdd_customComponentMismatch() { final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true); + new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0); final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"BAR"}, false).build(); assertThrows(IllegalArgumentException.class, () -> builder.add(stats)); @@ -231,7 +231,7 @@ public class BatteryUsageStatsTest { @Test public void testAdd_processStateDataMismatch() { final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true); + new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0); final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"FOO"}, false).build(); assertThrows(IllegalArgumentException.class, () -> builder.add(stats)); @@ -260,7 +260,7 @@ public class BatteryUsageStatsTest { final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks); final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true) + new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0) .setBatteryCapacity(4000) .setDischargePercentage(20) .setDischargedPowerRange(1000, 2000) @@ -305,7 +305,7 @@ public class BatteryUsageStatsTest { final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(customPowerComponentNames, true, - includeProcessStateData); + includeProcessStateData, 0); builder.setDischargePercentage(30) .setDischargedPowerRange(1234, 2345) .setStatsStartTimestamp(2000) diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java index 93464cdd13d0..d9bc74dfb1cb 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java @@ -35,7 +35,7 @@ public class FakeServiceConfigAccessor implements ServiceConfigAccessor { private final List<StateChangeListener> mConfigurationInternalChangeListeners = new ArrayList<>(); - private ConfigurationInternal mConfigurationInternal; + private ConfigurationInternal mCurrentUserConfigurationInternal; @Override public void addConfigurationInternalChangeListener(StateChangeListener listener) { @@ -49,21 +49,23 @@ public class FakeServiceConfigAccessor implements ServiceConfigAccessor { @Override public ConfigurationInternal getCurrentUserConfigurationInternal() { - return mConfigurationInternal; + return mCurrentUserConfigurationInternal; } @Override public boolean updateConfiguration( - @UserIdInt int userID, @NonNull TimeConfiguration requestedChanges, + @UserIdInt int userId, @NonNull TimeConfiguration requestedChanges, boolean bypassUserPolicyChecks) { - assertNotNull(mConfigurationInternal); + assertNotNull(mCurrentUserConfigurationInternal); assertNotNull(requestedChanges); + ConfigurationInternal toUpdate = getConfigurationInternal(userId); + // Simulate the real strategy's behavior: the new configuration will be updated to be the // old configuration merged with the new if the user has the capability to up the settings. // Then, if the configuration changed, the change listener is invoked. TimeCapabilitiesAndConfig capabilitiesAndConfig = - mConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks); + toUpdate.createCapabilitiesAndConfig(bypassUserPolicyChecks); TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); TimeConfiguration newConfiguration = @@ -73,28 +75,36 @@ public class FakeServiceConfigAccessor implements ServiceConfigAccessor { } if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) { - mConfigurationInternal = mConfigurationInternal.merge(newConfiguration); + mCurrentUserConfigurationInternal = toUpdate.merge(newConfiguration); // Note: Unlike the real strategy, the listeners are invoked synchronously. - simulateConfigurationChangeForTests(); + notifyConfigurationChange(); } return true; } - void initializeConfiguration(ConfigurationInternal configurationInternal) { - mConfigurationInternal = configurationInternal; + + void initializeCurrentUserConfiguration(ConfigurationInternal configurationInternal) { + mCurrentUserConfigurationInternal = configurationInternal; } - void simulateConfigurationChangeForTests() { - for (StateChangeListener listener : mConfigurationInternalChangeListeners) { - listener.onChange(); - } + void simulateCurrentUserConfigurationInternalChange( + ConfigurationInternal configurationInternal) { + mCurrentUserConfigurationInternal = configurationInternal; + // Note: Unlike the real strategy, the listeners are invoked synchronously. + notifyConfigurationChange(); } @Override public ConfigurationInternal getConfigurationInternal(int userId) { assertEquals("Multi-user testing not supported currently", - userId, mConfigurationInternal.getUserId()); - return mConfigurationInternal; + userId, mCurrentUserConfigurationInternal.getUserId()); + return mCurrentUserConfigurationInternal; + } + + private void notifyConfigurationChange() { + for (StateChangeListener listener : mConfigurationInternalChangeListeners) { + listener.onChange(); + } } } diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java index 87aa2725491b..a7a9c0cd86f2 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java @@ -18,6 +18,8 @@ package com.android.server.timedetector; import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; +import android.app.time.TimeCapabilitiesAndConfig; +import android.app.time.TimeConfiguration; import android.app.time.TimeState; import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; @@ -31,10 +33,20 @@ import com.android.server.timezonedetector.StateChangeListener; * in tests. */ public class FakeTimeDetectorStrategy implements TimeDetectorStrategy { + private final FakeServiceConfigAccessor mFakeServiceConfigAccessor; + // State private TimeState mTimeState; private NetworkTimeSuggestion mLatestNetworkTimeSuggestion; + FakeTimeDetectorStrategy() { + mFakeServiceConfigAccessor = new FakeServiceConfigAccessor(); + } + + void initializeConfiguration(ConfigurationInternal configuration) { + mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration); + } + @Override public TimeState getTimeState() { return mTimeState; @@ -51,6 +63,26 @@ public class FakeTimeDetectorStrategy implements TimeDetectorStrategy { } @Override + public void addChangeListener(StateChangeListener listener) { + mFakeServiceConfigAccessor.addConfigurationInternalChangeListener(listener); + } + + @Override + public TimeCapabilitiesAndConfig getCapabilitiesAndConfig(int userId, + boolean bypassUserPolicyChecks) { + ConfigurationInternal configurationInternal = + mFakeServiceConfigAccessor.getConfigurationInternal(userId); + return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks); + } + + @Override + public boolean updateConfiguration(int userId, TimeConfiguration configuration, + boolean bypassUserPolicyChecks) { + return mFakeServiceConfigAccessor.updateConfiguration( + userId, configuration, bypassUserPolicyChecks); + } + + @Override public void suggestTelephonyTime(TelephonyTimeSuggestion suggestion) { } diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java index a0845a64757e..de5a37b43df8 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java @@ -89,7 +89,7 @@ public class TimeDetectorInternalImplTest { public void testGetCapabilitiesAndConfigForDpm() throws Exception { final boolean autoDetectionEnabled = true; ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled); - mFakeServiceConfigAccessorSpy.initializeConfiguration(testConfig); + mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(testConfig); TimeCapabilitiesAndConfig actualCapabilitiesAndConfig = mTimeDetectorInternal.getCapabilitiesAndConfigForDpm(); @@ -108,7 +108,8 @@ public class TimeDetectorInternalImplTest { final boolean autoDetectionEnabled = false; ConfigurationInternal initialConfigurationInternal = createConfigurationInternal(autoDetectionEnabled); - mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfigurationInternal); + mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration( + initialConfigurationInternal); TimeConfiguration timeConfiguration = new TimeConfiguration.Builder() .setAutoDetectionEnabled(true) diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index daa682342836..6b2d4b01dd08 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -83,7 +83,6 @@ public class TimeDetectorServiceTest { private HandlerThread mHandlerThread; private TestHandler mTestHandler; private TestCallerIdentityInjector mTestCallerIdentityInjector; - private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy; private FakeTimeDetectorStrategy mFakeTimeDetectorStrategySpy; private NtpTrustedTime mMockNtpTrustedTime; @@ -101,13 +100,12 @@ public class TimeDetectorServiceTest { mTestCallerIdentityInjector = new TestCallerIdentityInjector(); mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID); - mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor()); mFakeTimeDetectorStrategySpy = spy(new FakeTimeDetectorStrategy()); mMockNtpTrustedTime = mock(NtpTrustedTime.class); mTimeDetectorService = new TimeDetectorService( mMockContext, mTestHandler, mTestCallerIdentityInjector, - mFakeServiceConfigAccessorSpy, mFakeTimeDetectorStrategySpy, mMockNtpTrustedTime); + mFakeTimeDetectorStrategySpy, mMockNtpTrustedTime); } @After @@ -132,14 +130,14 @@ public class TimeDetectorServiceTest { ConfigurationInternal configuration = createConfigurationInternal(true /* autoDetectionEnabled*/); - mFakeServiceConfigAccessorSpy.initializeConfiguration(configuration); + mFakeTimeDetectorStrategySpy.initializeConfiguration(configuration); TimeCapabilitiesAndConfig actualCapabilitiesAndConfig = mTimeDetectorService.getCapabilitiesAndConfig(); verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString()); int expectedUserId = mTestCallerIdentityInjector.getCallingUserId(); - verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId); + verify(mFakeTimeDetectorStrategySpy).getCapabilitiesAndConfig(expectedUserId, false); boolean bypassUserPolicyChecks = false; TimeCapabilitiesAndConfig expectedCapabilitiesAndConfig = @@ -174,7 +172,7 @@ public class TimeDetectorServiceTest { public void testListenerRegistrationAndCallbacks() throws Exception { ConfigurationInternal initialConfiguration = createConfigurationInternal(false /* autoDetectionEnabled */); - mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfiguration); + mFakeTimeDetectorStrategySpy.initializeConfiguration(initialConfiguration); IBinder mockListenerBinder = mock(IBinder.class); ITimeDetectorListener mockListener = mock(ITimeDetectorListener.class); diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index 4df21e00d61c..dd58135a8e87 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -29,14 +29,22 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; +import android.app.time.TimeCapabilitiesAndConfig; +import android.app.time.TimeConfiguration; import android.app.time.TimeState; import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.TelephonyTimeSuggestion; import android.os.TimestampedValue; +import android.util.IndentingPrintWriter; import com.android.server.SystemClockTime.TimeConfidence; import com.android.server.timedetector.TimeDetectorStrategy.Origin; @@ -47,14 +55,12 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.io.PrintWriter; import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import junitparams.JUnitParamsRunner; import junitparams.Parameters; @@ -120,13 +126,108 @@ public class TimeDetectorStrategyImplTest { .build(); private FakeEnvironment mFakeEnvironment; + private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy; + private TimeDetectorStrategyImpl mTimeDetectorStrategy; @Before public void setUp() { mFakeEnvironment = new FakeEnvironment(); - mFakeEnvironment.initializeConfig(CONFIG_AUTO_DISABLED); mFakeEnvironment.initializeFakeClocks( ARBITRARY_CLOCK_INITIALIZATION_INFO, TIME_CONFIDENCE_LOW); + + mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor()); + mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(CONFIG_AUTO_DISABLED); + + mTimeDetectorStrategy = new TimeDetectorStrategyImpl( + mFakeEnvironment, mFakeServiceConfigAccessorSpy); + } + + @Test + public void testChangeListenerBehavior() throws Exception { + TestStateChangeListener stateChangeListener = new TestStateChangeListener(); + mTimeDetectorStrategy.addChangeListener(stateChangeListener); + + boolean bypassUserPolicyChecks = false; + + // Report a config change, but not one that actually changes anything. + { + mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange( + CONFIG_AUTO_DISABLED); + assertStateChangeNotificationsSent(stateChangeListener, 0); + assertEquals(CONFIG_AUTO_DISABLED, + mTimeDetectorStrategy.getCachedCapabilitiesAndConfigForTests()); + } + + // Report a config change that actually changes something. + { + mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange( + CONFIG_AUTO_ENABLED); + assertStateChangeNotificationsSent(stateChangeListener, 1); + assertEquals(CONFIG_AUTO_ENABLED, + mTimeDetectorStrategy.getCachedCapabilitiesAndConfigForTests()); + } + + // Perform a (current user) update via the strategy. + { + TimeConfiguration requestedChanges = + new TimeConfiguration.Builder().setAutoDetectionEnabled(false).build(); + mTimeDetectorStrategy.updateConfiguration( + ARBITRARY_USER_ID, requestedChanges, bypassUserPolicyChecks); + assertStateChangeNotificationsSent(stateChangeListener, 1); + } + } + + // Current user behavior: the strategy caches and returns the latest configuration. + @Test + public void testReadAndWriteConfiguration() throws Exception { + ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED; + mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange( + currentUserConfig); + + final boolean bypassUserPolicyChecks = false; + + ConfigurationInternal cachedConfigurationInternal = + mTimeDetectorStrategy.getCachedCapabilitiesAndConfigForTests(); + assertEquals(currentUserConfig, cachedConfigurationInternal); + + // Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor. + { + reset(mFakeServiceConfigAccessorSpy); + TimeCapabilitiesAndConfig actualCapabilitiesAndConfig = + mTimeDetectorStrategy.getCapabilitiesAndConfig( + currentUserConfig.getUserId(), bypassUserPolicyChecks); + verify(mFakeServiceConfigAccessorSpy, never()).getConfigurationInternal( + currentUserConfig.getUserId()); + + TimeCapabilitiesAndConfig expectedCapabilitiesAndConfig = + currentUserConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks); + assertEquals(expectedCapabilitiesAndConfig.getCapabilities(), + actualCapabilitiesAndConfig.getCapabilities()); + assertEquals(expectedCapabilitiesAndConfig.getConfiguration(), + actualCapabilitiesAndConfig.getConfiguration()); + } + + // Confirm updateConfiguration() calls through to the ServiceConfigAccessor and updates + // the cached copy. + { + boolean newAutoDetectionEnabled = + !cachedConfigurationInternal.getAutoDetectionEnabledBehavior(); + TimeConfiguration requestedChanges = new TimeConfiguration.Builder() + .setAutoDetectionEnabled(newAutoDetectionEnabled) + .build(); + ConfigurationInternal expectedConfigAfterChange = + new ConfigurationInternal.Builder(cachedConfigurationInternal) + .setAutoDetectionEnabledSetting(newAutoDetectionEnabled) + .build(); + + reset(mFakeServiceConfigAccessorSpy); + mTimeDetectorStrategy.updateConfiguration( + currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks); + verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration( + currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks); + assertEquals(expectedConfigAfterChange, + mTimeDetectorStrategy.getCachedCapabilitiesAndConfigForTests()); + } } @Test @@ -1939,20 +2040,14 @@ public class TimeDetectorStrategyImplTest { private final List<Runnable> mAsyncRunnables = new ArrayList<>(); - private ConfigurationInternal mConfigurationInternal; private boolean mWakeLockAcquired; private long mElapsedRealtimeMillis; private long mSystemClockMillis; private int mSystemClockConfidence = TIME_CONFIDENCE_LOW; - private StateChangeListener mConfigurationInternalChangeListener; // Tracking operations. private boolean mSystemClockWasSet; - void initializeConfig(ConfigurationInternal configurationInternal) { - mConfigurationInternal = configurationInternal; - } - public void initializeFakeClocks( TimestampedValue<Instant> timeInfo, @TimeConfidence int timeConfidence) { pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis()); @@ -1960,16 +2055,6 @@ public class TimeDetectorStrategyImplTest { } @Override - public void setConfigurationInternalChangeListener(StateChangeListener listener) { - mConfigurationInternalChangeListener = Objects.requireNonNull(listener); - } - - @Override - public ConfigurationInternal getCurrentUserConfigurationInternal() { - return mConfigurationInternal; - } - - @Override public void acquireWakeLock() { if (mWakeLockAcquired) { fail("Wake lock already acquired"); @@ -2019,7 +2104,7 @@ public class TimeDetectorStrategyImplTest { } @Override - public void dumpDebugLog(PrintWriter printWriter) { + public void dumpDebugLog(IndentingPrintWriter pw) { // No-op for tests } @@ -2037,11 +2122,6 @@ public class TimeDetectorStrategyImplTest { mAsyncRunnables.clear(); } - void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) { - mConfigurationInternal = configurationInternal; - mConfigurationInternalChangeListener.onChange(); - } - void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) { mElapsedRealtimeMillis = elapsedRealtimeMillis; } @@ -2095,13 +2175,6 @@ public class TimeDetectorStrategyImplTest { */ private class Script { - private final TimeDetectorStrategyImpl mTimeDetectorStrategy; - - Script() { - mFakeEnvironment = new FakeEnvironment(); - mTimeDetectorStrategy = new TimeDetectorStrategyImpl(mFakeEnvironment); - } - Script pokeFakeClocks(TimestampedValue<Instant> initialClockTime, @TimeConfidence int timeConfidence) { mFakeEnvironment.pokeElapsedRealtimeMillis(initialClockTime.getReferenceTimeMillis()); @@ -2122,7 +2195,8 @@ public class TimeDetectorStrategyImplTest { * Simulates the user / user's configuration changing. */ Script simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) { - mFakeEnvironment.simulateConfigurationInternalChange(configurationInternal); + mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange( + configurationInternal); return this; } @@ -2167,14 +2241,15 @@ public class TimeDetectorStrategyImplTest { Script simulateAutoTimeDetectionToggle() { ConfigurationInternal configurationInternal = - mFakeEnvironment.getCurrentUserConfigurationInternal(); + mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal(); boolean autoDetectionEnabledSetting = !configurationInternal.getAutoDetectionEnabledSetting(); ConfigurationInternal newConfigurationInternal = new ConfigurationInternal.Builder(configurationInternal) .setAutoDetectionEnabledSetting(autoDetectionEnabledSetting) .build(); - mFakeEnvironment.simulateConfigurationInternalChange(newConfigurationInternal); + mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange( + newConfigurationInternal); return this; } @@ -2389,4 +2464,12 @@ public class TimeDetectorStrategyImplTest { return LocalDateTime.of(year, monthInYear, day, hourOfDay, minute, second) .toInstant(ZoneOffset.UTC); } + + private void assertStateChangeNotificationsSent( + TestStateChangeListener stateChangeListener, int expectedCount) { + // The fake environment needs to be told to run posted work. + mFakeEnvironment.runAsyncRunnables(); + + stateChangeListener.assertNotificationsReceivedAndReset(expectedCount); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 1ba8f7db531e..9c754b969604 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -1679,9 +1679,10 @@ public class ActivityStarterTests extends WindowTestsBase { @Test public void testResultCanceledWhenNotAllowedStartingActivity() { + final Task task = new TaskBuilder(mSupervisor).build(); final ActivityStarter starter = prepareStarter(0, false); final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build(); - final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).build(); + final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setTask(task).build(); targetRecord.resultTo = sourceRecord; // Abort the activity start and ensure the sourceRecord gets the result (RESULT_CANCELED). diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java index 06033c7ebf75..3fcec963593c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java @@ -184,7 +184,7 @@ public class LetterboxConfigurationPersisterTest { LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); firstPersister.setLetterboxPositionForVerticalReachability(false, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP); - waitForCompletion(mPersisterQueue); + waitForCompletion(firstPersisterQueue); final int newPositionForHorizontalReachability = firstPersister.getLetterboxPositionForHorizontalReachability(false); final int newPositionForVerticalReachability = diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java index e1fc0cfdc317..80e169d8d579 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java @@ -250,4 +250,42 @@ public class LetterboxConfigurationTest { times(expectedTime)).setLetterboxPositionForVerticalReachability(halfFoldPose, expected); } + + @Test + public void test_letterboxPositionWhenReachabilityEnabledIsReset() { + // Check that horizontal reachability is set with correct arguments + mLetterboxConfiguration.resetPersistentLetterboxPositionForHorizontalReachability(); + verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability( + false /* forBookMode */, + LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER); + verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability( + true /* forBookMode */, + LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); + + // Check that vertical reachability is set with correct arguments + mLetterboxConfiguration.resetPersistentLetterboxPositionForVerticalReachability(); + verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability( + false /* forTabletopMode */, + LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER); + verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability( + true /* forTabletopMode */, + LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP); + } + + @Test + public void test_lettterboxPositionWhenReachabilityEnabledIsSet() { + // Check that horizontal reachability is set with correct arguments + mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability( + false /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); + verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability( + false /* forBookMode */, + LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); + + // Check that vertical reachability is set with correct arguments + mLetterboxConfiguration.setPersistentLetterboxPositionForVerticalReachability( + false /* forTabletopMode */, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP); + verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability( + false /* forTabletopMode */, + LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index 739737eb318d..07cfbf094e5d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -678,6 +678,39 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { WINDOWING_MODE_FULLSCREEN); } + @Test + public void testInheritsSourceTaskWindowingModeWhenActivityIsInDifferentWindowingMode() { + final TestDisplayContent fullscreenDisplay = createNewDisplayContent( + WINDOWING_MODE_FULLSCREEN); + final ActivityRecord source = createSourceActivity(fullscreenDisplay); + source.setWindowingMode(WINDOWING_MODE_PINNED); + source.getTask().setWindowingMode(WINDOWING_MODE_FREEFORM); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder().setSource(source).calculate()); + + assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode, + WINDOWING_MODE_FULLSCREEN); + } + + @Test + public void testDoesNotInheritsSourceTaskWindowingModeWhenActivityIsInFreeformWindowingMode() { + // The activity could end up in different windowing mode state after calling finish() + // while the task would still hold the WINDOWING_MODE_PINNED state, or in other words + // be still in the Picture in Picture mode. + final TestDisplayContent fullscreenDisplay = createNewDisplayContent( + WINDOWING_MODE_FULLSCREEN); + final ActivityRecord source = createSourceActivity(fullscreenDisplay); + source.setWindowingMode(WINDOWING_MODE_FREEFORM); + source.getTask().setWindowingMode(WINDOWING_MODE_PINNED); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder().setSource(source).calculate()); + + assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode, + WINDOWING_MODE_FULLSCREEN); + } + @Test public void testKeepsPictureInPictureLaunchModeInOptions() { diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java index fe2fe0b40891..08430f2f2744 100644 --- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java +++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java @@ -159,7 +159,7 @@ public class BatteryUsageStatsPerfTest { private static BatteryUsageStats buildBatteryUsageStats() { final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false) + new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, 0) .setBatteryCapacity(4000) .setDischargePercentage(20) .setDischargedPowerRange(1000, 2000) |