Merge "Allow SearchBox title to scale to system font size" into udc-qpr-dev
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index cd45f4d..b4f4a7e 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -212,6 +212,11 @@
}
/** @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 @@
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 7a153ef..c5f5614 100644
--- a/core/java/android/os/AggregateBatteryConsumer.java
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -116,8 +116,9 @@
* 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 0ba8d51..ca84b35 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -795,11 +795,12 @@
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 e2c52ce..cc37b54 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -707,7 +707,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 b3f4d98..49d7e8b 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -80,6 +80,7 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 @@
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 @@
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 @@
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 5dffa0a..9e5f539 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -461,9 +461,11 @@
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 @@
@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 @@
*/
@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 103452d..03a1b6f 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -207,17 +207,18 @@
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 6b4a5cf..a2ff078 100644
--- a/core/java/android/os/UserBatteryConsumer.java
+++ b/core/java/android/os/UserBatteryConsumer.java
@@ -107,8 +107,8 @@
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 1cf41cf..d66ffce 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5164,7 +5164,6 @@
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 5127f05..dfa0000 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -1155,9 +1155,15 @@
}
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 @@
*/
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 963e806..d680d04 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -50,6 +50,8 @@
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.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.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 @@
}
/**
+ * 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 @@
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 @@
private final InsetsController mInsetsController;
private final ImeFocusController mImeFocusController;
+ private ViewRootRefreshRateController mRefreshRateController;
+
private boolean mIsSurfaceOpaque;
private final BackgroundBlurDrawable.Aggregator mBlurRegionAggregator =
@@ -1048,6 +1148,13 @@
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 @@
if (!mIsInTraversal) {
scheduleTraversals();
}
+
+ if (!mInsetsController.getState().isSourceOrDefaultVisible(ID_IME, Type.ime())) {
+ notifyLeaveTypingEvent();
+ }
}
@Override
@@ -6850,6 +6961,17 @@
}
/**
+ * 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 @@
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 0000000..cb9a81c
--- /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 e9d7b9b..364adc7 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -28,6 +28,7 @@
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 @@
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 @@
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 @@
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 @@
return;
}
ic.commitText(text, newCursorPosition);
+ notifyTypingHint(true /* isTyping */);
});
}
@@ -783,6 +799,7 @@
return;
}
ic.setComposingText(text, newCursorPosition);
+ notifyTypingHint(true /* isTyping */);
});
}
@@ -910,6 +927,7 @@
return;
}
ic.deleteSurroundingText(beforeLength, afterLength);
+ notifyTypingHint(true /* isTyping */);
});
}
@@ -1473,4 +1491,16 @@
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 d1a4935..37d2fa0 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 85529d6..bed19a8 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 4184e79..478b01c 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 79964b3..d238a130 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1500,12 +1500,8 @@
<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 81fbc38..644597c 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 0676f89..aaaa3c7 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 @@
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 @@
@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 a358c4f..57f0920 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 0000000..ad57fc1
--- /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 0000000..2b11851
--- /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 0000000..d278bc3
--- /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 896fe61..d894487 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 @@
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 @@
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 @@
}
@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 43acdd5..ff369c8 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -40,7 +40,6 @@
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 @@
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 @@
}
}
- /**
- * 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 15446b6..3790490 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.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 @@
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 @@
mAbortInstall = true;
}
- checkDevicePolicyRestriction();
+ checkDevicePolicyRestrictions();
final String installerPackageNameFromIntent = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
@@ -149,7 +147,9 @@
if (mAbortInstall) {
setResult(RESULT_CANCELED);
- finish();
+ if (mShouldFinish) {
+ finish();
+ }
return;
}
@@ -291,58 +291,52 @@
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);
- }
- mAbortInstall = true;
- startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
- return;
- }
+ private void checkDevicePolicyRestrictions() {
+ final String[] restrictions = new String[] {
+ UserManager.DISALLOW_INSTALL_APPS,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
+ };
- 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");
+ final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
+ for (String restriction : restrictions) {
+ if (!mUserManager.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
+ continue;
+ }
+
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 @@
/**
* 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 67f4418..0a33b9b 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 @@
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 @@
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 765edd7..e45913c 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.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.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.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 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 @@
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 @@
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 @@
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 @@
}
if (someSettingChanged) {
settingsState.persistSyncLocked();
+ success = true;
}
}
} break;
@@ -3321,6 +3384,7 @@
}
if (someSettingChanged) {
settingsState.persistSyncLocked();
+ success = true;
}
}
} break;
@@ -3348,6 +3412,7 @@
}
if (someSettingChanged) {
settingsState.persistSyncLocked();
+ success = true;
}
}
} break;
@@ -3372,10 +3437,12 @@
}
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 a9d2b30..4be572f 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.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 @@
private final FeatureFlags mFeatureFlags;
private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
- private final ActivityStarter mActivityStarter;
private String mPackageName;
private int mUid;
@@ -89,10 +87,8 @@
@Inject
public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
- Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
- ActivityStarter activityStarter) {
+ Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
mFeatureFlags = featureFlags;
- mActivityStarter = activityStarter;
mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
}
@@ -313,16 +309,8 @@
// 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 37f032b..2c15e27 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 @@
// 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 @@
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 @@
// 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 e76f26d..e6f8c48 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 @@
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 dc6f858..2c745ae 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 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.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 @@
.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 @@
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 898a3c4..6546f6e 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 @@
private SensorManager mSensorManager;
private BrightnessTracker mBrightnessTracker;
+ private SmallAreaDetectionController mSmallAreaDetectionController;
+
// Whether minimal post processing is allowed by the user.
@GuardedBy("mSyncRoot")
@@ -731,6 +733,8 @@
filter.addAction(Intent.ACTION_DOCK_EVENT);
mContext.registerReceiver(mIdleModeReceiver, filter);
+
+ mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext);
}
@VisibleForTesting
@@ -3046,6 +3050,9 @@
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 0000000..adaa539
--- /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 d662aae..204e358 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 @@
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 297ad73..c24d523 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 @@
}
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 @@
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 ebd4aec..5e5f6f2 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 @@
&& 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 @@
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 4f221b5..9718194 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 @@
&& 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 @@
@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 fc960d8..c52f8f8 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.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 @@
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 @@
}
@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 @@
}
@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 22f096b..d88f426 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 @@
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 @@
@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 @@
@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 @@
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 @@
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 @@
}
}
- 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 11cec66..15c0a80 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.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 @@
*/
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 b293bac..fd35df6 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.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.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 @@
@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 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 @@
/** 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 @@
@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 @@
}
}
+ @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 @@
doAutoTimeDetection(reason);
}
- private synchronized void handleConfigurationInternalChanged() {
- ConfigurationInternal currentUserConfig =
- mEnvironment.getCurrentUserConfigurationInternal();
- String logMsg = "handleConfigurationInternalChanged:"
- + " oldConfiguration=" + mCurrentConfigurationInternal
- + ", newConfiguration=" + currentUserConfig;
- addDebugLogEntry(logMsg);
- mCurrentConfigurationInternal = currentUserConfig;
+ /**
+ * Handles a configuration change notification.
+ */
+ private synchronized void handleConfigurationInternalMaybeChanged() {
+ String logMsg = "handleConfigurationInternalMaybeChanged:";
+ updateCurrentConfigurationInternalIfRequired(logMsg);
+ }
- 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;
+ @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 @@
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 @@
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 9e3a611..97b3e32 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 @@
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 ad46770..bd39130 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 @@
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 ceebb27..bfe0553 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 @@
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 @@
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 @@
case "defaultPositionForVerticalReachability":
mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
break;
+ case "persistentPositionForHorizontalReachability":
+ mLetterboxConfiguration
+ .resetPersistentLetterboxPositionForHorizontalReachability();
+ break;
+ case "persistentPositionForVerticalReachability":
+ mLetterboxConfiguration
+ .resetPersistentLetterboxPositionForVerticalReachability();
+ break;
case "isEducationEnabled":
mLetterboxConfiguration.resetIsEducationEnabled();
break;
@@ -1206,6 +1284,8 @@
mLetterboxConfiguration.resetEnabledAutomaticReachabilityInBookMode();
mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability();
mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
+ mLetterboxConfiguration.resetPersistentLetterboxPositionForHorizontalReachability();
+ mLetterboxConfiguration.resetPersistentLetterboxPositionForVerticalReachability();
mLetterboxConfiguration.resetIsEducationEnabled();
mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
mLetterboxConfiguration.resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
@@ -1233,6 +1313,12 @@
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 @@
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 @@
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 d5217c8..77db960 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -41,6 +41,7 @@
"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 0000000..b256f16
--- /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 290ad8d..63c3b4d 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -66,6 +66,7 @@
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 @@
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 f3c2de6..b104185 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 @@
// 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 @@
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 @@
}
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 06f11be..913860c 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 @@
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 @@
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 a421db0..8eb1b0b 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -27,6 +27,7 @@
"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 50cf169..14586ae 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.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.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 @@
@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 @@
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 0000000..1ce79a5
--- /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 0000000..4785a88
--- /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 0000000..07ec229
--- /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 eb2ee35..d2b921d 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 @@
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 @@
}
@Test
- public void testEffectiveKeepProfilesRunningSet() throws Exception {
+ public void testEffectiveKeepProfilesRunningSetToFalse4To5() throws Exception {
writeVersionToXml(4);
final int userId = UserHandle.USER_SYSTEM;
@@ -327,8 +327,109 @@
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() {
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 3135215..a1e1da6 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 @@
& 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 266a226..07c486c 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 @@
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 @@
@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 @@
@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 @@
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 @@
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 93464cd..d9bc74d 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 @@
private final List<StateChangeListener> mConfigurationInternalChangeListeners =
new ArrayList<>();
- private ConfigurationInternal mConfigurationInternal;
+ private ConfigurationInternal mCurrentUserConfigurationInternal;
@Override
public void addConfigurationInternalChangeListener(StateChangeListener listener) {
@@ -49,21 +49,23 @@
@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 @@
}
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 87aa272..a7a9c0c 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 @@
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 @@
* 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 @@
}
@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 a0845a6..de5a37b 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 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 @@
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 daa6823..6b2d4b0 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 @@
private HandlerThread mHandlerThread;
private TestHandler mTestHandler;
private TestCallerIdentityInjector mTestCallerIdentityInjector;
- private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeTimeDetectorStrategy mFakeTimeDetectorStrategySpy;
private NtpTrustedTime mMockNtpTrustedTime;
@@ -101,13 +100,12 @@
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 @@
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 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 4df21e0..dd58135 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.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.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 @@
.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 @@
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 @@
}
@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 @@
}
@Override
- public void dumpDebugLog(PrintWriter printWriter) {
+ public void dumpDebugLog(IndentingPrintWriter pw) {
// No-op for tests
}
@@ -2037,11 +2122,6 @@
mAsyncRunnables.clear();
}
- void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
- mConfigurationInternal = configurationInternal;
- mConfigurationInternalChangeListener.onChange();
- }
-
void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
mElapsedRealtimeMillis = elapsedRealtimeMillis;
}
@@ -2095,13 +2175,6 @@
*/
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 @@
* Simulates the user / user's configuration changing.
*/
Script simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
- mFakeEnvironment.simulateConfigurationInternalChange(configurationInternal);
+ mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+ configurationInternal);
return this;
}
@@ -2167,14 +2241,15 @@
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 @@
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 1ba8f7d..9c754b9 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 @@
@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 06033c7..3fcec96 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 @@
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 e1fc0cf..80e169d 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 @@
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 739737e..07cfbf0 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 @@
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 fe2fe0b..08430f2 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 @@
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)