Merge "Fix DualDisplayAreaGroupPolicyTest for non-rotatable devices"
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 0746176..c336e62 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -363,6 +363,11 @@
private Location mLastGenericLocation;
@GuardedBy("this")
private Location mLastGpsLocation;
+ @GuardedBy("this")
+ private boolean mBatterySaverEnabled;
+ @GuardedBy("this")
+ private boolean mIsOffBody;
+ private Sensor mOffBodySensor;
/** Time in the elapsed realtime timebase when this listener last received a motion event. */
@GuardedBy("this")
@@ -421,6 +426,7 @@
private static final int ACTIVE_REASON_FORCED = 6;
private static final int ACTIVE_REASON_ALARM = 7;
private static final int ACTIVE_REASON_EMERGENCY_CALL = 8;
+ private static final int ACTIVE_REASON_ONBODY = 9;
@VisibleForTesting
static String stateToString(int state) {
@@ -817,6 +823,55 @@
}
}
+ /**
+ * LowLatencyOffBodyListener monitors if a device is on body or off body.
+ */
+ @VisibleForTesting
+ final class LowLatencyOffBodyListener implements SensorEventListener {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (DEBUG) {
+ Slog.d(TAG, "LowLatencyOffBodyListener detects onSensorChanged event, values are: "
+ + Arrays.toString(event.values));
+ }
+ if (event.values == null || event.values.length == 0) {
+ // The event returned should contain a single value to indicate off-body state.
+ // No value indicates something went wrong. Take no action and log an error.
+ Slog.e(TAG,
+ "LowLatencyOffBodyListener detects onSensorChanged event but no event "
+ + "value returns.");
+ return;
+ }
+ synchronized (DeviceIdleController.this) {
+ mIsOffBody = (event.values[0] == 0);
+ // Get into quick doze faster when the device is off body instead of taking
+ // traditional multi-stage approach.
+ updateQuickDozeFlagLocked();
+ if (!mIsOffBody && !mBatterySaverEnabled) {
+ mActiveReason = ACTIVE_REASON_ONBODY;
+ becomeActiveLocked("onbody", Process.myUid());
+ }
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {}
+
+ public void registerLocked() {
+ mOffBodySensor =
+ mSensorManager.getDefaultSensor(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT, true);
+ if (mOffBodySensor == null) {
+ Slog.w(TAG, "Body sensor is NULL, unable to register mOffBodySensor.");
+ return;
+ }
+ mSensorManager.registerListener(this, mOffBodySensor,
+ SensorManager.SENSOR_DELAY_NORMAL);
+ }
+ }
+
+ @VisibleForTesting
+ final LowLatencyOffBodyListener mLowLatencyOffBodyListener = new LowLatencyOffBodyListener();
+
@VisibleForTesting
final class MotionListener extends TriggerEventListener
implements SensorEventListener {
@@ -978,6 +1033,7 @@
*/
private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock";
private static final String KEY_USE_WINDOW_ALARMS = "use_window_alarms";
+ private static final String KEY_USE_BODY_SENSOR = "use_body_sensor";
private long mDefaultFlexTimeShort =
!COMPRESS_TIME ? 60 * 1000L : 5 * 1000L;
@@ -1037,6 +1093,7 @@
private long mDefaultNotificationAllowlistDurationMs = 30 * 1000L;
private boolean mDefaultWaitForUnlock = true;
private boolean mDefaultUseWindowAlarms = true;
+ private boolean mDefaultUseBodySensor = false;
/**
* A somewhat short alarm window size that we will tolerate for various alarm timings.
@@ -1277,6 +1334,11 @@
*/
public boolean USE_WINDOW_ALARMS = mDefaultUseWindowAlarms;
+ /**
+ * Whether to use an on/off body signal to affect state transition policy.
+ */
+ public boolean USE_BODY_SENSOR = mDefaultUseBodySensor;
+
private final boolean mSmallBatteryDevice;
public Constants() {
@@ -1383,6 +1445,8 @@
com.android.internal.R.bool.device_idle_wait_for_unlock);
mDefaultUseWindowAlarms = res.getBoolean(
com.android.internal.R.bool.device_idle_use_window_alarms);
+ mDefaultUseBodySensor = res.getBoolean(
+ com.android.internal.R.bool.device_idle_use_body_sensor);
FLEX_TIME_SHORT = mDefaultFlexTimeShort;
LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mDefaultLightIdleAfterInactiveTimeout;
@@ -1416,6 +1480,7 @@
NOTIFICATION_ALLOWLIST_DURATION_MS = mDefaultNotificationAllowlistDurationMs;
WAIT_FOR_UNLOCK = mDefaultWaitForUnlock;
USE_WINDOW_ALARMS = mDefaultUseWindowAlarms;
+ USE_BODY_SENSOR = mDefaultUseBodySensor;
}
private long getTimeout(long defTimeout, long compTimeout) {
@@ -1577,6 +1642,10 @@
USE_WINDOW_ALARMS = properties.getBoolean(
KEY_USE_WINDOW_ALARMS, mDefaultUseWindowAlarms);
break;
+ case KEY_USE_BODY_SENSOR:
+ USE_BODY_SENSOR = properties.getBoolean(
+ KEY_USE_BODY_SENSOR, mDefaultUseBodySensor);
+ break;
default:
Slog.e(TAG, "Unknown configuration key: " + name);
break;
@@ -1713,6 +1782,9 @@
pw.print(" "); pw.print(KEY_USE_WINDOW_ALARMS); pw.print("=");
pw.println(USE_WINDOW_ALARMS);
+
+ pw.print(" "); pw.print(KEY_USE_BODY_SENSOR); pw.print("=");
+ pw.println(USE_BODY_SENSOR);
}
}
@@ -2577,15 +2649,19 @@
mPowerSaveWhitelistAllAppIdArray, mPowerSaveWhitelistExceptIdleAppIdArray);
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
+ if (mConstants.USE_BODY_SENSOR) {
+ mLowLatencyOffBodyListener.registerLocked();
+ }
mLocalPowerManager.registerLowPowerModeObserver(ServiceType.QUICK_DOZE,
state -> {
synchronized (DeviceIdleController.this) {
- updateQuickDozeFlagLocked(state.batterySaverEnabled);
+ mBatterySaverEnabled = state.batterySaverEnabled;
+ updateQuickDozeFlagLocked();
}
});
- updateQuickDozeFlagLocked(
- mLocalPowerManager.getLowPowerState(
- ServiceType.QUICK_DOZE).batterySaverEnabled);
+ mBatterySaverEnabled = mLocalPowerManager.getLowPowerState(
+ ServiceType.QUICK_DOZE).batterySaverEnabled;
+ updateQuickDozeFlagLocked();
mLocalActivityTaskManager.registerScreenObserver(mScreenObserver);
@@ -3274,6 +3350,17 @@
}
}
+ /** Calls to {@link #updateQuickDozeFlagLocked(boolean)} by considering appropriate signals. */
+ @GuardedBy("this")
+ private void updateQuickDozeFlagLocked() {
+ if (mConstants.USE_BODY_SENSOR) {
+ // Only disable the quick doze flag when the device is on body and battery saver is off.
+ updateQuickDozeFlagLocked(mIsOffBody || mBatterySaverEnabled);
+ } else {
+ updateQuickDozeFlagLocked(mBatterySaverEnabled);
+ }
+ }
+
/** Updates the quick doze flag and enters deep doze if appropriate. */
@VisibleForTesting
@GuardedBy("this")
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index c540517..0a7bffc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -49,8 +49,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.BitUtils;
+import com.android.modules.expresslog.Histogram;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.IoThread;
import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
import com.android.server.job.controllers.JobStatus;
@@ -94,6 +96,7 @@
/** Threshold to adjust how often we want to write to the db. */
private static final long JOB_PERSIST_DELAY = 2000L;
+ private static final long SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS = 30 * 60_000L;
@VisibleForTesting
static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
private static final int ALL_UIDS = -1;
@@ -131,6 +134,30 @@
private JobStorePersistStats mPersistInfo = new JobStorePersistStats();
+ /**
+ * Separately updated value of the JobSet size to avoid recalculating it frequently for logging
+ * purposes. Continue to use {@link JobSet#size()} for the up-to-date and accurate value.
+ */
+ private int mCurrentJobSetSize = 0;
+ private int mScheduledJob30MinHighWaterMark = 0;
+ private static final Histogram sScheduledJob30MinHighWaterMarkLogger = new Histogram(
+ "job_scheduler.value_hist_scheduled_job_30_min_high_water_mark",
+ new Histogram.ScaledRangeOptions(15, 1, 99, 1.5f));
+ private final Runnable mScheduledJobHighWaterMarkLoggingRunnable = new Runnable() {
+ @Override
+ public void run() {
+ AppSchedulingModuleThread.getHandler().removeCallbacks(this);
+ synchronized (mLock) {
+ sScheduledJob30MinHighWaterMarkLogger.logSample(mScheduledJob30MinHighWaterMark);
+ mScheduledJob30MinHighWaterMark = mJobSet.size();
+ }
+ // The count doesn't need to be logged at exact times. Logging based on system uptime
+ // should be fine.
+ AppSchedulingModuleThread.getHandler()
+ .postDelayed(this, SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS);
+ }
+ };
+
/** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
static JobStore get(JobSchedulerService jobManagerService) {
synchronized (sSingletonLock) {
@@ -183,6 +210,9 @@
mXmlTimestamp = mJobsFile.exists()
? mJobsFile.getLastModifiedTime() : mJobFileDirectory.lastModified();
mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
+
+ AppSchedulingModuleThread.getHandler().postDelayed(
+ mScheduledJobHighWaterMarkLoggingRunnable, SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS);
}
private void init() {
@@ -252,7 +282,10 @@
* @param jobStatus Job to add.
*/
public void add(JobStatus jobStatus) {
- mJobSet.add(jobStatus);
+ if (mJobSet.add(jobStatus)) {
+ mCurrentJobSetSize++;
+ maybeUpdateHighWaterMark();
+ }
if (jobStatus.isPersisted()) {
mPendingJobWriteUids.put(jobStatus.getUid(), true);
maybeWriteStatusToDiskAsync();
@@ -267,7 +300,10 @@
*/
@VisibleForTesting
public void addForTesting(JobStatus jobStatus) {
- mJobSet.add(jobStatus);
+ if (mJobSet.add(jobStatus)) {
+ mCurrentJobSetSize++;
+ maybeUpdateHighWaterMark();
+ }
if (jobStatus.isPersisted()) {
mPendingJobWriteUids.put(jobStatus.getUid(), true);
}
@@ -303,6 +339,7 @@
}
return false;
}
+ mCurrentJobSetSize--;
if (removeFromPersisted && jobStatus.isPersisted()) {
mPendingJobWriteUids.put(jobStatus.getUid(), true);
maybeWriteStatusToDiskAsync();
@@ -315,7 +352,9 @@
*/
@VisibleForTesting
public void removeForTesting(JobStatus jobStatus) {
- mJobSet.remove(jobStatus);
+ if (mJobSet.remove(jobStatus)) {
+ mCurrentJobSetSize--;
+ }
if (jobStatus.isPersisted()) {
mPendingJobWriteUids.put(jobStatus.getUid(), true);
}
@@ -327,6 +366,7 @@
*/
public void removeJobsOfUnlistedUsers(int[] keepUserIds) {
mJobSet.removeJobsOfUnlistedUsers(keepUserIds);
+ mCurrentJobSetSize = mJobSet.size();
}
/** Note a change in the specified JobStatus that necessitates writing job state to disk. */
@@ -342,6 +382,7 @@
public void clear() {
mJobSet.clear();
mPendingJobWriteUids.put(ALL_UIDS, true);
+ mCurrentJobSetSize = 0;
maybeWriteStatusToDiskAsync();
}
@@ -352,6 +393,7 @@
public void clearForTesting() {
mJobSet.clear();
mPendingJobWriteUids.put(ALL_UIDS, true);
+ mCurrentJobSetSize = 0;
}
void setUseSplitFiles(boolean useSplitFiles) {
@@ -442,6 +484,12 @@
mJobSet.forEachJobForSourceUid(sourceUid, functor);
}
+ private void maybeUpdateHighWaterMark() {
+ if (mScheduledJob30MinHighWaterMark < mCurrentJobSetSize) {
+ mScheduledJob30MinHighWaterMark = mCurrentJobSetSize;
+ }
+ }
+
/** Version of the db schema. */
private static final int JOBS_FILE_VERSION = 1;
/**
@@ -1125,6 +1173,12 @@
if (needFileMigration) {
migrateJobFilesAsync();
}
+
+ // Log the count immediately after loading from boot.
+ mCurrentJobSetSize = numJobs;
+ mScheduledJob30MinHighWaterMark = mCurrentJobSetSize;
+ mScheduledJobHighWaterMarkLoggingRunnable.run();
+
if (mCompletionLatch != null) {
mCompletionLatch.countDown();
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 5e60305..7b8e342 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2368,6 +2368,7 @@
method public static boolean isApp(int);
field public static final int MIN_SECONDARY_USER_ID = 10; // 0xa
field public static final int USER_ALL = -1; // 0xffffffff
+ field public static final int USER_CURRENT = -2; // 0xfffffffe
field public static final int USER_NULL = -10000; // 0xffffd8f0
field public static final int USER_SYSTEM = 0; // 0x0
}
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 4ce9184..ef39010 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -56,6 +56,7 @@
/** @hide A user id to indicate the currently active user */
@UnsupportedAppUsage
+ @TestApi
public static final @UserIdInt int USER_CURRENT = -2;
/** @hide A user handle to indicate the current user of the device */
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 196bac2..cd76754 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -1106,6 +1106,16 @@
mTransformedTextUpdate.before = before;
mTransformedTextUpdate.after = after;
}
+ // When there is a transformed text, we have to reflow the DynamicLayout based on
+ // the transformed indices instead of the range in base text.
+ // For example,
+ // base text: abcd > abce
+ // updated range: where = 3, before = 1, after = 1
+ // transformed text: abxxcd > abxxce
+ // updated range: where = 5, before = 1, after = 1
+ //
+ // Because the transformedText is udapted simultaneously with the base text,
+ // the range must be transformed before the base text changes.
transformedText.originalToTransformed(mTransformedTextUpdate);
}
}
@@ -1113,9 +1123,20 @@
public void onTextChanged(CharSequence s, int where, int before, int after) {
final DynamicLayout dynamicLayout = mLayout.get();
if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
- where = mTransformedTextUpdate.where;
- before = mTransformedTextUpdate.before;
- after = mTransformedTextUpdate.after;
+ if (mTransformedTextUpdate != null && mTransformedTextUpdate.where >= 0) {
+ where = mTransformedTextUpdate.where;
+ before = mTransformedTextUpdate.before;
+ after = mTransformedTextUpdate.after;
+ // Set where to -1 so that we know if beforeTextChanged is called.
+ mTransformedTextUpdate.where = -1;
+ } else {
+ // onTextChanged is called without beforeTextChanged. Reflow the entire text.
+ where = 0;
+ // We can't get the before length from the text, use the line end of the
+ // last line instead.
+ before = dynamicLayout.getLineEnd(dynamicLayout.getLineCount() - 1);
+ after = dynamicLayout.mDisplay.length();
+ }
}
reflow(s, where, before, after);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7911933..a7bfdf3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4976,11 +4976,11 @@
android:protectionLevel="signature" />
<!-- Allows an application to subscribe to keyguard locked (i.e., showing) state.
- <p>Protection level: internal|role
- <p>Intended for use by ROLE_ASSISTANT only.
+ <p>Protection level: signature|role
+ <p>Intended for use by ROLE_ASSISTANT and signature apps only.
-->
<permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE"
- android:protectionLevel="internal|role"/>
+ android:protectionLevel="signature|role"/>
<!-- Must be required by a {@link android.service.autofill.AutofillService},
to ensure that only the system can bind to it.
diff --git a/core/res/res/values/config_device_idle.xml b/core/res/res/values/config_device_idle.xml
index 1481d50..764dbbe 100644
--- a/core/res/res/values/config_device_idle.xml
+++ b/core/res/res/values/config_device_idle.xml
@@ -119,5 +119,8 @@
<!-- Default for DeviceIdleController.Constants.USE_WINDOW_ALARMS -->
<bool name="device_idle_use_window_alarms">true</bool>
+
+ <!-- Default for DeviceIdleController.Constants.USE_BODY_SENSOR -->
+ <bool name="device_idle_use_body_sensor">false</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9509d0a..9f4277d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4474,6 +4474,7 @@
<java-symbol type="integer" name="device_idle_notification_allowlist_duration_ms" />
<java-symbol type="bool" name="device_idle_wait_for_unlock" />
<java-symbol type="bool" name="device_idle_use_window_alarms" />
+ <java-symbol type="bool" name="device_idle_use_body_sensor" />
<!-- Binder heavy hitter watcher configs -->
<java-symbol type="bool" name="config_defaultBinderHeavyHitterWatcherEnabled" />
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
index 76f4171..5939c06 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
@@ -119,6 +119,86 @@
assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 8);
}
+ @Test
+ public void textWithOffsetMapping_blockBeforeTextChanged_deletion() {
+ final String text = "abcdef";
+ final SpannableStringBuilder spannable = new TestNoBeforeTextChangeSpannableString(text);
+ final CharSequence transformedText =
+ new TestOffsetMapping(spannable, 5, "\n\n");
+
+ final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(false)
+ .setDisplayText(transformedText)
+ .build();
+
+ // delete "cd", original text becomes "abef"
+ spannable.delete(2, 4);
+ assertThat(transformedText.toString()).isEqualTo("abe\n\nf");
+ assertLineRange(layout, /* lineBreaks */ 0, 4, 5, 6);
+
+ // delete "abe", original text becomes "f"
+ spannable.delete(0, 3);
+ assertThat(transformedText.toString()).isEqualTo("\n\nf");
+ assertLineRange(layout, /* lineBreaks */ 0, 1, 2, 3);
+ }
+
+ @Test
+ public void textWithOffsetMapping_blockBeforeTextChanged_insertion() {
+ final String text = "abcdef";
+ final SpannableStringBuilder spannable = new TestNoBeforeTextChangeSpannableString(text);
+ final CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n");
+
+ final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(false)
+ .setDisplayText(transformedText)
+ .build();
+
+ spannable.insert(3, "x");
+ assertThat(transformedText.toString()).isEqualTo("abcx\n\ndef");
+ assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 9);
+
+ spannable.insert(5, "x");
+ assertThat(transformedText.toString()).isEqualTo("abcx\n\ndxef");
+ assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 10);
+ }
+
+ @Test
+ public void textWithOffsetMapping_blockBeforeTextChanged_replace() {
+ final String text = "abcdef";
+ final SpannableStringBuilder spannable = new TestNoBeforeTextChangeSpannableString(text);
+ final CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n");
+
+ final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(false)
+ .setDisplayText(transformedText)
+ .build();
+
+ spannable.replace(2, 4, "xx");
+ assertThat(transformedText.toString()).isEqualTo("abxx\n\nef");
+ assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 8);
+ }
+
+ @Test
+ public void textWithOffsetMapping_onlyCallOnTextChanged_notCrash() {
+ String text = "abcdef";
+ SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+ CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n");
+
+ DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(false)
+ .setDisplayText(transformedText)
+ .build();
+
+ TextWatcher[] textWatcher = spannable.getSpans(0, spannable.length(), TextWatcher.class);
+ assertThat(textWatcher.length).isEqualTo(1);
+
+ textWatcher[0].onTextChanged(spannable, 0, 2, 2);
+ }
+
private void assertLineRange(Layout layout, int... lineBreaks) {
final int lineCount = lineBreaks.length - 1;
assertThat(layout.getLineCount()).isEqualTo(lineCount);
@@ -129,6 +209,50 @@
}
/**
+ * A test SpannableStringBuilder that doesn't call beforeTextChanged. It's used to test
+ * DynamicLayout against some special cases where beforeTextChanged callback is not properly
+ * called.
+ */
+ private static class TestNoBeforeTextChangeSpannableString extends SpannableStringBuilder {
+
+ TestNoBeforeTextChangeSpannableString(CharSequence text) {
+ super(text);
+ }
+
+ @Override
+ public void setSpan(Object what, int start, int end, int flags) {
+ if (what instanceof TextWatcher) {
+ super.setSpan(new TestNoBeforeTextChangeWatcherWrapper((TextWatcher) what), start,
+ end, flags);
+ } else {
+ super.setSpan(what, start, end, flags);
+ }
+ }
+ }
+
+ /** A TextWatcherWrapper that blocks beforeTextChanged callback. */
+ private static class TestNoBeforeTextChangeWatcherWrapper implements TextWatcher {
+ private final TextWatcher mTextWatcher;
+
+ TestNoBeforeTextChangeWatcherWrapper(TextWatcher textWatcher) {
+ mTextWatcher = textWatcher;
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ mTextWatcher.onTextChanged(s, start, before, count);
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ mTextWatcher.afterTextChanged(s);
+ }
+ }
+
+ /**
* A test TransformedText that inserts some text at the given offset.
*/
private static class TestOffsetMapping implements OffsetMapping, CharSequence {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 19eff0e..1793a3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -255,7 +255,7 @@
private boolean shouldShowBackdrop(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change) {
final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE,
- mTransitionAnimation);
+ mTransitionAnimation, false);
return a != null && a.getShowBackdrop();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 00cc57f..3ab175d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -23,6 +23,8 @@
import androidx.core.util.forEach
import androidx.core.util.keyIterator
import androidx.core.util.valueIterator
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.util.KtProtoLog
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -140,6 +142,12 @@
val added = displayData.getOrCreate(displayId).activeTasks.add(taskId)
if (added) {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: add active task=%d displayId=%d",
+ taskId,
+ displayId
+ )
activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
}
return added
@@ -158,6 +166,9 @@
result = true
}
}
+ if (result) {
+ KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId)
+ }
return result
}
@@ -221,6 +232,17 @@
displayData[displayId]?.visibleTasks?.remove(taskId)
}
val newCount = getVisibleTaskCount(displayId)
+
+ if (prevCount != newCount) {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: update task visibility taskId=%d visible=%b displayId=%d",
+ taskId,
+ visible,
+ displayId
+ )
+ }
+
// Check if count changed and if there was no tasks or this is the first task
if (prevCount != newCount && (prevCount == 0 || newCount == 0)) {
notifyVisibleTaskListeners(displayId, newCount > 0)
@@ -244,6 +266,11 @@
* Add (or move if it already exists) the task to the top of the ordered list.
*/
fun addOrMoveFreeformTaskToTop(taskId: Int) {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: add or move task to top taskId=%d",
+ taskId
+ )
if (freeformTasksInZOrder.contains(taskId)) {
freeformTasksInZOrder.remove(taskId)
}
@@ -254,6 +281,11 @@
* Remove the task from the ordered list.
*/
fun removeFreeformTask(taskId: Int) {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: remove freeform task from ordered list taskId=%d",
+ taskId
+ )
freeformTasksInZOrder.remove(taskId)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 9de9855..91bb155 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -102,7 +102,7 @@
/** Show all tasks, that are part of the desktop, on top of launcher */
fun showDesktopApps(displayId: Int) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps")
+ KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
val wct = WindowContainerTransaction()
// TODO(b/278084491): pass in display id
bringDesktopAppsToFront(displayId, wct)
@@ -130,8 +130,11 @@
/** Move a task to desktop */
fun moveToDesktop(task: RunningTaskInfo) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDesktop: %d", task.taskId)
-
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveToDesktop taskId=%d",
+ task.taskId
+ )
val wct = WindowContainerTransaction()
// Bring other apps to front first
bringDesktopAppsToFront(task.displayId, wct)
@@ -147,10 +150,12 @@
* Moves a single task to freeform and sets the taskBounds to the passed in bounds,
* startBounds
*/
- fun moveToFreeform(
- taskInfo: RunningTaskInfo,
- startBounds: Rect
- ) {
+ fun moveToFreeform(taskInfo: RunningTaskInfo, startBounds: Rect) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveToFreeform with bounds taskId=%d",
+ taskInfo.taskId
+ )
val wct = WindowContainerTransaction()
moveHomeTaskToFront(wct)
addMoveToDesktopChanges(wct, taskInfo.getToken())
@@ -165,10 +170,12 @@
}
/** Brings apps to front and sets freeform task bounds */
- private fun moveToDesktopWithAnimation(
- taskInfo: RunningTaskInfo,
- freeformBounds: Rect
- ) {
+ private fun moveToDesktopWithAnimation(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveToDesktop with animation taskId=%d",
+ taskInfo.taskId
+ )
val wct = WindowContainerTransaction()
bringDesktopAppsToFront(taskInfo.displayId, wct)
addMoveToDesktopChanges(wct, taskInfo.getToken())
@@ -190,7 +197,11 @@
/** Move a task to fullscreen */
fun moveToFullscreen(task: RunningTaskInfo) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveToFullscreen taskId=%d",
+ task.taskId
+ )
val wct = WindowContainerTransaction()
addMoveToFullscreenChanges(wct, task.token)
@@ -206,6 +217,11 @@
* status bar area
*/
fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: cancelMoveToFreeform taskId=%d",
+ task.taskId
+ )
val wct = WindowContainerTransaction()
addMoveToFullscreenChanges(wct, task.token)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -218,6 +234,11 @@
}
private fun moveToFullscreenWithAnimation(task: RunningTaskInfo, position: Point) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveToFullscreen with animation taskId=%d",
+ task.taskId
+ )
val wct = WindowContainerTransaction()
addMoveToFullscreenChanges(wct, task.token)
@@ -230,8 +251,14 @@
}
}
- /** Move a task to the front **/
+ /** Move a task to the front */
fun moveTaskToFront(taskInfo: RunningTaskInfo) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveTaskToFront taskId=%d",
+ taskInfo.taskId
+ )
+
val wct = WindowContainerTransaction()
wct.reorder(taskInfo.token, true)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -316,7 +343,7 @@
}
private fun bringDesktopAppsToFront(displayId: Int, wct: WindowContainerTransaction) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront")
+ KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: bringDesktopAppsToFront")
val activeTasks = desktopModeTaskRepository.getActiveTasks(displayId)
// First move home to front and then other tasks on top of it
@@ -399,7 +426,7 @@
if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController#handleRequest: switch fullscreen task to freeform," +
+ "DesktopTasksController: switch fullscreen task to freeform on transition" +
" taskId=%d",
task.taskId
)
@@ -416,7 +443,7 @@
if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController#handleRequest: switch freeform task to fullscreen," +
+ "DesktopTasksController: switch freeform task to fullscreen oon transition" +
" taskId=%d",
task.taskId
)
@@ -627,8 +654,6 @@
}
}
-
-
/** The interface for calls from outside the host process. */
@BinderThread
private class IDesktopModeImpl(private var controller: DesktopTasksController?) :
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 1ee52ae..dfad8b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -24,6 +24,7 @@
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -329,6 +330,8 @@
@ColorInt int backgroundColorForTransition = 0;
final int wallpaperTransit = getWallpaperTransitType(info);
boolean isDisplayRotationAnimationStarted = false;
+ final boolean isDreamTransition = isDreamTransition(info);
+
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change.hasAllFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY
@@ -424,7 +427,7 @@
// Don't animate anything that isn't independent.
if (!TransitionInfo.isIndependent(change, info)) continue;
- Animation a = loadAnimation(info, change, wallpaperTransit);
+ Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition);
if (a != null) {
if (isTask) {
final @TransitionType int type = info.getType();
@@ -519,6 +522,18 @@
return true;
}
+ private static boolean isDreamTransition(@NonNull TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -572,7 +587,8 @@
@Nullable
private Animation loadAnimation(@NonNull TransitionInfo info,
- @NonNull TransitionInfo.Change change, int wallpaperTransit) {
+ @NonNull TransitionInfo.Change change, int wallpaperTransit,
+ boolean isDreamTransition) {
Animation a;
final int type = info.getType();
@@ -630,7 +646,8 @@
// If there's a scene-transition, then jump-cut.
return null;
} else {
- a = loadAttributeAnimation(info, change, wallpaperTransit, mTransitionAnimation);
+ a = loadAttributeAnimation(
+ info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition);
}
if (a != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index 0f4645c0..19d8384 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -18,7 +18,6 @@
import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_NONE;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -63,7 +62,7 @@
@Nullable
public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, int wallpaperTransit,
- @NonNull TransitionAnimation transitionAnimation) {
+ @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) {
final int type = info.getType();
final int changeMode = change.getMode();
final int changeFlags = change.getFlags();
@@ -71,11 +70,9 @@
final boolean isTask = change.getTaskInfo() != null;
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
final int overrideType = options != null ? options.getType() : ANIM_NONE;
- final boolean isDream =
- isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
int animAttr = 0;
boolean translucent = false;
- if (isDream) {
+ if (isDreamTransition) {
if (type == TRANSIT_OPEN) {
animAttr = enter
? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 214c903..4e75792 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -244,7 +244,7 @@
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the Hearing Aid profile. -->
<string name="bluetooth_profile_hearing_aid">Hearing Aids</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the LE audio profile. -->
- <string name="bluetooth_profile_le_audio">LE audio</string>
+ <string name="bluetooth_profile_le_audio">LE audio (experimental)</string>
<!-- Bluetooth settings. Connection options screen. The summary for the Hearing Aid checkbox preference when Hearing Aid is connected. -->
<string name="bluetooth_hearing_aid_profile_summary_connected">Connected to Hearing Aids</string>
<!-- Bluetooth settings. Connection options screen. The summary for the LE audio checkbox preference when LE audio is connected. -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index d8bf570..676f342 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -179,6 +179,20 @@
}
/**
+ * Set alpha directly to mView will clip clock, so we set alpha to clock face instead
+ */
+ public void setAlpha(float alpha) {
+ ClockController clock = getClock();
+ if (clock != null) {
+ clock.getLargeClock().getView().setAlpha(alpha);
+ clock.getSmallClock().getView().setAlpha(alpha);
+ }
+ if (mStatusArea != null) {
+ mStatusArea.setAlpha(alpha);
+ }
+ }
+
+ /**
* Attach the controller to the view it relates to.
*/
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index d8e1eb0f..2313609 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -135,4 +135,31 @@
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Trace.endSection();
}
+
+ /**
+ * Clock content will be clipped when goes beyond bounds,
+ * so we setAlpha for all views except clock
+ */
+ public void setAlpha(float alpha, boolean excludeClock) {
+ if (!excludeClock) {
+ setAlpha(alpha);
+ return;
+ }
+ if (alpha == 1 || alpha == 0) {
+ setAlpha(alpha);
+ }
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child == mStatusViewContainer) {
+ for (int j = 0; j < mStatusViewContainer.getChildCount(); j++) {
+ View innerChild = mStatusViewContainer.getChildAt(j);
+ if (innerChild != mClockView) {
+ innerChild.setAlpha(alpha);
+ }
+ }
+ } else {
+ child.setAlpha(alpha);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 794eeda..af47466 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -180,7 +180,8 @@
*/
public void setAlpha(float alpha) {
if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
- mView.setAlpha(alpha);
+ mView.setAlpha(alpha, true);
+ mKeyguardClockSwitchController.setAlpha(alpha);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index fb73845..d8e2a38 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -134,6 +134,7 @@
private KeyguardClockSwitchController mController;
private View mSliceView;
+ private LinearLayout mStatusArea;
private FakeExecutor mExecutor;
@Before
@@ -195,8 +196,8 @@
mSliceView = new View(getContext());
when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
- when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(
- new LinearLayout(getContext()));
+ mStatusArea = new LinearLayout(getContext());
+ when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
}
@Test
@@ -401,6 +402,15 @@
assertNull(mController.getClock());
}
+ @Test
+ public void testSetAlpha_setClockAlphaForCLockFace() {
+ mController.onViewAttached();
+ mController.setAlpha(0.5f);
+ verify(mLargeClockView).setAlpha(0.5f);
+ verify(mSmallClockView).setAlpha(0.5f);
+ assertEquals(0.5f, mStatusArea.getAlpha(), 0.0f);
+ }
+
private void verifyAttachment(VerificationMode times) {
verify(mClockRegistry, times).registerClockChangeListener(
any(ClockRegistry.ClockChangeListener.class));
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index 508aea5..a8c281c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -24,6 +24,8 @@
get() = keyguardStatusView.findViewById(R.id.status_view_media_container)
private val statusViewContainer: ViewGroup
get() = keyguardStatusView.findViewById(R.id.status_view_container)
+ private val clockView: ViewGroup
+ get() = keyguardStatusView.findViewById(R.id.keyguard_clock_container)
private val childrenExcludingMedia
get() = statusViewContainer.children.filter { it != mediaView }
@@ -56,4 +58,12 @@
assertThat(it.translationY).isEqualTo(translationY)
}
}
+
+ @Test
+ fun setAlphaExcludeClock() {
+ keyguardStatusView.setAlpha(0.5f, /* excludeClock= */true)
+ assertThat(statusViewContainer.alpha).isNotEqualTo(0.5f)
+ assertThat(mediaView.alpha).isEqualTo(0.5f)
+ assertThat(clockView.alpha).isNotEqualTo(0.5f)
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 57b3c0d..86dbe11 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -25,7 +25,6 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
-import android.provider.Settings;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
@@ -727,10 +726,13 @@
"setEnabledProviders",
null);
+ Set<String> enableProvider = new HashSet<>(providers);
+ enableProvider.addAll(primaryProviders);
+
boolean writeEnabledStatus =
Settings.Secure.putStringForUser(getContext().getContentResolver(),
Settings.Secure.CREDENTIAL_SERVICE,
- String.join(":", providers),
+ String.join(":", enableProvider),
userId);
boolean writePrimaryStatus =
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 5d5e0f9..88f3b2e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -48,6 +48,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -144,6 +145,8 @@
private SensorManager mSensorManager;
@Mock
private TelephonyManager mTelephonyManager;
+ @Mock
+ private Sensor mOffBodySensor;
class InjectorForTest extends DeviceIdleController.Injector {
ConnectivityManager connectivityManager;
@@ -2409,6 +2412,82 @@
verifyLightStateConditions(LIGHT_STATE_ACTIVE);
}
+ @Test
+ public void testLowLatencyBodyDetection_NoBodySensor() {
+ mConstants.USE_BODY_SENSOR = true;
+ doReturn(null).when(mSensorManager).getDefaultSensor(
+ eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean());
+ cleanupDeviceIdleController();
+ setupDeviceIdleController();
+ verify(mSensorManager, never())
+ .registerListener(any(), any(), anyInt());
+ }
+
+ @Test
+ public void testLowLatencyBodyDetection_NoBatterySaver_QuickDoze() {
+ mConstants.USE_BODY_SENSOR = true;
+ doReturn(mOffBodySensor)
+ .when(mSensorManager)
+ .getDefaultSensor(eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean());
+ PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+ false).build();
+ when(mPowerManagerInternal.getLowPowerState(anyInt()))
+ .thenReturn(powerSaveState);
+ cleanupDeviceIdleController();
+ setupDeviceIdleController();
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager)
+ .registerListener(listenerCaptor.capture(), eq(mOffBodySensor),
+ eq(SensorManager.SENSOR_DELAY_NORMAL));
+ final SensorEventListener listener = listenerCaptor.getValue();
+ // Set the device as off body
+ float[] valsZero = {0.0f};
+ SensorEvent offbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsZero);
+ listener.onSensorChanged(offbodyEvent);
+ assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+
+ // Set the device as on body
+ float[] valsNonZero = {1.0f};
+ SensorEvent onbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsNonZero);
+ listener.onSensorChanged(onbodyEvent);
+ assertFalse(mDeviceIdleController.isQuickDozeEnabled());
+ verifyStateConditions(STATE_ACTIVE);
+ }
+
+ @Test
+ public void testLowLatencyBodyDetection_WithBatterySaver_QuickDoze() {
+ mConstants.USE_BODY_SENSOR = true;
+ doReturn(mOffBodySensor)
+ .when(mSensorManager)
+ .getDefaultSensor(eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean());
+ PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+ true).build();
+ when(mPowerManagerInternal.getLowPowerState(anyInt()))
+ .thenReturn(powerSaveState);
+ cleanupDeviceIdleController();
+ setupDeviceIdleController();
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager)
+ .registerListener(listenerCaptor.capture(), eq(mOffBodySensor),
+ eq(SensorManager.SENSOR_DELAY_NORMAL));
+ final SensorEventListener listener = listenerCaptor.getValue();
+ // Set the device as off body
+ float[] valsZero = {0.0f};
+ SensorEvent offbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsZero);
+ listener.onSensorChanged(offbodyEvent);
+ assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+
+ // Set the device as on body. Quick doze should remain enabled because battery saver is on.
+ float[] valsNonZero = {1.0f};
+ SensorEvent onbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsNonZero);
+ listener.onSensorChanged(onbodyEvent);
+ assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+ }
+
private void enterDeepState(int state) {
switch (state) {
case STATE_ACTIVE:
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index b003f59..331caa1 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -43,8 +43,8 @@
/** Disconnected because of a local user-initiated action, such as hanging up. */
public static final int LOCAL = TelecomProtoEnums.LOCAL; // = 2
/**
- * Disconnected because of a remote user-initiated action, such as the other party hanging up
- * up.
+ * Disconnected because the remote party hung up an ongoing call, or because an outgoing call
+ * was not answered by the remote party.
*/
public static final int REMOTE = TelecomProtoEnums.REMOTE; // = 3
/** Disconnected because it has been canceled. */