Merge "Extract getIntentUser function in ResolverActivity"
diff --git a/boot/Android.bp b/boot/Android.bp
index 7839918..6e52914 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -136,6 +136,10 @@
apex: "com.android.car.framework",
module: "com.android.car.framework-bootclasspath-fragment",
},
+ {
+ apex: "com.android.virt",
+ module: "com.android.virt-bootclasspath-fragment",
+ },
],
// Additional information needed by hidden api processing.
diff --git a/core/api/current.txt b/core/api/current.txt
index 6a875b8..07ced0e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32315,6 +32315,7 @@
method public static final boolean is64Bit();
method public static boolean isApplicationUid(int);
method public static final boolean isIsolated();
+ method public static final boolean isIsolatedUid(int);
method public static final boolean isSdkSandbox();
method public static final void killProcess(int);
method public static final int myPid();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 001ebf5c..5169413 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3507,6 +3507,7 @@
field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg";
+ field public static final String FEATURE_VIRTUALIZATION_FRAMEWORK = "android.software.virtualization_framework";
field public static final int FLAGS_PERMISSION_RESERVED_PERMISSION_CONTROLLER = -268435456; // 0xf0000000
field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
field public static final int FLAG_PERMISSION_AUTO_REVOKED = 131072; // 0x20000
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index 2e83308..99f864c 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -57,8 +57,7 @@
* @deprecated IntentService is subject to all the
* <a href="{@docRoot}about/versions/oreo/background.html">background execution limits</a>
* imposed with Android 8.0 (API level 26). Consider using {@link androidx.work.WorkManager}
- * or {@link androidx.core.app.JobIntentService}, which uses jobs
- * instead of services when running on Android 8.0 or higher.
+ * instead.
*/
@Deprecated
public abstract class IntentService extends Service {
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 6d28972..a035375 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -229,6 +229,8 @@
public static final int CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = 1;
/** @hide */
public static final int CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = 2;
+ /** @hide */
+ public static final int CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE = 3;
/**
* Session flag for {@link #registerSessionListener} indicating the listener
diff --git a/core/java/android/app/search/SearchSession.java b/core/java/android/app/search/SearchSession.java
index 2cd1d96..10db337 100644
--- a/core/java/android/app/search/SearchSession.java
+++ b/core/java/android/app/search/SearchSession.java
@@ -23,9 +23,11 @@
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.util.Log;
import dalvik.system.CloseGuard;
@@ -70,7 +72,7 @@
* @hide
*/
@SystemApi
-public final class SearchSession implements AutoCloseable{
+public final class SearchSession implements AutoCloseable {
private static final String TAG = SearchSession.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -229,7 +231,14 @@
if (DEBUG) {
Log.d(TAG, "CallbackWrapper.onResult result=" + result.getList());
}
- mExecutor.execute(() -> mCallback.accept(result.getList()));
+ List<SearchTarget> list = result.getList();
+ if (list.size() > 0) {
+ Bundle bundle = list.get(0).getExtras();
+ if (bundle != null) {
+ bundle.putLong("key_ipc_start", SystemClock.elapsedRealtime());
+ }
+ }
+ mExecutor.execute(() -> mCallback.accept(list));
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6c1d84b..485d04d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2959,6 +2959,18 @@
public static final String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep";
/**
+ * Feature for {@link #getSystemAvailableFeatures()} and {@link #hasSystemFeature(String)}.
+ * This feature indicates whether device supports
+ * <a href="https://source.android.com/docs/core/virtualization">Android Virtualization Framework</a>.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VIRTUALIZATION_FRAMEWORK =
+ "android.software.virtualization_framework";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature(String, int)}: If this feature is supported, the Vulkan
* implementation on this device is hardware accelerated, and the Vulkan native API will
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index e483328..ac1583a 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -895,9 +895,21 @@
return isIsolated(myUid());
}
- /** {@hide} */
- @UnsupportedAppUsage
+ /**
+ * @deprecated Use {@link #isIsolatedUid(int)} instead.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+ publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
public static final boolean isIsolated(int uid) {
+ return isIsolatedUid(uid);
+ }
+
+ /**
+ * Returns whether the process with the given {@code uid} is an isolated sandbox.
+ */
+ public static final boolean isIsolatedUid(int uid) {
uid = UserHandle.getAppId(uid);
return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
|| (uid >= FIRST_APP_ZYGOTE_ISOLATED_UID && uid <= LAST_APP_ZYGOTE_ISOLATED_UID);
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 696f0ff..556e146 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -403,7 +403,7 @@
* Returns true if this instance only supports reading history.
*/
public boolean isReadOnly() {
- return mActiveFile == null;
+ return mActiveFile == null || mHistoryDir == null;
}
/**
@@ -1292,7 +1292,9 @@
&& mHistoryLastWritten.batteryHealth == cur.batteryHealth
&& mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
&& mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
- && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
+ && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage
+ && mHistoryLastWritten.measuredEnergyDetails == null
+ && mHistoryLastWritten.cpuUsageDetails == null) {
// We can merge this new change in with the last one. Merging is
// allowed as long as only the states have changed, and within those states
// as long as no bit has changed both between now and the last entry, as
@@ -1761,8 +1763,8 @@
* Saves the accumulated history buffer in the active file, see {@link #getActiveFile()} .
*/
public void writeHistory() {
- if (mActiveFile == null) {
- Slog.w(TAG, "writeHistory: no history file associated with this instance");
+ if (isReadOnly()) {
+ Slog.w(TAG, "writeHistory: this instance instance is read-only");
return;
}
diff --git a/core/java/com/android/internal/os/ProcLocksReader.java b/core/java/com/android/internal/os/ProcLocksReader.java
index 2143bc1..9ddb8c7 100644
--- a/core/java/com/android/internal/os/ProcLocksReader.java
+++ b/core/java/com/android/internal/os/ProcLocksReader.java
@@ -16,6 +16,8 @@
package com.android.internal.os;
+import android.util.IntArray;
+
import com.android.internal.util.ProcFileReader;
import java.io.FileInputStream;
@@ -35,6 +37,7 @@
public class ProcLocksReader {
private final String mPath;
private ProcFileReader mReader = null;
+ private IntArray mPids = new IntArray();
public ProcLocksReader() {
mPath = "/proc/locks";
@@ -51,9 +54,13 @@
public interface ProcLocksReaderCallback {
/**
* Call the callback function of handleBlockingFileLocks().
- * @param pid Each process that hold file locks blocking other processes.
+ * @param pids Each process that hold file locks blocking other processes.
+ * pids[0] is the process blocking others
+ * pids[1..n-1] are the processes being blocked
+ * NOTE: pids are cleared immediately after onBlockingFileLock() returns. If the caller
+ * needs to cache it, please make a copy, e.g. by calling pids.toArray().
*/
- void onBlockingFileLock(int pid);
+ void onBlockingFileLock(IntArray pids);
}
/**
@@ -64,8 +71,7 @@
public void handleBlockingFileLocks(ProcLocksReaderCallback callback) throws IOException {
long last = -1;
long id; // ordinal position of the lock in the list
- int owner = -1; // the PID of the process that owns the lock
- int pid = -1; // the PID of the process blocking others
+ int pid = -1; // the PID of the process being blocked
if (mReader == null) {
mReader = new ProcFileReader(new FileInputStream(mPath));
@@ -73,26 +79,49 @@
mReader.rewind();
}
+ mPids.clear();
while (mReader.hasMoreData()) {
id = mReader.nextLong(true); // lock id
if (id == last) {
- mReader.finishLine(); // blocked lock
- if (pid < 0) {
- pid = owner; // get pid from the previous line
- callback.onBlockingFileLock(pid);
+ // blocked lock found
+ mReader.nextIgnored(); // ->
+ mReader.nextIgnored(); // lock type: POSIX?
+ mReader.nextIgnored(); // lock type: MANDATORY?
+ mReader.nextIgnored(); // lock type: RW?
+
+ pid = mReader.nextInt(); // pid
+ if (pid > 0) {
+ mPids.add(pid);
}
- continue;
+
+ mReader.finishLine();
} else {
- pid = -1; // a new lock
+ // process blocking lock and move on to a new lock
+ if (mPids.size() > 1) {
+ callback.onBlockingFileLock(mPids);
+ mPids.clear();
+ }
+
+ // new lock found
+ mReader.nextIgnored(); // lock type: POSIX?
+ mReader.nextIgnored(); // lock type: MANDATORY?
+ mReader.nextIgnored(); // lock type: RW?
+
+ pid = mReader.nextInt(); // pid
+ if (pid > 0) {
+ if (mPids.size() == 0) {
+ mPids.add(pid);
+ } else {
+ mPids.set(0, pid);
+ }
+ }
+ mReader.finishLine();
+ last = id;
}
-
- mReader.nextIgnored(); // lock type: POSIX?
- mReader.nextIgnored(); // lock type: MANDATORY?
- mReader.nextIgnored(); // lock type: RW?
-
- owner = mReader.nextInt(); // pid
- mReader.finishLine();
- last = id;
+ }
+ // The last unprocessed blocking lock immediately before EOF
+ if (mPids.size() > 1) {
+ callback.onBlockingFileLock(mPids);
}
}
}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 28b98d6..8a9445d 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -16,10 +16,14 @@
package com.android.internal.os;
+import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
+
+import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ApplicationErrorReport;
import android.app.IActivityManager;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.type.DefaultMimeMapFactory;
import android.net.TrafficStats;
@@ -36,6 +40,7 @@
import dalvik.system.RuntimeHooks;
import dalvik.system.VMRuntime;
+import dalvik.system.ZipPathValidator;
import libcore.content.type.MimeMap;
@@ -260,10 +265,31 @@
*/
TrafficStats.attachSocketTagger();
+ /*
+ * Initialize the zip path validator callback depending on the targetSdk.
+ */
+ initZipPathValidatorCallback();
+
initialized = true;
}
/**
+ * If targetSDK >= U: set the safe zip path validator callback which disallows dangerous zip
+ * entry names.
+ * Otherwise: clear the callback to the default validation.
+ *
+ * @hide
+ */
+ @TestApi
+ public static void initZipPathValidatorCallback() {
+ if (CompatChanges.isChangeEnabled(VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL)) {
+ ZipPathValidator.setCallback(new SafeZipPathValidatorCallback());
+ } else {
+ ZipPathValidator.clearCallback();
+ }
+ }
+
+ /**
* Returns an HTTP user agent of the form
* "Dalvik/1.1.0 (Linux; U; Android Eclair Build/MAIN)".
*/
diff --git a/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java b/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java
new file mode 100644
index 0000000..a6ee108
--- /dev/null
+++ b/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.annotation.NonNull;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
+
+import dalvik.system.ZipPathValidator;
+
+import java.io.File;
+import java.util.zip.ZipException;
+
+/**
+ * A child implementation of the {@link dalvik.system.ZipPathValidator.Callback} that removes the
+ * risk of zip path traversal vulnerabilities.
+ *
+ * @hide
+ */
+public class SafeZipPathValidatorCallback implements ZipPathValidator.Callback {
+ /**
+ * This change targets zip path traversal vulnerabilities by throwing
+ * {@link java.util.zip.ZipException} if zip path entries contain ".." or start with "/".
+ * <p>
+ * The exception will be thrown in {@link java.util.zip.ZipInputStream#getNextEntry} or
+ * {@link java.util.zip.ZipFile#ZipFile(String)}.
+ * <p>
+ * This validation is enabled for apps with targetSDK >= U.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL = 242716250L;
+
+ @Override
+ public void onZipEntryAccess(@NonNull String path) throws ZipException {
+ if (path.startsWith("/")) {
+ throw new ZipException("Invalid zip entry path: " + path);
+ }
+ if (path.contains("..")) {
+ // If the string does contain "..", break it down into its actual name elements to
+ // ensure it actually contains ".." as a name, not just a name like "foo..bar" or even
+ // "foo..", which should be fine.
+ File file = new File(path);
+ while (file != null) {
+ if (file.getName().equals("..")) {
+ throw new ZipException("Invalid zip entry path: " + path);
+ }
+ file = file.getParentFile();
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index be3f172..680f8fe 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.content.Intent;
import android.os.SystemClock;
import com.android.internal.os.anr.AnrLatencyTracker;
@@ -92,7 +93,17 @@
/** Record for a broadcast receiver timeout. */
@NonNull
- public static TimeoutRecord forBroadcastReceiver(@NonNull String reason) {
+ public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent) {
+ String reason = "Broadcast of " + intent.toString();
+ return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
+ }
+
+ /** Record for a broadcast receiver timeout. */
+ @NonNull
+ public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent,
+ long timeoutDurationMs) {
+ String reason = "Broadcast of " + intent.toString() + ", waited " + timeoutDurationMs
+ + "ms";
return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
}
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 2b932cb..98814bf 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -380,8 +380,8 @@
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Received motion event.", getInputChannelName().c_str());
}
- MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
- if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
+ const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*inputEvent);
+ if ((motionEvent.getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
*outConsumedBatch = true;
}
inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index a30935b..80df0ea 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -82,7 +82,7 @@
reinterpret_cast<jlong>(event));
}
-jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event) {
+jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent& event) {
jobject eventObj = env->CallStaticObjectMethod(gMotionEventClassInfo.clazz,
gMotionEventClassInfo.obtain);
if (env->ExceptionCheck() || !eventObj) {
@@ -98,7 +98,7 @@
android_view_MotionEvent_setNativePtr(env, eventObj, destEvent);
}
- destEvent->copyFrom(event, true);
+ destEvent->copyFrom(&event, true);
return eventObj;
}
diff --git a/core/jni/android_view_MotionEvent.h b/core/jni/android_view_MotionEvent.h
index 9ce4bf3..32a280e 100644
--- a/core/jni/android_view_MotionEvent.h
+++ b/core/jni/android_view_MotionEvent.h
@@ -26,7 +26,7 @@
/* Obtains an instance of a DVM MotionEvent object as a copy of a native MotionEvent instance.
* Returns NULL on error. */
-extern jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event);
+extern jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent& event);
/* Gets the underlying native MotionEvent instance within a DVM MotionEvent object.
* Returns NULL if the event is NULL or if it is uninitialized. */
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 932b24e..6c18259 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3688,6 +3688,12 @@
experience while the device is non-interactive. -->
<bool name="config_emergencyGestureEnabled">true</bool>
+ <!-- Default value for Use Emergency SOS in Settings false = disabled, true = enabled -->
+ <bool name="config_defaultEmergencyGestureEnabled">true</bool>
+
+ <!-- Default value for Use Play countdown alarm in Settings false = disabled, true = enabled -->
+ <bool name="config_defaultEmergencyGestureSoundEnabled">false</bool>
+
<!-- Allow the gesture power + volume up to change the ringer mode while the device
is interactive. -->
<bool name="config_volumeHushGestureEnabled">true</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 437dedc..5811ed9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3015,6 +3015,8 @@
<java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
<java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
<java-symbol type="bool" name="config_emergencyGestureEnabled" />
+ <java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" />
+ <java-symbol type="bool" name="config_defaultEmergencyGestureSoundEnabled" />
<java-symbol type="bool" name="config_volumeHushGestureEnabled" />
<java-symbol type="drawable" name="platlogo_m" />
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index 212cc44..8459330 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -156,8 +156,8 @@
@DisableCompatChanges({DateFormat.DISALLOW_DUPLICATE_FIELD_IN_SKELETON})
public void testGetBestDateTimePattern_enableDuplicateField() {
// en-US uses 12-hour format by default.
- assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
- assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
+ assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
+ assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
}
private static void assertIllegalArgumentException(Locale l, String skeleton) {
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index 9c06395..de7244d 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -93,7 +93,8 @@
assertEquals("January 19",
formatDateRange(en_US, tz, timeWithCurrentYear, timeWithCurrentYear + HOUR,
FORMAT_SHOW_DATE));
- assertEquals("3:30 AM", formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_SHOW_TIME));
+ assertEquals("3:30\u202fAM", formatDateRange(en_US, tz, fixedTime, fixedTime,
+ FORMAT_SHOW_TIME));
assertEquals("January 19, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + HOUR, FORMAT_SHOW_YEAR));
assertEquals("January 19",
@@ -101,27 +102,27 @@
assertEquals("January",
formatDateRange(en_US, tz, timeWithCurrentYear, timeWithCurrentYear + HOUR,
FORMAT_NO_MONTH_DAY));
- assertEquals("3:30 AM",
+ assertEquals("3:30\u202fAM",
formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_12HOUR | FORMAT_SHOW_TIME));
assertEquals("03:30",
formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_24HOUR | FORMAT_SHOW_TIME));
- assertEquals("3:30 AM", formatDateRange(en_US, tz, fixedTime, fixedTime,
+ assertEquals("3:30\u202fAM", formatDateRange(en_US, tz, fixedTime, fixedTime,
FORMAT_12HOUR /*| FORMAT_CAP_AMPM*/ | FORMAT_SHOW_TIME));
- assertEquals("12:00 PM",
+ assertEquals("12:00\u202fPM",
formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
FORMAT_12HOUR | FORMAT_SHOW_TIME));
- assertEquals("12:00 PM",
+ assertEquals("12:00\u202fPM",
formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
FORMAT_12HOUR | FORMAT_SHOW_TIME /*| FORMAT_CAP_NOON*/));
- assertEquals("12:00 PM",
+ assertEquals("12:00\u202fPM",
formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
FORMAT_12HOUR /*| FORMAT_NO_NOON*/ | FORMAT_SHOW_TIME));
- assertEquals("12:00 AM", formatDateRange(en_US, tz, fixedTime - midnightDuration,
+ assertEquals("12:00\u202fAM", formatDateRange(en_US, tz, fixedTime - midnightDuration,
fixedTime - midnightDuration,
FORMAT_12HOUR | FORMAT_SHOW_TIME /*| FORMAT_NO_MIDNIGHT*/));
- assertEquals("3:30 AM",
+ assertEquals("3:30\u202fAM",
formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_SHOW_TIME | FORMAT_UTC));
- assertEquals("3 AM", formatDateRange(en_US, tz, onTheHour, onTheHour,
+ assertEquals("3\u202fAM", formatDateRange(en_US, tz, onTheHour, onTheHour,
FORMAT_SHOW_TIME | FORMAT_ABBREV_TIME));
assertEquals("Mon", formatDateRange(en_US, tz, fixedTime, fixedTime + HOUR,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_WEEKDAY));
@@ -134,13 +135,13 @@
assertEquals("1/19/2009", formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * HOUR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("1/19/2009 – 1/22/2009",
+ assertEquals("1/19/2009\u2009\u2013\u20091/22/2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("1/19/2009 – 4/22/2009",
+ assertEquals("1/19/2009\u2009\u2013\u20094/22/2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("1/19/2009 – 2/9/2012",
+ assertEquals("1/19/2009\u2009\u2013\u20092/9/2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
@@ -151,7 +152,7 @@
assertEquals("19.01. – 22.04.2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("19.01.2009 – 09.02.2012",
+ assertEquals("19.01.2009\u2009\u2013\u200909.02.2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
@@ -169,48 +170,48 @@
assertEquals("19/1/2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + HOUR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("19/1/2009 – 22/1/2009",
+ assertEquals("19/1/2009\u2009\u2013\u200922/1/2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("19/1/2009 – 22/4/2009",
+ assertEquals("19/1/2009\u2009\u2013\u200922/4/2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("19/1/2009 – 9/2/2012",
+ assertEquals("19/1/2009\u2009\u2013\u20099/2/2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
// These are some random other test cases I came up with.
- assertEquals("January 19 – 22, 2009",
+ assertEquals("January 19\u2009\u2013\u200922, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY, 0));
- assertEquals("Jan 19 – 22, 2009", formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
- FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Mon, Jan 19 – Thu, Jan 22, 2009",
+ assertEquals("Jan 19\u2009\u2013\u200922, 2009", formatDateRange(en_US, tz, fixedTime,
+ fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
+ assertEquals("Mon, Jan 19\u2009\u2013\u2009Thu, Jan 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
- assertEquals("Monday, January 19 – Thursday, January 22, 2009",
+ assertEquals("Monday, January 19\u2009\u2013\u2009Thursday, January 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
- assertEquals("January 19 – April 22, 2009",
+ assertEquals("January 19\u2009\u2013\u2009April 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH, 0));
- assertEquals("Jan 19 – Apr 22, 2009",
+ assertEquals("Jan 19\u2009\u2013\u2009Apr 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Mon, Jan 19 – Wed, Apr 22, 2009",
+ assertEquals("Mon, Jan 19\u2009\u2013\u2009Wed, Apr 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
- assertEquals("January – April 2009",
+ assertEquals("January\u2009\u2013\u2009April 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
- assertEquals("Jan 19, 2009 – Feb 9, 2012",
+ assertEquals("Jan 19, 2009\u2009\u2013\u2009Feb 9, 2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Jan 2009 – Feb 2012",
+ assertEquals("Jan 2009\u2009\u2013\u2009Feb 2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
- assertEquals("January 19, 2009 – February 9, 2012",
+ assertEquals("January 19, 2009\u2009\u2013\u2009February 9, 2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR, 0));
- assertEquals("Monday, January 19, 2009 – Thursday, February 9, 2012",
+ assertEquals("Monday, January 19, 2009\u2009\u2013\u2009Thursday, February 9, 2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
// The same tests but for de_DE.
@@ -225,26 +226,26 @@
assertEquals("Montag, 19. – Donnerstag, 22. Januar 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
- assertEquals("19. Januar – 22. April 2009",
+ assertEquals("19. Januar\u2009\u2013\u200922. April 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, 0));
- assertEquals("19. Jan. – 22. Apr. 2009",
+ assertEquals("19. Jan.\u2009\u2013\u200922. Apr. 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Mo., 19. Jan. – Mi., 22. Apr. 2009",
+ assertEquals("Mo., 19. Jan.\u2009\u2013\u2009Mi., 22. Apr. 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
assertEquals("Januar–April 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
- assertEquals("19. Jan. 2009 – 9. Feb. 2012",
+ assertEquals("19. Jan. 2009\u2009\u2013\u20099. Feb. 2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Jan. 2009 – Feb. 2012",
+ assertEquals("Jan. 2009\u2009\u2013\u2009Feb. 2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
- assertEquals("19. Januar 2009 – 9. Februar 2012",
+ assertEquals("19. Januar 2009\u2009\u2013\u20099. Februar 2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, 0));
- assertEquals("Montag, 19. Januar 2009 – Donnerstag, 9. Februar 2012",
+ assertEquals("Montag, 19. Januar 2009\u2009\u2013\u2009Donnerstag, 9. Februar 2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
// The same tests but for es_US.
@@ -254,32 +255,32 @@
assertEquals("19–22 de ene de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("lun, 19 de ene – jue, 22 de ene de 2009",
+ assertEquals("lun, 19 de ene\u2009\u2013\u2009jue, 22 de ene de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
- assertEquals("lunes, 19 de enero – jueves, 22 de enero de 2009",
+ assertEquals("lunes, 19 de enero\u2009\u2013\u2009jueves, 22 de enero de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
- assertEquals("19 de enero – 22 de abril de 2009",
+ assertEquals("19 de enero\u2009\u2013\u200922 de abril de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, 0));
- assertEquals("19 de ene – 22 de abr 2009",
+ assertEquals("19 de ene\u2009\u2013\u200922 de abr 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("lun, 19 de ene – mié, 22 de abr de 2009",
+ assertEquals("lun, 19 de ene\u2009\u2013\u2009mié, 22 de abr de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
assertEquals("enero–abril de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
- assertEquals("19 de ene de 2009 – 9 de feb de 2012",
+ assertEquals("19 de ene de 2009\u2009\u2013\u20099 de feb de 2012",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("ene de 2009 – feb de 2012",
+ assertEquals("ene de 2009\u2009\u2013\u2009feb de 2012",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
- assertEquals("19 de enero de 2009 – 9 de febrero de 2012",
+ assertEquals("19 de enero de 2009\u2009\u2013\u20099 de febrero de 2012",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, 0));
- assertEquals("lunes, 19 de enero de 2009 – jueves, 9 de febrero de 2012",
+ assertEquals("lunes, 19 de enero de 2009\u2009\u2013\u2009jueves, 9 de febrero de 2012",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
// The same tests but for es_ES.
@@ -288,32 +289,32 @@
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, 0));
assertEquals("19–22 ene 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("lun, 19 ene – jue, 22 ene 2009",
+ assertEquals("lun, 19 ene\u2009\u2013\u2009jue, 22 ene 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
- assertEquals("lunes, 19 de enero – jueves, 22 de enero de 2009",
+ assertEquals("lunes, 19 de enero\u2009\u2013\u2009jueves, 22 de enero de 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
- assertEquals("19 de enero – 22 de abril de 2009",
+ assertEquals("19 de enero\u2009\u2013\u200922 de abril de 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, 0));
- assertEquals("19 ene – 22 abr 2009",
+ assertEquals("19 ene\u2009\u2013\u200922 abr 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("lun, 19 ene – mié, 22 abr 2009",
+ assertEquals("lun, 19 ene\u2009\u2013\u2009mié, 22 abr 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
assertEquals("enero–abril de 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
- assertEquals("19 ene 2009 – 9 feb 2012",
+ assertEquals("19 ene 2009\u2009\u2013\u20099 feb 2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("ene 2009 – feb 2012",
+ assertEquals("ene 2009\u2009\u2013\u2009feb 2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
- assertEquals("19 de enero de 2009 – 9 de febrero de 2012",
+ assertEquals("19 de enero de 2009\u2009\u2013\u20099 de febrero de 2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, 0));
- assertEquals("lunes, 19 de enero de 2009 – jueves, 9 de febrero de 2012",
+ assertEquals("lunes, 19 de enero de 2009\u2009\u2013\u2009jueves, 9 de febrero de 2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
}
@@ -330,7 +331,7 @@
c.set(2046, Calendar.OCTOBER, 4, 3, 30);
long oct_4_2046 = c.getTimeInMillis();
int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL;
- assertEquals("Jan 19, 2042 – Oct 4, 2046",
+ assertEquals("Jan 19, 2042\u2009\u2013\u2009Oct 4, 2046",
formatDateRange(l, tz, jan_19_2042, oct_4_2046, flags));
}
@@ -343,15 +344,15 @@
int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL | FORMAT_SHOW_TIME | FORMAT_24HOUR;
// The Unix epoch is UTC, so 0 is 1970-01-01T00:00Z...
- assertEquals("Jan 1, 1970, 00:00 – Jan 2, 1970, 00:00",
+ assertEquals("Jan 1, 1970, 00:00\u2009\u2013\u2009Jan 2, 1970, 00:00",
formatDateRange(l, utc, 0, DAY + 1, flags));
// But MTV is hours behind, so 0 was still the afternoon of the previous day...
- assertEquals("Dec 31, 1969, 16:00 – Jan 1, 1970, 16:00",
+ assertEquals("Dec 31, 1969, 16:00\u2009\u2013\u2009Jan 1, 1970, 16:00",
formatDateRange(l, pacific, 0, DAY, flags));
}
// http://b/10318326 - we can drop the minutes in a 12-hour time if they're zero,
- // but not if we're using the 24-hour clock. That is: "4 PM" is reasonable, "16" is not.
+ // but not if we're using the 24-hour clock. That is: "4\u202fPM" is reasonable, "16" is not.
@Test
public void test10318326() throws Exception {
long midnight = 0;
@@ -367,23 +368,26 @@
// Full length on-the-hour times.
assertEquals("00:00", formatDateRange(l, utc, midnight, midnight, time24));
- assertEquals("12:00 AM", formatDateRange(l, utc, midnight, midnight, time12));
+ assertEquals("12:00\u202fAM", formatDateRange(l, utc, midnight, midnight, time12));
assertEquals("16:00", formatDateRange(l, utc, teaTime, teaTime, time24));
- assertEquals("4:00 PM", formatDateRange(l, utc, teaTime, teaTime, time12));
+ assertEquals("4:00\u202fPM", formatDateRange(l, utc, teaTime, teaTime, time12));
// Abbreviated on-the-hour times.
assertEquals("00:00", formatDateRange(l, utc, midnight, midnight, abbr24));
- assertEquals("12 AM", formatDateRange(l, utc, midnight, midnight, abbr12));
+ assertEquals("12\u202fAM", formatDateRange(l, utc, midnight, midnight, abbr12));
assertEquals("16:00", formatDateRange(l, utc, teaTime, teaTime, abbr24));
- assertEquals("4 PM", formatDateRange(l, utc, teaTime, teaTime, abbr12));
+ assertEquals("4\u202fPM", formatDateRange(l, utc, teaTime, teaTime, abbr12));
// Abbreviated on-the-hour ranges.
- assertEquals("00:00 – 16:00", formatDateRange(l, utc, midnight, teaTime, abbr24));
- assertEquals("12 AM – 4 PM", formatDateRange(l, utc, midnight, teaTime, abbr12));
+ assertEquals("00:00\u2009\u2013\u200916:00", formatDateRange(l, utc, midnight, teaTime,
+ abbr24));
+ assertEquals("12\u202fAM\u2009\u2013\u20094\u202fPM", formatDateRange(l, utc, midnight,
+ teaTime, abbr12));
// Abbreviated mixed ranges.
- assertEquals("00:00 – 16:01", formatDateRange(l, utc, midnight, teaTime + MINUTE, abbr24));
- assertEquals("12:00 AM – 4:01 PM",
+ assertEquals("00:00\u2009\u2013\u200916:01", formatDateRange(l, utc, midnight,
+ teaTime + MINUTE, abbr24));
+ assertEquals("12:00\u202fAM\u2009\u2013\u20094:01\u202fPM",
formatDateRange(l, utc, midnight, teaTime + MINUTE, abbr12));
}
@@ -406,12 +410,12 @@
// Run one millisecond over, though, and you're into the next day.
long nextMorning = 1 * DAY + 1;
- assertEquals("Thursday, January 1 – Friday, January 2, 1970",
+ assertEquals("Thursday, January 1\u2009\u2013\u2009Friday, January 2, 1970",
formatDateRange(l, utc, midnight, nextMorning, flags));
// But the same reasoning applies for that day.
long nextMidnight = 2 * DAY;
- assertEquals("Thursday, January 1 – Friday, January 2, 1970",
+ assertEquals("Thursday, January 1\u2009\u2013\u2009Friday, January 2, 1970",
formatDateRange(l, utc, midnight, nextMidnight, flags));
}
@@ -424,9 +428,9 @@
int flags = FORMAT_SHOW_TIME | FORMAT_24HOUR | FORMAT_SHOW_DATE;
- assertEquals("January 1, 1970, 22:00 – 00:00",
+ assertEquals("January 1, 1970, 22:00\u2009\u2013\u200900:00",
formatDateRange(l, utc, 22 * HOUR, 24 * HOUR, flags));
- assertEquals("January 1, 1970 at 22:00 – January 2, 1970 at 00:30",
+ assertEquals("January 1, 1970 at 22:00\u2009\u2013\u2009January 2, 1970 at 00:30",
formatDateRange(l, utc, 22 * HOUR, 24 * HOUR + 30 * MINUTE, flags));
}
@@ -443,9 +447,9 @@
c.clear();
c.set(1980, Calendar.JANUARY, 1, 0, 0);
long jan_1_1980 = c.getTimeInMillis();
- assertEquals("January 1, 1980, 22:00 – 00:00",
+ assertEquals("January 1, 1980, 22:00\u2009\u2013\u200900:00",
formatDateRange(l, utc, jan_1_1980 + 22 * HOUR, jan_1_1980 + 24 * HOUR, flags));
- assertEquals("January 1, 1980 at 22:00 – January 2, 1980 at 00:30",
+ assertEquals("January 1, 1980 at 22:00\u2009\u2013\u2009January 2, 1980 at 00:30",
formatDateRange(l, utc, jan_1_1980 + 22 * HOUR,
jan_1_1980 + 24 * HOUR + 30 * MINUTE, flags));
}
@@ -463,12 +467,12 @@
c.clear();
c.set(1980, Calendar.JANUARY, 1, 0, 0);
long jan_1_1980 = c.getTimeInMillis();
- assertEquals("January 1, 1980, 22:00 – 00:00",
+ assertEquals("January 1, 1980, 22:00\u2009\u2013\u200900:00",
formatDateRange(l, pacific, jan_1_1980 + 22 * HOUR, jan_1_1980 + 24 * HOUR, flags));
c.set(1980, Calendar.JULY, 1, 0, 0);
long jul_1_1980 = c.getTimeInMillis();
- assertEquals("July 1, 1980, 22:00 – 00:00",
+ assertEquals("July 1, 1980, 22:00\u2009\u2013\u200900:00",
formatDateRange(l, pacific, jul_1_1980 + 22 * HOUR, jul_1_1980 + 24 * HOUR, flags));
}
@@ -531,11 +535,13 @@
formatDateRange(l, utc, oldYear, oldYear, FORMAT_SHOW_DATE | FORMAT_NO_YEAR));
// ...or the start and end years aren't the same...
- assertEquals(String.format("February 10, 1980 – February 10, %d", c.get(Calendar.YEAR)),
+ assertEquals(String.format("February 10, 1980\u2009\u2013\u2009February 10, %d",
+ c.get(Calendar.YEAR)),
formatDateRange(l, utc, oldYear, thisYear, FORMAT_SHOW_DATE));
// (And you can't avoid that --- icu4c steps in and overrides you.)
- assertEquals(String.format("February 10, 1980 – February 10, %d", c.get(Calendar.YEAR)),
+ assertEquals(String.format("February 10, 1980\u2009\u2013\u2009February 10, %d",
+ c.get(Calendar.YEAR)),
formatDateRange(l, utc, oldYear, thisYear, FORMAT_SHOW_DATE | FORMAT_NO_YEAR));
}
@@ -595,7 +601,7 @@
formatDateRange(new ULocale("fa"), utc, thisYear, thisYear, flags));
assertEquals("يونۍ د ۱۹۸۰ د فبروري ۱۰",
formatDateRange(new ULocale("ps"), utc, thisYear, thisYear, flags));
- assertEquals("วันอาทิตย์ที่ 10 กุมภาพันธ์ ค.ศ. 1980",
+ assertEquals("วันอาทิตย์ที่ 10 กุมภาพันธ์ 1980",
formatDateRange(new ULocale("th"), utc, thisYear, thisYear, flags));
}
@@ -607,9 +613,12 @@
int flags = FORMAT_SHOW_TIME | FORMAT_ABBREV_ALL | FORMAT_12HOUR;
- assertEquals("10 – 11 AM", formatDateRange(l, utc, 10 * HOUR, 11 * HOUR, flags));
- assertEquals("11 AM – 1 PM", formatDateRange(l, utc, 11 * HOUR, 13 * HOUR, flags));
- assertEquals("2 – 3 PM", formatDateRange(l, utc, 14 * HOUR, 15 * HOUR, flags));
+ assertEquals("10\u2009\u2013\u200911\u202fAM", formatDateRange(l, utc,
+ 10 * HOUR, 11 * HOUR, flags));
+ assertEquals("11\u202fAM\u2009\u2013\u20091\u202fPM", formatDateRange(l, utc,
+ 11 * HOUR, 13 * HOUR, flags));
+ assertEquals("2\u2009\u2013\u20093\u202fPM", formatDateRange(l, utc,
+ 14 * HOUR, 15 * HOUR, flags));
}
// http://b/20708022
@@ -618,8 +627,8 @@
final ULocale locale = new ULocale("en");
final TimeZone timeZone = TimeZone.getTimeZone("UTC");
- assertEquals("11:00 PM – 12:00 AM", formatDateRange(locale, timeZone,
- 1430434800000L, 1430438400000L, FORMAT_SHOW_TIME));
+ assertEquals("11:00\u202fPM\u2009\u2013\u200912:00\u202fAM", formatDateRange(locale,
+ timeZone, 1430434800000L, 1430438400000L, FORMAT_SHOW_TIME));
}
// http://b/68847519
@@ -629,23 +638,25 @@
ENGLISH, GMT_ZONE, from, to, FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_24HOUR);
// If we're showing times and the end-point is midnight the following day, we want the
// behaviour of suppressing the date for the end...
- assertEquals("February 27, 2007, 04:00 – 00:00", fmt.apply(1172548800000L, 1172620800000L));
+ assertEquals("February 27, 2007, 04:00\u2009\u2013\u200900:00", fmt.apply(1172548800000L,
+ 1172620800000L));
// ...unless the start-point is also midnight, in which case we need dates to disambiguate.
- assertEquals("February 27, 2007 at 00:00 – February 28, 2007 at 00:00",
+ assertEquals("February 27, 2007 at 00:00\u2009\u2013\u2009February 28, 2007 at 00:00",
fmt.apply(1172534400000L, 1172620800000L));
// We want to show the date if the end-point is a millisecond after midnight the following
// day, or if it is exactly midnight the day after that.
- assertEquals("February 27, 2007 at 04:00 – February 28, 2007 at 00:00",
+ assertEquals("February 27, 2007 at 04:00\u2009\u2013\u2009February 28, 2007 at 00:00",
fmt.apply(1172548800000L, 1172620800001L));
- assertEquals("February 27, 2007 at 04:00 – March 1, 2007 at 00:00",
+ assertEquals("February 27, 2007 at 04:00\u2009\u2013\u2009March 1, 2007 at 00:00",
fmt.apply(1172548800000L, 1172707200000L));
// We want to show the date if the start-point is anything less than a minute after
// midnight,
// since that gets displayed as midnight...
- assertEquals("February 27, 2007 at 00:00 – February 28, 2007 at 00:00",
+ assertEquals("February 27, 2007 at 00:00\u2009\u2013\u2009February 28, 2007 at 00:00",
fmt.apply(1172534459999L, 1172620800000L));
// ...but not if it is exactly one minute after midnight.
- assertEquals("February 27, 2007, 00:01 – 00:00", fmt.apply(1172534460000L, 1172620800000L));
+ assertEquals("February 27, 2007, 00:01\u2009\u2013\u200900:00", fmt.apply(1172534460000L,
+ 1172620800000L));
}
// http://b/68847519
@@ -656,16 +667,20 @@
// If we're only showing dates and the end-point is midnight of any day, we want the
// behaviour of showing an end date one earlier. So if the end-point is March 2, 2007 00:00,
// show March 1, 2007 instead (whether the start-point is midnight or not).
- assertEquals("February 27 – March 1, 2007", fmt.apply(1172534400000L, 1172793600000L));
- assertEquals("February 27 – March 1, 2007", fmt.apply(1172548800000L, 1172793600000L));
+ assertEquals("February 27\u2009\u2013\u2009March 1, 2007",
+ fmt.apply(1172534400000L, 1172793600000L));
+ assertEquals("February 27\u2009\u2013\u2009March 1, 2007",
+ fmt.apply(1172548800000L, 1172793600000L));
// We want to show the true date if the end-point is a millisecond after midnight.
- assertEquals("February 27 – March 2, 2007", fmt.apply(1172534400000L, 1172793600001L));
+ assertEquals("February 27\u2009\u2013\u2009March 2, 2007",
+ fmt.apply(1172534400000L, 1172793600001L));
// 2006-02-27 00:00:00.000 GMT - 2007-03-02 00:00:00.000 GMT
- assertEquals("February 27, 2006 – March 1, 2007",
+ assertEquals("February 27, 2006\u2009\u2013\u2009March 1, 2007",
fmt.apply(1140998400000L, 1172793600000L));
// Spans a leap year's Feb 29th.
- assertEquals("February 27 – March 1, 2004", fmt.apply(1077840000000L, 1078185600000L));
+ assertEquals("February 27\u2009\u2013\u2009March 1, 2004",
+ fmt.apply(1077840000000L, 1078185600000L));
}
}
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 381c051..39ed82ef 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -139,16 +139,16 @@
fixedTime, java.text.DateFormat.SHORT, java.text.DateFormat.FULL));
final long hourDuration = 2 * 60 * 60 * 1000;
- assertEquals("5:30:15 AM Greenwich Mean Time", DateUtils.formatSameDayTime(
+ assertEquals("5:30:15\u202fAM Greenwich Mean Time", DateUtils.formatSameDayTime(
fixedTime + hourDuration, fixedTime, java.text.DateFormat.FULL,
java.text.DateFormat.FULL));
- assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.DEFAULT));
- assertEquals("5:30:15 AM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30:15\u202fAM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.LONG));
- assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.MEDIUM));
- assertEquals("5:30 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.SHORT));
}
diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
index b342516..2337802 100644
--- a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
@@ -468,37 +468,37 @@
cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0);
final long base = cal.getTimeInMillis();
- assertEquals("5 seconds ago, 10:49 AM",
+ assertEquals("5 seconds ago, 10:49\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * SECOND_IN_MILLIS, base, 0,
MINUTE_IN_MILLIS, 0));
- assertEquals("5 min. ago, 10:45 AM",
+ assertEquals("5 min. ago, 10:45\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base, 0,
HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
- assertEquals("0 hr. ago, 10:45 AM",
+ assertEquals("0 hr. ago, 10:45\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base,
HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
- assertEquals("5 hours ago, 5:50 AM",
+ assertEquals("5 hours ago, 5:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base,
HOUR_IN_MILLIS, DAY_IN_MILLIS, 0));
- assertEquals("Yesterday, 7:50 PM",
+ assertEquals("Yesterday, 7:50\u202fPM",
getRelativeDateTimeString(en_US, tz, base - 15 * HOUR_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
- assertEquals("5 days ago, 10:50 AM",
+ assertEquals("5 days ago, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * DAY_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
- assertEquals("Jan 29, 10:50 AM",
+ assertEquals("Jan 29, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 7 * DAY_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
- assertEquals("11/27/2014, 10:50 AM",
+ assertEquals("11/27/2014, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
- assertEquals("11/27/2014, 10:50 AM",
+ assertEquals("11/27/2014, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
YEAR_IN_MILLIS, 0));
// User-supplied flags should be ignored when formatting the date clause.
final int FORMAT_SHOW_WEEKDAY = 0x00002;
- assertEquals("11/27/2014, 10:50 AM",
+ assertEquals("11/27/2014, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
WEEK_IN_MILLIS,
FORMAT_ABBREV_ALL | FORMAT_SHOW_WEEKDAY));
@@ -514,14 +514,14 @@
// So 5 hours before 3:15 AM should be formatted as 'Yesterday, 9:15 PM'.
cal.set(2014, Calendar.MARCH, 9, 3, 15, 0);
long base = cal.getTimeInMillis();
- assertEquals("Yesterday, 9:15 PM",
+ assertEquals("Yesterday, 9:15\u202fPM",
getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
// 1 hour after 2:00 AM should be formatted as 'In 1 hour, 4:00 AM'.
cal.set(2014, Calendar.MARCH, 9, 2, 0, 0);
base = cal.getTimeInMillis();
- assertEquals("In 1 hour, 4:00 AM",
+ assertEquals("In 1 hour, 4:00\u202fAM",
getRelativeDateTimeString(en_US, tz, base + 1 * HOUR_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
@@ -529,22 +529,22 @@
// 1:00 AM. 8 hours before 5:20 AM should be 'Yesterday, 10:20 PM'.
cal.set(2014, Calendar.NOVEMBER, 2, 5, 20, 0);
base = cal.getTimeInMillis();
- assertEquals("Yesterday, 10:20 PM",
+ assertEquals("Yesterday, 10:20\u202fPM",
getRelativeDateTimeString(en_US, tz, base - 8 * HOUR_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
cal.set(2014, Calendar.NOVEMBER, 2, 0, 45, 0);
base = cal.getTimeInMillis();
// 45 minutes after 0:45 AM should be 'In 45 minutes, 1:30 AM'.
- assertEquals("In 45 minutes, 1:30 AM",
+ assertEquals("In 45 minutes, 1:30\u202fAM",
getRelativeDateTimeString(en_US, tz, base + 45 * MINUTE_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
// 45 minutes later, it should be 'In 45 minutes, 1:15 AM'.
- assertEquals("In 45 minutes, 1:15 AM",
+ assertEquals("In 45 minutes, 1:15\u202fAM",
getRelativeDateTimeString(en_US, tz, base + 90 * MINUTE_IN_MILLIS,
base + 45 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
// Another 45 minutes later, it should be 'In 45 minutes, 2:00 AM'.
- assertEquals("In 45 minutes, 2:00 AM",
+ assertEquals("In 45 minutes, 2:00\u202fAM",
getRelativeDateTimeString(en_US, tz, base + 135 * MINUTE_IN_MILLIS,
base + 90 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
}
@@ -593,7 +593,7 @@
Calendar yesterdayCalendar1 = Calendar.getInstance(tz, en_US);
yesterdayCalendar1.set(2011, Calendar.SEPTEMBER, 1, 10, 24, 0);
long yesterday1 = yesterdayCalendar1.getTimeInMillis();
- assertEquals("Yesterday, 10:24 AM",
+ assertEquals("Yesterday, 10:24\u202fAM",
getRelativeDateTimeString(en_US, tz, yesterday1, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -601,7 +601,7 @@
Calendar yesterdayCalendar2 = Calendar.getInstance(tz, en_US);
yesterdayCalendar2.set(2011, Calendar.SEPTEMBER, 1, 10, 22, 0);
long yesterday2 = yesterdayCalendar2.getTimeInMillis();
- assertEquals("Yesterday, 10:22 AM",
+ assertEquals("Yesterday, 10:22\u202fAM",
getRelativeDateTimeString(en_US, tz, yesterday2, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -609,7 +609,7 @@
Calendar twoDaysAgoCalendar1 = Calendar.getInstance(tz, en_US);
twoDaysAgoCalendar1.set(2011, Calendar.AUGUST, 31, 10, 24, 0);
long twoDaysAgo1 = twoDaysAgoCalendar1.getTimeInMillis();
- assertEquals("2 days ago, 10:24 AM",
+ assertEquals("2 days ago, 10:24\u202fAM",
getRelativeDateTimeString(en_US, tz, twoDaysAgo1, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -617,7 +617,7 @@
Calendar twoDaysAgoCalendar2 = Calendar.getInstance(tz, en_US);
twoDaysAgoCalendar2.set(2011, Calendar.AUGUST, 31, 10, 22, 0);
long twoDaysAgo2 = twoDaysAgoCalendar2.getTimeInMillis();
- assertEquals("2 days ago, 10:22 AM",
+ assertEquals("2 days ago, 10:22\u202fAM",
getRelativeDateTimeString(en_US, tz, twoDaysAgo2, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -625,7 +625,7 @@
Calendar tomorrowCalendar1 = Calendar.getInstance(tz, en_US);
tomorrowCalendar1.set(2011, Calendar.SEPTEMBER, 3, 10, 22, 0);
long tomorrow1 = tomorrowCalendar1.getTimeInMillis();
- assertEquals("Tomorrow, 10:22 AM",
+ assertEquals("Tomorrow, 10:22\u202fAM",
getRelativeDateTimeString(en_US, tz, tomorrow1, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -633,7 +633,7 @@
Calendar tomorrowCalendar2 = Calendar.getInstance(tz, en_US);
tomorrowCalendar2.set(2011, Calendar.SEPTEMBER, 3, 10, 24, 0);
long tomorrow2 = tomorrowCalendar2.getTimeInMillis();
- assertEquals("Tomorrow, 10:24 AM",
+ assertEquals("Tomorrow, 10:24\u202fAM",
getRelativeDateTimeString(en_US, tz, tomorrow2, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -641,7 +641,7 @@
Calendar twoDaysLaterCalendar1 = Calendar.getInstance(tz, en_US);
twoDaysLaterCalendar1.set(2011, Calendar.SEPTEMBER, 4, 10, 22, 0);
long twoDaysLater1 = twoDaysLaterCalendar1.getTimeInMillis();
- assertEquals("In 2 days, 10:22 AM",
+ assertEquals("In 2 days, 10:22\u202fAM",
getRelativeDateTimeString(en_US, tz, twoDaysLater1, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -649,7 +649,7 @@
Calendar twoDaysLaterCalendar2 = Calendar.getInstance(tz, en_US);
twoDaysLaterCalendar2.set(2011, Calendar.SEPTEMBER, 4, 10, 24, 0);
long twoDaysLater2 = twoDaysLaterCalendar2.getTimeInMillis();
- assertEquals("In 2 days, 10:24 AM",
+ assertEquals("In 2 days, 10:24\u202fAM",
getRelativeDateTimeString(en_US, tz, twoDaysLater2, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
}
@@ -664,11 +664,11 @@
cal.set(2012, Calendar.FEBRUARY, 5, 10, 50, 0);
long base = cal.getTimeInMillis();
- assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("Feb 5, 5:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0));
- assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("Jan 29, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
- assertEquals("11/27/2011, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("11/27/2011, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
@@ -687,11 +687,11 @@
// Feb 5, 2018 at 10:50 PST
cal.set(2018, Calendar.FEBRUARY, 5, 10, 50, 0);
base = cal.getTimeInMillis();
- assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("Feb 5, 5:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0));
- assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("Jan 29, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
- assertEquals("11/27/2017, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("11/27/2017, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
index b34554c..c3d40eb 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.os.FileUtils;
+import android.util.IntArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -33,13 +34,15 @@
import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList;
+import java.util.Arrays;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ProcLocksReaderTest implements
ProcLocksReader.ProcLocksReaderCallback {
private File mProcDirectory;
- private ArrayList<Integer> mPids = new ArrayList<>();
+
+ private ArrayList<int[]> mPids = new ArrayList<>();
@Before
public void setUp() {
@@ -54,41 +57,51 @@
@Test
public void testRunSimpleLocks() throws Exception {
- String simpleLocks =
- "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
- "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n";
+ String simpleLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n"
+ + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n";
runHandleBlockingFileLocks(simpleLocks);
assertTrue(mPids.isEmpty());
}
@Test
public void testRunBlockingLocks() throws Exception {
- String blockedLocks =
- "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
- "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" +
- "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n" +
- "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n" +
- "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n" +
- "4: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n";
+ String blockedLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n"
+ + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n"
+ + "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n"
+ + "4: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n";
runHandleBlockingFileLocks(blockedLocks);
- assertTrue(mPids.remove(0).equals(18292));
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
+ assertTrue(mPids.isEmpty());
+ }
+
+ @Test
+ public void testRunLastBlockingLocks() throws Exception {
+ String blockedLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n"
+ + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n";
+ runHandleBlockingFileLocks(blockedLocks);
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
assertTrue(mPids.isEmpty());
}
@Test
public void testRunMultipleBlockingLocks() throws Exception {
- String blockedLocks =
- "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
- "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" +
- "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n" +
- "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n" +
- "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n" +
- "4: FLOCK ADVISORY WRITE 3840 fe:01:5111809 0 EOF\n" +
- "4: -> FLOCK ADVISORY WRITE 3841 fe:01:5111809 0 EOF\n" +
- "5: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n";
+ String blockedLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n"
+ + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n"
+ + "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n"
+ + "4: FLOCK ADVISORY WRITE 3840 fe:01:5111809 0 EOF\n"
+ + "4: -> FLOCK ADVISORY WRITE 3841 fe:01:5111809 0 EOF\n"
+ + "5: FLOCK ADVISORY READ 3888 fd:09:14230 0 EOF\n"
+ + "5: -> FLOCK ADVISORY READ 3887 fd:09:14230 0 EOF\n";
runHandleBlockingFileLocks(blockedLocks);
- assertTrue(mPids.remove(0).equals(18292));
- assertTrue(mPids.remove(0).equals(3840));
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{3840, 3841}));
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{3888, 3887}));
assertTrue(mPids.isEmpty());
}
@@ -102,11 +115,12 @@
/**
* Call the callback function of handleBlockingFileLocks().
- *
- * @param pid Each process that hold file locks blocking other processes.
+ * @param pids Each process that hold file locks blocking other processes.
+ * pids[0] is the process blocking others
+ * pids[1..n-1] are the processes being blocked
*/
@Override
- public void onBlockingFileLock(int pid) {
- mPids.add(pid);
+ public void onBlockingFileLock(IntArray pids) {
+ mPids.add(pids.toArray());
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java b/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
new file mode 100644
index 0000000..c540a15
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static org.junit.Assert.assertThrows;
+
+import android.compat.testing.PlatformCompatChangeRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Test SafeZipPathCallback.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SafeZipPathValidatorCallbackTest {
+ @Rule
+ public TestRule mCompatChangeRule = new PlatformCompatChangeRule();
+
+ @Before
+ public void setUp() {
+ RuntimeInit.initZipPathValidatorCallback();
+ }
+
+ @Test
+ @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void testNewZipFile_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
+ throws Exception {
+ final String[] dangerousEntryNames = {
+ "../foo.bar",
+ "foo/../bar.baz",
+ "foo/../../bar.baz",
+ "foo.bar/..",
+ "foo.bar/../",
+ "..",
+ "../",
+ "/foo",
+ };
+ for (String entryName : dangerousEntryNames) {
+ final File tempFile = File.createTempFile("smdc", "zip");
+ try {
+ writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+
+ assertThrows(
+ "ZipException expected for entry: " + entryName,
+ ZipException.class,
+ () -> {
+ new ZipFile(tempFile);
+ });
+ } finally {
+ tempFile.delete();
+ }
+ }
+ }
+
+ @Test
+ @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void
+ testZipInputStreamGetNextEntry_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
+ throws Exception {
+ final String[] dangerousEntryNames = {
+ "../foo.bar",
+ "foo/../bar.baz",
+ "foo/../../bar.baz",
+ "foo.bar/..",
+ "foo.bar/../",
+ "..",
+ "../",
+ "/foo",
+ };
+ for (String entryName : dangerousEntryNames) {
+ byte[] badZipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+ try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(badZipBytes))) {
+ assertThrows(
+ "ZipException expected for entry: " + entryName,
+ ZipException.class,
+ () -> {
+ zis.getNextEntry();
+ });
+ }
+ }
+ }
+
+ @Test
+ @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void testNewZipFile_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
+ throws Exception {
+ final String[] normalEntryNames = {
+ "foo", "foo.bar", "foo..bar",
+ };
+ for (String entryName : normalEntryNames) {
+ final File tempFile = File.createTempFile("smdc", "zip");
+ try {
+ writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+ try {
+ new ZipFile((tempFile));
+ } catch (ZipException e) {
+ throw new AssertionError("ZipException not expected for entry: " + entryName);
+ }
+ } finally {
+ tempFile.delete();
+ }
+ }
+ }
+
+ @Test
+ @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void
+ testZipInputStreamGetNextEntry_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
+ throws Exception {
+ final String[] normalEntryNames = {
+ "foo", "foo.bar", "foo..bar",
+ };
+ for (String entryName : normalEntryNames) {
+ byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+ try {
+ ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
+ zis.getNextEntry();
+ } catch (ZipException e) {
+ throw new AssertionError("ZipException not expected for entry: " + entryName);
+ }
+ }
+ }
+
+ @Test
+ @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void
+ testNewZipFile_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
+ throws Exception {
+ final String[] entryNames = {
+ "../foo.bar",
+ "foo/../bar.baz",
+ "foo/../../bar.baz",
+ "foo.bar/..",
+ "foo.bar/../",
+ "..",
+ "../",
+ "/foo",
+ "foo",
+ "foo.bar",
+ "foo..bar",
+ };
+ for (String entryName : entryNames) {
+ final File tempFile = File.createTempFile("smdc", "zip");
+ try {
+ writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+ try {
+ new ZipFile((tempFile));
+ } catch (ZipException e) {
+ throw new AssertionError("ZipException not expected for entry: " + entryName);
+ }
+ } finally {
+ tempFile.delete();
+ }
+ }
+ }
+
+ @Test
+ @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void
+ testZipInputStreamGetNextEntry_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
+ throws Exception {
+ final String[] entryNames = {
+ "../foo.bar",
+ "foo/../bar.baz",
+ "foo/../../bar.baz",
+ "foo.bar/..",
+ "foo.bar/../",
+ "..",
+ "../",
+ "/foo",
+ "foo",
+ "foo.bar",
+ "foo..bar",
+ };
+ for (String entryName : entryNames) {
+ byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+ try {
+ ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
+ zis.getNextEntry();
+ } catch (ZipException e) {
+ throw new AssertionError("ZipException not expected for entry: " + entryName);
+ }
+ }
+ }
+
+ private void writeZipFileOutputStreamWithEmptyEntry(File tempFile, String entryName)
+ throws IOException {
+ FileOutputStream tempFileStream = new FileOutputStream(tempFile);
+ writeZipOutputStreamWithEmptyEntry(tempFileStream, entryName);
+ tempFileStream.close();
+ }
+
+ private byte[] getZipBytesFromZipOutputStreamWithEmptyEntry(String entryName)
+ throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ writeZipOutputStreamWithEmptyEntry(bos, entryName);
+ return bos.toByteArray();
+ }
+
+ private void writeZipOutputStreamWithEmptyEntry(OutputStream os, String entryName)
+ throws IOException {
+ ZipOutputStream zos = new ZipOutputStream(os);
+ ZipEntry entry = new ZipEntry(entryName);
+ zos.putNextEntry(entry);
+ zos.write(new byte[2]);
+ zos.closeEntry();
+ zos.close();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 5b7ed27..6e116b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -163,7 +163,8 @@
/** Showing resizing hint. */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
- Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY) {
+ Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
+ boolean immediately) {
if (mResizingIconView == null) {
return;
}
@@ -178,8 +179,8 @@
final boolean show =
newBounds.width() > mBounds.width() || newBounds.height() > mBounds.height();
- final boolean animate = show != mShown;
- if (animate && mFadeAnimator != null && mFadeAnimator.isRunning()) {
+ final boolean update = show != mShown;
+ if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) {
// If we need to animate and animator still running, cancel it before we ensure both
// background and icon surfaces are non null for next animation.
mFadeAnimator.cancel();
@@ -192,7 +193,7 @@
.setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
}
- if (mGapBackgroundLeash == null) {
+ if (mGapBackgroundLeash == null && !immediately) {
final boolean isLandscape = newBounds.height() == sideBounds.height();
final int left = isLandscape ? mBounds.width() : 0;
final int top = isLandscape ? 0 : mBounds.height();
@@ -221,8 +222,13 @@
newBounds.width() / 2 - mIconSize / 2,
newBounds.height() / 2 - mIconSize / 2);
- if (animate) {
- startFadeAnimation(show, null /* finishedConsumer */);
+ if (update) {
+ if (immediately) {
+ t.setVisibility(mBackgroundLeash, show);
+ t.setVisibility(mIconLeash, show);
+ } else {
+ startFadeAnimation(show, null /* finishedConsumer */);
+ }
mShown = show;
}
}
@@ -319,10 +325,12 @@
@Override
public void onAnimationStart(@NonNull Animator animation) {
if (show) {
- animT.show(mBackgroundLeash).show(mIconLeash).show(mGapBackgroundLeash).apply();
- } else {
- animT.hide(mGapBackgroundLeash).apply();
+ animT.show(mBackgroundLeash).show(mIconLeash);
}
+ if (mGapBackgroundLeash != null) {
+ animT.setVisibility(mGapBackgroundLeash, show);
+ }
+ animT.apply();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 3de1045..ec9e6f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -83,8 +83,8 @@
private static final int FLING_RESIZE_DURATION = 250;
private static final int FLING_SWITCH_DURATION = 350;
- private static final int FLING_ENTER_DURATION = 350;
- private static final int FLING_EXIT_DURATION = 350;
+ private static final int FLING_ENTER_DURATION = 450;
+ private static final int FLING_EXIT_DURATION = 450;
private int mDividerWindowWidth;
private int mDividerInsets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c2ab7ef..3bb630d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -169,6 +169,7 @@
private ValueAnimator mDividerFadeInAnimator;
private boolean mDividerVisible;
private boolean mKeyguardShowing;
+ private boolean mShowDecorImmediately;
private final SyncTransactionQueue mSyncQueue;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -1556,6 +1557,7 @@
if (mLogger.isEnterRequestedByDrag()) {
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
} else {
+ mShowDecorImmediately = true;
mSplitLayout.flingDividerToCenter();
}
});
@@ -1631,14 +1633,16 @@
updateSurfaceBounds(layout, t, true /* applyResizingOffset */);
getMainStageBounds(mTempRect1);
getSideStageBounds(mTempRect2);
- mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY);
- mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY);
+ mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
+ mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
t.apply();
mTransactionPool.release(t);
}
@Override
public void onLayoutSizeChanged(SplitLayout layout) {
+ // Reset this flag every time onLayoutSizeChanged.
+ mShowDecorImmediately = false;
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateWindowBounds(layout, wct);
sendOnBoundsChanged();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index acad5d9..bcf900b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -289,10 +289,10 @@
}
void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
- int offsetY) {
+ int offsetY, boolean immediately) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
- offsetY);
+ offsetY, immediately);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 48c0cea1..d3f1332 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -317,7 +317,7 @@
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- if (mDragging) {
+ if (mShouldHandleEvents && mDragging) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
mCallback.onDragResizeEnd(
e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
index b3fb145..b68143d 100644
--- a/libs/androidfw/LocaleDataTables.cpp
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -143,6 +143,7 @@
{0xACE00000u, 46u}, // ahl -> Latn
{0xB8E00000u, 1u}, // aho -> Ahom
{0x99200000u, 46u}, // ajg -> Latn
+ {0xCD200000u, 2u}, // ajt -> Arab
{0x616B0000u, 46u}, // ak -> Latn
{0xA9400000u, 101u}, // akk -> Xsux
{0x81600000u, 46u}, // ala -> Latn
@@ -1053,6 +1054,7 @@
{0xB70D0000u, 46u}, // nyn -> Latn
{0xA32D0000u, 46u}, // nzi -> Latn
{0x6F630000u, 46u}, // oc -> Latn
+ {0x6F634553u, 46u}, // oc-ES -> Latn
{0x88CE0000u, 46u}, // ogc -> Latn
{0x6F6A0000u, 11u}, // oj -> Cans
{0xC92E0000u, 11u}, // ojs -> Cans
@@ -1093,6 +1095,7 @@
{0xB4EF0000u, 71u}, // phn -> Phnx
{0xAD0F0000u, 46u}, // pil -> Latn
{0xBD0F0000u, 46u}, // pip -> Latn
+ {0xC90F0000u, 46u}, // pis -> Latn
{0x814F0000u, 9u}, // pka -> Brah
{0xB94F0000u, 46u}, // pko -> Latn
{0x706C0000u, 46u}, // pl -> Latn
@@ -1204,12 +1207,14 @@
{0xE1720000u, 46u}, // sly -> Latn
{0x736D0000u, 46u}, // sm -> Latn
{0x81920000u, 46u}, // sma -> Latn
+ {0x8D920000u, 46u}, // smd -> Latn
{0xA5920000u, 46u}, // smj -> Latn
{0xB5920000u, 46u}, // smn -> Latn
{0xBD920000u, 76u}, // smp -> Samr
{0xC1920000u, 46u}, // smq -> Latn
{0xC9920000u, 46u}, // sms -> Latn
{0x736E0000u, 46u}, // sn -> Latn
+ {0x85B20000u, 46u}, // snb -> Latn
{0x89B20000u, 46u}, // snc -> Latn
{0xA9B20000u, 46u}, // snk -> Latn
{0xBDB20000u, 46u}, // snp -> Latn
@@ -1314,6 +1319,7 @@
{0x746F0000u, 46u}, // to -> Latn
{0x95D30000u, 46u}, // tof -> Latn
{0x99D30000u, 46u}, // tog -> Latn
+ {0xA9D30000u, 46u}, // tok -> Latn
{0xC1D30000u, 46u}, // toq -> Latn
{0xA1F30000u, 46u}, // tpi -> Latn
{0xB1F30000u, 46u}, // tpm -> Latn
@@ -1527,6 +1533,7 @@
0x61665A414C61746ELLU, // af_Latn_ZA
0xC0C0434D4C61746ELLU, // agq_Latn_CM
0xB8E0494E41686F6DLLU, // aho_Ahom_IN
+ 0xCD20544E41726162LLU, // ajt_Arab_TN
0x616B47484C61746ELLU, // ak_Latn_GH
0xA940495158737578LLU, // akk_Xsux_IQ
0xB560584B4C61746ELLU, // aln_Latn_XK
@@ -1534,6 +1541,7 @@
0x616D455445746869LLU, // am_Ethi_ET
0xB9804E474C61746ELLU, // amo_Latn_NG
0x616E45534C61746ELLU, // an_Latn_ES
+ 0xB5A04E474C61746ELLU, // ann_Latn_NG
0xE5C049444C61746ELLU, // aoz_Latn_ID
0x8DE0544741726162LLU, // apd_Arab_TG
0x6172454741726162LLU, // ar_Arab_EG
@@ -2039,6 +2047,7 @@
0xB88F49525870656FLLU, // peo_Xpeo_IR
0xACAF44454C61746ELLU, // pfl_Latn_DE
0xB4EF4C4250686E78LLU, // phn_Phnx_LB
+ 0xC90F53424C61746ELLU, // pis_Latn_SB
0x814F494E42726168LLU, // pka_Brah_IN
0xB94F4B454C61746ELLU, // pko_Latn_KE
0x706C504C4C61746ELLU, // pl_Latn_PL
@@ -2119,11 +2128,13 @@
0xE17249444C61746ELLU, // sly_Latn_ID
0x736D57534C61746ELLU, // sm_Latn_WS
0x819253454C61746ELLU, // sma_Latn_SE
+ 0x8D92414F4C61746ELLU, // smd_Latn_AO
0xA59253454C61746ELLU, // smj_Latn_SE
0xB59246494C61746ELLU, // smn_Latn_FI
0xBD92494C53616D72LLU, // smp_Samr_IL
0xC99246494C61746ELLU, // sms_Latn_FI
0x736E5A574C61746ELLU, // sn_Latn_ZW
+ 0x85B24D594C61746ELLU, // snb_Latn_MY
0xA9B24D4C4C61746ELLU, // snk_Latn_ML
0x736F534F4C61746ELLU, // so_Latn_SO
0x99D2555A536F6764LLU, // sog_Sogd_UZ
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index c18edcd..1504930 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -360,6 +360,7 @@
lnb,
gFields.onLnbEventID,
(jint)lnbEventType);
+ env->DeleteLocalRef(lnb);
} else {
ALOGE("LnbClientCallbackImpl::onEvent:"
"Lnb object has been freed. Ignoring callback.");
@@ -378,6 +379,7 @@
lnb,
gFields.onLnbDiseqcMessageID,
array);
+ env->DeleteLocalRef(lnb);
} else {
ALOGE("LnbClientCallbackImpl::onDiseqcMessage:"
"Lnb object has been freed. Ignoring callback.");
@@ -404,6 +406,7 @@
jobject dvr(env->NewLocalRef(mDvrObj));
if (!env->IsSameObject(dvr, nullptr)) {
env->CallVoidMethod(dvr, gFields.onDvrRecordStatusID, (jint)status);
+ env->DeleteLocalRef(dvr);
} else {
ALOGE("DvrClientCallbackImpl::onRecordStatus:"
"Dvr object has been freed. Ignoring callback.");
@@ -416,6 +419,7 @@
jobject dvr(env->NewLocalRef(mDvrObj));
if (!env->IsSameObject(dvr, nullptr)) {
env->CallVoidMethod(dvr, gFields.onDvrPlaybackStatusID, (jint)status);
+ env->DeleteLocalRef(dvr);
} else {
ALOGE("DvrClientCallbackImpl::onPlaybackStatus:"
"Dvr object has been freed. Ignoring callback.");
@@ -603,6 +607,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, tableId, version, sectionNum, dataLength);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size,
@@ -673,6 +678,10 @@
}
env->SetObjectArrayElement(arr, size, obj);
+ if(audioDescriptor != nullptr) {
+ env->DeleteLocalRef(audioDescriptor);
+ }
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
@@ -688,6 +697,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, streamId, dataLength, mpuSequenceNumber);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int size,
@@ -725,6 +735,7 @@
jobject obj =
env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber, pts, firstMbInSlice);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getMmtpRecordEvent(jobjectArray &arr, const int size,
@@ -745,6 +756,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber,
mpuSequenceNumber, pts, firstMbInSlice, tsIndexMask);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getDownloadEvent(jobjectArray &arr, const int size,
@@ -764,6 +776,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, itemId, downloadId, mpuSequenceNumber,
itemFragmentIndex, lastItemFragmentIndex, dataLength);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getIpPayloadEvent(jobjectArray &arr, const int size,
@@ -776,6 +789,7 @@
jint dataLength = ipPayloadEvent.dataLength;
jobject obj = env->NewObject(eventClazz, eventInit, dataLength);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getTemiEvent(jobjectArray &arr, const int size,
@@ -794,6 +808,8 @@
jobject obj = env->NewObject(eventClazz, eventInit, pts, descrTag, array);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(array);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getScramblingStatusEvent(jobjectArray &arr, const int size,
@@ -807,6 +823,7 @@
.get<DemuxFilterMonitorEvent::Tag::scramblingStatus>();
jobject obj = env->NewObject(eventClazz, eventInit, scramblingStatus);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getIpCidChangeEvent(jobjectArray &arr, const int size,
@@ -819,6 +836,7 @@
.get<DemuxFilterMonitorEvent::Tag::cid>();
jobject obj = env->NewObject(eventClazz, eventInit, cid);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getRestartEvent(jobjectArray &arr, const int size,
@@ -922,10 +940,12 @@
methodID = gFields.onSharedFilterEventID;
}
env->CallVoidMethod(filter, methodID, array);
+ env->DeleteLocalRef(filter);
} else {
ALOGE("FilterClientCallbackImpl::onFilterEvent:"
"Filter object has been freed. Ignoring callback.");
}
+ env->DeleteLocalRef(array);
}
void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
@@ -938,6 +958,7 @@
methodID = gFields.onSharedFilterStatusID;
}
env->CallVoidMethod(filter, methodID, (jint)static_cast<uint8_t>(status));
+ env->DeleteLocalRef(filter);
} else {
ALOGE("FilterClientCallbackImpl::onFilterStatus:"
"Filter object has been freed. Ignoring callback.");
@@ -1006,6 +1027,7 @@
frontend,
gFields.onFrontendEventID,
(jint)frontendEventType);
+ env->DeleteLocalRef(frontend);
} else {
ALOGW("FrontendClientCallbackImpl::onEvent:"
"Frontend object has been freed. Ignoring callback.");
@@ -1028,6 +1050,7 @@
continue;
}
executeOnScanMessage(env, clazz, frontend, type, message);
+ env->DeleteLocalRef(frontend);
}
}
@@ -1069,6 +1092,7 @@
env->SetLongArrayRegion(freqs, 0, v.size(), reinterpret_cast<jlong *>(&v[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onFrequenciesReport", "([J)V"),
freqs);
+ env->DeleteLocalRef(freqs);
break;
}
case FrontendScanMessageType::SYMBOL_RATE: {
@@ -1077,6 +1101,7 @@
env->SetIntArrayRegion(symbolRates, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onSymbolRates", "([I)V"),
symbolRates);
+ env->DeleteLocalRef(symbolRates);
break;
}
case FrontendScanMessageType::HIERARCHY: {
@@ -1094,6 +1119,7 @@
jintArray plpIds = env->NewIntArray(jintV.size());
env->SetIntArrayRegion(plpIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onPlpIds", "([I)V"), plpIds);
+ env->DeleteLocalRef(plpIds);
break;
}
case FrontendScanMessageType::GROUP_IDS: {
@@ -1101,6 +1127,7 @@
jintArray groupIds = env->NewIntArray(jintV.size());
env->SetIntArrayRegion(groupIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onGroupIds", "([I)V"), groupIds);
+ env->DeleteLocalRef(groupIds);
break;
}
case FrontendScanMessageType::INPUT_STREAM_IDS: {
@@ -1109,6 +1136,7 @@
env->SetIntArrayRegion(streamIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onInputStreamIds", "([I)V"),
streamIds);
+ env->DeleteLocalRef(streamIds);
break;
}
case FrontendScanMessageType::STANDARD: {
@@ -1142,12 +1170,14 @@
jboolean lls = info.bLlsFlag;
jobject obj = env->NewObject(plpClazz, init, plpId, lls);
env->SetObjectArrayElement(array, i, obj);
+ env->DeleteLocalRef(obj);
}
env->CallVoidMethod(frontend,
env->GetMethodID(clazz, "onAtsc3PlpInfos",
"([Landroid/media/tv/tuner/frontend/"
"Atsc3PlpInfo;)V"),
array);
+ env->DeleteLocalRef(array);
break;
}
case FrontendScanMessageType::MODULATION: {
@@ -1219,6 +1249,7 @@
env->SetIntArrayRegion(cellIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onDvbtCellIdsReported", "([I)V"),
cellIds);
+ env->DeleteLocalRef(cellIds);
break;
}
default:
@@ -1673,6 +1704,7 @@
for (int i = 0; i < size; i++) {
jobject readinessObj = env->NewObject(clazz, init, intTypes[i], readiness[i]);
env->SetObjectArrayElement(valObj, i, readinessObj);
+ env->DeleteLocalRef(readinessObj);
}
return valObj;
}
@@ -2081,6 +2113,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isDemodLocked>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::snr: {
@@ -2088,6 +2121,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::snr>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::ber: {
@@ -2095,6 +2129,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::ber>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::per: {
@@ -2102,6 +2137,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::per>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::preBer: {
@@ -2109,6 +2145,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::preBer>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::signalQuality: {
@@ -2116,6 +2153,7 @@
jobject newIntegerObj = env->NewObject(intClazz, initInt,
s.get<FrontendStatus::Tag::signalQuality>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::signalStrength: {
@@ -2124,6 +2162,7 @@
env->NewObject(intClazz, initInt,
s.get<FrontendStatus::Tag::signalStrength>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::symbolRate: {
@@ -2131,6 +2170,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::symbolRate>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::innerFec: {
@@ -2141,6 +2181,8 @@
env->NewObject(longClazz, initLong,
static_cast<long>(s.get<FrontendStatus::Tag::innerFec>()));
env->SetObjectField(statusObj, field, newLongObj);
+ env->DeleteLocalRef(longClazz);
+ env->DeleteLocalRef(newLongObj);
break;
}
case FrontendStatus::Tag::modulationStatus: {
@@ -2183,6 +2225,7 @@
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intModulation);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2192,6 +2235,7 @@
env->NewObject(intClazz, initInt,
static_cast<jint>(s.get<FrontendStatus::Tag::inversion>()));
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::lnbVoltage: {
@@ -2200,6 +2244,7 @@
env->NewObject(intClazz, initInt,
static_cast<jint>(s.get<FrontendStatus::Tag::lnbVoltage>()));
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::plpId: {
@@ -2207,6 +2252,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::plpId>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::isEWBS: {
@@ -2214,6 +2260,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isEWBS>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::agc: {
@@ -2221,6 +2268,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::agc>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::isLnaOn: {
@@ -2228,6 +2276,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isLnaOn>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::isLayerError: {
@@ -2241,6 +2290,7 @@
env->SetBooleanArrayRegion(valObj, i, 1, &x);
}
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::mer: {
@@ -2248,6 +2298,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::mer>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::freqOffset: {
@@ -2255,6 +2306,7 @@
jobject newLongObj = env->NewObject(longClazz, initLong,
s.get<FrontendStatus::Tag::freqOffset>());
env->SetObjectField(statusObj, field, newLongObj);
+ env->DeleteLocalRef(newLongObj);
break;
}
case FrontendStatus::Tag::hierarchy: {
@@ -2263,6 +2315,7 @@
env->NewObject(intClazz, initInt,
static_cast<jint>(s.get<FrontendStatus::Tag::hierarchy>()));
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::isRfLocked: {
@@ -2270,6 +2323,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isRfLocked>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::plpInfo: {
@@ -2289,9 +2343,12 @@
jobject plpObj = env->NewObject(plpClazz, initPlp, plpId, isLocked, uec);
env->SetObjectArrayElement(valObj, i, plpObj);
+ env->DeleteLocalRef(plpObj);
}
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(plpClazz);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::modulations: {
@@ -2374,6 +2431,7 @@
if (valid) {
env->SetObjectField(statusObj, field, valObj);
}
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::bers: {
@@ -2384,6 +2442,7 @@
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::codeRates: {
@@ -2394,6 +2453,7 @@
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::bandwidth: {
@@ -2434,6 +2494,7 @@
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intBandwidth);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2465,6 +2526,7 @@
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intInterval);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2497,6 +2559,7 @@
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intTransmissionMode);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2505,6 +2568,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::uec>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::systemId: {
@@ -2512,6 +2576,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::systemId>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::interleaving: {
@@ -2558,6 +2623,7 @@
if (valid) {
env->SetObjectField(statusObj, field, valObj);
}
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::isdbtSegment: {
@@ -2568,6 +2634,7 @@
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint*>(&v[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::tsDataRate: {
@@ -2578,6 +2645,7 @@
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::rollOff: {
@@ -2605,6 +2673,7 @@
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intRollOff);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2613,6 +2682,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isMiso>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::isLinear: {
@@ -2620,6 +2690,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isLinear>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::isShortFrames: {
@@ -2627,6 +2698,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isShortFrames>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::isdbtMode: {
@@ -2634,6 +2706,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::isdbtMode>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::partialReceptionFlag: {
@@ -2643,6 +2716,7 @@
env->NewObject(intClazz, initInt,
s.get<FrontendStatus::Tag::partialReceptionFlag>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::streamIdList: {
@@ -2653,6 +2727,7 @@
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::dvbtCellIds: {
@@ -2663,6 +2738,7 @@
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::allPlpInfo: {
@@ -2678,9 +2754,12 @@
jobject plpObj = env->NewObject(plpClazz, initPlp, plpInfos[i].plpId,
plpInfos[i].bLlsFlag);
env->SetObjectArrayElement(valObj, i, plpObj);
+ env->DeleteLocalRef(plpObj);
}
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(plpClazz);
+ env->DeleteLocalRef(valObj);
break;
}
}
@@ -2837,6 +2916,7 @@
.fec = fec,
};
plps[i] = frontendAtsc3PlpSettings;
+ env->DeleteLocalRef(plp);
}
return plps;
}
@@ -3192,6 +3272,7 @@
env->GetIntField(layer, env->GetFieldID(layerClazz, "mCodeRate", "I")));
frontendIsdbtSettings.layerSettings[i].numOfSegment =
env->GetIntField(layer, env->GetFieldID(layerClazz, "mNumOfSegments", "I"));
+ env->DeleteLocalRef(layer);
}
frontendSettings.set<FrontendSettings::Tag::isdbt>(frontendIsdbtSettings);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 220aa33..c524037 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -28,6 +28,7 @@
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;
+import android.webkit.URLUtil;
import android.webkit.WebView;
import com.android.phone.slice.SlicePurchaseController;
@@ -60,36 +61,38 @@
@NonNull private WebView mWebView;
@NonNull private Context mApplicationContext;
+ @NonNull private Intent mIntent;
+ @Nullable private URL mUrl;
private int mSubId;
@TelephonyManager.PremiumCapability protected int mCapability;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Intent intent = getIntent();
- mSubId = intent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
+ mIntent = getIntent();
+ mSubId = mIntent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- mCapability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+ mCapability = mIntent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
mApplicationContext = getApplicationContext();
- URL url = getUrl();
+ mUrl = getUrl();
logd("onCreate: subId=" + mSubId + ", capability="
+ TelephonyManager.convertPremiumCapabilityToString(mCapability)
- + ", url=" + url);
+ + ", url=" + mUrl);
// Cancel network boost notification
mApplicationContext.getSystemService(NotificationManager.class)
.cancel(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
// Verify intent and values are valid
- if (!SlicePurchaseBroadcastReceiver.isIntentValid(intent)) {
- loge("Not starting SlicePurchaseActivity with an invalid Intent: " + intent);
+ if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) {
+ loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
- intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
finishAndRemoveTask();
return;
}
- if (url == null) {
+ if (mUrl == null) {
String error = "Unable to create a URL from carrier configs.";
loge(error);
Intent data = new Intent();
@@ -97,7 +100,7 @@
SlicePurchaseController.FAILURE_CODE_CARRIER_URL_UNAVAILABLE);
data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, error);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
- getIntent(), SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
finishAndRemoveTask();
return;
}
@@ -105,7 +108,7 @@
loge("Unable to start the slice purchase application on the non-default data "
+ "subscription: " + mSubId);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
- intent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION);
finishAndRemoveTask();
return;
}
@@ -114,16 +117,7 @@
SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(mCapability, this);
// Create and configure WebView
- mWebView = new WebView(this);
- // Enable JavaScript for the carrier purchase website to send results back to
- // the slice purchase application.
- mWebView.getSettings().setJavaScriptEnabled(true);
- mWebView.addJavascriptInterface(
- new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
-
- // Display WebView
- setContentView(mWebView);
- mWebView.loadUrl(url.toString());
+ setupWebView();
}
protected void onPurchaseSuccessful(long duration) {
@@ -134,7 +128,7 @@
Intent intent = new Intent();
intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_DURATION, duration);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
- getIntent(), SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
finishAndRemoveTask();
}
@@ -147,7 +141,7 @@
data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE, failureCode);
data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, failureReason);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
- getIntent(), SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
finishAndRemoveTask();
}
@@ -166,7 +160,7 @@
protected void onDestroy() {
logd("onDestroy: User canceled the purchase by closing the application.");
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
- getIntent(), SlicePurchaseController.EXTRA_INTENT_CANCELED);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(mCapability);
super.onDestroy();
}
@@ -175,14 +169,37 @@
String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
.getConfigForSubId(mSubId).getString(
CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
- try {
- return new URL(url);
- } catch (MalformedURLException e) {
- loge("Invalid URL: " + url);
+ boolean isUrlValid = URLUtil.isValidUrl(url);
+ if (URLUtil.isAssetUrl(url)) {
+ isUrlValid = url.equals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
}
+ if (isUrlValid) {
+ try {
+ return new URL(url);
+ } catch (MalformedURLException ignored) {
+ }
+ }
+ loge("Invalid URL: " + url);
return null;
}
+ private void setupWebView() {
+ // Create WebView
+ mWebView = new WebView(this);
+
+ // Enable JavaScript for the carrier purchase website to send results back to
+ // the slice purchase application.
+ mWebView.getSettings().setJavaScriptEnabled(true);
+ mWebView.addJavascriptInterface(
+ new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
+
+ // Display WebView
+ setContentView(mWebView);
+
+ // Load the URL
+ mWebView.loadUrl(mUrl.toString());
+ }
+
private static void logd(@NonNull String s) {
Log.d(TAG, s);
}
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 8388b67..bafdb11 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -26,12 +26,12 @@
android:fitsSystemWindows="true">
<com.android.systemui.statusbar.BackDropView
- android:id="@+id/backdrop"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- sysui:ignoreRightInset="true"
- >
+ android:id="@+id/backdrop"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ sysui:ignoreRightInset="true"
+ >
<ImageView android:id="@+id/backdrop_back"
android:layout_width="match_parent"
android:scaleType="centerCrop"
@@ -49,7 +49,7 @@
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
- />
+ />
<com.android.systemui.scrim.ScrimView
android:id="@+id/scrim_notifications"
@@ -57,17 +57,17 @@
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
- />
+ />
<com.android.systemui.statusbar.LightRevealScrim
- android:id="@+id/light_reveal_scrim"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:id="@+id/light_reveal_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
<include layout="@layout/status_bar_expanded"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="invisible" />
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
<include layout="@layout/brightness_mirror_container" />
diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml
index 8221d78..04fc4b8 100644
--- a/packages/SystemUI/res/values/bools.xml
+++ b/packages/SystemUI/res/values/bools.xml
@@ -25,6 +25,9 @@
<!-- Whether to enable clipping on Quick Settings -->
<bool name="qs_enable_clipping">true</bool>
+ <!-- Whether to enable clipping on Notification Views -->
+ <bool name="notification_enable_clipping">true</bool>
+
<!-- Whether to enable transparent background for notification scrims -->
<bool name="notification_scrim_transparent">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index f4d802b..88af179 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -806,4 +806,15 @@
<!-- Whether the device should display hotspot UI. If true, UI will display only when tethering
is available. If false, UI will never show regardless of tethering availability" -->
<bool name="config_show_wifi_tethering">true</bool>
+
+ <!-- A collection of "slots" for placing quick affordance actions on the lock screen when the
+ device is locked. Each item is a string consisting of two parts, separated by the ':' character.
+ The first part is the unique ID for the slot, it is not a human-visible name, but should still
+ be unique across all slots specified. The second part is the capacity and must be a positive
+ integer; this is how many quick affordance actions that user is allowed to add to the slot. -->
+ <string-array name="config_keyguardQuickAffordanceSlots" translatable="false">
+ <item>bottom_start:1</item>
+ <item>bottom_end:1</item>
+ </string-array>
+
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index eb291fd..72c8163e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -439,16 +439,16 @@
<string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string>
<!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
+ <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g></string>
<!-- Content description of the battery level icon for accessibility while the device is charging (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_battery_level_charging">Battery charging, <xliff:g id="battery_percentage">%d</xliff:g> percent.</string>
<!-- Content description of the battery level icon for accessibility, with information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent. Charging paused for battery protection.</string>
+ <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent, charging paused for battery protection.</string>
<!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery *and* information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage. Charging paused for battery protection.</string>
+ <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g>, charging paused for battery protection.</string>
<!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] -->
<string name="accessibility_overflow_action">See all notifications</string>
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index 74519c2..05372fe 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -22,7 +22,11 @@
private val flagMap = mutableMapOf<String, Flag<*>>()
val knownFlags: Map<String, Flag<*>>
- get() = flagMap
+ get() {
+ // We need to access Flags in order to initialize our map.
+ assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+ return flagMap
+ }
fun unreleasedFlag(
id: Int,
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index 7b216017..8323d09 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -34,6 +34,9 @@
@Binds
abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags
+ @Binds
+ abstract fun bindsRestarter(debugRestarter: FeatureFlagsDebugRestarter): Restarter
+
@Module
companion object {
@JvmStatic
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
index 89c0786..27c5699 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -22,7 +22,11 @@
private val flagMap = mutableMapOf<String, Flag<*>>()
val knownFlags: Map<String, Flag<*>>
- get() = flagMap
+ get() {
+ // We need to access Flags in order to initialize our map.
+ assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+ return flagMap
+ }
fun unreleasedFlag(
id: Int,
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index aef8876..87beff7 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -27,4 +27,7 @@
abstract class FlagsModule {
@Binds
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
+
+ @Binds
+ abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 115edd11..c6428ef 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -91,11 +91,12 @@
override var currentUserId = userTracker.userId
private set
- private val serviceListingCallback = ServiceListing.Callback {
+ private val serviceListingCallback = ServiceListing.Callback { list ->
+ Log.d(TAG, "ServiceConfig reloaded, count: ${list.size}")
+ val newServices = list.map { ControlsServiceInfo(userTracker.userContext, it) }
+ // After here, `list` is not captured, so we don't risk modifying it outside of the callback
backgroundExecutor.execute {
if (userChangeInProgress.get() > 0) return@execute
- Log.d(TAG, "ServiceConfig reloaded, count: ${it.size}")
- val newServices = it.map { ControlsServiceInfo(userTracker.userContext, it) }
if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
newServices.forEach(ControlsServiceInfo::resolvePanelActivity)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index d3555ee..b30e0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -17,6 +17,7 @@
package com.android.systemui.dagger;
import com.android.systemui.keyguard.KeyguardQuickAffordanceProvider;
+import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
import dagger.Subcomponent;
@@ -28,6 +29,7 @@
@Subcomponent(modules = {
DefaultComponentBinder.class,
DependencyProvider.class,
+ NotificationInsetsModule.class,
QsFrameTranslateModule.class,
SystemUIBinder.class,
SystemUIModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index a14b0ee..6dc4f5c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -28,6 +28,7 @@
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.people.PeopleProvider;
+import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.FoldStateLogger;
@@ -65,6 +66,7 @@
@Subcomponent(modules = {
DefaultComponentBinder.class,
DependencyProvider.class,
+ NotificationInsetsModule.class,
QsFrameTranslateModule.class,
SystemUIBinder.class,
SystemUIModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
index d537d4b..000bbe6 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
@@ -54,6 +54,9 @@
private val receiverMap: Map<String, MutableList<DemoMode>>
init {
+ // Don't persist demo mode across restarts.
+ requestFinishDemoMode()
+
val m = mutableMapOf<String, MutableList<DemoMode>>()
DemoMode.COMMANDS.map { command ->
m.put(command, mutableListOf())
@@ -74,7 +77,6 @@
// content changes to know if the setting turned on or off
tracker.startTracking()
- // TODO: We should probably exit demo mode if we booted up with it on
isInDemoMode = tracker.isInDemoMode
val demoFilter = IntentFilter()
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index b69afeb..0c14ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -133,9 +133,9 @@
/**
* Appends fling event to the logs
*/
- public void traceFling(boolean expand, boolean aboveThreshold, boolean thresholdNeeded,
+ public void traceFling(boolean expand, boolean aboveThreshold,
boolean screenOnFromTouch) {
- mLogger.logFling(expand, aboveThreshold, thresholdNeeded, screenOnFromTouch);
+ mLogger.logFling(expand, aboveThreshold, screenOnFromTouch);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 18c8e01..b5dbe21 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -96,13 +96,11 @@
fun logFling(
expand: Boolean,
aboveThreshold: Boolean,
- thresholdNeeded: Boolean,
screenOnFromTouch: Boolean
) {
buffer.log(TAG, DEBUG, {
bool1 = expand
bool2 = aboveThreshold
- bool3 = thresholdNeeded
bool4 = screenOnFromTouch
}, {
"Fling expand=$bool1 aboveThreshold=$bool2 thresholdNeeded=$bool3 " +
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index b03ae59..81df4ed 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -337,7 +337,6 @@
Log.i(TAG, "Android Restart Suppressed");
return;
}
- Log.i(TAG, "Restarting Android");
mRestarter.restart();
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
new file mode 100644
index 0000000..3d9f627
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.util.Log
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import javax.inject.Inject
+
+/** Restarts SystemUI when the screen is locked. */
+class FeatureFlagsDebugRestarter
+@Inject
+constructor(
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+ private val systemExitRestarter: SystemExitRestarter,
+) : Restarter {
+
+ val observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onFinishedGoingToSleep() {
+ Log.d(FeatureFlagsDebug.TAG, "Restarting due to systemui flag change")
+ restartNow()
+ }
+ }
+
+ override fun restart() {
+ Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting on next screen off.")
+ if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
+ restartNow()
+ } else {
+ wakefulnessLifecycle.addObserver(observer)
+ }
+ }
+
+ private fun restartNow() {
+ systemExitRestarter.restart()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
new file mode 100644
index 0000000..a3f0f66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+/** Restarts SystemUI when the device appears idle. */
+class FeatureFlagsReleaseRestarter
+@Inject
+constructor(
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+ private val batteryController: BatteryController,
+ @Background private val bgExecutor: DelayableExecutor,
+ private val systemExitRestarter: SystemExitRestarter
+) : Restarter {
+ var shouldRestart = false
+ var pendingRestart: Runnable? = null
+
+ val observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onFinishedGoingToSleep() {
+ maybeScheduleRestart()
+ }
+ }
+
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+ maybeScheduleRestart()
+ }
+ }
+
+ override fun restart() {
+ Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting when plugged in and idle.")
+ if (!shouldRestart) {
+ // Don't bother scheduling twice.
+ shouldRestart = true
+ wakefulnessLifecycle.addObserver(observer)
+ batteryController.addCallback(batteryCallback)
+ maybeScheduleRestart()
+ }
+ }
+
+ private fun maybeScheduleRestart() {
+ if (
+ wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
+ ) {
+ if (pendingRestart == null) {
+ pendingRestart = bgExecutor.executeDelayed(this::restartNow, 30L, TimeUnit.SECONDS)
+ }
+ } else if (pendingRestart != null) {
+ pendingRestart?.run()
+ pendingRestart = null
+ }
+ }
+
+ private fun restartNow() {
+ Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
+ systemExitRestarter.restart()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 784e92d..453647e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -90,7 +90,8 @@
// TODO(b/257315550): Tracking Bug
val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when")
- // next id: 119
+ val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
+ unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
// 200 - keyguard/lockscreen
// ** Flag retired **
@@ -141,9 +142,9 @@
/**
* Whether to enable the code powering customizable lock screen quick affordances.
*
- * Note that this flag does not enable individual implementations of quick affordances like the
- * new camera quick affordance. Look for individual flags for those.
+ * This flag enables any new prebuilt quick affordances as well.
*/
+ // TODO(b/255618149): Tracking Bug
@JvmField
val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = false)
@@ -386,9 +387,7 @@
unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
// 1700 - clipboard
- @JvmField
- val CLIPBOARD_OVERLAY_REFACTOR =
- unreleasedFlag(1700, "clipboard_overlay_refactor", teamfood = true)
+ @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = unreleasedFlag(1701, "clipboard_remote_behavior")
// 1800 - shade container
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index 18d7bcf..8442230 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -15,7 +15,6 @@
*/
package com.android.systemui.flags
-import com.android.internal.statusbar.IStatusBarService
import dagger.Module
import dagger.Provides
import javax.inject.Named
@@ -32,15 +31,5 @@
fun providesAllFlags(): Map<Int, Flag<*>> {
return FlagsFactory.knownFlags.map { it.value.id to it.value }.toMap()
}
-
- @JvmStatic
- @Provides
- fun providesRestarter(barService: IStatusBarService): Restarter {
- return object : Restarter {
- override fun restart() {
- barService.restart()
- }
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
new file mode 100644
index 0000000..f1b1be4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import javax.inject.Inject
+
+class SystemExitRestarter @Inject constructor() : Restarter {
+ override fun restart() {
+ System.exit(0)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index a069582..f5220b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -24,6 +24,7 @@
*/
object BuiltInKeyguardQuickAffordanceKeys {
// Please keep alphabetical order of const names to simplify future maintenance.
+ const val CAMERA = "camera"
const val HOME_CONTROLS = "home"
const val QR_CODE_SCANNER = "qr_code_scanner"
const val QUICK_ACCESS_WALLET = "wallet"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
new file mode 100644
index 0000000..3c09aab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.camera.CameraGestureHelper
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import javax.inject.Inject
+
+@SysUISingleton
+class CameraQuickAffordanceConfig @Inject constructor(
+ @Application private val context: Context,
+ private val cameraGestureHelper: CameraGestureHelper,
+) : KeyguardQuickAffordanceConfig {
+
+ override val key: String
+ get() = BuiltInKeyguardQuickAffordanceKeys.CAMERA
+
+ override val pickerName: String
+ get() = context.getString(R.string.accessibility_camera_button)
+
+ override val pickerIconResourceId: Int
+ get() = com.android.internal.R.drawable.perm_group_camera
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
+ get() = flowOf(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = Icon.Resource(
+ com.android.internal.R.drawable.perm_group_camera,
+ ContentDescription.Resource(R.string.accessibility_camera_button)
+ )
+ )
+ )
+
+ override fun onTriggered(expandable: Expandable?): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ cameraGestureHelper.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index bea9363..f7225a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -29,8 +29,10 @@
home: HomeControlsKeyguardQuickAffordanceConfig,
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+ camera: CameraQuickAffordanceConfig,
): Set<KeyguardQuickAffordanceConfig> {
return setOf(
+ camera,
home,
quickAccessWallet,
qrCodeScanner,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 95f614f..9fda98c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -17,6 +17,8 @@
package com.android.systemui.keyguard.data.repository
+import android.content.Context
+import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -24,7 +26,6 @@
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -39,6 +40,7 @@
class KeyguardQuickAffordanceRepository
@Inject
constructor(
+ @Application private val appContext: Context,
@Application private val scope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val selectionManager: KeyguardQuickAffordanceSelectionManager,
@@ -61,6 +63,30 @@
initialValue = emptyMap(),
)
+ private val _slotPickerRepresentations: List<KeyguardSlotPickerRepresentation> by lazy {
+ fun parseSlot(unparsedSlot: String): Pair<String, Int> {
+ val split = unparsedSlot.split(SLOT_CONFIG_DELIMITER)
+ check(split.size == 2)
+ val slotId = split[0]
+ val slotCapacity = split[1].toInt()
+ return slotId to slotCapacity
+ }
+
+ val unparsedSlots =
+ appContext.resources.getStringArray(R.array.config_keyguardQuickAffordanceSlots)
+
+ val seenSlotIds = mutableSetOf<String>()
+ unparsedSlots.mapNotNull { unparsedSlot ->
+ val (slotId, slotCapacity) = parseSlot(unparsedSlot)
+ check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" }
+ seenSlotIds.add(slotId)
+ KeyguardSlotPickerRepresentation(
+ id = slotId,
+ maxSelectedAffordances = slotCapacity,
+ )
+ }
+ }
+
/**
* Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
* slot with the given ID. The configs are sorted in descending priority order.
@@ -115,14 +141,10 @@
* each slot and select which affordance(s) is/are installed in each slot on the keyguard.
*/
fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
- // TODO(b/256195304): source these from a config XML file.
- return listOf(
- KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- ),
- KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- ),
- )
+ return _slotPickerRepresentations
+ }
+
+ companion object {
+ private const val SLOT_CONFIG_DELIMITER = ":"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ceef8c8..b92cf5a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -177,6 +177,7 @@
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -253,6 +254,8 @@
private static final int FLING_COLLAPSE = 1;
/** Fling until QS is completely hidden. */
private static final int FLING_HIDE = 2;
+ /** The delay to reset the hint text when the hint animation is finished running. */
+ private static final int HINT_RESET_DELAY_MS = 1200;
private static final long ANIMATION_DELAY_ICON_FADE_IN =
ActivityLaunchAnimator.TIMINGS.getTotalDuration()
- CollapsedStatusBarFragment.FADE_IN_DURATION
@@ -343,6 +346,7 @@
private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired;
private final FragmentListener mQsFragmentListener = new QsFragmentListener();
private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
+ private final NotificationGutsManager mGutsManager;
private long mDownTime;
private boolean mTouchSlopExceededBeforeDown;
@@ -625,7 +629,6 @@
private float mLastGesturedOverExpansion = -1;
/** Whether the current animator is the spring back animation. */
private boolean mIsSpringBackAnimation;
- private boolean mInSplitShade;
private float mHintDistance;
private float mInitialOffsetOnTouch;
private boolean mCollapsedAndHeadsUpOnDown;
@@ -702,6 +705,7 @@
ConversationNotificationManager conversationNotificationManager,
MediaHierarchyManager mediaHierarchyManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ NotificationGutsManager gutsManager,
NotificationsQSContainerController notificationsQSContainerController,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
@@ -755,6 +759,7 @@
mLockscreenGestureLogger = lockscreenGestureLogger;
mShadeExpansionStateManager = shadeExpansionStateManager;
mShadeLog = shadeLogger;
+ mGutsManager = gutsManager;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -1061,7 +1066,6 @@
mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
- mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade);
mFlingAnimationUtils = mFlingAnimationUtilsBuilder.get()
.setMaxLengthSeconds(0.4f).build();
mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
@@ -1569,23 +1573,31 @@
// Find the clock, so we can exclude it from this transition.
FrameLayout clockContainerView =
mView.findViewById(R.id.lockscreen_clock_view_large);
- View clockView = clockContainerView.getChildAt(0);
- transition.excludeTarget(clockView, /* exclude= */ true);
+ // The clock container can sometimes be null. If it is, just fall back to the
+ // old animation rather than setting up the custom animations.
+ if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
+ TransitionManager.beginDelayedTransition(
+ mNotificationContainerParent, transition);
+ } else {
+ View clockView = clockContainerView.getChildAt(0);
- TransitionSet set = new TransitionSet();
- set.addTransition(transition);
+ transition.excludeTarget(clockView, /* exclude= */ true);
- SplitShadeTransitionAdapter adapter =
- new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
+ TransitionSet set = new TransitionSet();
+ set.addTransition(transition);
- // Use linear here, so the actual clock can pick its own interpolator.
- adapter.setInterpolator(Interpolators.LINEAR);
- adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- adapter.addTarget(clockView);
- set.addTransition(adapter);
+ SplitShadeTransitionAdapter adapter =
+ new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
- TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+ // Use linear here, so the actual clock can pick its own interpolator.
+ adapter.setInterpolator(Interpolators.LINEAR);
+ adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ adapter.addTarget(clockView);
+ set.addTransition(adapter);
+
+ TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+ }
} else {
TransitionManager.beginDelayedTransition(
mNotificationContainerParent, transition);
@@ -1760,7 +1772,7 @@
}
public void resetViews(boolean animate) {
- mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
+ mGutsManager.closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
if (animate && !isFullyCollapsed()) {
animateCloseQs(true /* animateAway */);
@@ -1836,6 +1848,10 @@
public void closeQs() {
cancelQsAnimation();
setQsExpansionHeight(mQsMinExpansionHeight);
+ // qsExpandImmediate is a safety latch in case we're calling closeQS while we're in the
+ // middle of animation - we need to make sure that value is always false when shade if
+ // fully collapsed or expanded
+ setQsExpandImmediate(false);
}
@VisibleForTesting
@@ -1938,7 +1954,7 @@
// we want to perform an overshoot animation when flinging open
final boolean addOverscroll =
expand
- && !mInSplitShade // Split shade has its own overscroll logic
+ && !mSplitShadeEnabled // Split shade has its own overscroll logic
&& mStatusBarStateController.getState() != KEYGUARD
&& mOverExpansion == 0.0f
&& vel >= 0;
@@ -2727,8 +2743,10 @@
* as well based on the bounds of the shade and QS state.
*/
private void setQSClippingBounds() {
- final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction());
- final boolean qsVisible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0);
+ float qsExpansionFraction = computeQsExpansionFraction();
+ final int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
+ final boolean qsVisible = (qsExpansionFraction > 0 || qsPanelBottomY > 0);
+ checkCorrectScrimVisibility(qsExpansionFraction);
int top = calculateTopQsClippingBound(qsPanelBottomY);
int bottom = calculateBottomQsClippingBound(top);
@@ -2739,6 +2757,19 @@
applyQSClippingBounds(left, top, right, bottom, qsVisible);
}
+ private void checkCorrectScrimVisibility(float expansionFraction) {
+ // issues with scrims visible on keyguard occur only in split shade
+ if (mSplitShadeEnabled) {
+ boolean keyguardViewsVisible = mBarState == KEYGUARD && mKeyguardOnlyContentAlpha == 1;
+ // expansionFraction == 1 means scrims are fully visible as their size/visibility depend
+ // on QS expansion
+ if (expansionFraction == 1 && keyguardViewsVisible) {
+ Log.wtf(TAG,
+ "Incorrect state, scrim is visible at the same time when clock is visible");
+ }
+ }
+ }
+
private int calculateTopQsClippingBound(int qsPanelBottomY) {
int top;
if (mSplitShadeEnabled) {
@@ -3686,7 +3717,6 @@
private void onTrackingStopped(boolean expand) {
mFalsingCollector.onTrackingStopped();
mTracking = false;
- mCentralSurfaces.onTrackingStopped(expand);
updatePanelExpansionAndVisibility();
if (expand) {
mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
@@ -3729,14 +3759,16 @@
@VisibleForTesting
void onUnlockHintFinished() {
- mCentralSurfaces.onHintFinished();
+ // Delay the reset a bit so the user can read the text.
+ mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS);
mScrimController.setExpansionAffectsAlpha(true);
mNotificationStackScrollLayoutController.setUnlockHintRunning(false);
}
@VisibleForTesting
void onUnlockHintStarted() {
- mCentralSurfaces.onUnlockHintStarted();
+ mFalsingCollector.onUnlockHintStarted();
+ mKeyguardIndicationController.showActionToUnlock();
mScrimController.setExpansionAffectsAlpha(false);
mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
}
@@ -4393,7 +4425,7 @@
ipw.print("mPanelFlingOvershootAmount="); ipw.println(mPanelFlingOvershootAmount);
ipw.print("mLastGesturedOverExpansion="); ipw.println(mLastGesturedOverExpansion);
ipw.print("mIsSpringBackAnimation="); ipw.println(mIsSpringBackAnimation);
- ipw.print("mInSplitShade="); ipw.println(mInSplitShade);
+ ipw.print("mSplitShadeEnabled="); ipw.println(mSplitShadeEnabled);
ipw.print("mHintDistance="); ipw.println(mHintDistance);
ipw.print("mInitialOffsetOnTouch="); ipw.println(mInitialOffsetOnTouch);
ipw.print("mCollapsedAndHeadsUpOnDown="); ipw.println(mCollapsedAndHeadsUpOnDown);
@@ -4762,7 +4794,6 @@
}
mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
- mCentralSurfaces.isFalsingThresholdNeeded(),
mCentralSurfaces.isWakeUpComingFromTouch());
// Log collapse gesture if on lock screen.
if (!expand && onKeyguard) {
@@ -4811,9 +4842,6 @@
*/
private boolean isFalseTouch(float x, float y,
@Classifier.InteractionType int interactionType) {
- if (!mCentralSurfaces.isFalsingThresholdNeeded()) {
- return false;
- }
if (mFalsingManager.isClassifierEnabled()) {
return mFalsingManager.isFalseTouch(interactionType);
}
@@ -4911,7 +4939,7 @@
float maxPanelHeight = getMaxPanelTransitionDistance();
if (mHeightAnimator == null) {
// Split shade has its own overscroll logic
- if (mTracking && !mInSplitShade) {
+ if (mTracking && !mSplitShadeEnabled) {
float overExpansionPixels = Math.max(0, h - maxPanelHeight);
setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
}
@@ -5459,6 +5487,12 @@
// - from SHADE to KEYGUARD
// - from SHADE_LOCKED to SHADE
// - getting notified again about the current SHADE or KEYGUARD state
+ if (mSplitShadeEnabled && oldState == SHADE && statusBarState == KEYGUARD) {
+ // user can go to keyguard from different shade states and closing animation
+ // may not fully run - we always want to make sure we close QS when that happens
+ // as we never need QS open in fresh keyguard state
+ closeQs();
+ }
final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
&& statusBarState == KEYGUARD
&& mScreenOffAnimationController.isKeyguardShowDelayed();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 400b0ba..6acf417 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -24,6 +24,7 @@
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.LayoutRes;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -36,6 +37,7 @@
import android.os.Bundle;
import android.os.Trace;
import android.util.AttributeSet;
+import android.util.Pair;
import android.view.ActionMode;
import android.view.DisplayCutout;
import android.view.InputQueue;
@@ -74,6 +76,7 @@
private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
private InteractionEventHandler mInteractionEventHandler;
+ private LayoutInsetsController mLayoutInsetProvider;
public NotificationShadeWindowView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -108,12 +111,10 @@
mLeftInset = 0;
mRightInset = 0;
DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
- if (displayCutout != null) {
- mLeftInset = displayCutout.getSafeInsetLeft();
- mRightInset = displayCutout.getSafeInsetRight();
- }
- mLeftInset = Math.max(insets.left, mLeftInset);
- mRightInset = Math.max(insets.right, mRightInset);
+ Pair<Integer, Integer> pairInsets = mLayoutInsetProvider
+ .getinsets(windowInsets, displayCutout);
+ mLeftInset = pairInsets.first;
+ mRightInset = pairInsets.second;
applyMargins();
return windowInsets;
}
@@ -172,6 +173,10 @@
mInteractionEventHandler = listener;
}
+ protected void setLayoutInsetsController(LayoutInsetsController provider) {
+ mLayoutInsetProvider = provider;
+ }
+
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
@@ -353,6 +358,18 @@
}
}
+ /**
+ * Controller responsible for calculating insets for the shade window.
+ */
+ public interface LayoutInsetsController {
+
+ /**
+ * Update the insets and calculate them accordingly.
+ */
+ Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
+ @Nullable DisplayCutout displayCutout);
+ }
+
interface InteractionEventHandler {
/**
* Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index bb67280c..8379e51 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -42,6 +42,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationInsetsController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -76,6 +77,7 @@
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final AmbientState mAmbientState;
private final PulsingGestureListener mPulsingGestureListener;
+ private final NotificationInsetsController mNotificationInsetsController;
private GestureDetector mPulsingWakeupGestureHandler;
private View mBrightnessMirror;
@@ -111,6 +113,7 @@
CentralSurfaces centralSurfaces,
NotificationShadeWindowController controller,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+ NotificationInsetsController notificationInsetsController,
AmbientState ambientState,
PulsingGestureListener pulsingGestureListener,
FeatureFlags featureFlags,
@@ -134,6 +137,7 @@
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mAmbientState = ambientState;
mPulsingGestureListener = pulsingGestureListener;
+ mNotificationInsetsController = notificationInsetsController;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -165,6 +169,7 @@
mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mPulsingGestureListener);
+ mView.setLayoutInsetsController(mNotificationInsetsController);
mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
@Override
public Boolean handleDispatchTouchEvent(MotionEvent ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 8355c64..d21f2ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -198,7 +198,9 @@
public void onScreenTurnedOn() {
mHandler.removeMessages(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON);
if (mBiometricErrorMessageToShowOnScreenOn != null) {
- showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn);
+ String followUpMessage = mFaceLockedOutThisAuthSession
+ ? faceLockedOutFollowupMessage() : null;
+ showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn, followUpMessage);
// We want to keep this message around in case the screen was off
hideBiometricMessageDelayed(DEFAULT_HIDE_DELAY_MS);
mBiometricErrorMessageToShowOnScreenOn = null;
@@ -1263,9 +1265,7 @@
}
private void handleFaceLockoutError(String errString) {
- int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
- : R.string.keyguard_unlock;
- String followupMessage = mContext.getString(followupMsgId);
+ String followupMessage = faceLockedOutFollowupMessage();
// Lockout error can happen multiple times in a session because we trigger face auth
// even when it is locked out so that the user is aware that face unlock would have
// triggered but didn't because it is locked out.
@@ -1283,6 +1283,12 @@
}
}
+ private String faceLockedOutFollowupMessage() {
+ int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
+ : R.string.keyguard_unlock;
+ return mContext.getString(followupMsgId);
+ }
+
private static boolean isLockoutError(int msgId) {
return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
|| msgId == FaceManager.FACE_ERROR_LOCKOUT;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
new file mode 100644
index 0000000..39d7d66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import com.android.systemui.shade.NotificationShadeWindowView;
+
+/**
+ * Calculates insets for the notification shade window view.
+ */
+public abstract class NotificationInsetsController
+ implements NotificationShadeWindowView.LayoutInsetsController {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java
new file mode 100644
index 0000000..1ed704e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static android.view.WindowInsets.Type.systemBars;
+
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.util.Pair;
+import android.view.DisplayCutout;
+import android.view.WindowInsets;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Default implementation of NotificationsInsetsController.
+ */
+@SysUISingleton
+public class NotificationInsetsImpl extends NotificationInsetsController {
+
+ @Inject
+ public NotificationInsetsImpl() {
+
+ }
+
+ @Override
+ public Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
+ @Nullable DisplayCutout displayCutout) {
+ final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
+ int leftInset = 0;
+ int rightInset = 0;
+
+ if (displayCutout != null) {
+ leftInset = displayCutout.getSafeInsetLeft();
+ rightInset = displayCutout.getSafeInsetRight();
+ }
+ leftInset = Math.max(insets.left, leftInset);
+ rightInset = Math.max(insets.right, rightInset);
+
+ return new Pair(leftInset, rightInset);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java
new file mode 100644
index 0000000..614bc0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+@Module
+public interface NotificationInsetsModule {
+
+ @Binds
+ @SysUISingleton
+ NotificationInsetsController bindNotificationInsetsController(NotificationInsetsImpl impl);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index cd13085..d7eddf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -71,6 +71,7 @@
private int[] mTmp = new int[2];
private boolean mHideBackground;
private int mStatusBarHeight;
+ private boolean mEnableNotificationClipping;
private AmbientState mAmbientState;
private NotificationStackScrollLayoutController mHostLayoutController;
private int mPaddingBetweenElements;
@@ -117,7 +118,7 @@
// Setting this to first in section to get the clipping to the top roundness correct. This
// value determines the way we are clipping to the top roundness of the overall shade
setFirstInSection(true);
- initDimens();
+ updateResources();
}
public void bind(AmbientState ambientState,
@@ -126,7 +127,7 @@
mHostLayoutController = hostLayoutController;
}
- private void initDimens() {
+ private void updateResources() {
Resources res = getResources();
mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
@@ -144,6 +145,7 @@
mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
mCornerAnimationDistance = res.getDimensionPixelSize(
R.dimen.notification_corner_animation_distance);
+ mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
mShelfIcons.setInNotificationIconShelf(true);
if (!mShowNotificationShelf) {
@@ -154,7 +156,7 @@
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- initDimens();
+ updateResources();
}
@Override
@@ -639,7 +641,8 @@
}
if (!isPinned) {
if (viewEnd > notificationClipEnd && !shouldClipOwnTop) {
- int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
+ int clipBottomAmount =
+ mEnableNotificationClipping ? (int) (viewEnd - notificationClipEnd) : 0;
view.setClipBottomAmount(clipBottomAmount);
} else {
view.setClipBottomAmount(0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 2734511..7eb8906 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -40,4 +40,8 @@
val isSemiStableSortEnabled: Boolean by lazy {
featureFlags.isEnabled(Flags.SEMI_STABLE_SORT)
}
+
+ val shouldFilterUnseenNotifsOnKeyguard: Boolean by lazy {
+ featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
deleted file mode 100644
index e3d71c8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
-
-import javax.inject.Inject;
-
-/**
- * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen.
- */
-@CoordinatorScope
-public class KeyguardCoordinator implements Coordinator {
- private static final String TAG = "KeyguardCoordinator";
- private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
- private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
- private final StatusBarStateController mStatusBarStateController;
-
- @Inject
- public KeyguardCoordinator(
- KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
- SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider,
- StatusBarStateController statusBarStateController) {
- mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
- mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider;
- mStatusBarStateController = statusBarStateController;
- }
-
- @Override
- public void attach(NotifPipeline pipeline) {
-
- setupInvalidateNotifListCallbacks();
- // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
- pipeline.addFinalizeFilter(mNotifFilter);
- mKeyguardNotificationVisibilityProvider
- .addOnStateChangedListener(this::invalidateListFromFilter);
- updateSectionHeadersVisibility();
- }
-
- private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
- @Override
- public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) {
- return mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry);
- }
- };
-
- // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
- // these same updates
- private void setupInvalidateNotifListCallbacks() {
-
- }
-
- private void invalidateListFromFilter(String reason) {
- updateSectionHeadersVisibility();
- mNotifFilter.invalidateList(reason);
- }
-
- private void updateSectionHeadersVisibility() {
- boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
- boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
- boolean showSections = !onKeyguard && !neverShowSections;
- mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
new file mode 100644
index 0000000..6e5fceb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/**
+ * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
+ * headers on the lockscreen.
+ */
+@CoordinatorScope
+class KeyguardCoordinator
+@Inject
+constructor(
+ private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
+ private val keyguardRepository: KeyguardRepository,
+ private val notifPipelineFlags: NotifPipelineFlags,
+ @Application private val scope: CoroutineScope,
+ private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
+ private val statusBarStateController: StatusBarStateController,
+) : Coordinator {
+
+ private val unseenNotifications = mutableSetOf<NotificationEntry>()
+
+ override fun attach(pipeline: NotifPipeline) {
+ setupInvalidateNotifListCallbacks()
+ // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
+ pipeline.addFinalizeFilter(notifFilter)
+ keyguardNotificationVisibilityProvider.addOnStateChangedListener(::invalidateListFromFilter)
+ updateSectionHeadersVisibility()
+ if (notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard) {
+ attachUnseenFilter(pipeline)
+ }
+ }
+
+ private fun attachUnseenFilter(pipeline: NotifPipeline) {
+ pipeline.addFinalizeFilter(unseenNotifFilter)
+ pipeline.addCollectionListener(collectionListener)
+ scope.launch { clearUnseenWhenKeyguardIsDismissed() }
+ }
+
+ private suspend fun clearUnseenWhenKeyguardIsDismissed() {
+ // Use collectLatest so that the suspending block is cancelled if isKeyguardShowing changes
+ // during the timeout period
+ keyguardRepository.isKeyguardShowing.collectLatest { isKeyguardShowing ->
+ if (!isKeyguardShowing) {
+ unseenNotifFilter.invalidateList("keyguard no longer showing")
+ delay(SEEN_TIMEOUT)
+ unseenNotifications.clear()
+ }
+ }
+ }
+
+ private val collectionListener =
+ object : NotifCollectionListener {
+ override fun onEntryAdded(entry: NotificationEntry) {
+ if (keyguardRepository.isKeyguardShowing()) {
+ unseenNotifications.add(entry)
+ }
+ }
+
+ override fun onEntryUpdated(entry: NotificationEntry) {
+ if (keyguardRepository.isKeyguardShowing()) {
+ unseenNotifications.add(entry)
+ }
+ }
+
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ unseenNotifications.remove(entry)
+ }
+ }
+
+ @VisibleForTesting
+ internal val unseenNotifFilter =
+ object : NotifFilter("$TAG-unseen") {
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
+ when {
+ // Don't apply filter if the keyguard isn't currently showing
+ !keyguardRepository.isKeyguardShowing() -> false
+ // Don't apply the filter if the notification is unseen
+ unseenNotifications.contains(entry) -> false
+ // Don't apply the filter to (non-promoted) group summaries
+ // - summary will be pruned if necessary, depending on if children are filtered
+ entry.parent?.summary == entry -> false
+ else -> true
+ }
+ }
+
+ private val notifFilter: NotifFilter =
+ object : NotifFilter(TAG) {
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
+ keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
+ }
+
+ // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
+ // these same updates
+ private fun setupInvalidateNotifListCallbacks() {}
+
+ private fun invalidateListFromFilter(reason: String) {
+ updateSectionHeadersVisibility()
+ notifFilter.invalidateList(reason)
+ }
+
+ private fun updateSectionHeadersVisibility() {
+ val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD
+ val neverShowSections = sectionHeaderVisibilityProvider.neverShowSectionHeaders
+ val showSections = !onKeyguard && !neverShowSections
+ sectionHeaderVisibilityProvider.sectionHeadersVisible = showSections
+ }
+
+ companion object {
+ private const val TAG = "KeyguardCoordinator"
+ private val SEEN_TIMEOUT = 5.seconds
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 3002a68..a2379b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeStateEvents;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -62,6 +63,7 @@
private final HeadsUpManager mHeadsUpManager;
private final ShadeStateEvents mShadeStateEvents;
private final StatusBarStateController mStatusBarStateController;
+ private final VisibilityLocationProvider mVisibilityLocationProvider;
private final VisualStabilityProvider mVisualStabilityProvider;
private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -94,9 +96,11 @@
HeadsUpManager headsUpManager,
ShadeStateEvents shadeStateEvents,
StatusBarStateController statusBarStateController,
+ VisibilityLocationProvider visibilityLocationProvider,
VisualStabilityProvider visualStabilityProvider,
WakefulnessLifecycle wakefulnessLifecycle) {
mHeadsUpManager = headsUpManager;
+ mVisibilityLocationProvider = visibilityLocationProvider;
mVisualStabilityProvider = visualStabilityProvider;
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
@@ -123,6 +127,11 @@
// HUNs to the top of the shade
private final NotifStabilityManager mNotifStabilityManager =
new NotifStabilityManager("VisualStabilityCoordinator") {
+ private boolean canMoveForHeadsUp(NotificationEntry entry) {
+ return entry != null && mHeadsUpManager.isAlerting(entry.getKey())
+ && !mVisibilityLocationProvider.isInVisibleLocation(entry);
+ }
+
@Override
public void onBeginRun() {
mIsSuppressingPipelineRun = false;
@@ -140,7 +149,7 @@
@Override
public boolean isGroupChangeAllowed(@NonNull NotificationEntry entry) {
final boolean isGroupChangeAllowedForEntry =
- mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey());
+ mReorderingAllowed || canMoveForHeadsUp(entry);
mIsSuppressingGroupChange |= !isGroupChangeAllowedForEntry;
return isGroupChangeAllowedForEntry;
}
@@ -156,7 +165,7 @@
public boolean isSectionChangeAllowed(@NonNull NotificationEntry entry) {
final boolean isSectionChangeAllowedForEntry =
mReorderingAllowed
- || mHeadsUpManager.isAlerting(entry.getKey())
+ || canMoveForHeadsUp(entry)
|| mEntriesThatCanChangeSection.containsKey(entry.getKey());
if (!isSectionChangeAllowedForEntry) {
mEntriesWithSuppressedSectionChange.add(entry.getKey());
@@ -165,8 +174,8 @@
}
@Override
- public boolean isEntryReorderingAllowed(@NonNull ListEntry section) {
- return mReorderingAllowed;
+ public boolean isEntryReorderingAllowed(@NonNull ListEntry entry) {
+ return mReorderingAllowed || canMoveForHeadsUp(entry.getRepresentativeEntry());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt
new file mode 100644
index 0000000..4bc4ecf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/**
+ * An injectable component which delegates the visibility location computation to a delegate which
+ * can be initialized after the initial injection, generally because it's provided by a view.
+ */
+@SysUISingleton
+class VisibilityLocationProviderDelegator @Inject constructor() : VisibilityLocationProvider {
+ private var delegate: VisibilityLocationProvider? = null
+
+ fun setDelegate(provider: VisibilityLocationProvider) {
+ delegate = provider
+ }
+
+ override fun isInVisibleLocation(entry: NotificationEntry): Boolean =
+ requireNotNull(this.delegate) { "delegate not initialized" }.isInVisibleLocation(entry)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index df2de56..a7b7a23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -37,6 +37,7 @@
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
@@ -51,6 +52,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -151,6 +153,11 @@
@Binds
NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager);
+ /** Provides an instance of {@link VisibilityLocationProvider} */
+ @Binds
+ VisibilityLocationProvider bindVisibilityLocationProvider(
+ VisibilityLocationProviderDelegator visibilityLocationProviderDelegator);
+
/** Provides an instance of {@link NotificationLogger} */
@SysUISingleton
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e1337826..0240bbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -89,6 +89,7 @@
import com.android.systemui.statusbar.notification.collection.PipelineDumper;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
@@ -157,6 +158,7 @@
private final NotifCollection mNotifCollection;
private final UiEventLogger mUiEventLogger;
private final NotificationRemoteInputManager mRemoteInputManager;
+ private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
private final ShadeController mShadeController;
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
@@ -638,6 +640,7 @@
ShadeTransitionController shadeTransitionController,
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
+ VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
@@ -679,6 +682,7 @@
mNotifCollection = notifCollection;
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
+ mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
mShadeController = shadeController;
mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
@@ -750,6 +754,8 @@
mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+ mVisibilityLocationProviderDelegator.setDelegate(this::isInVisibleLocation);
+
mTunerService.addTunable(
(key, newValue) -> {
switch (key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index eea1d911..62f57b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -57,6 +57,7 @@
private float mGapHeight;
private float mGapHeightOnLockscreen;
private int mCollapsedSize;
+ private boolean mEnableNotificationClipping;
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
private boolean mIsExpanded;
@@ -85,6 +86,7 @@
mPaddingBetweenElements = res.getDimensionPixelSize(
R.dimen.notification_divider_height);
mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
+ mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
mClipNotificationScrollToTop = res.getBoolean(R.bool.config_clipNotificationScrollToTop);
int statusBarHeight = SystemBarUtils.getStatusBarHeight(context);
mHeadsUpInset = statusBarHeight + res.getDimensionPixelSize(
@@ -289,7 +291,7 @@
// The bottom of this view is peeking out from under the previous view.
// Clip the part that is peeking out.
float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
- state.clipBottomAmount = (int) overlapAmount;
+ state.clipBottomAmount = mEnableNotificationClipping ? (int) overlapAmount : 0;
} else {
state.clipBottomAmount = 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 1ab9be7..be08183 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -54,7 +54,6 @@
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.LightRevealScrim;
import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import java.io.PrintWriter;
@@ -254,8 +253,6 @@
boolean isWakeUpComingFromTouch();
- boolean isFalsingThresholdNeeded();
-
void onKeyguardViewManagerStatesUpdated();
ViewGroup getNotificationScrollLayout();
@@ -413,12 +410,6 @@
void onClosingFinished();
- void onUnlockHintStarted();
-
- void onHintFinished();
-
- void onTrackingStopped(boolean expand);
-
// TODO: Figure out way to remove these.
NavigationBarView getNavigationBarView();
@@ -500,8 +491,6 @@
boolean isKeyguardSecure();
- NotificationGutsManager getGutsManager();
-
void updateNotificationPanelTouchState();
void makeExpandedVisible(boolean force);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 18a08f7..4562e69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -279,6 +279,7 @@
// 1020-1040 reserved for BaseStatusBar
/**
+ * TODO(b/249277686) delete this
* The delay to reset the hint text when the hint animation is finished running.
*/
private static final int HINT_RESET_DELAY_MS = 1200;
@@ -1784,11 +1785,6 @@
return mWakeUpComingFromTouch;
}
- @Override
- public boolean isFalsingThresholdNeeded() {
- return true;
- }
-
/**
* To be called when there's a state change in StatusBarKeyguardViewManager.
*/
@@ -3392,22 +3388,6 @@
}
}
- @Override
- public void onUnlockHintStarted() {
- mFalsingCollector.onUnlockHintStarted();
- mKeyguardIndicationController.showActionToUnlock();
- }
-
- @Override
- public void onHintFinished() {
- // Delay the reset a bit so the user can read the text.
- mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS);
- }
-
- @Override
- public void onTrackingStopped(boolean expand) {
- }
-
// TODO: Figure out way to remove these.
@Override
public NavigationBarView getNavigationBarView() {
@@ -4138,11 +4118,6 @@
// End Extra BaseStatusBarMethods.
- @Override
- public NotificationGutsManager getGutsManager() {
- return mGutsManager;
- }
-
boolean isTransientShown() {
return mTransientShown;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 34cd1ce..7dcdc0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -33,7 +33,7 @@
private val lastConfig = Configuration()
private var density: Int = 0
private var smallestScreenWidth: Int = 0
- private var maxBounds: Rect? = null
+ private var maxBounds = Rect()
private var fontScale: Float = 0.toFloat()
private val inCarMode: Boolean
private var uiMode: Int = 0
@@ -47,6 +47,7 @@
fontScale = currentConfig.fontScale
density = currentConfig.densityDpi
smallestScreenWidth = currentConfig.smallestScreenWidthDp
+ maxBounds.set(currentConfig.windowConfiguration.maxBounds)
inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK ==
Configuration.UI_MODE_TYPE_CAR
uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
@@ -92,7 +93,11 @@
val maxBounds = newConfig.windowConfiguration.maxBounds
if (maxBounds != this.maxBounds) {
- this.maxBounds = maxBounds
+ // Update our internal rect to have the same bounds, instead of using
+ // `this.maxBounds = maxBounds` directly. Setting it directly means that `maxBounds`
+ // would be a direct reference to windowConfiguration.maxBounds, so the if statement
+ // above would always fail. See b/245799099 for more information.
+ this.maxBounds.set(maxBounds)
listeners.filterForEach({ this.listeners.contains(it) }) {
it.onMaxBoundsChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 3c2ac7b..2ee5232 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -156,6 +156,7 @@
pw.print(" mPluggedIn="); pw.println(mPluggedIn);
pw.print(" mCharging="); pw.println(mCharging);
pw.print(" mCharged="); pw.println(mCharged);
+ pw.print(" mIsOverheated="); pw.println(mIsOverheated);
pw.print(" mPowerSave="); pw.println(mPowerSave);
pw.print(" mStateUnknown="); pw.println(mStateUnknown);
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 6b81bf2..516c650 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -43,6 +43,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.domain.model.ShowDialogRequestModel
@@ -61,6 +62,7 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -108,12 +110,16 @@
private val callbackMutex = Mutex()
private val callbacks = mutableSetOf<UserCallback>()
+ private val userInfos =
+ combine(repository.userSwitcherSettings, repository.userInfos) { settings, userInfos ->
+ userInfos.filter { !it.isGuest || canCreateGuestUser(settings) }
+ }
/** List of current on-device users to select from. */
val users: Flow<List<UserModel>>
get() =
combine(
- repository.userInfos,
+ userInfos,
repository.selectedUserInfo,
repository.userSwitcherSettings,
) { userInfos, selectedUserInfo, settings ->
@@ -147,22 +153,13 @@
get() =
combine(
repository.selectedUserInfo,
- repository.userInfos,
+ userInfos,
repository.userSwitcherSettings,
keyguardInteractor.isKeyguardShowing,
) { _, userInfos, settings, isDeviceLocked ->
buildList {
val hasGuestUser = userInfos.any { it.isGuest }
- if (
- !hasGuestUser &&
- (guestUserInteractor.isGuestUserAutoCreated ||
- UserActionsUtil.canCreateGuest(
- manager,
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
- ))
- ) {
+ if (!hasGuestUser && canCreateGuestUser(settings)) {
add(UserActionModel.ENTER_GUEST_MODE)
}
@@ -211,7 +208,7 @@
val userRecords: StateFlow<ArrayList<UserRecord>> =
combine(
- repository.userInfos,
+ userInfos,
repository.selectedUserInfo,
actions,
repository.userSwitcherSettings,
@@ -687,6 +684,16 @@
)
}
+ private fun canCreateGuestUser(settings: UserSwitcherSettingsModel): Boolean {
+ return guestUserInteractor.isGuestUserAutoCreated ||
+ UserActionsUtil.canCreateGuest(
+ manager,
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ )
+ }
+
companion object {
private const val TAG = "UserInteractor"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index dedc723..98ff8d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -46,6 +46,7 @@
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -480,6 +481,19 @@
assertNull(controller.getCurrentServices()[0].panelActivity)
}
+ @Test
+ fun testListingsNotModifiedByCallback() {
+ // This test checks that if the list passed to the callback is modified, it has no effect
+ // in the resulting services
+ val list = mutableListOf<ServiceInfo>()
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ list.add(ServiceInfo(ComponentName("a", "b")))
+ executor.runAllReady()
+
+ assertTrue(controller.getCurrentServices().isEmpty())
+ }
+
private fun ServiceInfo(
componentName: ComponentName,
panelActivityComponentName: ComponentName? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
new file mode 100644
index 0000000..1e7b1f2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class FeatureFlagsDebugRestarterTest : SysuiTestCase() {
+ private lateinit var restarter: FeatureFlagsDebugRestarter
+
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ restarter = FeatureFlagsDebugRestarter(wakefulnessLifecycle, systemExitRestarter)
+ }
+
+ @Test
+ fun testRestart_ImmediateWhenAsleep() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ restarter.restart()
+ verify(systemExitRestarter).restart()
+ }
+
+ @Test
+ fun testRestart_WaitsForSceenOff() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+
+ restarter.restart()
+ verify(systemExitRestarter, never()).restart()
+
+ val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
+ verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+ captor.value.onFinishedGoingToSleep()
+
+ verify(systemExitRestarter).restart()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
new file mode 100644
index 0000000..68ca48d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
+ private lateinit var restarter: FeatureFlagsReleaseRestarter
+
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var batteryController: BatteryController
+ @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ restarter =
+ FeatureFlagsReleaseRestarter(
+ wakefulnessLifecycle,
+ batteryController,
+ executor,
+ systemExitRestarter
+ )
+ }
+
+ @Test
+ fun testRestart_ScheduledWhenReady() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testRestart_RestartsWhenIdle() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ restarter.restart()
+ verify(systemExitRestarter, never()).restart()
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ verify(systemExitRestarter).restart()
+ }
+
+ @Test
+ fun testRestart_NotScheduledWhenAwake() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
+
+ @Test
+ fun testRestart_NotScheduledWhenNotPluggedIn() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
+
+ @Test
+ fun testRestart_NotDoubleSheduled() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testWakefulnessLifecycle_CanRestart() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+
+ val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
+ verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+
+ captor.value.onFinishedGoingToSleep()
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testBatteryController_CanRestart() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+
+ val captor =
+ ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
+ verify(batteryController).addCallback(captor.capture())
+
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ captor.value.onBatteryLevelChanged(0, true, true)
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..623becf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.CameraGestureHelper
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class CameraQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var cameraGestureHelper: CameraGestureHelper
+ @Mock private lateinit var context: Context
+ private lateinit var underTest: CameraQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest = CameraQuickAffordanceConfig(
+ context,
+ cameraGestureHelper,
+ )
+ }
+
+ @Test
+ fun `affordance triggered -- camera launch called`() {
+ //when
+ val result = underTest.onTriggered(null)
+
+ //then
+ verify(cameraGestureHelper)
+ .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 5a7f2bb..dceb492 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -18,13 +18,13 @@
package com.android.systemui.keyguard.data.repository
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -53,6 +53,7 @@
config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
underTest =
KeyguardQuickAffordanceRepository(
+ appContext = context,
scope = CoroutineScope(IMMEDIATE),
backgroundDispatcher = IMMEDIATE,
selectionManager = KeyguardQuickAffordanceSelectionManager(),
@@ -119,16 +120,32 @@
@Test
fun getSlotPickerRepresentations() {
+ val slot1 = "slot1"
+ val slot2 = "slot2"
+ val slot3 = "slot3"
+ context.orCreateTestableResources.addOverride(
+ R.array.config_keyguardQuickAffordanceSlots,
+ arrayOf(
+ "$slot1:2",
+ "$slot2:4",
+ "$slot3:5",
+ ),
+ )
+
assertThat(underTest.getSlotPickerRepresentations())
.isEqualTo(
listOf(
KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- maxSelectedAffordances = 1,
+ id = slot1,
+ maxSelectedAffordances = 2,
),
KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- maxSelectedAffordances = 1,
+ id = slot2,
+ maxSelectedAffordances = 4,
+ ),
+ KeyguardSlotPickerRepresentation(
+ id = slot3,
+ maxSelectedAffordances = 5,
),
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 8b6603d..737f242 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -230,6 +230,7 @@
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
+ appContext = context,
scope = CoroutineScope(IMMEDIATE),
backgroundDispatcher = IMMEDIATE,
selectionManager = KeyguardQuickAffordanceSelectionManager(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 3364535..ffcf832 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -92,6 +92,7 @@
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
+ appContext = context,
scope = CoroutineScope(IMMEDIATE),
backgroundDispatcher = IMMEDIATE,
selectionManager = KeyguardQuickAffordanceSelectionManager(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 78148c4..fa2ac46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -117,6 +117,7 @@
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
+ appContext = context,
scope = CoroutineScope(IMMEDIATE),
backgroundDispatcher = IMMEDIATE,
selectionManager = KeyguardQuickAffordanceSelectionManager(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 7d2251e..69a4559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -133,6 +133,7 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
@@ -198,6 +199,7 @@
@Mock private KeyguardBottomAreaView mQsFrame;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationShelfController mNotificationShelfController;
+ @Mock private NotificationGutsManager mGutsManager;
@Mock private KeyguardStatusBarView mKeyguardStatusBar;
@Mock private KeyguardUserSwitcherView mUserSwitcherView;
@Mock private ViewStub mUserSwitcherStubView;
@@ -453,6 +455,7 @@
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
mConversationNotificationManager, mMediaHierarchyManager,
mStatusBarKeyguardViewManager,
+ mGutsManager,
mNotificationsQSContainerController,
mNotificationStackScrollLayoutController,
mKeyguardStatusViewComponentFactory,
@@ -754,6 +757,8 @@
@Test
public void testOnTouchEvent_expansionResumesAfterBriefTouch() {
+ mFalsingManager.setIsClassifierEnabled(true);
+ mFalsingManager.setIsFalseTouch(false);
// Start shade collapse with swipe up
onTouchEvent(MotionEvent.obtain(0L /* downTime */,
0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
@@ -1119,6 +1124,19 @@
}
@Test
+ public void testUnlockedSplitShadeTransitioningToKeyguard_closesQS() {
+ enableSplitShade(true);
+ mStatusBarStateController.setState(SHADE);
+ mNotificationPanelViewController.setQsExpanded(true);
+
+ mStatusBarStateController.setState(KEYGUARD);
+
+
+ assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
+ }
+
+ @Test
public void testSwitchesToCorrectClockInSinglePaneShade() {
mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index db7e017..c3207c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.NotificationInsetsController
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -94,6 +95,8 @@
private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock
private lateinit var pulsingGestureListener: PulsingGestureListener
+ @Mock
+ private lateinit var notificationInsetsController: NotificationInsetsController
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerContainer: ViewGroup
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -124,6 +127,7 @@
centralSurfaces,
notificationShadeWindowController,
keyguardUnlockAnimationController,
+ notificationInsetsController,
ambientState,
pulsingGestureListener,
featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index a6c80ab6..4bf00c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationInsetsController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -91,6 +92,7 @@
@Mock private FeatureFlags mFeatureFlags;
@Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
@Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
+ @Mock private NotificationInsetsController mNotificationInsetsController;
@Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
mInteractionEventHandlerCaptor;
@@ -125,6 +127,7 @@
mCentralSurfaces,
mNotificationShadeWindowController,
mKeyguardUnlockAnimationController,
+ mNotificationInsetsController,
mAmbientState,
mPulsingGestureListener,
mFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index ab7c52e..32a281e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -38,6 +38,7 @@
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_OFF;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
+import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_TURNING_ON;
import static com.google.common.truth.Truth.assertThat;
@@ -1506,6 +1507,44 @@
mContext.getString(R.string.keyguard_face_unlock_unavailable));
}
+ @Test
+ public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsNotAvailable_showsMessage() {
+ createController();
+ screenIsTurningOn();
+ fingerprintUnlockIsNotPossible();
+
+ onFaceLockoutError("lockout error");
+ verifyNoMoreInteractions(mRotateTextViewController);
+
+ mScreenObserver.onScreenTurnedOn();
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ "lockout error");
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
+ createController();
+ screenIsTurningOn();
+ fingerprintUnlockIsPossible();
+
+ onFaceLockoutError("lockout error");
+ verifyNoMoreInteractions(mRotateTextViewController);
+
+ mScreenObserver.onScreenTurnedOn();
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ "lockout error");
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_suggest_fingerprint));
+ }
+
+ private void screenIsTurningOn() {
+ when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_TURNING_ON);
+ }
+
private void sendUpdateDisclosureBroadcast() {
mBroadcastReceiver.onReceive(mContext, new Intent());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 7e2e6f6..bdedd24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -13,57 +13,55 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar.notification.collection.coordinator
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
-import java.util.function.Consumer
-import org.junit.Before
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
+import java.util.function.Consumer
+import kotlin.time.Duration.Companion.seconds
import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
class KeyguardCoordinatorTest : SysuiTestCase() {
- private val notifPipeline: NotifPipeline = mock()
+
private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val notifPipelineFlags: NotifPipelineFlags = mock()
+ private val notifPipeline: NotifPipeline = mock()
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
private val statusBarStateController: StatusBarStateController = mock()
- private lateinit var onStateChangeListener: Consumer<String>
- private lateinit var keyguardFilter: NotifFilter
-
- @Before
- fun setup() {
- val keyguardCoordinator = KeyguardCoordinator(
- keyguardNotifVisibilityProvider,
- sectionHeaderVisibilityProvider,
- statusBarStateController
- )
- keyguardCoordinator.attach(notifPipeline)
- onStateChangeListener = withArgCaptor {
- verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
- }
- keyguardFilter = withArgCaptor {
- verify(notifPipeline).addFinalizeFilter(capture())
- }
- }
-
@Test
- fun testSetSectionHeadersVisibleInShade() {
+ fun testSetSectionHeadersVisibleInShade() = runKeyguardCoordinatorTest {
clearInvocations(sectionHeaderVisibilityProvider)
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
onStateChangeListener.accept("state change")
@@ -71,10 +69,176 @@
}
@Test
- fun testSetSectionHeadersNotVisibleOnKeyguard() {
+ fun testSetSectionHeadersNotVisibleOnKeyguard() = runKeyguardCoordinatorTest {
clearInvocations(sectionHeaderVisibilityProvider)
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
onStateChangeListener.accept("state change")
verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(false)
}
+
+ @Test
+ fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is not showing, and a notification is present
+ keyguardRepository.setKeyguardShowing(false)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: The keyguard is now showing
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+ // WHEN: The keyguard goes away
+ keyguardRepository.setKeyguardShowing(false)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is shown regardless
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenFilterAllowsNewNotif() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is showing, no notifications present
+ keyguardRepository.setKeyguardShowing(true)
+ runKeyguardCoordinatorTest {
+ // WHEN: A new notification is posted
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // THEN: The notification is recognized as "unseen" and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenFilterSeenGroupSummaryWithUnseenChild() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is not showing, and a notification is present
+ keyguardRepository.setKeyguardShowing(false)
+ runKeyguardCoordinatorTest {
+ // WHEN: A new notification is posted
+ val fakeSummary = NotificationEntryBuilder().build()
+ val fakeChild = NotificationEntryBuilder()
+ .setGroup(context, "group")
+ .setGroupSummary(context, false)
+ .build()
+ GroupEntryBuilder()
+ .setSummary(fakeSummary)
+ .addChild(fakeChild)
+ .build()
+
+ collectionListener.onEntryAdded(fakeSummary)
+ collectionListener.onEntryAdded(fakeChild)
+
+ // WHEN: Keyguard is now showing, both notifications are marked as seen
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // WHEN: The child notification is now unseen
+ collectionListener.onEntryUpdated(fakeChild)
+
+ // THEN: The summary is not filtered out, because the child is unseen
+ assertThat(unseenFilter.shouldFilterOut(fakeSummary, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is showing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: Keyguard is no longer showing for 5 seconds
+ keyguardRepository.setKeyguardShowing(false)
+ testScheduler.runCurrent()
+ testScheduler.advanceTimeBy(5.seconds.inWholeMilliseconds)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is now recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+ }
+ }
+
+ @Test
+ fun unseenNotificationIsNotMarkedAsSeenIfTimeThresholdNotMet() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is showing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: Keyguard is no longer showing for <5 seconds
+ keyguardRepository.setKeyguardShowing(false)
+ testScheduler.runCurrent()
+ testScheduler.advanceTimeBy(1.seconds.inWholeMilliseconds)
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is not recognized as "seen" and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
+ private fun runKeyguardCoordinatorTest(
+ testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
+ ) {
+ val testScope = TestScope(UnconfinedTestDispatcher())
+ val keyguardCoordinator =
+ KeyguardCoordinator(
+ keyguardNotifVisibilityProvider,
+ keyguardRepository,
+ notifPipelineFlags,
+ testScope.backgroundScope,
+ sectionHeaderVisibilityProvider,
+ statusBarStateController,
+ )
+ keyguardCoordinator.attach(notifPipeline)
+ KeyguardCoordinatorTestScope(keyguardCoordinator, testScope).run {
+ testScheduler.advanceUntilIdle()
+ testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) { testBlock() }
+ }
+ }
+
+ private inner class KeyguardCoordinatorTestScope(
+ private val keyguardCoordinator: KeyguardCoordinator,
+ private val scope: TestScope,
+ ) : CoroutineScope by scope {
+ val testScheduler: TestCoroutineScheduler
+ get() = scope.testScheduler
+
+ val onStateChangeListener: Consumer<String> =
+ withArgCaptor {
+ verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+ }
+
+ val unseenFilter: NotifFilter
+ get() = keyguardCoordinator.unseenNotifFilter
+
+ // TODO(254647461): Remove lazy once Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD is enabled and
+ // removed
+ val collectionListener: NotifCollectionListener by lazy {
+ withArgCaptor { verify(notifPipeline).addCollectionListener(capture()) }
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index b4a5f5c..e488f39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -38,6 +40,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeStateEvents;
import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -73,6 +76,7 @@
@Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
@Mock private HeadsUpManager mHeadsUpManager;
@Mock private ShadeStateEvents mShadeStateEvents;
+ @Mock private VisibilityLocationProvider mVisibilityLocationProvider;
@Mock private VisualStabilityProvider mVisualStabilityProvider;
@Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@@ -100,6 +104,7 @@
mHeadsUpManager,
mShadeStateEvents,
mStatusBarStateController,
+ mVisibilityLocationProvider,
mVisualStabilityProvider,
mWakefulnessLifecycle);
@@ -355,6 +360,38 @@
}
@Test
+ public void testMovingVisibleHeadsUpNotAllowed() {
+ // GIVEN stability enforcing conditions
+ setPanelExpanded(true);
+ setSleepy(false);
+
+ // WHEN a notification is alerting and visible
+ when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
+ .thenReturn(true);
+
+ // VERIFY the notification cannot be reordered
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isFalse();
+ assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isFalse();
+ }
+
+ @Test
+ public void testMovingInvisibleHeadsUpAllowed() {
+ // GIVEN stability enforcing conditions
+ setPanelExpanded(true);
+ setSleepy(false);
+
+ // WHEN a notification is alerting but not visible
+ when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
+ .thenReturn(false);
+
+ // VERIFY the notification can be reordered
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isTrue();
+ assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isTrue();
+ }
+
+ @Test
public void testNeverSuppressedChanges_noInvalidationCalled() {
// GIVEN no notifications are currently being suppressed from grouping nor being sorted
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 90061b0..026c82e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -61,6 +61,7 @@
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
@@ -122,6 +123,7 @@
@Mock private UiEventLogger mUiEventLogger;
@Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
+ @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
@Mock private ShadeController mShadeController;
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private StackStateLogger mStackLogger;
@@ -173,6 +175,7 @@
mShadeTransitionController,
mUiEventLogger,
mRemoteInputManager,
+ mVisibilityLocationProviderDelegator,
mShadeController,
mJankMonitor,
mStackLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index fee3ccb..038af8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -14,23 +14,37 @@
package com.android.systemui.statusbar.phone
-import androidx.test.filters.SmallTest
+import android.content.res.Configuration
+import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_LTR
+import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
+import android.content.res.Configuration.UI_MODE_NIGHT_NO
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.content.res.Configuration.UI_MODE_TYPE_CAR
+import android.os.LocaleList
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import java.util.Locale
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ConfigurationControllerImplTest : SysuiTestCase() {
- private val mConfigurationController =
- com.android.systemui.statusbar.phone.ConfigurationControllerImpl(mContext)
+ private lateinit var mConfigurationController: ConfigurationControllerImpl
+
+ @Before
+ fun setUp() {
+ mConfigurationController = ConfigurationControllerImpl(mContext)
+ }
@Test
fun testThemeChange() {
@@ -57,4 +71,303 @@
verify(listener).onThemeChanged()
verify(listener2, never()).onThemeChanged()
}
+
+ @Test
+ fun configChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.densityDpi = 12
+ config.smallestScreenWidthDp = 240
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the config is updated
+ config.densityDpi = 20
+ config.smallestScreenWidthDp = 300
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.changedConfig?.densityDpi).isEqualTo(20)
+ assertThat(listener.changedConfig?.smallestScreenWidthDp).isEqualTo(300)
+ }
+
+ @Test
+ fun densityChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.densityDpi = 12
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the density is updated
+ config.densityDpi = 20
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ }
+
+ @Test
+ fun fontChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.fontScale = 1.5f
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the font is updated
+ config.fontScale = 1.4f
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ }
+
+ @Test
+ fun isCarAndUiModeChanged_densityListenerNotified() {
+ val config = mContext.resources.configuration
+ config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_YES
+ // Re-create the controller since we calculate car mode on creation
+ mConfigurationController = ConfigurationControllerImpl(mContext)
+
+ val listener = createAndAddListener()
+
+ // WHEN the ui mode is updated
+ config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ }
+
+ @Test
+ fun isNotCarAndUiModeChanged_densityListenerNotNotified() {
+ val config = mContext.resources.configuration
+ config.uiMode = UI_MODE_NIGHT_YES
+ // Re-create the controller since we calculate car mode on creation
+ mConfigurationController = ConfigurationControllerImpl(mContext)
+
+ val listener = createAndAddListener()
+
+ // WHEN the ui mode is updated
+ config.uiMode = UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is not notified because it's not car mode
+ assertThat(listener.densityOrFontScaleChanged).isFalse()
+ }
+
+ @Test
+ fun smallestScreenWidthChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.smallestScreenWidthDp = 240
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the width is updated
+ config.smallestScreenWidthDp = 300
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.smallestScreenWidthChanged).isTrue()
+ }
+
+ @Test
+ fun maxBoundsChange_newConfigObject_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN a new configuration object with new bounds is sent
+ val newConfig = Configuration()
+ newConfig.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+ mConfigurationController.onConfigurationChanged(newConfig)
+
+ // THEN the listener is notified
+ assertThat(listener.maxBoundsChanged).isTrue()
+ }
+
+ // Regression test for b/245799099
+ @Test
+ fun maxBoundsChange_sameObject_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the existing config is updated with new bounds
+ config.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.maxBoundsChanged).isTrue()
+ }
+
+
+ @Test
+ fun localeListChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.locales = LocaleList(Locale.CANADA, Locale.GERMANY)
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the locales are updated
+ config.locales = LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE)
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.localeListChanged).isTrue()
+ }
+
+ @Test
+ fun uiModeChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.uiMode = UI_MODE_NIGHT_YES
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the ui mode is updated
+ config.uiMode = UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.uiModeChanged).isTrue()
+ }
+
+ @Test
+ fun layoutDirectionUpdated_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.screenLayout = SCREENLAYOUT_LAYOUTDIR_LTR
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the layout is updated
+ config.screenLayout = SCREENLAYOUT_LAYOUTDIR_RTL
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.layoutDirectionChanged).isTrue()
+ }
+
+ @Test
+ fun assetPathsUpdated_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.assetsSeq = 45
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the assets sequence is updated
+ config.assetsSeq = 46
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.themeChanged).isTrue()
+ }
+
+ @Test
+ fun multipleUpdates_listenerNotifiedOfAll() {
+ val config = mContext.resources.configuration
+ config.densityDpi = 14
+ config.windowConfiguration.setMaxBounds(0, 0, 2, 2)
+ config.uiMode = UI_MODE_NIGHT_YES
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN multiple fields are updated
+ config.densityDpi = 20
+ config.windowConfiguration.setMaxBounds(0, 0, 3, 3)
+ config.uiMode = UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified of all of them
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ assertThat(listener.maxBoundsChanged).isTrue()
+ assertThat(listener.uiModeChanged).isTrue()
+ }
+
+ @Test
+ fun equivalentConfigObject_listenerNotNotified() {
+ val config = mContext.resources.configuration
+ val listener = createAndAddListener()
+
+ // WHEN we update with the new object that has all the same fields
+ mConfigurationController.onConfigurationChanged(Configuration(config))
+
+ listener.assertNoMethodsCalled()
+ }
+
+ private fun createAndAddListener(): TestListener {
+ val listener = TestListener()
+ mConfigurationController.addCallback(listener)
+ // Adding a listener can trigger some callbacks, so we want to reset the values right
+ // after the listener is added
+ listener.reset()
+ return listener
+ }
+
+ private class TestListener : ConfigurationListener {
+ var changedConfig: Configuration? = null
+ var densityOrFontScaleChanged = false
+ var smallestScreenWidthChanged = false
+ var maxBoundsChanged = false
+ var uiModeChanged = false
+ var themeChanged = false
+ var localeListChanged = false
+ var layoutDirectionChanged = false
+
+ override fun onConfigChanged(newConfig: Configuration?) {
+ changedConfig = newConfig
+ }
+ override fun onDensityOrFontScaleChanged() {
+ densityOrFontScaleChanged = true
+ }
+ override fun onSmallestScreenWidthChanged() {
+ smallestScreenWidthChanged = true
+ }
+ override fun onMaxBoundsChanged() {
+ maxBoundsChanged = true
+ }
+ override fun onUiModeChanged() {
+ uiModeChanged = true
+ }
+ override fun onThemeChanged() {
+ themeChanged = true
+ }
+ override fun onLocaleListChanged() {
+ localeListChanged = true
+ }
+ override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
+ layoutDirectionChanged = true
+ }
+
+ fun assertNoMethodsCalled() {
+ assertThat(densityOrFontScaleChanged).isFalse()
+ assertThat(smallestScreenWidthChanged).isFalse()
+ assertThat(maxBoundsChanged).isFalse()
+ assertThat(uiModeChanged).isFalse()
+ assertThat(themeChanged).isFalse()
+ assertThat(localeListChanged).isFalse()
+ assertThat(layoutDirectionChanged).isFalse()
+ }
+
+ fun reset() {
+ changedConfig = null
+ densityOrFontScaleChanged = false
+ smallestScreenWidthChanged = false
+ maxBoundsChanged = false
+ uiModeChanged = false
+ themeChanged = false
+ localeListChanged = false
+ layoutDirectionChanged = false
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index e86676b..1759fb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -19,9 +19,9 @@
import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
-import android.test.suitebuilder.annotation.SmallTest
import android.view.Display
import android.view.DisplayCutout
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -463,16 +463,10 @@
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
mock(DumpManager::class.java))
- givenDisplay(
- screenBounds = Rect(0, 0, 1080, 2160),
- displayUniqueId = "1"
- )
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
- givenDisplay(
- screenBounds = Rect(0, 0, 800, 600),
- displayUniqueId = "2"
- )
- configurationController.onConfigurationChanged(configuration)
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
// WHEN: get insets on the second display
val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -487,23 +481,15 @@
// get insets and switch back
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
mock(DumpManager::class.java))
- givenDisplay(
- screenBounds = Rect(0, 0, 1080, 2160),
- displayUniqueId = "1"
- )
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
val firstDisplayInsetsFirstCall = provider
.getStatusBarContentAreaForRotation(ROTATION_NONE)
- givenDisplay(
- screenBounds = Rect(0, 0, 800, 600),
- displayUniqueId = "2"
- )
- configurationController.onConfigurationChanged(configuration)
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
- givenDisplay(
- screenBounds = Rect(0, 0, 1080, 2160),
- displayUniqueId = "1"
- )
- configurationController.onConfigurationChanged(configuration)
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
// WHEN: get insets on the first display again
val firstDisplayInsetsSecondCall = provider
@@ -513,9 +499,70 @@
assertThat(firstDisplayInsetsFirstCall).isEqualTo(firstDisplayInsetsSecondCall)
}
- private fun givenDisplay(screenBounds: Rect, displayUniqueId: String) {
- `when`(display.uniqueId).thenReturn(displayUniqueId)
- configuration.windowConfiguration.maxBounds = screenBounds
+ // Regression test for b/245799099
+ @Test
+ fun onMaxBoundsChanged_listenerNotified() {
+ // Start out with an existing configuration with bounds
+ configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+ configurationController.onConfigurationChanged(configuration)
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+ val listener = object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ // WHEN the config is updated with new bounds
+ configuration.windowConfiguration.setMaxBounds(0, 0, 456, 789)
+ configurationController.onConfigurationChanged(configuration)
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
+ }
+
+ @Test
+ fun onDensityOrFontScaleChanged_listenerNotified() {
+ configuration.densityDpi = 12
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+ val listener = object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ // WHEN the config is updated
+ configuration.densityDpi = 20
+ configurationController.onConfigurationChanged(configuration)
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
+ }
+
+ @Test
+ fun onThemeChanged_listenerNotified() {
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+ val listener = object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ configurationController.notifyThemeChanged()
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
}
private fun assertRects(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 8fb98c1..463f517 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -672,6 +672,49 @@
)
}
+ @Test
+ fun `users - secondary user - no guest user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserModel>? = null
+ val job = underTest.users.onEach { res = it }.launchIn(this)
+ assertThat(res?.size == 2).isTrue()
+ assertThat(res?.find { it.isGuest }).isNull()
+ job.cancel()
+ }
+
+ @Test
+ fun `users - secondary user - no guest action`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { res = it }.launchIn(this)
+ assertThat(res?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull()
+ job.cancel()
+ }
+
+ @Test
+ fun `users - secondary user - no guest user record`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserRecord>? = null
+ val job = underTest.userRecords.onEach { res = it }.launchIn(this)
+ assertThat(res?.find { it.isGuest }).isNull()
+ job.cancel()
+ }
+
private fun assertUsers(
models: List<UserModel>?,
count: Int,
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index e529010..7d2e276 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -466,7 +466,8 @@
public static boolean isEmergencyGestureSettingEnabled(Context context, int userId) {
return isEmergencyGestureEnabled(context.getResources())
&& Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.EMERGENCY_GESTURE_ENABLED, 1, userId) != 0;
+ Settings.Secure.EMERGENCY_GESTURE_ENABLED,
+ isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0;
}
/**
@@ -513,6 +514,11 @@
return resources.getBoolean(com.android.internal.R.bool.config_emergencyGestureEnabled);
}
+ private static boolean isDefaultEmergencyGestureEnabled(Resources resources) {
+ return resources.getBoolean(
+ com.android.internal.R.bool.config_defaultEmergencyGestureEnabled);
+ }
+
/**
* Whether GestureLauncherService should be enabled according to system properties.
*/
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index c3cd135..a05b84b 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -351,12 +351,24 @@
* Starts the given user.
*/
public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
- EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
-
final TargetUser targetUser = newTargetUser(userId);
synchronized (mTargetUsers) {
+ // On Automotive / Headless System User Mode, the system user will be started twice:
+ // - Once by some external or local service that switches the system user to
+ // the background.
+ // - Once by the ActivityManagerService, when the system is marked ready.
+ // These two events are not synchronized and the order of execution is
+ // non-deterministic. To avoid starting the system user twice, verify whether
+ // the system user has already been started by checking the mTargetUsers.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move
+ // the headless-user start logic to UserManager-land.
+ if (userId == UserHandle.USER_SYSTEM && mTargetUsers.contains(userId)) {
+ Slog.e(TAG, "Skipping starting system user twice");
+ return;
+ }
mTargetUsers.put(userId, targetUser);
}
+ EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d44b727..28f5bf3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8400,13 +8400,10 @@
// On Automotive / Headless System User Mode, at this point the system user has already been
// started and unlocked, and some of the tasks we do here have already been done. So skip
- // those in that case.
+ // those in that case. The duplicate system user start is guarded in SystemServiceManager.
// TODO(b/242195409): this workaround shouldn't be necessary once we move the headless-user
- // start logic to UserManager-land
- final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
- if (bootingSystemUser) {
- mUserController.onSystemUserStarting();
- }
+ // start logic to UserManager-land.
+ mUserController.onSystemUserStarting();
synchronized (this) {
// Only start up encryption-aware persistent apps; once user is
@@ -8436,7 +8433,15 @@
t.traceEnd();
}
- if (bootingSystemUser) {
+ // Some systems - like automotive - will explicitly unlock system user then switch
+ // to a secondary user. Hence, we don't want to send duplicate broadcasts for
+ // the system user here.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move
+ // the headless-user start logic to UserManager-land.
+ final boolean isBootingSystemUser = (currentUserId == UserHandle.USER_SYSTEM)
+ && !UserManager.isHeadlessSystemUserMode();
+
+ if (isBootingSystemUser) {
t.traceBegin("startHomeOnAllDisplays");
mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
t.traceEnd();
@@ -8447,7 +8452,7 @@
t.traceEnd();
- if (bootingSystemUser) {
+ if (isBootingSystemUser) {
t.traceBegin("sendUserStartBroadcast");
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
@@ -8488,7 +8493,7 @@
mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
t.traceEnd();
- if (bootingSystemUser) {
+ if (isBootingSystemUser) {
t.traceBegin("sendUserSwitchBroadcasts");
mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
t.traceEnd();
@@ -13504,9 +13509,19 @@
// Don't enforce the flag check if we're EITHER registering for only protected
// broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
// not be used generally, so we will be marking them as exported by default
- final boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
+ boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid)
&& mConstants.mEnforceReceiverExportedFlagRequirement;
+ // STOPSHIP(b/259139792): Allow apps that are currently targeting U and in process of
+ // updating their receivers to be exempt from this requirement until their receivers
+ // are flagged.
+ if (requireExplicitFlagForDynamicReceivers) {
+ if ("com.google.android.apps.messaging".equals(callerPackage)) {
+ // Note, a versionCode check for this package is not performed because it could
+ // cause breakage with a subsequent update outside the system image.
+ requireExplicitFlagForDynamicReceivers = false;
+ }
+ }
if (!onlyProtectedBroadcasts) {
if (receiver == null && !explicitExportStateDefined) {
// sticky broadcast, no flag specified (flag isn't required)
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 454b284..fb7e0be 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -51,7 +51,6 @@
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
@@ -1639,9 +1638,8 @@
}
Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
logBroadcastReceiverDiscardLocked(r);
- String anrMessage =
- "Broadcast of " + r.intent.toString() + ", waited " + timeoutDurationMs + "ms";
- TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(anrMessage);
+ TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(r.intent,
+ timeoutDurationMs);
if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
BroadcastFilter bf = (BroadcastFilter) curReceiver;
if (bf.receiverList.pid != 0
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 008d95a..a7d8433 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -907,8 +907,7 @@
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
r.anrCount++;
if (app != null && !app.isDebugging()) {
- mService.appNotResponding(queue.app, TimeoutRecord
- .forBroadcastReceiver("Broadcast of " + r.toShortString()));
+ mService.appNotResponding(queue.app, TimeoutRecord.forBroadcastReceiver(r.intent));
}
} else {
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index e34cd12..b98639e 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -37,6 +37,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.EventLog;
+import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -2110,15 +2111,28 @@
@GuardedBy({"mAm"})
@Override
- public void onBlockingFileLock(int pid) {
+ public void onBlockingFileLock(IntArray pids) {
if (DEBUG_FREEZER) {
- Slog.d(TAG_AM, "Process (pid=" + pid + ") holds blocking file lock");
+ Slog.d(TAG_AM, "Blocking file lock found: " + pids);
}
synchronized (mProcLock) {
+ int pid = pids.get(0);
ProcessRecord app = mFrozenProcesses.get(pid);
+ ProcessRecord pr;
if (app != null) {
- Slog.i(TAG_AM, app.processName + " (" + pid + ") holds blocking file lock");
- unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ for (int i = 1; i < pids.size(); i++) {
+ int blocked = pids.get(i);
+ synchronized (mAm.mPidsSelfLocked) {
+ pr = mAm.mPidsSelfLocked.get(blocked);
+ }
+ if (pr != null && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
+ Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
+ + pr.processName + " (" + blocked + ")");
+ // Found at least one blocked non-cached process
+ unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ break;
+ }
+ }
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING
index 36acc3c..8b80674 100644
--- a/services/core/java/com/android/server/biometrics/TEST_MAPPING
+++ b/services/core/java/com/android/server/biometrics/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "CtsBiometricsTestCases"
+ },
+ {
+ "name": "CtsBiometricsHostTestCases"
}
]
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index da43618..d584c99 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -120,7 +120,7 @@
// if a final consumer is set it will call destroy/disable on the next value if requested
if (!mDestroyed && mNextConsumer == null) {
- disableLightSensorLoggingLocked();
+ disableLightSensorLoggingLocked(false /* destroying */);
}
}
@@ -130,7 +130,7 @@
// if a final consumer is set it will call destroy/disable on the next value if requested
if (!mDestroyed && mNextConsumer == null) {
- disableLightSensorLoggingLocked();
+ disableLightSensorLoggingLocked(true /* destroying */);
mDestroyed = true;
}
}
@@ -177,11 +177,10 @@
final float current = mLastAmbientLux;
if (current > -1f) {
nextConsumer.consume(current);
- } else if (mDestroyed) {
- nextConsumer.consume(-1f);
} else if (mNextConsumer != null) {
mNextConsumer.add(nextConsumer);
} else {
+ mDestroyed = false;
mNextConsumer = nextConsumer;
enableLightSensorLoggingLocked();
}
@@ -199,12 +198,14 @@
resetTimerLocked(true /* start */);
}
- private void disableLightSensorLoggingLocked() {
+ private void disableLightSensorLoggingLocked(boolean destroying) {
resetTimerLocked(false /* start */);
if (mEnabled) {
mEnabled = false;
- mLastAmbientLux = -1;
+ if (!destroying) {
+ mLastAmbientLux = -1;
+ }
mSensorManager.unregisterListener(mLightSensorListener);
Slog.v(TAG, "Disable ALS: " + mLightSensorListener.hashCode());
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index 7a13c91..d11f099 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -22,6 +22,7 @@
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.face.AuthenticationFrame;
import android.hardware.biometrics.face.BaseFrame;
+import android.hardware.biometrics.face.EnrollmentFrame;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticationFrame;
import android.hardware.face.FaceEnrollFrame;
@@ -33,6 +34,7 @@
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.EnrollClient;
import com.android.server.biometrics.sensors.face.FaceUtils;
import java.util.HashSet;
@@ -200,22 +202,24 @@
}
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- // TODO(b/178414967): replace with notifyAuthenticationFrame and notifyEnrollmentFrame.
@Override
public void notifyAcquired(int userId, int acquireInfo) {
-
super.notifyAcquired_enforcePermission();
BaseFrame data = new BaseFrame();
data.acquiredInfo = (byte) acquireInfo;
- AuthenticationFrame authenticationFrame = new AuthenticationFrame();
- authenticationFrame.data = data;
-
- // TODO(b/178414967): Currently onAuthenticationFrame and onEnrollmentFrame are the same.
- // This will need to call the correct callback once the onAcquired callback is removed.
- mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFrame(
- authenticationFrame);
+ if (mSensor.getScheduler().getCurrentClient() instanceof EnrollClient) {
+ final EnrollmentFrame frame = new EnrollmentFrame();
+ frame.data = data;
+ mSensor.getSessionForUser(userId).getHalSessionCallback()
+ .onEnrollmentFrame(frame);
+ } else {
+ final AuthenticationFrame frame = new AuthenticationFrame();
+ frame.data = data;
+ mSensor.getSessionForUser(userId).getHalSessionCallback()
+ .onAuthenticationFrame(frame);
+ }
}
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 0741d46..bc9bc03 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -645,7 +645,8 @@
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
- .setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE, null))
+ .setTransportInfo(new VpnTransportInfo(
+ VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
.build();
loadAlwaysOnPackage();
@@ -709,7 +710,8 @@
private void resetNetworkCapabilities() {
mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
.setUids(null)
- .setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE, null))
+ .setTransportInfo(new VpnTransportInfo(
+ VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
.build();
}
@@ -1567,7 +1569,8 @@
capsBuilder.setUids(createUserAndRestrictedProfilesRanges(mUserId,
mConfig.allowedApplications, mConfig.disallowedApplications));
- capsBuilder.setTransportInfo(new VpnTransportInfo(getActiveVpnType(), mConfig.session));
+ capsBuilder.setTransportInfo(
+ new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass));
// Only apps targeting Q and above can explicitly declare themselves as metered.
// These VPNs are assumed metered unless they state otherwise.
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 838bb53..d6f0fd0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1703,6 +1703,7 @@
mTempBrightnessEvent.setRbcStrength(mCdsi != null
? mCdsi.getReduceBrightColorsStrength() : -1);
mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
+ mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -1713,12 +1714,6 @@
|| brightnessAdjustmentFlags != 0) {
float lastBrightness = mLastBrightnessEvent.getBrightness();
mTempBrightnessEvent.setInitialBrightness(lastBrightness);
- mTempBrightnessEvent.setFastAmbientLux(
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
- mTempBrightnessEvent.setSlowAmbientLux(
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
@@ -2846,9 +2841,9 @@
FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
convertToNits(event.getInitialBrightness()),
convertToNits(event.getBrightness()),
- event.getSlowAmbientLux(),
+ event.getLux(),
event.getPhysicalDisplayId(),
- event.isShortTermModelActive(),
+ event.wasShortTermModelActive(),
appliedLowPowerMode,
appliedRbcStrength,
appliedHbmMaxNits,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index a57d802..300b589 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1568,6 +1568,7 @@
mTempBrightnessEvent.setRbcStrength(mCdsi != null
? mCdsi.getReduceBrightColorsStrength() : -1);
mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
+ mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -1578,12 +1579,6 @@
|| brightnessAdjustmentFlags != 0) {
float lastBrightness = mLastBrightnessEvent.getBrightness();
mTempBrightnessEvent.setInitialBrightness(lastBrightness);
- mTempBrightnessEvent.setFastAmbientLux(
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
- mTempBrightnessEvent.setSlowAmbientLux(
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
@@ -2517,9 +2512,9 @@
FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
convertToNits(event.getInitialBrightness()),
convertToNits(event.getBrightness()),
- event.getSlowAmbientLux(),
+ event.getLux(),
event.getPhysicalDisplayId(),
- event.isShortTermModelActive(),
+ event.wasShortTermModelActive(),
appliedLowPowerMode,
appliedRbcStrength,
appliedHbmMaxNits,
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index e3fa622..f19852b 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -39,8 +39,6 @@
private String mPhysicalDisplayId;
private long mTime;
private float mLux;
- private float mFastAmbientLux;
- private float mSlowAmbientLux;
private float mPreThresholdLux;
private float mInitialBrightness;
private float mBrightness;
@@ -51,6 +49,7 @@
private int mRbcStrength;
private float mThermalMax;
private float mPowerFactor;
+ private boolean mWasShortTermModelActive;
private int mFlags;
private int mAdjustmentFlags;
private boolean mAutomaticBrightnessEnabled;
@@ -76,8 +75,6 @@
mTime = that.getTime();
// Lux values
mLux = that.getLux();
- mFastAmbientLux = that.getFastAmbientLux();
- mSlowAmbientLux = that.getSlowAmbientLux();
mPreThresholdLux = that.getPreThresholdLux();
// Brightness values
mInitialBrightness = that.getInitialBrightness();
@@ -90,6 +87,7 @@
mRbcStrength = that.getRbcStrength();
mThermalMax = that.getThermalMax();
mPowerFactor = that.getPowerFactor();
+ mWasShortTermModelActive = that.wasShortTermModelActive();
mFlags = that.getFlags();
mAdjustmentFlags = that.getAdjustmentFlags();
// Auto-brightness setting
@@ -105,8 +103,6 @@
mPhysicalDisplayId = "";
// Lux values
mLux = 0;
- mFastAmbientLux = 0;
- mSlowAmbientLux = 0;
mPreThresholdLux = 0;
// Brightness values
mInitialBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -119,6 +115,7 @@
mRbcStrength = 0;
mThermalMax = PowerManager.BRIGHTNESS_MAX;
mPowerFactor = 1f;
+ mWasShortTermModelActive = false;
mFlags = 0;
mAdjustmentFlags = 0;
// Auto-brightness setting
@@ -140,10 +137,6 @@
&& mDisplayId == that.mDisplayId
&& mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
&& Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
- && Float.floatToRawIntBits(mFastAmbientLux)
- == Float.floatToRawIntBits(that.mFastAmbientLux)
- && Float.floatToRawIntBits(mSlowAmbientLux)
- == Float.floatToRawIntBits(that.mSlowAmbientLux)
&& Float.floatToRawIntBits(mPreThresholdLux)
== Float.floatToRawIntBits(that.mPreThresholdLux)
&& Float.floatToRawIntBits(mInitialBrightness)
@@ -161,6 +154,7 @@
== Float.floatToRawIntBits(that.mThermalMax)
&& Float.floatToRawIntBits(mPowerFactor)
== Float.floatToRawIntBits(that.mPowerFactor)
+ && mWasShortTermModelActive == that.mWasShortTermModelActive
&& mFlags == that.mFlags
&& mAdjustmentFlags == that.mAdjustmentFlags
&& mAutomaticBrightnessEnabled == that.mAutomaticBrightnessEnabled;
@@ -182,14 +176,13 @@
+ ", rcmdBrt=" + mRecommendedBrightness
+ ", preBrt=" + mPreThresholdBrightness
+ ", lux=" + mLux
- + ", fastLux=" + mFastAmbientLux
- + ", slowLux=" + mSlowAmbientLux
+ ", preLux=" + mPreThresholdLux
+ ", hbmMax=" + mHbmMax
+ ", hbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
+ ", rbcStrength=" + mRbcStrength
+ ", thrmMax=" + mThermalMax
+ ", powerFactor=" + mPowerFactor
+ + ", wasShortTermModelActive=" + mWasShortTermModelActive
+ ", flags=" + flagsToString()
+ ", reason=" + mReason.toString(mAdjustmentFlags)
+ ", autoBrightness=" + mAutomaticBrightnessEnabled;
@@ -240,22 +233,6 @@
this.mLux = lux;
}
- public float getFastAmbientLux() {
- return mFastAmbientLux;
- }
-
- public void setFastAmbientLux(float mFastAmbientLux) {
- this.mFastAmbientLux = mFastAmbientLux;
- }
-
- public float getSlowAmbientLux() {
- return mSlowAmbientLux;
- }
-
- public void setSlowAmbientLux(float mSlowAmbientLux) {
- this.mSlowAmbientLux = mSlowAmbientLux;
- }
-
public float getPreThresholdLux() {
return mPreThresholdLux;
}
@@ -344,6 +321,20 @@
return (mFlags & FLAG_LOW_POWER_MODE) != 0;
}
+ /**
+ * Set whether the short term model was active before the brightness event.
+ */
+ public boolean setWasShortTermModelActive(boolean wasShortTermModelActive) {
+ return this.mWasShortTermModelActive = wasShortTermModelActive;
+ }
+
+ /**
+ * Returns whether the short term model was active before the brightness event.
+ */
+ public boolean wasShortTermModelActive() {
+ return this.mWasShortTermModelActive;
+ }
+
public int getFlags() {
return mFlags;
}
@@ -352,10 +343,6 @@
this.mFlags = flags;
}
- public boolean isShortTermModelActive() {
- return (mFlags & FLAG_USER_SET) != 0;
- }
-
public int getAdjustmentFlags() {
return mAdjustmentFlags;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 90135ad..d6b9bd5 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3797,13 +3797,13 @@
}
private void createNotificationChannelsImpl(String pkg, int uid,
- ParceledListSlice channelsList) {
- createNotificationChannelsImpl(pkg, uid, channelsList,
+ ParceledListSlice channelsList, boolean fromTargetApp) {
+ createNotificationChannelsImpl(pkg, uid, channelsList, fromTargetApp,
ActivityTaskManager.INVALID_TASK_ID);
}
private void createNotificationChannelsImpl(String pkg, int uid,
- ParceledListSlice channelsList, int startingTaskId) {
+ ParceledListSlice channelsList, boolean fromTargetApp, int startingTaskId) {
List<NotificationChannel> channels = channelsList.getList();
final int channelsSize = channels.size();
ParceledListSlice<NotificationChannel> oldChannels =
@@ -3815,7 +3815,7 @@
final NotificationChannel channel = channels.get(i);
Objects.requireNonNull(channel, "channel in list is null");
needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(pkg, uid,
- channel, true /* fromTargetApp */,
+ channel, fromTargetApp,
mConditionProviders.isPackageOrComponentAllowed(
pkg, UserHandle.getUserId(uid)));
if (needsPolicyFileChange) {
@@ -3851,6 +3851,7 @@
@Override
public void createNotificationChannels(String pkg, ParceledListSlice channelsList) {
checkCallerIsSystemOrSameApp(pkg);
+ boolean fromTargetApp = !isCallerSystemOrPhone(); // if not system, it's from the app
int taskId = ActivityTaskManager.INVALID_TASK_ID;
try {
int uid = mPackageManager.getPackageUid(pkg, 0,
@@ -3859,14 +3860,15 @@
} catch (RemoteException e) {
// Do nothing
}
- createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, taskId);
+ createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, fromTargetApp,
+ taskId);
}
@Override
public void createNotificationChannelsForPackage(String pkg, int uid,
ParceledListSlice channelsList) {
enforceSystemOrSystemUI("only system can call this");
- createNotificationChannelsImpl(pkg, uid, channelsList);
+ createNotificationChannelsImpl(pkg, uid, channelsList, false /* fromTargetApp */);
}
@Override
@@ -3881,7 +3883,8 @@
CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId));
conversationChannel.setConversationId(parentId, conversationId);
createNotificationChannelsImpl(
- pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
+ pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)),
+ false /* fromTargetApp */);
mRankingHandler.requestSort();
handleSavePolicyFile();
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 1bbcc83..444fef6 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -918,7 +918,7 @@
throw new IllegalArgumentException("Reserved id");
}
NotificationChannel existing = r.channels.get(channel.getId());
- if (existing != null && fromTargetApp) {
+ if (existing != null) {
// Actually modifying an existing channel - keep most of the existing settings
if (existing.isDeleted()) {
// The existing channel was deleted - undelete it.
@@ -1004,9 +1004,7 @@
}
if (fromTargetApp) {
channel.setLockscreenVisibility(r.visibility);
- channel.setAllowBubbles(existing != null
- ? existing.getAllowBubbles()
- : NotificationChannel.DEFAULT_ALLOW_BUBBLE);
+ channel.setAllowBubbles(NotificationChannel.DEFAULT_ALLOW_BUBBLE);
}
clearLockedFieldsLocked(channel);
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 88a3f8e..095a7f6 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -261,6 +261,7 @@
final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
info.sendPackageRemovedBroadcasts(killApp, removedBySystem);
info.sendSystemPackageUpdatedBroadcasts();
+ PackageMetrics.onUninstallSucceeded(info, deleteFlags, mUserManagerInternal);
}
// Force a gc to clear up things.
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 0391163..cb87ff5 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -19,12 +19,13 @@
import static android.os.Process.INVALID_UID;
import android.annotation.IntDef;
+import android.content.pm.PackageManager;
import android.content.pm.parsing.ApkLiteParseUtils;
-import android.os.UserManager;
import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
import com.android.server.pm.pkg.PackageStateInternal;
import java.io.File;
@@ -73,6 +74,8 @@
}
private void reportInstallationStats(Computer snapshot, boolean success) {
+ UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
// TODO(b/249294752): do not log if adb
final long installDurationMillis =
System.currentTimeMillis() - mInstallStartTimestampMillis;
@@ -93,9 +96,9 @@
success ? null : packageName /* not report package_name on success */,
mInstallRequest.getUid() /* uid */,
newUsers /* user_ids */,
- getUserTypes(newUsers) /* user_types */,
+ userManagerInternal.getUserTypesForStatsd(newUsers) /* user_types */,
originalUsers /* original_user_ids */,
- getUserTypes(originalUsers) /* original_user_types */,
+ userManagerInternal.getUserTypesForStatsd(originalUsers) /* original_user_types */,
mInstallRequest.getReturnCode() /* public_return_code */,
0 /* internal_error_code */,
apksSize /* apks_size_bytes */,
@@ -163,18 +166,6 @@
return new Pair<>(stepsArray, durationsArray);
}
- private static int[] getUserTypes(int[] userIds) {
- if (userIds == null) {
- return null;
- }
- final int[] userTypes = new int[userIds.length];
- for (int i = 0; i < userTypes.length; i++) {
- String userType = UserManagerService.getInstance().getUserInfo(userIds[i]).userType;
- userTypes[i] = UserManager.getUserTypeForStatsd(userType);
- }
- return userTypes;
- }
-
private static class InstallStep {
private final long mStartTimestampMillis;
private long mDurationMillis = -1;
@@ -191,4 +182,20 @@
return mDurationMillis;
}
}
+
+ public static void onUninstallSucceeded(PackageRemovedInfo info, int deleteFlags,
+ UserManagerInternal userManagerInternal) {
+ if (info.mIsUpdate) {
+ // Not logging uninstalls caused by app updates
+ return;
+ }
+ final int[] removedUsers = info.mRemovedUsers;
+ final int[] removedUserTypes = userManagerInternal.getUserTypesForStatsd(removedUsers);
+ final int[] originalUsers = info.mOrigUsers;
+ final int[] originalUserTypes = userManagerInternal.getUserTypesForStatsd(originalUsers);
+ FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_UNINSTALLATION_REPORTED,
+ info.mUid, removedUsers, removedUserTypes, originalUsers, originalUserTypes,
+ deleteFlags, PackageManager.DELETE_SUCCEEDED, info.mIsRemovedPackageSystemUpdate,
+ !info.mRemovedForAllUsers);
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 9dafcce..1420cbf 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -437,4 +437,8 @@
/** TODO(b/244333150): temporary method until UserVisibilityMediator handles that logic */
public abstract void onUserVisibilityChanged(@UserIdInt int userId, boolean visible);
+
+ /** Return the integer types of the given user IDs. Only used for reporting metrics to statsd.
+ */
+ public abstract int[] getUserTypesForStatsd(@UserIdInt int[] userIds);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4a4a231..9f84ab0 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -6893,6 +6893,24 @@
}
});
}
+
+ @Override
+ public int[] getUserTypesForStatsd(@UserIdInt int[] userIds) {
+ if (userIds == null) {
+ return null;
+ }
+ final int[] userTypes = new int[userIds.length];
+ for (int i = 0; i < userTypes.length; i++) {
+ final UserInfo userInfo = getUserInfo(userIds[i]);
+ if (userInfo == null) {
+ // Not possible because the input user ids should all be valid
+ userTypes[i] = UserManager.getUserTypeForStatsd("");
+ } else {
+ userTypes[i] = UserManager.getUserTypeForStatsd(userInfo.userType);
+ }
+ }
+ return userTypes;
+ }
} // class LocalService
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 916df89..0c5e451 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -11507,6 +11507,9 @@
mHistory.reset();
+ // Store the empty state to disk to ensure consistency
+ writeSyncLocked();
+
// Flush external data, gathering snapshots, but don't process it since it is pre-reset data
mIgnoreNextExternalStats = true;
mExternalSync.scheduleSync("reset", ExternalStatsSync.UPDATE_ON_RESET);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index cdb3321..dfb61a8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4291,13 +4291,14 @@
}
/**
- * @return true if the task is currently focused.
+ * @return {@code true} if the task is currently focused or one of its children is focused.
*/
boolean isFocused() {
if (mDisplayContent == null || mDisplayContent.mFocusedApp == null) {
return false;
}
- return mDisplayContent.mFocusedApp.getTask() == this;
+ final Task focusedTask = mDisplayContent.mFocusedApp.getTask();
+ return focusedTask == this || (focusedTask != null && focusedTask.getParent() == this);
}
/**
@@ -4317,6 +4318,8 @@
*/
void onAppFocusChanged(boolean hasFocus) {
dispatchTaskInfoChangedIfNeeded(false /* force */);
+ final Task parentTask = getParent().asTask();
+ if (parentTask != null) parentTask.dispatchTaskInfoChangedIfNeeded(false /* force */);
}
void onPictureInPictureParamsChanged() {
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index 16eaa77..3678ced 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -98,7 +98,7 @@
public:
binder::Status notifyWakeup(bool success,
const std::vector<std::string>& wakeupReasons) override {
- ALOGI("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted");
+ ALOGV("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted");
bool reasonsCaptured = false;
{
std::unique_lock<std::mutex> reasonsLock(mReasonsMutex, std::defer_lock);
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 2030347..5d0551b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1191,8 +1191,9 @@
static_cast<const KeyEvent*>(inputEvent));
break;
case AINPUT_EVENT_TYPE_MOTION:
- inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
- static_cast<const MotionEvent*>(inputEvent));
+ inputEventObj =
+ android_view_MotionEvent_obtainAsCopy(env,
+ static_cast<const MotionEvent&>(*inputEvent));
break;
default:
return true; // dispatch the event normally
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
index 7ccd6d9..e0662c4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
@@ -51,6 +51,7 @@
mUserManagerInternal = rule.mocks().injector.userManagerInternal
whenever(mUserManagerInternal.getUserIds()).thenReturn(intArrayOf(0, 1))
+ whenever(mUserManagerInternal.getUserTypesForStatsd(any())).thenReturn(intArrayOf(1, 1))
mPms = createPackageManagerService()
doAnswer { false }.`when`(mPms).isPackageDeviceAdmin(any(), any())
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index 0cff4f1..bb00634 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -125,12 +125,9 @@
mProbe.destroy();
mProbe.enable();
- AtomicInteger lux = new AtomicInteger(10);
- mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
-
verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
verifyNoMoreInteractions(mSensorManager);
- assertThat(lux.get()).isLessThan(0);
+ assertThat(mProbe.getMostRecentLux()).isLessThan(0);
}
@Test
@@ -323,15 +320,27 @@
}
@Test
- public void testNoNextLuxWhenDestroyed() {
+ public void testDestroyAllowsAwaitLuxExactlyOnce() {
+ final float lastValue = 5.5f;
mProbe.destroy();
- AtomicInteger lux = new AtomicInteger(-20);
+ AtomicInteger lux = new AtomicInteger(10);
mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
- assertThat(lux.get()).isEqualTo(-1);
- verify(mSensorManager, never()).registerListener(
+ verify(mSensorManager).registerListener(
mSensorEventListenerCaptor.capture(), any(), anyInt());
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{lastValue}));
+
+ assertThat(lux.get()).isEqualTo(Math.round(lastValue));
+ verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
+
+ lux.set(22);
+ mProbe.enable();
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+ mProbe.enable();
+
+ assertThat(lux.get()).isEqualTo(Math.round(lastValue));
verifyNoMoreInteractions(mSensorManager);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
index fabf535..d332b30 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -39,8 +39,6 @@
getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER));
mBrightnessEvent.setPhysicalDisplayId("test");
mBrightnessEvent.setLux(100.0f);
- mBrightnessEvent.setFastAmbientLux(90.0f);
- mBrightnessEvent.setSlowAmbientLux(85.0f);
mBrightnessEvent.setPreThresholdLux(150.0f);
mBrightnessEvent.setTime(System.currentTimeMillis());
mBrightnessEvent.setInitialBrightness(25.0f);
@@ -50,6 +48,7 @@
mBrightnessEvent.setRbcStrength(-1);
mBrightnessEvent.setThermalMax(0.65f);
mBrightnessEvent.setPowerFactor(0.2f);
+ mBrightnessEvent.setWasShortTermModelActive(true);
mBrightnessEvent.setHbmMode(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
mBrightnessEvent.setFlags(0);
mBrightnessEvent.setAdjustmentFlags(0);
@@ -69,9 +68,9 @@
String actualString = mBrightnessEvent.toString(false);
String expectedString =
"BrightnessEvent: disp=1, physDisp=test, brt=0.6, initBrt=25.0, rcmdBrt=0.6,"
- + " preBrt=NaN, lux=100.0, fastLux=90.0, slowLux=85.0, preLux=150.0, hbmMax=0.62,"
- + " hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, flags=, reason=doze"
- + " [ low_pwr ], autoBrightness=true";
+ + " preBrt=NaN, lux=100.0, preLux=150.0, hbmMax=0.62, hbmMode=off, rbcStrength=-1,"
+ + " thrmMax=0.65, powerFactor=0.2, wasShortTermModelActive=true, flags=,"
+ + " reason=doze [ low_pwr ], autoBrightness=true";
assertEquals(expectedString, actualString);
}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 164161e..dc47b5e 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -8,7 +8,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -20,8 +19,6 @@
import android.content.pm.PackageManagerInternal;
import android.net.NetworkRequest;
import android.os.Build;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.SystemClock;
import android.test.RenamingDelegatingContext;
@@ -32,7 +29,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.util.HexDump;
import com.android.server.LocalServices;
import com.android.server.job.JobStore.JobSet;
import com.android.server.job.controllers.JobStatus;
@@ -44,7 +40,6 @@
import java.time.Clock;
import java.time.ZoneOffset;
-import java.util.Arrays;
import java.util.Iterator;
/**
@@ -143,15 +138,8 @@
assertEquals("Didn't get expected number of persisted tasks.", 1, jobStatusSet.size());
final JobStatus loadedTaskStatus = jobStatusSet.getAllJobs().get(0);
- assertTasksEqual(task, loadedTaskStatus.getJob());
+ assertJobsEqual(ts, loadedTaskStatus);
assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(ts));
- assertEquals("Different uids.", SOME_UID, loadedTaskStatus.getUid());
- assertEquals(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION,
- loadedTaskStatus.getInternalFlags());
- compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
- ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime());
- compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
- ts.getLatestRunTimeElapsed(), loadedTaskStatus.getLatestRunTimeElapsed());
}
@Test
@@ -202,19 +190,10 @@
loaded2 = tmp;
}
- assertTasksEqual(task1, loaded1.getJob());
- assertTasksEqual(task2, loaded2.getJob());
+ assertJobsEqual(taskStatus1, loaded1);
+ assertJobsEqual(taskStatus2, loaded2);
assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1));
assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2));
- // Check that the loaded task has the correct runtimes.
- compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
- taskStatus1.getEarliestRunTime(), loaded1.getEarliestRunTime());
- compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
- taskStatus1.getLatestRunTimeElapsed(), loaded1.getLatestRunTimeElapsed());
- compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
- taskStatus2.getEarliestRunTime(), loaded2.getEarliestRunTime());
- compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
- taskStatus2.getLatestRunTimeElapsed(), loaded2.getLatestRunTimeElapsed());
}
@Test
@@ -240,7 +219,7 @@
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
- assertTasksEqual(task, loaded.getJob());
+ assertJobsEqual(taskStatus, loaded);
}
@Test
@@ -544,71 +523,30 @@
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
final JobStatus second = jobStatusSet.getAllJobs().iterator().next();
- assertTasksEqual(first.getJob(), second.getJob());
+ assertJobsEqual(first, second);
}
/**
- * Helper function to throw an error if the provided task and TaskStatus objects are not equal.
+ * Helper function to throw an error if the provided JobStatus objects are not equal.
*/
- private void assertTasksEqual(JobInfo first, JobInfo second) {
- assertEquals("Different task ids.", first.getId(), second.getId());
- assertEquals("Different components.", first.getService(), second.getService());
- assertEquals("Different periodic status.", first.isPeriodic(), second.isPeriodic());
- assertEquals("Different period.", first.getIntervalMillis(), second.getIntervalMillis());
- assertEquals("Different inital backoff.", first.getInitialBackoffMillis(),
- second.getInitialBackoffMillis());
- assertEquals("Different backoff policy.", first.getBackoffPolicy(),
- second.getBackoffPolicy());
+ private void assertJobsEqual(JobStatus expected, JobStatus actual) {
+ assertEquals(expected.getJob(), actual.getJob());
- assertEquals("Invalid charging constraint.", first.isRequireCharging(),
- second.isRequireCharging());
- assertEquals("Invalid battery not low constraint.", first.isRequireBatteryNotLow(),
- second.isRequireBatteryNotLow());
- assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(),
- second.isRequireDeviceIdle());
- assertEquals("Invalid network type.",
- first.getNetworkType(), second.getNetworkType());
- assertEquals("Invalid network.",
- first.getRequiredNetwork(), second.getRequiredNetwork());
- assertEquals("Download bytes don't match",
- first.getEstimatedNetworkDownloadBytes(),
- second.getEstimatedNetworkDownloadBytes());
- assertEquals("Upload bytes don't match",
- first.getEstimatedNetworkUploadBytes(),
- second.getEstimatedNetworkUploadBytes());
- assertEquals("Minimum chunk bytes don't match",
- first.getMinimumNetworkChunkBytes(),
- second.getMinimumNetworkChunkBytes());
- assertEquals("Invalid deadline constraint.",
- first.hasLateConstraint(),
- second.hasLateConstraint());
- assertEquals("Invalid delay constraint.",
- first.hasEarlyConstraint(),
- second.hasEarlyConstraint());
- assertEquals("Extras don't match",
- first.getExtras().toString(), second.getExtras().toString());
- assertEquals("Transient xtras don't match",
- first.getTransientExtras().toString(), second.getTransientExtras().toString());
+ // Source UID isn't persisted, but the rest of the app info is.
+ assertEquals("Source package not equal",
+ expected.getSourcePackageName(), actual.getSourcePackageName());
+ assertEquals("Source user not equal", expected.getSourceUserId(), actual.getSourceUserId());
+ assertEquals("Calling UID not equal", expected.getUid(), actual.getUid());
+ assertEquals("Calling user not equal", expected.getUserId(), actual.getUserId());
- // Since people can forget to add tests here for new fields, do one last
- // validity check based on bits-on-wire equality.
- final byte[] firstBytes = marshall(first);
- final byte[] secondBytes = marshall(second);
- if (!Arrays.equals(firstBytes, secondBytes)) {
- Log.w(TAG, "First: " + HexDump.dumpHexString(firstBytes));
- Log.w(TAG, "Second: " + HexDump.dumpHexString(secondBytes));
- fail("Raw JobInfo aren't equal; see logs for details");
- }
- }
+ assertEquals("Internal flags not equal",
+ expected.getInternalFlags(), actual.getInternalFlags());
- private static byte[] marshall(Parcelable p) {
- final Parcel parcel = Parcel.obtain();
- try {
- p.writeToParcel(parcel, 0);
- return parcel.marshall();
- } finally {
- parcel.recycle();
- }
+ // Check that the loaded task has the correct runtimes.
+ compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
+ expected.getEarliestRunTime(), actual.getEarliestRunTime());
+ compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
+ expected.getLatestRunTimeElapsed(), actual.getLatestRunTimeElapsed());
}
/**
@@ -623,5 +561,4 @@
}
private static class StubClass {}
-
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index d54d1fe..afec085 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1101,6 +1101,10 @@
new NotificationChannel("id", "name", IMPORTANCE_HIGH);
mBinderService.updateNotificationChannelForPackage(PKG, mUid, updatedChannel);
+ // pretend only this following part is called by the app (system permissions are required to
+ // update the notification channel on behalf of the user above)
+ mService.isSystemUid = false;
+
// Recreating with a lower importance leaves channel unchanged.
final NotificationChannel dupeChannel =
new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_LOW);
@@ -1126,6 +1130,46 @@
}
@Test
+ public void testCreateNotificationChannels_fromAppCannotSetFields() throws Exception {
+ // Confirm that when createNotificationChannels is called from the relevant app and not
+ // system, then it cannot set fields that can't be set by apps
+ mService.isSystemUid = false;
+
+ final NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ channel.setBypassDnd(true);
+ channel.setAllowBubbles(true);
+
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(channel)));
+
+ final NotificationChannel createdChannel =
+ mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
+ assertFalse(createdChannel.canBypassDnd());
+ assertFalse(createdChannel.canBubble());
+ }
+
+ @Test
+ public void testCreateNotificationChannels_fromSystemCanSetFields() throws Exception {
+ // Confirm that when createNotificationChannels is called from system,
+ // then it can set fields that can't be set by apps
+ mService.isSystemUid = true;
+
+ final NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ channel.setBypassDnd(true);
+ channel.setAllowBubbles(true);
+
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(channel)));
+
+ final NotificationChannel createdChannel =
+ mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
+ assertTrue(createdChannel.canBypassDnd());
+ assertTrue(createdChannel.canBubble());
+ }
+
+ @Test
public void testBlockedNotifications_suspended() throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(true);
@@ -3088,6 +3132,8 @@
@Test
public void testDeleteChannelGroupChecksForFgses() throws Exception {
+ // the setup for this test requires it to seem like it's coming from the app
+ mService.isSystemUid = false;
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
.thenReturn(singletonList(mock(AssociationInfo.class)));
CountDownLatch latch = new CountDownLatch(2);
@@ -3100,7 +3146,7 @@
ParceledListSlice<NotificationChannel> pls =
new ParceledListSlice(ImmutableList.of(notificationChannel));
try {
- mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+ mBinderService.createNotificationChannels(PKG, pls);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -3119,8 +3165,10 @@
ParceledListSlice<NotificationChannel> pls =
new ParceledListSlice(ImmutableList.of(notificationChannel));
try {
- mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
- mBinderService.deleteNotificationChannelGroup(PKG, "group");
+ // Because existing channels won't have their groups overwritten when the call
+ // is from the app, this call won't take the channel out of the group
+ mBinderService.createNotificationChannels(PKG, pls);
+ mBinderService.deleteNotificationChannelGroup(PKG, "group");
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -8681,7 +8729,7 @@
assertEquals("friend", friendChannel.getConversationId());
assertEquals(null, original.getConversationId());
assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
- assertFalse(friendChannel.canBubble()); // can't be modified by app
+ assertEquals(original.canBubble(), friendChannel.canBubble()); // called by system
assertFalse(original.getId().equals(friendChannel.getId()));
assertNotNull(friendChannel.getId());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 66bf78b..0b6cea2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -433,6 +433,24 @@
}
@Test
+ public void testPropagateFocusedStateToRootTask() {
+ final Task rootTask = createTask(mDefaultDisplay);
+ final Task leafTask = createTaskInRootTask(rootTask, 0 /* userId */);
+
+ final ActivityRecord activity = createActivityRecord(leafTask);
+
+ leafTask.getDisplayContent().setFocusedApp(activity);
+
+ assertTrue(leafTask.getTaskInfo().isFocused);
+ assertTrue(rootTask.getTaskInfo().isFocused);
+
+ leafTask.getDisplayContent().setFocusedApp(null);
+
+ assertFalse(leafTask.getTaskInfo().isFocused);
+ assertFalse(rootTask.getTaskInfo().isFocused);
+ }
+
+ @Test
public void testReturnsToHomeRootTask() throws Exception {
final Task task = createTask(1);
spyOn(task);
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 06cfd67..6e3cfac 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -107,7 +107,7 @@
if ((mMccStr != null && mMncStr == null) || (mMccStr == null && mMncStr != null)) {
AnomalyReporter.reportAnomaly(
- UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+ UUID.fromString("e257ae06-ac0a-44c0-ba63-823b9f07b3e4"),
"CellIdentity Missing Half of PLMN ID");
}
diff --git a/telephony/java/android/telephony/RadioAccessSpecifier.java b/telephony/java/android/telephony/RadioAccessSpecifier.java
index a403095..9511db6 100644
--- a/telephony/java/android/telephony/RadioAccessSpecifier.java
+++ b/telephony/java/android/telephony/RadioAccessSpecifier.java
@@ -161,9 +161,17 @@
}
@Override
- public int hashCode () {
+ public int hashCode() {
return ((mRadioAccessNetwork * 31)
+ (Arrays.hashCode(mBands) * 37)
+ (Arrays.hashCode(mChannels)) * 39);
}
+
+ @Override
+ public String toString() {
+ return "RadioAccessSpecifier[mRadioAccessNetwork="
+ + AccessNetworkConstants.AccessNetworkType.toString(mRadioAccessNetwork)
+ + ", mBands=" + Arrays.toString(mBands)
+ + ", mChannels=" + Arrays.toString(mChannels) + "]";
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 541573c..73551b9 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -12530,7 +12530,7 @@
Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
} catch (NullPointerException e) {
AnomalyReporter.reportAnomaly(
- UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+ UUID.fromString("e2bed88e-def9-476e-bd71-3e572a8de6d1"),
"getServiceStateForSubscriber " + subId + " NPE");
}
return null;
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 9f612e6..340ee72 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -542,6 +542,7 @@
int RIL_REQUEST_STOP_IMS_TRAFFIC = 236;
int RIL_REQUEST_SEND_ANBR_QUERY = 237;
int RIL_REQUEST_TRIGGER_EPS_FALLBACK = 238;
+ int RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED = 239;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
new file mode 100644
index 0000000..0f96634
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link TimeoutRecord}. */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class TimeoutRecordTest {
+
+ @Test
+ public void forBroadcastReceiver_returnsCorrectTimeoutRecord() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(ComponentName.createRelative("com.example.app", "ExampleClass"));
+
+ TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent);
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER);
+ assertEquals(record.mReason,
+ "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example"
+ + ".app/ExampleClass }");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forBroadcastReceiver_withTimeoutDurationMs_returnsCorrectTimeoutRecord() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(ComponentName.createRelative("com.example.app", "ExampleClass"));
+
+ TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent, 1000L);
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER);
+ assertEquals(record.mReason,
+ "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example"
+ + ".app/ExampleClass }, waited 1000ms");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forInputDispatchNoFocusedWindow_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forInputDispatchNoFocusedWindow("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW);
+ assertEquals(record.mReason,
+ "Test ANR reason");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forInputDispatchWindowUnresponsive_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forInputDispatchWindowUnresponsive("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forServiceExec_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forServiceExec("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.SERVICE_EXEC);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forServiceStartWithEndTime_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forServiceStartWithEndTime("Test ANR reason", 1000L);
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.SERVICE_START);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertEquals(record.mEndUptimeMillis, 1000L);
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forContentProvider_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forContentProvider("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.CONTENT_PROVIDER);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertFalse(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forApp_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forApp("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.APP_REGISTERED);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertFalse(record.mEndTakenBeforeLocks);
+ }
+}