Merge "Synchronize PluginInstance mutation methods" into main
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 9a1796f..c7797c7 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -64,3 +64,10 @@
bug: "296829976"
is_fixed_read_only: true
}
+
+flag {
+ name: "allow_resolver_sheet_for_private_space"
+ namespace: "profile_experiences"
+ description: "Add support for Private Space in resolver sheet"
+ bug: "307515485"
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 40e03db..60ad8e8 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -86,6 +86,8 @@
private static native long nativeCreate(String opPackageName);
private static native boolean nativeGetSensorAtIndex(long nativeInstance,
Sensor sensor, int index);
+ private static native boolean nativeGetDefaultDeviceSensorAtIndex(long nativeInstance,
+ Sensor sensor, int index);
private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
private static native void nativeGetRuntimeSensors(
long nativeInstance, int deviceId, List<Sensor> list);
@@ -162,11 +164,14 @@
// initialize the sensor list
for (int index = 0;; ++index) {
Sensor sensor = new Sensor();
- if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+ if (android.companion.virtual.flags.Flags.enableNativeVdm()) {
+ if (!nativeGetDefaultDeviceSensorAtIndex(mNativeInstance, sensor, index)) break;
+ } else {
+ if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+ }
mFullSensorsList.add(sensor);
mHandleToSensor.put(sensor.getHandle(), sensor);
}
-
}
/** @hide */
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 3ab889d..665d8d2 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -557,13 +557,15 @@
* on a particular SessionConfiguration.</p>
*
* @return List of CameraCharacteristic keys containing characterisitics specific to a session
- * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE.
+ * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE and
+ * SCALER_AVAILABLE_MAX_DIGITAL_ZOOM.
*/
@NonNull
@FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY)
public List<CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys() {
if (mAvailableSessionCharacteristicsKeys == null) {
- mAvailableSessionCharacteristicsKeys = Arrays.asList(CONTROL_ZOOM_RATIO_RANGE);
+ mAvailableSessionCharacteristicsKeys =
+ Arrays.asList(CONTROL_ZOOM_RATIO_RANGE, SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
}
return mAvailableSessionCharacteristicsKeys;
}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index ffd7212..64a62a9 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -751,6 +751,23 @@
*/
boolean blockScreenOn(Runnable unblocker);
+ /**
+ * Get the brightness levels used to determine automatic brightness based on lux levels.
+ * @param mode The auto-brightness mode
+ * (AutomaticBrightnessController.AutomaticBrightnessMode)
+ * @return The brightness levels for the specified mode. The values are between
+ * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+ */
+ float[] getAutoBrightnessLevels(int mode);
+
+ /**
+ * Get the lux levels used to determine automatic brightness.
+ * @param mode The auto-brightness mode
+ * (AutomaticBrightnessController.AutomaticBrightnessMode)
+ * @return The lux levels for the specified mode
+ */
+ float[] getAutoBrightnessLuxLevels(int mode);
+
/** Returns whether displayoffload supports the given display state. */
static boolean isSupportedOffloadState(int displayState) {
return Display.isSuspendedState(displayState);
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index bd087f9..41dee3a 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -21,10 +21,10 @@
package android.nfc.cardemulation;
import android.annotation.FlaggedApi;
-import android.compat.annotation.UnsupportedAppUsage;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -374,7 +374,7 @@
// Set uid
mUid = si.applicationInfo.uid;
- mCategoryOtherServiceEnabled = false; // support other category
+ mCategoryOtherServiceEnabled = true; // support other category
}
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index 17e0427..0d073cc 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -48,3 +48,10 @@
description: "Enable NFC Polling Loop Notifications ST shim"
bug: "294217286"
}
+
+flag {
+ name: "enable_tag_detection_broadcasts"
+ namespace: "nfc"
+ description: "Enable sending broadcasts to Wallet role holder when a tag enters/leaves the field."
+ bug: "306203494"
+}
diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java
index 49ce40b..df503e8 100644
--- a/core/java/android/os/ServiceSpecificException.java
+++ b/core/java/android/os/ServiceSpecificException.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
/**
* An exception specific to a service.
@@ -33,6 +34,7 @@
* @hide
*/
@SystemApi
+@RavenwoodKeepWholeClass
public class ServiceSpecificException extends RuntimeException {
public final int errorCode;
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index b56bef3..30524a1 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -50,3 +50,11 @@
description: "Collect sepolicy hash from sysfs"
bug: "308471499"
}
+
+flag {
+ name: "frp_enforcement"
+ namespace: "android_hw_security"
+ description: "This flag controls whether PDB enforces FRP"
+ bug: "290312729"
+ is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7534d29..7dcbbea 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -249,6 +249,7 @@
private UserHandle mCloneProfileUserHandle;
private UserHandle mTabOwnerUserHandleForLaunch;
+ private UserHandle mPrivateProfileUserHandle;
protected final LatencyTracker mLatencyTracker = getLatencyTracker();
@@ -441,6 +442,7 @@
mPersonalProfileUserHandle = fetchPersonalProfileUserHandle();
mWorkProfileUserHandle = fetchWorkProfileUserProfile();
mCloneProfileUserHandle = fetchCloneProfileUserHandle();
+ mPrivateProfileUserHandle = fetchPrivateProfileUserHandle();
mTabOwnerUserHandleForLaunch = fetchTabOwnerUserHandleForLaunch();
// The last argument of createResolverListAdapter is whether to do special handling
@@ -648,7 +650,8 @@
initialIntents,
rList,
filterLastUsed,
- /* userHandle */ getPersonalProfileUserHandle());
+ getPersonalProfileUserHandle());
+
QuietModeManager quietModeManager = createQuietModeManager();
return new ResolverMultiProfilePagerAdapter(
/* context */ this,
@@ -747,6 +750,9 @@
}
protected UserHandle getPersonalProfileUserHandle() {
+ if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()){
+ return mPrivateProfileUserHandle;
+ }
return mPersonalProfileUserHandle;
}
protected @Nullable UserHandle getWorkProfileUserHandle() {
@@ -761,6 +767,10 @@
return mTabOwnerUserHandleForLaunch;
}
+ protected UserHandle getPrivateProfileUserHandle() {
+ return mPrivateProfileUserHandle;
+ }
+
protected UserHandle fetchPersonalProfileUserHandle() {
// ActivityManager.getCurrentUser() refers to the current Foreground user. When clone/work
// profile is active, we always make the personal tab from the foreground user.
@@ -795,12 +805,28 @@
return mCloneProfileUserHandle;
}
+ protected @Nullable UserHandle fetchPrivateProfileUserHandle() {
+ mPrivateProfileUserHandle = null;
+ UserManager userManager = getSystemService(UserManager.class);
+ for (final UserInfo userInfo :
+ userManager.getProfiles(mPersonalProfileUserHandle.getIdentifier())) {
+ if (userInfo.isPrivateProfile()) {
+ mPrivateProfileUserHandle = userInfo.getUserHandle();
+ break;
+ }
+ }
+ return mPrivateProfileUserHandle;
+ }
+
private UserHandle fetchTabOwnerUserHandleForLaunch() {
- // If we are in work profile's process, return WorkProfile user as owner, otherwise we
- // always return PersonalProfile user as owner
- return UserHandle.of(UserHandle.myUserId()).equals(getWorkProfileUserHandle())
- ? getWorkProfileUserHandle()
- : getPersonalProfileUserHandle();
+ // If we are in work or private profile's process, return WorkProfile/PrivateProfile user
+ // as owner, otherwise we always return PersonalProfile user as owner
+ if (UserHandle.of(UserHandle.myUserId()).equals(getWorkProfileUserHandle())) {
+ return getWorkProfileUserHandle();
+ } else if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
+ return getPrivateProfileUserHandle();
+ }
+ return getPersonalProfileUserHandle();
}
private boolean hasWorkProfile() {
@@ -816,7 +842,15 @@
&& (UserHandle.myUserId() == getCloneProfileUserHandle().getIdentifier());
}
+ protected final boolean isLaunchedAsPrivateProfile() {
+ return getPrivateProfileUserHandle() != null
+ && (UserHandle.myUserId() == getPrivateProfileUserHandle().getIdentifier());
+ }
+
protected boolean shouldShowTabs() {
+ if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
+ return false;
+ }
return hasWorkProfile() && ENABLE_TABBED_VIEW;
}
@@ -2619,6 +2653,11 @@
return resolveInfo.userHandle;
}
+ private boolean privateSpaceEnabled() {
+ return mIsIntentPicker && android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.allowResolverSheetForPrivateSpace();
+ }
+
/**
* An a11y delegate that expands resolver drawer when gesture navigation reaches a partially
* invisible target in the list.
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index c89cfc4..5705b7e 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -37,6 +37,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
+import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -63,6 +64,8 @@
PackageMonitorCallback mPackageMonitorCallback;
+ private Executor mExecutor;
+
@UnsupportedAppUsage
public PackageMonitor() {
final boolean isCore = UserHandle.isCore(android.os.Process.myUid());
@@ -106,8 +109,8 @@
if (mPackageMonitorCallback == null) {
PackageManager pm = mRegisteredContext.getPackageManager();
if (pm != null) {
- mPackageMonitorCallback = new PackageMonitorCallback(this,
- new HandlerExecutor(mRegisteredHandler));
+ mExecutor = new HandlerExecutor(mRegisteredHandler);
+ mPackageMonitorCallback = new PackageMonitorCallback(this);
int userId = user != null ? user.getIdentifier() : mRegisteredContext.getUserId();
pm.registerPackageMonitorCallback(mPackageMonitorCallback, userId);
}
@@ -131,6 +134,7 @@
}
mPackageMonitorCallback = null;
mRegisteredContext = null;
+ mExecutor = null;
}
public void onBeginPackageChanges() {
@@ -362,6 +366,13 @@
doHandlePackageEvent(intent);
}
+
+ private void postHandlePackageEvent(Intent intent) {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> doHandlePackageEvent(intent));
+ }
+ }
+
/**
* Handle the package related event
* @param intent the intent that contains package related event information
@@ -516,13 +527,10 @@
}
private static final class PackageMonitorCallback extends IRemoteCallback.Stub {
+ private final WeakReference<PackageMonitor> mMonitorWeakReference;
- private final PackageMonitor mPackageMonitor;
- private final Executor mExecutor;
-
- PackageMonitorCallback(PackageMonitor monitor, Executor executor) {
- mPackageMonitor = monitor;
- mExecutor = executor;
+ PackageMonitorCallback(PackageMonitor monitor) {
+ mMonitorWeakReference = new WeakReference<>(monitor);
}
@Override
@@ -537,7 +545,10 @@
Log.w(TAG, "No intent is set for PackageMonitorCallback");
return;
}
- mExecutor.execute(() -> mPackageMonitor.doHandlePackageEvent(intent));
+ PackageMonitor monitor = mMonitorWeakReference.get();
+ if (monitor != null) {
+ monitor.postHandlePackageEvent(intent);
+ }
}
}
}
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 9c883d1..56ea52d 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -225,6 +225,19 @@
return translateNativeSensorToJavaSensor(env, sensor, *sensorList[index]) != NULL;
}
+static jboolean nativeGetDefaultDeviceSensorAtIndex(JNIEnv *env, jclass clazz, jlong sensorManager,
+ jobject sensor, jint index) {
+ SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+
+ Vector<Sensor> sensorList;
+ ssize_t count = mgr->getDefaultDeviceSensorList(sensorList);
+ if (ssize_t(index) >= count) {
+ return false;
+ }
+
+ return translateNativeSensorToJavaSensor(env, sensor, sensorList[index]) != NULL;
+}
+
static void
nativeGetDynamicSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jobject sensorList) {
@@ -539,6 +552,9 @@
{"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
(void *)nativeGetSensorAtIndex},
+ {"nativeGetDefaultDeviceSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
+ (void *)nativeGetDefaultDeviceSensorAtIndex},
+
{"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors},
{"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors},
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index b6813ff..b209c7c 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -30,6 +30,7 @@
import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo;
import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.CoreMatchers.allOf;
@@ -46,6 +47,7 @@
import android.net.Uri;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
import android.view.View;
import android.widget.RelativeLayout;
@@ -88,7 +90,8 @@
public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
new ActivityTestRule<>(ResolverWrapperActivity.class, false,
false);
-
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void cleanOverrideData() {
sOverrides.reset();
@@ -1156,6 +1159,97 @@
sOverrides.cloneProfileUserHandle)));
}
+ @Test
+ public void testTriggerFromPrivateProfile_withoutWorkProfile() throws RemoteException {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ markPrivateProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> privateResolvedComponentInfos =
+ createResolvedComponentsForTest(3, sOverrides.privateProfileUserHandle);
+ setupResolverControllers(privateResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+ assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+ onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+ onView(withId(R.id.button_always)).check(matches(not(isEnabled())));
+ for (ResolvedComponentInfo resolvedInfo : privateResolvedComponentInfos) {
+ assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+ sOverrides.privateProfileUserHandle);
+ }
+ }
+
+ @Test
+ public void testTriggerFromPrivateProfile_withWorkProfilePresent(){
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ ResolverActivity.ENABLE_TABBED_VIEW = false;
+ markPrivateProfileUserAvailable();
+ markWorkProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> privateResolvedComponentInfos =
+ createResolvedComponentsForTest(3, sOverrides.privateProfileUserHandle);
+ setupResolverControllers(privateResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+ onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+ assertEquals(activity.getMultiProfilePagerAdapterCount(), 1);
+ for (ResolvedComponentInfo resolvedInfo : privateResolvedComponentInfos) {
+ assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+ sOverrides.privateProfileUserHandle);
+ }
+ }
+
+ @Test
+ public void testPrivateProfile_triggerFromPrimaryUser_withWorkProfilePresent(){
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ markPrivateProfileUserAvailable();
+ markWorkProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ assertThat(activity.getAdapter().getCount(), is(2));
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
+ onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+ for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+ assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+ activity.getPersonalProfileUserHandle());
+ }
+ }
+
+ @Test
+ public void testPrivateProfile_triggerFromWorkProfile(){
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ markPrivateProfileUserAvailable();
+ markWorkProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ assertThat(activity.getAdapter().getCount(), is(2));
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
+ onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+ for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+ assertTrue(resolvedInfo.getResolveInfoAt(0).userHandle.equals(
+ activity.getPersonalProfileUserHandle()) || resolvedInfo.getResolveInfoAt(
+ 0).userHandle.equals(activity.getWorkProfileUserHandle()));
+ }
+ }
+
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -1237,6 +1331,10 @@
ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(11);
}
+ private void markPrivateProfileUserAvailable() {
+ ResolverWrapperActivity.sOverrides.privateProfileUserHandle = UserHandle.of(12);
+ }
+
private void setupResolverControllers(
List<ResolvedComponentInfo> personalResolvedComponentInfos,
List<ResolvedComponentInfo> workResolvedComponentInfos) {
@@ -1256,4 +1354,13 @@
eq(UserHandle.SYSTEM)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
}
+
+ private void setupResolverControllers(
+ List<ResolvedComponentInfo> resolvedComponentInfos) {
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(resolvedComponentInfos));
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index e193de0..862cbd5 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -88,6 +88,10 @@
return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1));
}
+ int getMultiProfilePagerAdapterCount(){
+ return mMultiProfilePagerAdapter.getCount();
+ }
+
@Override
public boolean isVoiceInteraction() {
if (sOverrides.isVoiceInteraction != null) {
@@ -144,6 +148,11 @@
}
@Override
+ protected UserHandle getPrivateProfileUserHandle() {
+ return sOverrides.privateProfileUserHandle;
+ }
+
+ @Override
protected UserHandle getTabOwnerUserHandleForLaunch() {
if (sOverrides.tabOwnerUserHandleForLaunch == null) {
return super.getTabOwnerUserHandleForLaunch();
@@ -176,6 +185,7 @@
public Boolean isVoiceInteraction;
public UserHandle workProfileUserHandle;
public UserHandle cloneProfileUserHandle;
+ public UserHandle privateProfileUserHandle;
public UserHandle tabOwnerUserHandleForLaunch;
public Integer myUserId;
public boolean hasCrossProfileIntents;
@@ -191,6 +201,7 @@
workResolverListController = mock(ResolverListController.class);
workProfileUserHandle = null;
cloneProfileUserHandle = null;
+ privateProfileUserHandle = null;
tabOwnerUserHandleForLaunch = null;
myUserId = null;
hasCrossProfileIntents = true;
diff --git a/core/tests/overlaytests/Android.mk b/core/tests/overlaytests/Android.mk
deleted file mode 100644
index b798d87..0000000
--- a/core/tests/overlaytests/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2017 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.
-
-include $(call all-subdir-makefiles)
diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk
deleted file mode 100644
index d58d939..0000000
--- a/core/tests/overlaytests/host/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-# Include to build test-apps.
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/core/tests/overlaytests/host/test-apps/Android.mk b/core/tests/overlaytests/host/test-apps/Android.mk
deleted file mode 100644
index 5c7187e..0000000
--- a/core/tests/overlaytests/host/test-apps/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright (C) 2018 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.
-
-include $(call all-subdir-makefiles)
-
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
new file mode 100644
index 0000000..bb7d63e
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
@@ -0,0 +1,57 @@
+// Copyright (C) 2018 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.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_NonPlatformSignatureOverlay",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ aaptflags: [
+ "--custom-package com.android.server.om.hosttest.signature_overlay_bad",
+ ],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_PlatformSignatureStaticOverlay",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ manifest: "static/AndroidManifest.xml",
+ aaptflags: [
+ "--custom-package com.android.server.om.hosttest.signature_overlay_static",
+ ],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_PlatformSignatureOverlay",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.signature_overlay_v1",
+ "--version-code",
+ "1",
+ "--version-name",
+ "v1",
+ ],
+}
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
deleted file mode 100644
index b453cde9..0000000
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-my_package_prefix := com.android.server.om.hosttest.signature_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_NonPlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_MANIFEST_FILE := static/AndroidManifest.xml
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
new file mode 100644
index 0000000..ee0c0e5
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
@@ -0,0 +1,97 @@
+// Copyright (C) 2018 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.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_UpdateOverlay",
+ srcs: ["src/**/*.java"],
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ static_libs: ["androidx.test.rules"],
+ aaptflags: ["--no-resource-removal"],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_FrameworkOverlayV1",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.framework_overlay_v1",
+ "--version-code",
+ "1",
+ "--version-name",
+ "v1",
+ ],
+ resource_dirs: ["framework/v1/res"],
+ manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_FrameworkOverlayV2",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.framework_overlay_v2",
+ "--version-code",
+ "2",
+ "--version-name",
+ "v2",
+ ],
+ resource_dirs: ["framework/v2/res"],
+ manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_AppOverlayV1",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.app_overlay_v1",
+ "--version-code",
+ "1",
+ "--version-name",
+ "v1",
+ ],
+ resource_dirs: ["app/v1/res"],
+ manifest: "app/v1/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_AppOverlayV2",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.app_overlay_v2",
+ "--version-code",
+ "2",
+ "--version-name",
+ "v2",
+ ],
+ resource_dirs: ["app/v2/res"],
+ manifest: "app/v2/AndroidManifest.xml",
+}
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
deleted file mode 100644
index 77fc887..0000000
--- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_FLAGS := --no-resource-removal
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.framework_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v1/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v2/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.app_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res
-LOCAL_MANIFEST_FILE := app/v1/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res
-LOCAL_MANIFEST_FILE := app/v2/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index c4530f6..13d38d2 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -347,7 +347,9 @@
<!-- Allow IMS service entitlement app to schedule jobs to run when app in background. -->
<allow-in-power-save-except-idle package="com.android.imsserviceentitlement" />
- <!-- Allow device lock controller app to schedule jobs and alarms when app in background,
- otherwise, it may not be able to enforce provision for managed devices. -->
+ <!-- Allow device lock controller app to schedule jobs and alarms, and have network access
+ when app in background; otherwise, it may not be able to enforce provision for managed
+ devices. -->
<allow-in-power-save package="com.android.devicelockcontroller" />
+ <allow-in-data-usage-save package="com.android.devicelockcontroller" />
</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c6f920f..b9efe65 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -575,6 +575,7 @@
<permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
<permission name="android.permission.BIND_WALLPAPER"/>
<permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
+ <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/>
</privapp-permissions>
<privapp-permissions package="com.android.dynsystem">
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index 85bf2c1..e4f793c 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -30,8 +30,8 @@
android:orientation="horizontal"
android:clickable="true"
android:focusable="true"
- android:paddingStart="16dp">
-
+ android:paddingStart="6dp"
+ android:paddingEnd="8dp">
<ImageView
android:id="@+id/application_icon"
android:layout_width="@dimen/desktop_mode_caption_icon_radius"
@@ -43,7 +43,7 @@
android:id="@+id/application_name"
android:layout_width="0dp"
android:layout_height="20dp"
- android:minWidth="80dp"
+ android:maxWidth="86dp"
android:textAppearance="@android:style/TextAppearance.Material.Title"
android:textSize="14sp"
android:textFontWeight="500"
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8f9de61..0a40cea 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -413,6 +413,25 @@
<!-- Height of desktop mode caption for fullscreen tasks. -->
<dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
+ <!-- Required empty space to be visible for partially offscreen tasks. -->
+ <dimen name="freeform_required_visible_empty_space_in_header">48dp</dimen>
+
+ <!-- Required empty space to be visible for partially offscreen tasks on a smaller screen. -->
+ <dimen name="small_screen_required_visible_empty_space_in_header">12dp</dimen>
+
+ <!-- 32dp width back button + 10dp margin -->
+ <dimen name="caption_left_buttons_width">32dp</dimen>
+
+ <!-- (32 dp buttons + 10dp margins) * 3 buttons-->
+ <dimen name="caption_right_buttons_width">126dp</dimen>
+
+ <!-- 2 buttons * 48dp button size. -->
+ <dimen name="desktop_mode_right_edge_buttons_width">96dp</dimen>
+
+ <!-- 22dp padding + 24dp app icon + 16dp expand button.
+ Text varies in size, we will calculate that width separately. -->
+ <dimen name="desktop_mode_app_details_width_minus_text">62dp</dimen>
+
<!-- The width of the maximize menu in desktop mode. -->
<dimen name="desktop_mode_maximize_menu_width">287dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 473deba..af31f5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.wm.shell.transition.Transitions.TransitionObserver;
@@ -35,7 +36,8 @@
/**
* The {@link TransitionObserver} that observes for transitions involving the home
- * activity. It reports transitions to the caller via {@link IHomeTransitionListener}.
+ * activity on the {@link android.view.Display#DEFAULT_DISPLAY} only.
+ * It reports transitions to the caller via {@link IHomeTransitionListener}.
*/
public class HomeTransitionObserver implements TransitionObserver,
RemoteCallable<HomeTransitionObserver> {
@@ -58,6 +60,7 @@
for (TransitionInfo.Change change : info.getChanges()) {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo == null
+ || taskInfo.displayId != DEFAULT_DISPLAY
|| taskInfo.taskId == -1
|| !taskInfo.isRunning) {
continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
index 18716c6..72fba3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -20,12 +20,13 @@
import android.window.TransitionFilter;
/**
- * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks.
+ * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks
+ * on the default display.
*/
-interface IHomeTransitionListener {
+oneway interface IHomeTransitionListener {
/**
- * Called when a transition changes the visibility of the home activity.
+ * Called when a transition changes the visibility of the home activity on the default display.
*/
void onHomeVisibilityChanged(in boolean isVisible);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index c12ac8b..6e7d11d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
+import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
@@ -34,6 +35,7 @@
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
/**
@@ -84,6 +86,69 @@
mDragPositioningCallback = dragPositioningCallback;
}
+ @Override
+ Rect calculateValidDragArea() {
+ final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.caption_left_buttons_width);
+
+ // On a smaller screen, don't require as much empty space on screen, as offscreen
+ // drags will be restricted too much.
+ final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.taskId)
+ .getResources().getConfiguration().smallestScreenWidthDp >= 600
+ ? R.dimen.freeform_required_visible_empty_space_in_header :
+ R.dimen.small_screen_required_visible_empty_space_in_header;
+ final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+ requiredEmptySpaceId);
+
+ final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.caption_right_buttons_width);
+ final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ final int displayWidth = layout.width();
+ final Rect stableBounds = new Rect();
+ layout.getStableBounds(stableBounds);
+ return new Rect(
+ determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth),
+ stableBounds.top,
+ determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace, taskWidth,
+ displayWidth),
+ determineMaxY(requiredEmptySpace, stableBounds));
+ }
+
+
+ /**
+ * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth) {
+ // Do not let apps with < 48dp empty header space go off the left edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return 0;
+ }
+ return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+ }
+
+ /**
+ * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth, int displayWidth) {
+ // Do not let apps with < 48dp empty header space go off the right edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return displayWidth - taskWidth;
+ }
+ return displayWidth - requiredEmptySpace - leftButtonsWidth;
+ }
+
+ /**
+ * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+ return stableBounds.bottom - requiredEmptySpace;
+ }
+
+
void setDragDetector(DragDetector dragDetector) {
mDragDetector = dragDetector;
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 6ec91e0..3b6be8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -443,6 +443,66 @@
}
/**
+ * Determine valid drag area for this task based on elements in the app chip.
+ */
+ @Override
+ Rect calculateValidDragArea() {
+ final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
+ mWindowDecorViewHolder).getAppNameTextWidth();
+ final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.desktop_mode_app_details_width_minus_text) + appTextWidth;
+ final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.freeform_required_visible_empty_space_in_header);
+ final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.desktop_mode_right_edge_buttons_width);
+ final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ final int displayWidth = layout.width();
+ final Rect stableBounds = new Rect();
+ layout.getStableBounds(stableBounds);
+ return new Rect(
+ determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth),
+ stableBounds.top,
+ determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth, displayWidth),
+ determineMaxY(requiredEmptySpace, stableBounds));
+ }
+
+
+ /**
+ * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth) {
+ // Do not let apps with < 48dp empty header space go off the left edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return 0;
+ }
+ return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+ }
+
+ /**
+ * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth, int displayWidth) {
+ // Do not let apps with < 48dp empty header space go off the right edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return displayWidth - taskWidth;
+ }
+ return displayWidth - requiredEmptySpace - leftButtonsWidth;
+ }
+
+ /**
+ * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+ return stableBounds.bottom - requiredEmptySpace;
+ }
+
+
+ /**
* Create and display maximize menu window
*/
void createMaximizeMenu() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index cb0a6c7..677c7f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -162,18 +162,29 @@
/**
* Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
- * the bounds are outside of the stable bounds, they are shifted to place task at the top of the
- * stable bounds.
+ * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
+ * valid drag area.
*/
- static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds,
- PointF repositionStartPoint, float x, float y) {
+ static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+ PointF repositionStartPoint, float x, float y, Rect validDragArea) {
updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
x, y);
+ snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
+ }
- // If task is outside of stable bounds (in the status bar area), shift the task down.
- if (stableBounds.top > repositionTaskBounds.top) {
- final int yShift = stableBounds.top - repositionTaskBounds.top;
- repositionTaskBounds.offset(0, yShift);
+ private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+ // If we were never supplied a valid drag area, do not restrict movement.
+ // Otherwise, we restrict deltas to keep task position inside the Rect.
+ if (validDragArea.width() == 0) return;
+ if (repositionTaskBounds.left < validDragArea.left) {
+ repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+ } else if (repositionTaskBounds.left > validDragArea.right) {
+ repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+ }
+ if (repositionTaskBounds.top < validDragArea.top) {
+ repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+ } else if (repositionTaskBounds.top > validDragArea.bottom) {
+ repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 3a1ea0e..5d006fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -136,7 +136,8 @@
y)) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+ mWindowDecoration.calculateValidDragArea());
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTaskOrganizer.applyTransaction(wct);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 4b55a0c..4363558 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -152,7 +152,8 @@
mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
y)) {
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+ mDesktopWindowDecoration.calculateValidDragArea());
DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 634b755..ee0e31e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -21,6 +21,7 @@
import static android.view.WindowInsets.Type.statusBars;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -178,6 +179,13 @@
*/
abstract void relayout(RunningTaskInfo taskInfo);
+ /**
+ * Used by the {@link DragPositioningCallback} associated with the implementing class to
+ * enforce drags ending in a valid position. A null result means no restriction.
+ */
+ @Nullable
+ abstract Rect calculateValidDragArea();
+
void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
RelayoutResult<T> outResult) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 589a813..144373f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -42,6 +42,8 @@
private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
+ val appNameTextWidth: Int
+ get() = appNameTextView.width
init {
captionView.setOnTouchListener(onCaptionTouchListener)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index 5c0e04a..e60be71 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -181,6 +181,26 @@
}
@Test
+ fun testDragEndSnapsTaskBoundsWhenOutsideValidDragArea() {
+ val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect(STARTING_BOUNDS)
+ val validDragArea = Rect(DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100)
+
+ DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS,
+ startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(),
+ validDragArea)
+ assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
+ assertThat(repositionTaskBounds.right)
+ .isEqualTo(validDragArea.left + STARTING_BOUNDS.width())
+ assertThat(repositionTaskBounds.bottom)
+ .isEqualTo(validDragArea.bottom + STARTING_BOUNDS.height())
+ }
+
+ @Test
fun testChangeBounds_toDisallowedBounds_freezesAtLimit() {
var hasMoved = false
val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index add78b2..2ce49cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -103,6 +103,7 @@
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
configuration.windowConfiguration.displayRotation = ROTATION_90
}
+ `when`(mockWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -660,6 +661,38 @@
}
@Test
+ fun testDragResize_drag_taskPositionedInValidDragArea() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = VALID_DRAG_AREA.left - 500f
+ val newY = VALID_DRAG_AREA.bottom + 500f
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ VALID_DRAG_AREA.bottom &&
+ change.configuration.windowConfiguration.bounds.left ==
+ VALID_DRAG_AREA.left
+ }
+ })
+ }
+
+ @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
@@ -761,5 +794,11 @@
DISPLAY_BOUNDS.bottom,
DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
)
+ private val VALID_DRAG_AREA = Rect(
+ DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS_LANDSCAPE.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index a70ebf1..a759b53 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -118,6 +118,7 @@
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
configuration.windowConfiguration.displayRotation = ROTATION_90
}
+ `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockDesktopWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -379,6 +380,38 @@
}
@Test
+ fun testDragResize_drag_taskPositionedInValidDragArea() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = VALID_DRAG_AREA.left - 500f
+ val newY = VALID_DRAG_AREA.bottom + 500f
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ VALID_DRAG_AREA.bottom &&
+ change.configuration.windowConfiguration.bounds.left ==
+ VALID_DRAG_AREA.left
+ }
+ })
+ }
+
+ @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
@@ -470,5 +503,11 @@
DISPLAY_BOUNDS.bottom,
DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
)
+ private val VALID_DRAG_AREA = Rect(
+ DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS_LANDSCAPE.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 8e42f74..fe508e2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -702,6 +702,11 @@
relayout(taskInfo, false /* applyStartTransactionOnDraw */);
}
+ @Override
+ Rect calculateValidDragArea() {
+ return null;
+ }
+
void relayout(ActivityManager.RunningTaskInfo taskInfo,
boolean applyStartTransactionOnDraw) {
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
diff --git a/location/java/android/location/LocationResult.java b/location/java/android/location/LocationResult.java
index 8423000..67f4775 100644
--- a/location/java/android/location/LocationResult.java
+++ b/location/java/android/location/LocationResult.java
@@ -19,8 +19,11 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.location.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -37,6 +40,23 @@
* @hide
*/
public final class LocationResult implements Parcelable {
+ private static final String TAG = "LocationResult";
+
+ // maximum reasonable accuracy, somewhat arbitrarily chosen. this is a very high upper limit, it
+ // could likely be lower, but we only want to throw out really absurd values.
+ private static final float MAX_ACCURACY_M = 1000000;
+
+ // maximum reasonable speed we expect a device to travel at is currently mach 1 (top speed of
+ // current fastest private jet). Higher speed than the value is considered as a malfunction
+ // than a correct reading.
+ private static final float MAX_SPEED_MPS = 343;
+
+ /** Exception representing an invalid location within a {@link LocationResult}. */
+ public static class BadLocationException extends Exception {
+ public BadLocationException(String message) {
+ super(message);
+ }
+ }
/**
* Creates a new LocationResult from the given locations, making a copy of each location.
@@ -101,18 +121,60 @@
*
* @hide
*/
- public @NonNull LocationResult validate() {
+ public @NonNull LocationResult validate() throws BadLocationException {
long prevElapsedRealtimeNs = 0;
final int size = mLocations.size();
for (int i = 0; i < size; ++i) {
Location location = mLocations.get(i);
- if (!location.isComplete()) {
- throw new IllegalArgumentException(
- "incomplete location at index " + i + ": " + mLocations);
- }
- if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
- throw new IllegalArgumentException(
- "incorrectly ordered location at index " + i + ": " + mLocations);
+ if (Flags.locationValidation()) {
+ if (location.getLatitude() < -90.0
+ || location.getLatitude() > 90.0
+ || location.getLongitude() < -180.0
+ || location.getLongitude() > 180.0
+ || Double.isNaN(location.getLatitude())
+ || Double.isNaN(location.getLongitude())) {
+ throw new BadLocationException("location must have valid lat/lng");
+ }
+ if (!location.hasAccuracy()) {
+ throw new BadLocationException("location must have accuracy");
+ }
+ if (location.getAccuracy() < 0 || location.getAccuracy() > MAX_ACCURACY_M) {
+ throw new BadLocationException("location must have reasonable accuracy");
+ }
+ if (location.getTime() < 0) {
+ throw new BadLocationException("location must have valid time");
+ }
+ if (prevElapsedRealtimeNs > location.getElapsedRealtimeNanos()) {
+ throw new BadLocationException(
+ "location must have valid monotonically increasing realtime");
+ }
+ if (location.getElapsedRealtimeNanos()
+ > SystemClock.elapsedRealtimeNanos()) {
+ throw new BadLocationException("location must not have realtime in the future");
+ }
+ if (!location.isMock()) {
+ if (location.getProvider() == null) {
+ throw new BadLocationException("location must have valid provider");
+ }
+ if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+ throw new BadLocationException("location must not be at 0,0");
+ }
+ }
+
+ if (location.hasSpeed() && (location.getSpeed() < 0
+ || location.getSpeed() > MAX_SPEED_MPS)) {
+ Log.w(TAG, "removed bad location speed: " + location.getSpeed());
+ location.removeSpeed();
+ }
+ } else {
+ if (!location.isComplete()) {
+ throw new IllegalArgumentException(
+ "incomplete location at index " + i + ": " + mLocations);
+ }
+ if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
+ throw new IllegalArgumentException(
+ "incorrectly ordered location at index " + i + ": " + mLocations);
+ }
}
prevElapsedRealtimeNs = location.getElapsedRealtimeNanos();
}
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index f4b1056..a8464d3 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -27,3 +27,10 @@
description: "Flag for releasing SUPL connection on timeout"
bug: "315024652"
}
+
+flag {
+ name: "location_validation"
+ namespace: "location"
+ description: "Flag for location validation"
+ bug: "314328533"
+}
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 3da52cc..7f95886 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -77,3 +77,9 @@
bug: "279555229"
}
+flag {
+ name: "enable_notifying_activity_manager_with_media_session_status_change"
+ namespace: "media_solutions"
+ description: "Notify ActivityManager with the changes in playback state of the media session."
+ bug: "295518668"
+}
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 7891ee6..60497fe 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -524,6 +524,28 @@
return false;
}
+ /**
+ * Returns whether the service holding the media session should run in the foreground when the
+ * media session has this playback state or not.
+ *
+ * @hide
+ */
+ public boolean shouldAllowServiceToRunInForeground() {
+ switch (mState) {
+ case PlaybackState.STATE_PLAYING:
+ case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackState.STATE_REWINDING:
+ case PlaybackState.STATE_BUFFERING:
+ case PlaybackState.STATE_CONNECTING:
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
+ return true;
+ default:
+ return false;
+ }
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<PlaybackState> CREATOR =
new Parcelable.Creator<PlaybackState>() {
@Override
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 96e95fd..3fcb871 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -693,6 +693,8 @@
mpuSequenceNumber, isPesPrivateData, sc,
audioDescriptor.get(), presentationsJObj.get()));
+ // Protect mFilterClient from being set to null.
+ android::Mutex::Autolock autoLock(mLock);
uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
(dataLength > 0 && (dataLength + offset) < avSharedMemSize)) {
@@ -939,38 +941,52 @@
}
}
}
- ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
- if (!env->IsSameObject(filter.get(), nullptr)) {
- jmethodID methodID = gFields.onFilterEventID;
- if (mSharedFilter) {
- methodID = gFields.onSharedFilterEventID;
+
+ ScopedLocalRef<jobject> filter(env);
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (env->IsSameObject(mFilterObj, nullptr)) {
+ ALOGE("FilterClientCallbackImpl::onFilterEvent:"
+ "Filter object has been freed. Ignoring callback.");
+ return;
+ } else {
+ filter.reset(env->NewLocalRef(mFilterObj));
}
- env->CallVoidMethod(filter.get(), methodID, array.get());
- } else {
- ALOGE("FilterClientCallbackImpl::onFilterEvent:"
- "Filter object has been freed. Ignoring callback.");
}
+
+ jmethodID methodID = gFields.onFilterEventID;
+ if (mSharedFilter) {
+ methodID = gFields.onSharedFilterEventID;
+ }
+ env->CallVoidMethod(filter.get(), methodID, array.get());
}
void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
ALOGV("FilterClientCallbackImpl::onFilterStatus");
JNIEnv *env = AndroidRuntime::getJNIEnv();
- ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
- if (!env->IsSameObject(filter.get(), nullptr)) {
- jmethodID methodID = gFields.onFilterStatusID;
- if (mSharedFilter) {
- methodID = gFields.onSharedFilterStatusID;
+ ScopedLocalRef<jobject> filter(env);
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (env->IsSameObject(filter.get(), nullptr)) {
+ ALOGE("FilterClientCallbackImpl::onFilterStatus:"
+ "Filter object has been freed. Ignoring callback.");
+ return;
+ } else {
+ filter.reset(env->NewLocalRef(mFilterObj));
}
- env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
- } else {
- ALOGE("FilterClientCallbackImpl::onFilterStatus:"
- "Filter object has been freed. Ignoring callback.");
}
+
+ jmethodID methodID = gFields.onFilterStatusID;
+ if (mSharedFilter) {
+ methodID = gFields.onSharedFilterStatusID;
+ }
+ env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
}
void FilterClientCallbackImpl::setFilter(jweak filterObj, sp<FilterClient> filterClient) {
ALOGV("FilterClientCallbackImpl::setFilter");
// Java Object
+ android::Mutex::Autolock autoLock(mLock);
mFilterObj = filterObj;
mFilterClient = filterClient;
mSharedFilter = false;
@@ -979,6 +995,7 @@
void FilterClientCallbackImpl::setSharedFilter(jweak filterObj, sp<FilterClient> filterClient) {
ALOGV("FilterClientCallbackImpl::setFilter");
// Java Object
+ android::Mutex::Autolock autoLock(mLock);
mFilterObj = filterObj;
mFilterClient = filterClient;
mSharedFilter = true;
@@ -1047,11 +1064,14 @@
FilterClientCallbackImpl::~FilterClientCallbackImpl() {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- if (mFilterObj != nullptr) {
- env->DeleteWeakGlobalRef(mFilterObj);
- mFilterObj = nullptr;
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (mFilterObj != nullptr) {
+ env->DeleteWeakGlobalRef(mFilterObj);
+ mFilterObj = nullptr;
+ }
+ mFilterClient = nullptr;
}
- mFilterClient = nullptr;
env->DeleteGlobalRef(mEventClass);
env->DeleteGlobalRef(mSectionEventClass);
env->DeleteGlobalRef(mMediaEventClass);
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 01c998d..3de3ab9 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -136,6 +136,7 @@
private:
jweak mFilterObj;
sp<FilterClient> mFilterClient;
+ android::Mutex mLock;
jclass mEventClass;
jclass mSectionEventClass;
jclass mMediaEventClass;
diff --git a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
index db8ebb4..1ac5db6 100644
--- a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
@@ -18,54 +18,63 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- style="@style/ScrollViewStyle">
+ style="@style/ScrollViewStyle"
+ android:importantForAccessibility="no">
<LinearLayout
- android:id="@+id/data_transfer_confirmation"
- style="@style/ContainerLayout">
-
- <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
-
- <ImageView
- android:id="@+id/header_icon"
- android:layout_width="match_parent"
- android:layout_height="32dp"
- android:gravity="center"
- android:layout_marginTop="18dp"
- android:src="@drawable/ic_warning"
- android:contentDescription="@null" />
-
- <LinearLayout style="@style/Description">
-
- <TextView
- android:id="@+id/title"
- style="@style/DescriptionTitle" />
-
- <TextView
- android:id="@+id/summary"
- style="@style/DescriptionSummary" />
-
- </LinearLayout>
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:baselineAligned="false"
+ android:importantForAccessibility="no">
<LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="vertical"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="18dp">
+ android:id="@+id/data_transfer_confirmation"
+ style="@style/ContainerLayout">
- <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+ <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
- <Button
- android:id="@+id/btn_positive"
- style="@style/PositiveButton"
- android:text="@string/consent_yes" />
+ <ImageView
+ android:id="@+id/header_icon"
+ android:layout_width="match_parent"
+ android:layout_height="32dp"
+ android:gravity="center"
+ android:layout_marginTop="18dp"
+ android:src="@drawable/ic_warning"
+ android:contentDescription="@null" />
- <Button
- android:id="@+id/btn_negative"
- style="@style/NegativeButton"
- android:text="@string/consent_no" />
+ <LinearLayout style="@style/Description">
+
+ <TextView
+ android:id="@+id/title"
+ style="@style/DescriptionTitle" />
+
+ <TextView
+ android:id="@+id/summary"
+ style="@style/DescriptionSummary" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="18dp">
+
+ <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+
+ <Button
+ android:id="@+id/btn_positive"
+ style="@style/PositiveButton"
+ android:text="@string/consent_yes" />
+
+ <Button
+ android:id="@+id/btn_negative"
+ style="@style/NegativeButton"
+ android:text="@string/consent_no" />
+
+ </LinearLayout>
</LinearLayout>
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 933be11..6a4bb21 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -134,8 +134,7 @@
@UsesReflection({
// As the runtime class name is used to generate the returned name, and the returned
// name may be used used with reflection, generate the necessary keep rules.
- @KeepTarget(classConstant = LocalTransport.class),
- @KeepTarget(extendsClassConstant = LocalTransport.class)
+ @KeepTarget(instanceOfClassConstant = LocalTransport.class)
})
@Override
public String name() {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
new file mode 100644
index 0000000..472484a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package com.android.systemui.keyguard.ui.composable
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.pointer.pointerInput
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+
+/** Container for lockscreen content that handles long-press to bring up the settings menu. */
+@Composable
+fun LockscreenLongPress(
+ viewModel: KeyguardLongPressViewModel,
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.(onSettingsMenuPlaces: (coordinates: Rect?) -> Unit) -> Unit,
+) {
+ val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
+ val (settingsMenuBounds, setSettingsMenuBounds) = remember { mutableStateOf<Rect?>(null) }
+ val interactionSource = remember { MutableInteractionSource() }
+
+ Box(
+ modifier =
+ modifier
+ .combinedClickable(
+ enabled = isEnabled,
+ onLongClick = viewModel::onLongPress,
+ onClick = {},
+ interactionSource = interactionSource,
+ // Passing null for the indication removes the ripple effect.
+ indication = null,
+ )
+ .pointerInput(settingsMenuBounds) {
+ awaitEachGesture {
+ val pointerInputChange = awaitFirstDown()
+ if (settingsMenuBounds?.contains(pointerInputChange.position) == false) {
+ viewModel.onTouchedOutside()
+ }
+ }
+ },
+ ) {
+ content(setSettingsMenuBounds)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 93f31ec..67a6820 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -14,36 +14,15 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalFoundationApi::class)
-
package com.android.systemui.keyguard.ui.composable
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.toComposeRect
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.view.isVisible
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.qualifiers.KeyguardRootView
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
-import com.android.systemui.notifications.ui.composable.NotificationStack
-import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
@@ -68,8 +47,8 @@
constructor(
@Application private val applicationScope: CoroutineScope,
private val viewModel: LockscreenSceneViewModel,
- @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
private val lockscreenContent: Lazy<LockscreenContent>,
+ private val viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
) : ComposableScene {
override val key = SceneKey.Lockscreen
@@ -93,9 +72,8 @@
modifier: Modifier,
) {
LockscreenScene(
- viewProvider = viewProvider,
- viewModel = viewModel,
lockscreenContent = lockscreenContent,
+ viewBasedLockscreenContent = viewBasedLockscreenContent,
modifier = modifier,
)
}
@@ -116,98 +94,21 @@
@Composable
private fun SceneScope.LockscreenScene(
- viewProvider: () -> View,
- viewModel: LockscreenSceneViewModel,
lockscreenContent: Lazy<LockscreenContent>,
+ viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
modifier: Modifier = Modifier,
) {
- fun findSettingsMenu(): View {
- return viewProvider().requireViewById(R.id.keyguard_settings_button)
- }
-
- Box(
- modifier = modifier,
- ) {
- LongPressSurface(
- viewModel = viewModel.longPress,
- isSettingsMenuVisible = { findSettingsMenu().isVisible },
- settingsMenuBounds = {
- val bounds = android.graphics.Rect()
- findSettingsMenu().getHitRect(bounds)
- bounds.toComposeRect()
- },
- modifier = Modifier.fillMaxSize(),
- )
-
- if (UseLockscreenContent) {
- lockscreenContent
- .get()
- .Content(
- modifier = Modifier.fillMaxSize(),
- )
- } else {
- AndroidView(
- factory = { _ ->
- val keyguardRootView = viewProvider()
- // Remove the KeyguardRootView from any parent it might already have in legacy
- // code just in case (a view can't have two parents).
- (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
- keyguardRootView
- },
- modifier = Modifier.fillMaxSize(),
+ if (UseLockscreenContent) {
+ lockscreenContent
+ .get()
+ .Content(
+ modifier = modifier.fillMaxSize(),
+ )
+ } else {
+ with(viewBasedLockscreenContent.get()) {
+ Content(
+ modifier = modifier.fillMaxSize(),
)
}
-
- val notificationStackPosition by viewModel.keyguardRoot.notificationBounds.collectAsState()
-
- Layout(
- modifier = Modifier.fillMaxSize(),
- content = {
- NotificationStack(
- viewModel = viewModel.notifications,
- isScrimVisible = false,
- )
- }
- ) { measurables, constraints ->
- check(measurables.size == 1)
- val height = notificationStackPosition.height.toInt()
- val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
- val placeable = measurables[0].measure(childConstraints)
- layout(constraints.maxWidth, constraints.maxHeight) {
- val start = (constraints.maxWidth - placeable.measuredWidth) / 2
- placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
- }
- }
}
}
-
-@Composable
-private fun LongPressSurface(
- viewModel: KeyguardLongPressViewModel,
- isSettingsMenuVisible: () -> Boolean,
- settingsMenuBounds: () -> Rect,
- modifier: Modifier = Modifier,
-) {
- val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
-
- Box(
- modifier =
- modifier
- .combinedClickable(
- enabled = isEnabled,
- onLongClick = viewModel::onLongPress,
- onClick = {},
- )
- .pointerInput(Unit) {
- awaitEachGesture {
- val pointerInputChange = awaitFirstDown()
- if (
- isSettingsMenuVisible() &&
- !settingsMenuBounds().contains(pointerInputChange.position)
- ) {
- viewModel.onTouchedOutside()
- }
- }
- },
- )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
new file mode 100644
index 0000000..976161b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toComposeRect
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.qualifiers.KeyguardRootView
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/**
+ * Renders the content of the lockscreen.
+ *
+ * This is different from [LockscreenContent] (which is pure compose) and uses a view-based
+ * implementation of the lockscreen scene content that relies on [KeyguardRootView].
+ *
+ * TODO(b/316211368): remove this once [LockscreenContent] is feature complete.
+ */
+class ViewBasedLockscreenContent
+@Inject
+constructor(
+ private val viewModel: LockscreenSceneViewModel,
+ @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
+) {
+ @Composable
+ fun SceneScope.Content(
+ modifier: Modifier = Modifier,
+ ) {
+ fun findSettingsMenu(): View {
+ return viewProvider().requireViewById(R.id.keyguard_settings_button)
+ }
+
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { onSettingsMenuPlaced ->
+ AndroidView(
+ factory = { _ ->
+ val keyguardRootView = viewProvider()
+ // Remove the KeyguardRootView from any parent it might already have in legacy
+ // code just in case (a view can't have two parents).
+ (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
+ keyguardRootView
+ },
+ modifier = Modifier.fillMaxSize(),
+ )
+
+ val notificationStackPosition by
+ viewModel.keyguardRoot.notificationBounds.collectAsState()
+
+ Layout(
+ modifier =
+ Modifier.fillMaxSize().onPlaced {
+ val settingsMenuView = findSettingsMenu()
+ onSettingsMenuPlaced(
+ if (settingsMenuView.isVisible) {
+ val bounds = Rect()
+ settingsMenuView.getHitRect(bounds)
+ bounds.toComposeRect()
+ } else {
+ null
+ }
+ )
+ },
+ content = {
+ NotificationStack(
+ viewModel = viewModel.notifications,
+ isScrimVisible = false,
+ )
+ }
+ ) { measurables, constraints ->
+ check(measurables.size == 1)
+ val height = notificationStackPosition.height.toInt()
+ val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
+ val placeable = measurables[0].measure(childConstraints)
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ val start = (constraints.maxWidth - placeable.measuredWidth) / 2
+ placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
index 7eddaaf..86124c6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -24,24 +24,35 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
import javax.inject.Inject
/** Renders the lockscreen scene when showing the communal glanceable hub. */
-class CommunalBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class CommunalBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
override val id: String = "communal"
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- Box(modifier.background(Color.Black)) {
- Text(
- text = "TODO(b/316211368): communal blueprint",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { _ ->
+ Box(modifier.background(Color.Black)) {
+ Text(
+ text = "TODO(b/316211368): communal blueprint",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 7314453..d9d98cb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -17,17 +17,20 @@
package com.android.systemui.keyguard.ui.composable.blueprint
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.ClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -51,6 +54,7 @@
private val lockSection: LockSection,
private val ambientIndicationSection: AmbientIndicationSection,
private val bottomAreaSection: BottomAreaSection,
+ private val settingsMenuSection: SettingsMenuSection,
) : LockscreenSceneBlueprint {
override val id: String = "default"
@@ -59,102 +63,116 @@
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- Layout(
- content = {
- // Constrained to above the lock icon.
- Column(
- modifier = Modifier.fillMaxWidth(),
- ) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
- with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
- with(notificationSection) {
- Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
- }
- if (!isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
- }
-
- with(lockSection) { LockIcon() }
-
- // Aligned to bottom and constrained to below the lock icon.
- Column(modifier = Modifier.fillMaxWidth()) {
- if (isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
-
- with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
- }
-
- // Aligned to bottom and NOT constrained by the lock icon.
- with(bottomAreaSection) {
- Shortcut(isStart = true, applyPadding = true)
- Shortcut(isStart = false, applyPadding = true)
- }
- },
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
modifier = modifier,
- ) { measurables, constraints ->
- check(measurables.size == 5)
- val (
- aboveLockIconMeasurable,
- lockIconMeasurable,
- belowLockIconMeasurable,
- startShortcutMeasurable,
- endShortcutMeasurable,
- ) = measurables
+ ) { onSettingsMenuPlaced ->
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+ with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(notificationSection) {
+ Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+ }
+ if (!isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+ }
- val noMinConstraints =
- constraints.copy(
- minWidth = 0,
- minHeight = 0,
- )
- val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
- val lockIconBounds =
- IntRect(
- left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
- top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
- right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
- bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
- )
+ with(lockSection) { LockIcon() }
- val aboveLockIconPlaceable =
- aboveLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = lockIconBounds.top)
- )
- val belowLockIconPlaceable =
- belowLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
- )
- val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
- val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
- layout(constraints.maxWidth, constraints.maxHeight) {
- aboveLockIconPlaceable.place(
- x = 0,
- y = 0,
- )
- lockIconPlaceable.place(
- x = lockIconBounds.left,
- y = lockIconBounds.top,
- )
- belowLockIconPlaceable.place(
- x = 0,
- y = constraints.maxHeight - belowLockIconPlaceable.height,
- )
- startShortcutPleaceable.place(
- x = 0,
- y = constraints.maxHeight - startShortcutPleaceable.height,
- )
- endShortcutPleaceable.place(
- x = constraints.maxWidth - endShortcutPleaceable.width,
- y = constraints.maxHeight - endShortcutPleaceable.height,
- )
+ with(bottomAreaSection) {
+ IndicationArea(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ // Aligned to bottom and NOT constrained by the lock icon.
+ with(bottomAreaSection) {
+ Shortcut(isStart = true, applyPadding = true)
+ Shortcut(isStart = false, applyPadding = true)
+ }
+ with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+ },
+ modifier = Modifier.fillMaxSize(),
+ ) { measurables, constraints ->
+ check(measurables.size == 6)
+ val aboveLockIconMeasurable = measurables[0]
+ val lockIconMeasurable = measurables[1]
+ val belowLockIconMeasurable = measurables[2]
+ val startShortcutMeasurable = measurables[3]
+ val endShortcutMeasurable = measurables[4]
+ val settingsMenuMeasurable = measurables[5]
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight = constraints.maxHeight - lockIconBounds.bottom
+ )
+ )
+ val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+ val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+ val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ startShortcutPleaceable.place(
+ x = 0,
+ y = constraints.maxHeight - startShortcutPleaceable.height,
+ )
+ endShortcutPleaceable.place(
+ x = constraints.maxWidth - endShortcutPleaceable.width,
+ y = constraints.maxHeight - endShortcutPleaceable.height,
+ )
+ settingsMenuPlaceable.place(
+ x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+ y = constraints.maxHeight - settingsMenuPlaceable.height,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 4c119c7..4704f5c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -17,17 +17,20 @@
package com.android.systemui.keyguard.ui.composable.blueprint
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.ClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -51,6 +54,7 @@
private val lockSection: LockSection,
private val ambientIndicationSection: AmbientIndicationSection,
private val bottomAreaSection: BottomAreaSection,
+ private val settingsMenuSection: SettingsMenuSection,
) : LockscreenSceneBlueprint {
override val id: String = "shortcuts-besides-udfps"
@@ -59,105 +63,123 @@
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- Layout(
- content = {
- // Constrained to above the lock icon.
- Column(
- modifier = Modifier.fillMaxWidth(),
- ) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
- with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
- with(notificationSection) {
- Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
- }
- if (!isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
- }
-
- // Constrained to the left of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
-
- with(lockSection) { LockIcon() }
-
- // Constrained to the right of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
-
- // Aligned to bottom and constrained to below the lock icon.
- Column(modifier = Modifier.fillMaxWidth()) {
- if (isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
-
- with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
- }
- },
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
modifier = modifier,
- ) { measurables, constraints ->
- check(measurables.size == 5)
- val (
- aboveLockIconMeasurable,
- startSideShortcutMeasurable,
- lockIconMeasurable,
- endSideShortcutMeasurable,
- belowLockIconMeasurable,
- ) = measurables
+ ) { onSettingsMenuPlaced ->
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+ with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(notificationSection) {
+ Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+ }
+ if (!isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+ }
- val noMinConstraints =
- constraints.copy(
- minWidth = 0,
- minHeight = 0,
- )
+ // Constrained to the left of the lock icon (in left-to-right layouts).
+ with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
- val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
- val lockIconBounds =
- IntRect(
- left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
- top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
- right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
- bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
- )
+ with(lockSection) { LockIcon() }
- val aboveLockIconPlaceable =
- aboveLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = lockIconBounds.top)
- )
- val startSideShortcutPlaceable = startSideShortcutMeasurable.measure(noMinConstraints)
- val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
- val belowLockIconPlaceable =
- belowLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
- )
+ // Constrained to the right of the lock icon (in left-to-right layouts).
+ with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
- layout(constraints.maxWidth, constraints.maxHeight) {
- aboveLockIconPlaceable.place(
- x = 0,
- y = 0,
- )
- startSideShortcutPlaceable.placeRelative(
- x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
- y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
- )
- lockIconPlaceable.place(
- x = lockIconBounds.left,
- y = lockIconBounds.top,
- )
- endSideShortcutPlaceable.placeRelative(
- x =
- lockIconBounds.right + (constraints.maxWidth - lockIconBounds.right) / 2 -
- endSideShortcutPlaceable.width / 2,
- y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
- )
- belowLockIconPlaceable.place(
- x = 0,
- y = constraints.maxHeight - belowLockIconPlaceable.height,
- )
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ with(bottomAreaSection) {
+ IndicationArea(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ // Aligned to bottom and NOT constrained by the lock icon.
+ with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+ },
+ modifier = Modifier.fillMaxSize(),
+ ) { measurables, constraints ->
+ check(measurables.size == 6)
+ val aboveLockIconMeasurable = measurables[0]
+ val startSideShortcutMeasurable = measurables[1]
+ val lockIconMeasurable = measurables[2]
+ val endSideShortcutMeasurable = measurables[3]
+ val belowLockIconMeasurable = measurables[4]
+ val settingsMenuMeasurable = measurables[5]
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val startSideShortcutPlaceable =
+ startSideShortcutMeasurable.measure(noMinConstraints)
+ val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight = constraints.maxHeight - lockIconBounds.bottom
+ )
+ )
+ val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ startSideShortcutPlaceable.placeRelative(
+ x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
+ y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ endSideShortcutPlaceable.placeRelative(
+ x =
+ lockIconBounds.right +
+ (constraints.maxWidth - lockIconBounds.right) / 2 -
+ endSideShortcutPlaceable.width / 2,
+ y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ settingsMenuPlaceable.place(
+ x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+ y = constraints.maxHeight - settingsMenuPlaceable.height,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index 7545d5f..fdf1166 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -24,6 +24,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@@ -33,18 +35,27 @@
* Renders the lockscreen scene when showing with a split shade (e.g. unfolded foldable and/or
* tablet form factor).
*/
-class SplitShadeBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class SplitShadeBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
override val id: String = "split-shade"
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- Box(modifier.background(Color.Black)) {
- Text(
- text = "TODO(b/316211368): split shade blueprint",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { _ ->
+ Box(modifier.background(Color.Black)) {
+ Text(
+ text = "TODO(b/316211368): split shade blueprint",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index d93863d..2a6bea7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -19,25 +19,33 @@
import android.content.Context
import android.util.DisplayMetrics
import android.view.WindowManager
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.keyguard.LockIconView
+import com.android.keyguard.LockIconViewController
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import dagger.Lazy
import javax.inject.Inject
class LockSection
@@ -46,48 +54,70 @@
private val windowManager: WindowManager,
private val authController: AuthController,
private val featureFlags: FeatureFlagsClassic,
+ private val lockIconViewController: Lazy<LockIconViewController>,
+ private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>,
+ private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
+ private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
+ private val falsingManager: Lazy<FalsingManager>,
+ private val vibratorHelper: Lazy<VibratorHelper>,
) {
@Composable
fun SceneScope.LockIcon(modifier: Modifier = Modifier) {
- MovableElement(
- key = LockIconElementKey,
- modifier = modifier,
- ) {
- val context = LocalContext.current
- Box(
- modifier =
- Modifier.background(Color.Red).layout { measurable, _ ->
- val lockIconBounds = lockIconBounds(context)
- val placeable =
- measurable.measure(
- Constraints.fixed(
- width = lockIconBounds.width,
- height = lockIconBounds.height,
- )
- )
- layout(
- width = placeable.width,
- height = placeable.height,
- alignmentLines =
- mapOf(
- BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
- BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
- BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
- BlueprintAlignmentLines.LockIcon.Bottom to
- lockIconBounds.bottom,
- ),
- ) {
- placeable.place(0, 0)
- }
- },
- ) {
- Text(
- text = "TODO(b/316211368): Lock",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
- }
+ if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
+ return
}
+
+ val context = LocalContext.current
+
+ AndroidView(
+ factory = { context ->
+ val view =
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ DeviceEntryIconView(context, null).apply {
+ id = R.id.device_entry_icon_view
+ DeviceEntryIconViewBinder.bind(
+ this,
+ deviceEntryIconViewModel.get(),
+ deviceEntryForegroundViewModel.get(),
+ deviceEntryBackgroundViewModel.get(),
+ falsingManager.get(),
+ vibratorHelper.get(),
+ )
+ }
+ } else {
+ // keyguardBottomAreaRefactor()
+ LockIconView(context, null).apply {
+ id = R.id.lock_icon_view
+ lockIconViewController.get().setLockIconView(this)
+ }
+ }
+ view
+ },
+ modifier =
+ modifier.element(LockIconElementKey).layout { measurable, _ ->
+ val lockIconBounds = lockIconBounds(context)
+ val placeable =
+ measurable.measure(
+ Constraints.fixed(
+ width = lockIconBounds.width,
+ height = lockIconBounds.height,
+ )
+ )
+ layout(
+ width = placeable.width,
+ height = placeable.height,
+ alignmentLines =
+ mapOf(
+ BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
+ BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
+ BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
+ BlueprintAlignmentLines.LockIcon.Bottom to lockIconBounds.bottom,
+ ),
+ ) {
+ placeable.place(0, 0)
+ }
+ },
+ )
}
/**
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index f135be2..c547e2b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -16,36 +16,25 @@
package com.android.systemui.keyguard.ui.composable.section
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
-class NotificationSection @Inject constructor() {
+class NotificationSection
+@Inject
+constructor(
+ private val viewModel: NotificationsPlaceholderViewModel,
+) {
@Composable
fun SceneScope.Notifications(modifier: Modifier = Modifier) {
- MovableElement(
- key = NotificationsElementKey,
+ NotificationStack(
+ viewModel = viewModel,
+ isScrimVisible = false,
modifier = modifier,
- ) {
- Box(
- modifier = Modifier.fillMaxSize().background(Color.Yellow),
- ) {
- Text(
- text = "TODO(b/316211368): Notifications",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
- }
- }
+ )
}
}
-
-private val NotificationsElementKey = ElementKey("Notifications")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
new file mode 100644
index 0000000..44b0535
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.section
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.positionInParent
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.toSize
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+
+class SettingsMenuSection
+@Inject
+constructor(
+ private val viewModel: KeyguardSettingsMenuViewModel,
+ private val longPressViewModel: KeyguardLongPressViewModel,
+ private val vibratorHelper: VibratorHelper,
+ private val activityStarter: ActivityStarter,
+) {
+ @Composable
+ @SuppressWarnings("InflateParams") // null is passed into the inflate call, on purpose.
+ fun SettingsMenu(
+ onPlaced: (Rect?) -> Unit,
+ modifier: Modifier = Modifier,
+ ) {
+ val (disposableHandle, setDisposableHandle) =
+ remember { mutableStateOf<DisposableHandle?>(null) }
+ AndroidView(
+ factory = { context ->
+ LayoutInflater.from(context)
+ .inflate(
+ R.layout.keyguard_settings_popup_menu,
+ null,
+ )
+ .apply {
+ isVisible = false
+ alpha = 0f
+
+ setDisposableHandle(
+ KeyguardSettingsViewBinder.bind(
+ view = this,
+ viewModel = viewModel,
+ longPressViewModel = longPressViewModel,
+ rootViewModel = null,
+ vibratorHelper = vibratorHelper,
+ activityStarter = activityStarter,
+ )
+ )
+ }
+ },
+ onRelease = { disposableHandle?.dispose() },
+ modifier =
+ modifier
+ .padding(
+ bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset),
+ )
+ .padding(
+ horizontal =
+ dimensionResource(R.dimen.keyguard_affordance_horizontal_offset),
+ )
+ .onPlaced { coordinates ->
+ onPlaced(
+ if (!coordinates.size.toSize().isEmpty()) {
+ Rect(coordinates.positionInParent(), coordinates.size.toSize())
+ } else {
+ null
+ }
+ )
+ },
+ )
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 26da1f0..4b21105 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -47,7 +47,7 @@
suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) {
withContext(backgroundDispatcher) {
- secureSettingsRepository.set(
+ secureSettingsRepository.setInt(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
value = if (enabled) 1 else 0,
)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 7ef16a8..754d5dc 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -37,15 +37,17 @@
): Flow<Int>
/** Updates the value of the setting with the given name. */
- suspend fun set(
+ suspend fun setInt(
name: String,
value: Int,
)
- suspend fun get(
+ suspend fun getInt(
name: String,
defaultValue: Int = 0,
): Int
+
+ suspend fun getString(name: String): String?
}
class SecureSettingsRepositoryImpl(
@@ -80,7 +82,7 @@
.flowOn(backgroundDispatcher)
}
- override suspend fun set(name: String, value: Int) {
+ override suspend fun setInt(name: String, value: Int) {
withContext(backgroundDispatcher) {
Settings.Secure.putInt(
contentResolver,
@@ -90,7 +92,7 @@
}
}
- override suspend fun get(name: String, defaultValue: Int): Int {
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
return withContext(backgroundDispatcher) {
Settings.Secure.getInt(
contentResolver,
@@ -99,4 +101,13 @@
)
}
}
+
+ override suspend fun getString(name: String): String? {
+ return withContext(backgroundDispatcher) {
+ Settings.Secure.getString(
+ contentResolver,
+ name,
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
index 1c86a07..37b9792 100644
--- a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
@@ -28,11 +28,15 @@
return settings.map { it.getOrDefault(name, defaultValue.toString()) }.map { it.toInt() }
}
- override suspend fun set(name: String, value: Int) {
+ override suspend fun setInt(name: String, value: Int) {
settings.value = settings.value.toMutableMap().apply { this[name] = value.toString() }
}
- override suspend fun get(name: String, defaultValue: Int): Int {
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
return settings.value[name]?.toInt() ?: defaultValue
}
+
+ override suspend fun getString(name: String): String? {
+ return settings.value[name]
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
rename to packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index b1de127..49e0df6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -31,7 +31,7 @@
* Controls the interaction between {@link MagnetizedObject} and
* {@link MagnetizedObject.MagneticTarget}.
*/
-class DismissAnimationController {
+class DragToInteractAnimationController {
private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false;
private static final float COMPLETELY_OPAQUE = 1.0f;
private static final float COMPLETELY_TRANSPARENT = 0.0f;
@@ -45,7 +45,7 @@
private float mMinDismissSize;
private float mSizePercent;
- DismissAnimationController(DismissView dismissView, MenuView menuView) {
+ DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
mDismissView = dismissView;
mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
@@ -127,7 +127,7 @@
* @param event that move the magnetized object which is also the menu list view.
* @return true if the location of the motion events moves within the magnetic field of a
* target, but false if didn't set
- * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
return mMagnetizedObject.maybeConsumeMotionEvent(event);
@@ -140,7 +140,7 @@
* @param event that move the magnetized object which is also the menu list view.
* @return true if the location of the motion events moves within the magnetic field of a
* target, but false if didn't set
- * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
boolean maybeConsumeUpMotionEvent(MotionEvent event) {
return mMagnetizedObject.maybeConsumeMotionEvent(event);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 34d7cec..a270558 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -73,7 +73,7 @@
private final ValueAnimator mFadeOutAnimator;
private final Handler mHandler;
private boolean mIsFadeEffectEnabled;
- private DismissAnimationController.DismissCallback mDismissCallback;
+ private DragToInteractAnimationController.DismissCallback mDismissCallback;
private Runnable mSpringAnimationsEndAction;
// Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
@@ -171,7 +171,7 @@
}
void setDismissCallback(
- DismissAnimationController.DismissCallback dismissCallback) {
+ DragToInteractAnimationController.DismissCallback dismissCallback) {
mDismissCallback = dismissCallback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index d01590f..52e7b91 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -40,13 +40,13 @@
private final PointF mMenuTranslationDown = new PointF();
private boolean mIsDragging = false;
private float mTouchSlop;
- private final DismissAnimationController mDismissAnimationController;
+ private final DragToInteractAnimationController mDragToInteractAnimationController;
private Optional<Runnable> mOnActionDownEnd = Optional.empty();
MenuListViewTouchHandler(MenuAnimationController menuAnimationController,
- DismissAnimationController dismissAnimationController) {
+ DragToInteractAnimationController dragToInteractAnimationController) {
mMenuAnimationController = menuAnimationController;
- mDismissAnimationController = dismissAnimationController;
+ mDragToInteractAnimationController = dragToInteractAnimationController;
}
@Override
@@ -67,7 +67,7 @@
mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY());
mMenuAnimationController.cancelAnimations();
- mDismissAnimationController.maybeConsumeDownMotionEvent(motionEvent);
+ mDragToInteractAnimationController.maybeConsumeDownMotionEvent(motionEvent);
mOnActionDownEnd.ifPresent(Runnable::run);
break;
@@ -78,9 +78,10 @@
mMenuAnimationController.onDraggingStart();
}
- mDismissAnimationController.showDismissView(/* show= */ true);
+ mDragToInteractAnimationController.showDismissView(/* show= */ true);
- if (!mDismissAnimationController.maybeConsumeMoveMotionEvent(motionEvent)) {
+ if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(
+ motionEvent)) {
mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
mMenuAnimationController.moveToPositionYIfNeeded(
mMenuTranslationDown.y + dy);
@@ -94,17 +95,18 @@
mIsDragging = false;
if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
- mDismissAnimationController.showDismissView(/* show= */ false);
+ mDragToInteractAnimationController.showDismissView(/* show= */ false);
mMenuAnimationController.fadeOutIfEnabled();
return true;
}
- if (!mDismissAnimationController.maybeConsumeUpMotionEvent(motionEvent)) {
+ if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent(
+ motionEvent)) {
mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
mMenuAnimationController.flingMenuThenSpringToEdge(endX,
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
- mDismissAnimationController.showDismissView(/* show= */ false);
+ mDragToInteractAnimationController.showDismissView(/* show= */ false);
}
// Avoid triggering the listener of the item.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index ff3a9e3..62d5feb 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -94,7 +94,7 @@
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final IAccessibilityFloatingMenu mFloatingMenu;
private final SecureSettings mSecureSettings;
- private final DismissAnimationController mDismissAnimationController;
+ private final DragToInteractAnimationController mDragToInteractAnimationController;
private final MenuViewModel mMenuViewModel;
private final Observer<Boolean> mDockTooltipObserver =
this::onDockTooltipVisibilityChanged;
@@ -188,29 +188,30 @@
mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
mDismissView = new DismissView(context);
DismissViewUtils.setup(mDismissView);
- mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
- mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, mMenuView);
+ mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- mDismissAnimationController.animateDismissMenu(/* scaleUp= */ true);
+ mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true);
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
float velocityX, float velocityY, boolean wasFlungOut) {
- mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
}
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
hideMenuAndShowMessage();
mDismissView.hide();
- mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
}
});
mMenuListViewTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
- mDismissAnimationController);
+ mDragToInteractAnimationController);
mMenuView.addOnItemTouchListenerToList(mMenuListViewTouchHandler);
mMenuView.setMoveToTuckedListener(this);
@@ -243,7 +244,7 @@
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
mDismissView.updateResources();
- mDismissAnimationController.updateResources();
+ mDragToInteractAnimationController.updateResources();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index a8c9446..c36e0e2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -47,6 +47,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
@@ -59,49 +60,56 @@
constructor(
@Application private val applicationScope: CoroutineScope,
@Application private val applicationContext: Context,
- private val biometricStatusInteractor: BiometricStatusInteractor,
- private val displayStateInteractor: DisplayStateInteractor,
- private val deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor,
- private val fpsUnlockTracker: FpsUnlockTracker,
- private val layoutInflater: LayoutInflater,
- private val sideFpsProgressBarViewModel: SideFpsProgressBarViewModel,
- private val sfpsSensorInteractor: SideFpsSensorInteractor,
- private val windowManager: WindowManager
+ private val biometricStatusInteractor: Lazy<BiometricStatusInteractor>,
+ private val displayStateInteractor: Lazy<DisplayStateInteractor>,
+ private val deviceEntrySideFpsOverlayInteractor: Lazy<DeviceEntrySideFpsOverlayInteractor>,
+ private val fpsUnlockTracker: Lazy<FpsUnlockTracker>,
+ private val layoutInflater: Lazy<LayoutInflater>,
+ private val sideFpsProgressBarViewModel: Lazy<SideFpsProgressBarViewModel>,
+ private val sfpsSensorInteractor: Lazy<SideFpsSensorInteractor>,
+ private val windowManager: Lazy<WindowManager>
) : CoreStartable {
override fun start() {
if (!SideFpsControllerRefactor.isEnabled) {
return
}
+
applicationScope
.launch {
- combine(
- biometricStatusInteractor.sfpsAuthenticationReason,
- deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry,
- sideFpsProgressBarViewModel.isVisible,
- ::Triple
- )
- .sample(displayStateInteractor.isInRearDisplayMode, ::Pair)
- .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
- val (
- systemServerAuthReason,
- showIndicatorForDeviceEntry,
- progressBarIsVisible) =
- combinedFlows
- if (!isInRearDisplayMode) {
- if (progressBarIsVisible) {
- hide()
- } else if (systemServerAuthReason != NotRunning) {
- show()
- } else if (showIndicatorForDeviceEntry) {
- show()
- } else {
- hide()
+ sfpsSensorInteractor.get().isAvailable.collect { isSfpsAvailable ->
+ if (isSfpsAvailable) {
+ combine(
+ biometricStatusInteractor.get().sfpsAuthenticationReason,
+ deviceEntrySideFpsOverlayInteractor
+ .get()
+ .showIndicatorForDeviceEntry,
+ sideFpsProgressBarViewModel.get().isVisible,
+ ::Triple
+ )
+ .sample(displayStateInteractor.get().isInRearDisplayMode, ::Pair)
+ .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
+ val (
+ systemServerAuthReason,
+ showIndicatorForDeviceEntry,
+ progressBarIsVisible) =
+ combinedFlows
+ if (!isInRearDisplayMode) {
+ if (progressBarIsVisible) {
+ hide()
+ } else if (systemServerAuthReason != NotRunning) {
+ show()
+ } else if (showIndicatorForDeviceEntry) {
+ show()
+ } else {
+ hide()
+ }
+ }
}
- }
}
+ }
}
- .invokeOnCompletion { fpsUnlockTracker.stopTracking() }
+ .invokeOnCompletion { fpsUnlockTracker.get().stopTracking() }
}
private var overlayView: View? = null
@@ -113,29 +121,29 @@
if (it.isAttachedToWindow) {
lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
lottie?.pauseAnimation()
- windowManager.removeView(it)
+ windowManager.get().removeView(it)
}
}
- overlayView = layoutInflater.inflate(R.layout.sidefps_view, null, false)
+ overlayView = layoutInflater.get().inflate(R.layout.sidefps_view, null, false)
val overlayViewModel =
SideFpsOverlayViewModel(
applicationContext,
- biometricStatusInteractor,
- deviceEntrySideFpsOverlayInteractor,
- displayStateInteractor,
- sfpsSensorInteractor,
- sideFpsProgressBarViewModel
+ biometricStatusInteractor.get(),
+ deviceEntrySideFpsOverlayInteractor.get(),
+ displayStateInteractor.get(),
+ sfpsSensorInteractor.get(),
+ sideFpsProgressBarViewModel.get()
)
- bind(overlayView!!, overlayViewModel, fpsUnlockTracker, windowManager)
+ bind(overlayView!!, overlayViewModel, fpsUnlockTracker.get(), windowManager.get())
overlayView!!.visibility = View.INVISIBLE
- windowManager.addView(overlayView, overlayViewModel.defaultOverlayViewParams)
+ windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
}
/** Hide the side fingerprint sensor indicator */
private fun hide() {
if (overlayView != null) {
- windowManager.removeView(overlayView)
+ windowManager.get().removeView(overlayView)
overlayView = null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
index 629b361..cfa5294 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
@@ -65,4 +65,11 @@
SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, previousEvent.currentProgress)
}
}
+
+ /** The arrow navigation that was operating the slider has stopped. */
+ fun onArrowUp() {
+ _currentEvent.update { previousEvent ->
+ SliderEvent(SliderEventType.ARROW_UP, previousEvent.currentProgress)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
index d89cf63..10098fa 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
@@ -58,7 +58,7 @@
override suspend fun iterateState(event: SliderEvent) {
when (currentState) {
- SliderState.IDLE -> handleIdle(event.type)
+ SliderState.IDLE -> handleIdle(event.type, event.currentProgress)
SliderState.WAIT -> handleWait(event.type, event.currentProgress)
SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH -> handleAcquired(event.type)
SliderState.DRAG_HANDLE_DRAGGING -> handleDragging(event.type, event.currentProgress)
@@ -67,17 +67,26 @@
SliderState.DRAG_HANDLE_RELEASED_FROM_TOUCH -> setState(SliderState.IDLE)
SliderState.JUMP_TRACK_LOCATION_SELECTED -> handleJumpToTrack(event.type)
SliderState.JUMP_BOOKEND_SELECTED -> handleJumpToBookend(event.type)
+ SliderState.ARROW_HANDLE_MOVED_ONCE -> handleArrowOnce(event.type)
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY ->
+ handleArrowContinuous(event.type, event.currentProgress)
+ SliderState.ARROW_HANDLE_REACHED_BOOKEND -> handleArrowBookend()
}
latestProgress = event.currentProgress
}
- private fun handleIdle(newEventType: SliderEventType) {
+ private fun handleIdle(newEventType: SliderEventType, currentProgress: Float) {
if (newEventType == SliderEventType.STARTED_TRACKING_TOUCH) {
timerJob = launchTimer()
// The WAIT state will wait for the timer to complete or a slider progress to occur.
// This will disambiguate between an imprecise touch that acquires the slider handle,
// and a select and jump operation in the slider track.
setState(SliderState.WAIT)
+ } else if (newEventType == SliderEventType.PROGRESS_CHANGE_BY_PROGRAM) {
+ val state =
+ if (bookendReached(currentProgress)) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+ else SliderState.ARROW_HANDLE_MOVED_ONCE
+ setState(state)
}
}
@@ -176,6 +185,13 @@
SliderState.DRAG_HANDLE_REACHED_BOOKEND -> executeOnBookend()
SliderState.JUMP_TRACK_LOCATION_SELECTED ->
sliderListener.onProgressJump(latestProgress)
+ SliderState.ARROW_HANDLE_MOVED_ONCE -> sliderListener.onSelectAndArrow(latestProgress)
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY -> sliderListener.onProgress(latestProgress)
+ SliderState.ARROW_HANDLE_REACHED_BOOKEND -> {
+ executeOnBookend()
+ // This transitory execution must also reset the state
+ resetState()
+ }
else -> {}
}
}
@@ -204,6 +220,43 @@
currentProgress <= config.lowerBookendThreshold
}
+ private fun handleArrowOnce(newEventType: SliderEventType) {
+ val nextState =
+ when (newEventType) {
+ SliderEventType.STARTED_TRACKING_TOUCH -> {
+ // Launching the timer and going to WAIT
+ timerJob = launchTimer()
+ SliderState.WAIT
+ }
+ SliderEventType.PROGRESS_CHANGE_BY_PROGRAM ->
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ SliderEventType.ARROW_UP -> SliderState.IDLE
+ else -> SliderState.ARROW_HANDLE_MOVED_ONCE
+ }
+ setState(nextState)
+ }
+
+ private fun handleArrowContinuous(newEventType: SliderEventType, currentProgress: Float) {
+ val reachedBookend = bookendReached(currentProgress)
+ val nextState =
+ when (newEventType) {
+ SliderEventType.ARROW_UP -> SliderState.IDLE
+ SliderEventType.STARTED_TRACKING_TOUCH -> {
+ // Launching the timer and going to WAIT
+ timerJob = launchTimer()
+ SliderState.WAIT
+ }
+ SliderEventType.PROGRESS_CHANGE_BY_PROGRAM -> {
+ if (reachedBookend) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+ else SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ }
+ else -> SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ }
+ setState(nextState)
+ }
+
+ private fun handleArrowBookend() = setState(SliderState.IDLE)
+
@VisibleForTesting
fun setState(state: SliderState) {
currentState = state
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
index 413e277..4a63941 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
@@ -29,5 +29,5 @@
/* The slider has stopped tracking touch events. */
STOPPED_TRACKING_TOUCH,
/* The external (not touch) stimulus that was modifying the slider progress has stopped. */
- EXTERNAL_STIMULUS_RELEASE,
+ ARROW_UP,
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
index fe092e6..de6ddd7 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
@@ -32,6 +32,12 @@
DRAG_HANDLE_REACHED_BOOKEND,
/* A location in the slider track has been selected. */
JUMP_TRACK_LOCATION_SELECTED,
- /* The slider handled moved to a bookend after it was selected. */
+ /* The slider handle moved to a bookend after it was selected. */
JUMP_BOOKEND_SELECTED,
+ /** The slider handle moved due to single select-and-arrow operation */
+ ARROW_HANDLE_MOVED_ONCE,
+ /** The slider handle moves continuously due to constant select-and-arrow operations */
+ ARROW_HANDLE_MOVES_CONTINUOUSLY,
+ /** The slider handle reached a bookend due to a select-and-arrow operation */
+ ARROW_HANDLE_REACHED_BOOKEND,
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index c98f637..ecf78d5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -23,15 +23,18 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
/**
* Encapsulates business logic for device entry events that impact the side fingerprint sensor
@@ -41,6 +44,7 @@
class DeviceEntrySideFpsOverlayInteractor
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
@Application private val context: Context,
deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
@@ -50,7 +54,13 @@
init {
if (!DeviceEntryUdfpsRefactor.isEnabled) {
- alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ applicationScope.launch {
+ deviceEntryFingerprintAuthRepository.availableFpSensorType.collect { sensorType ->
+ if (sensorType == BiometricType.SIDE_FINGERPRINT) {
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index eee5206..96e83b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -241,7 +241,6 @@
vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Activated)
settingsMenu.setOnTouchListener(
KeyguardSettingsButtonOnTouchListener(
- view = settingsMenu,
viewModel = viewModel.settingsMenuViewModel,
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
index c54203c..c6dfcb0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
@@ -20,12 +20,10 @@
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
-import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.common.ui.view.rawDistanceFrom
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
class KeyguardSettingsButtonOnTouchListener(
- private val view: LaunchableLinearLayout,
private val viewModel: KeyguardSettingsMenuViewModel,
) : View.OnTouchListener {
@@ -41,8 +39,10 @@
MotionEvent.ACTION_UP -> {
view.isPressed = false
val distanceMoved =
- motionEvent
- .rawDistanceFrom(downPositionDisplayCoords.x, downPositionDisplayCoords.y)
+ motionEvent.rawDistanceFrom(
+ downPositionDisplayCoords.x,
+ downPositionDisplayCoords.y
+ )
val isClick = distanceMoved < ViewConfiguration.getTouchSlop()
viewModel.onTouchGestureEnded(isClick)
if (isClick) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 11e63e7..f67cb68 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -23,7 +23,6 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.common.ui.binder.TextViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
@@ -43,15 +42,13 @@
object KeyguardSettingsViewBinder {
fun bind(
- parentView: View,
+ view: View,
viewModel: KeyguardSettingsMenuViewModel,
longPressViewModel: KeyguardLongPressViewModel,
- rootViewModel: KeyguardRootViewModel,
+ rootViewModel: KeyguardRootViewModel?,
vibratorHelper: VibratorHelper,
activityStarter: ActivityStarter
): DisposableHandle {
- val view = parentView.requireViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
-
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -62,7 +59,6 @@
vibratorHelper.vibrate(KeyguardBottomAreaVibrations.Activated)
view.setOnTouchListener(
KeyguardSettingsButtonOnTouchListener(
- view = view,
viewModel = viewModel,
)
)
@@ -96,7 +92,7 @@
}
launch {
- rootViewModel.lastRootViewTapPosition.filterNotNull().collect { point ->
+ rootViewModel?.lastRootViewTapPosition?.filterNotNull()?.collect { point ->
if (view.isVisible) {
val hitRect = Rect()
view.getHitRect(hitRect)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 24240df..940d1e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -142,6 +142,11 @@
return true
}
+ if (renderer == null || onDestroy == null) {
+ Log.wtf(TAG, "Renderer/onDestroy should not be null.")
+ return true
+ }
+
when (message.what) {
KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index d57e569..36bbe4e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -33,6 +33,7 @@
constructor(
private val interactor: KeyguardBlueprintInteractor,
private val authController: AuthController,
+ val longPress: KeyguardLongPressViewModel,
) {
val isUdfpsVisible: Boolean
get() = authController.isUdfpsSupported
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index e827a1e..3e6d46c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -25,12 +25,12 @@
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.res.R
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.ViewPriority
@@ -162,7 +162,7 @@
logger: MediaTttSenderLogger,
instanceId: InstanceId,
): ChipbarInfo {
- val packageName = checkNotNull(routeInfo.clientPackageName)
+ val packageName = routeInfo.clientPackageName
val otherDeviceName =
if (routeInfo.name.isBlank()) {
context.getString(R.string.media_ttt_default_device_type)
@@ -171,7 +171,7 @@
}
val icon =
MediaTttUtils.getIconInfoFromPackageName(context, packageName, isReceiver = false) {
- logger.logPackageNotFound(packageName)
+ packageName?.let { logger.logPackageNotFound(it) }
}
val timeout =
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index bc5090f..be1fa2b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -227,7 +227,7 @@
mListener.onChanged(mTracking, progress, false);
SeekableSliderEventProducer eventProducer =
mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
- if (eventProducer != null) {
+ if (eventProducer != null && fromUser) {
eventProducer.onProgressChanged(seekBar, progress, fromUser);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
similarity index 84%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index fd258e3..9bcab57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -40,12 +40,12 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-/** Tests for {@link DismissAnimationController}. */
+/** Tests for {@link DragToInteractAnimationController}. */
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class DismissAnimationControllerTest extends SysuiTestCase {
- private DismissAnimationController mDismissAnimationController;
+public class DragToInteractAnimationControllerTest extends SysuiTestCase {
+ private DragToInteractAnimationController mDragToInteractAnimationController;
private DismissView mDismissView;
@Rule
@@ -65,19 +65,20 @@
stubMenuViewAppearance);
mDismissView = spy(new DismissView(mContext));
DismissViewUtils.setup(mDismissView);
- mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, stubMenuView);
}
@Test
public void showDismissView_success() {
- mDismissAnimationController.showDismissView(true);
+ mDragToInteractAnimationController.showDismissView(true);
verify(mDismissView).show();
}
@Test
public void hideDismissView_success() {
- mDismissAnimationController.showDismissView(false);
+ mDragToInteractAnimationController.showDismissView(false);
verify(mDismissView).hide();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 7f12c05..9c8de30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -62,7 +62,7 @@
@Mock
private SecureSettings mSecureSettings;
@Mock
- private DismissAnimationController.DismissCallback mStubDismissCallback;
+ private DragToInteractAnimationController.DismissCallback mStubDismissCallback;
private RecyclerView mStubListView;
private MenuView mMenuView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 9797f2a..e1522f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -68,7 +68,7 @@
private MenuView mStubMenuView;
private MenuListViewTouchHandler mTouchHandler;
private MenuAnimationController mMenuAnimationController;
- private DismissAnimationController mDismissAnimationController;
+ private DragToInteractAnimationController mDragToInteractAnimationController;
private RecyclerView mStubListView;
private DismissView mDismissView;
@@ -92,10 +92,10 @@
mStubMenuView, stubMenuViewAppearance));
mDismissView = spy(new DismissView(mContext));
DismissViewUtils.setup(mDismissView);
- mDismissAnimationController =
- spy(new DismissAnimationController(mDismissView, mStubMenuView));
+ mDragToInteractAnimationController =
+ spy(new DragToInteractAnimationController(mDismissView, mStubMenuView));
mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
- mDismissAnimationController);
+ mDragToInteractAnimationController);
final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
mStubListView = (RecyclerView) mStubMenuView.getChildAt(0);
mStubListView.setAdapter(stubAdapter);
@@ -115,7 +115,7 @@
@Test
public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
- doReturn(false).when(mDismissAnimationController).maybeConsumeMoveMotionEvent(
+ doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
any(MotionEvent.class));
final int offset = 100;
final MotionEvent stubDownEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index b4ae00d..42d2c98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -217,6 +217,7 @@
deviceEntrySideFpsOverlayInteractor =
DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
primaryBouncerInteractor,
@@ -260,14 +261,14 @@
SideFpsOverlayViewBinder(
testScope.backgroundScope,
mContext,
- biometricStatusInteractor,
- displayStateInteractor,
- deviceEntrySideFpsOverlayInteractor,
- fpsUnlockTracker,
- layoutInflater,
- sideFpsProgressBarViewModel,
- sfpsSensorInteractor,
- windowManager
+ { biometricStatusInteractor },
+ { displayStateInteractor },
+ { deviceEntrySideFpsOverlayInteractor },
+ { fpsUnlockTracker },
+ { layoutInflater },
+ { sideFpsProgressBarViewModel },
+ { sfpsSensorInteractor },
+ { windowManager }
)
context.addMockSystemService(DisplayManager::class.java, displayManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 2267bdc..983e4b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -220,6 +220,7 @@
deviceEntrySideFpsOverlayInteractor =
DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
primaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
index 71a56cd..c22d35c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
@@ -123,4 +123,25 @@
assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest)
}
+
+ @Test
+ fun onArrowUp_afterStartTrackingTouch_ArrowUpProduced() = runTest {
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onStartTrackingTouch(seekBar)
+ eventProducer.onArrowUp()
+
+ assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0f), latest)
+ }
+
+ @Test
+ fun onArrowUp_afterChangeByProgram_ArrowUpProduced_withProgress() = runTest {
+ val progress = 50
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onProgressChanged(seekBar, progress, false)
+ eventProducer.onArrowUp()
+
+ assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0.5f), latest)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
index 8d12e49..db04962 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
@@ -528,6 +528,194 @@
verifyNoMoreInteractions(sliderStateListener)
}
+ @Test
+ fun onProgressChangeByProgram_atTheMiddle_onIdle_movesToArrowHandleMovedOnce() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ initTracker(testScheduler)
+
+ // GIVEN a progress due to an external source that lands at the middle of the slider
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the state moves to ARROW_HANDLE_MOVED_ONCE and the listener is called to play
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+ verify(sliderStateListener).onSelectAndArrow(progress)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_atUpperBookend_onIdle_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+
+ // GIVEN a progress due to an external source that lands at the upper bookend
+ val progress = config.upperBookendThreshold + 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes upper bookend haptics before moving back to IDLE
+ verify(sliderStateListener).onUpperBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_atLowerBookend_onIdle_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+
+ // WHEN a progress is recorded due to an external source that lands at the lower bookend
+ val progress = config.lowerBookendThreshold - 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes lower bookend haptics before moving to IDLE
+ verify(sliderStateListener).onLowerBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onArrowUp_onArrowMovedOnce_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the external stimulus is released
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+ // THEN the tracker moves back to IDLE and there are no haptics
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onStartTrackingTouch_onArrowMovedOnce_movesToWait() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the slider starts tracking touch
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+ // THEN the tracker moves back to WAIT and starts the waiting job. Also, there are no
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+ assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_onArrowMovedOnce_movesToArrowMovesContinuously() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the slider gets an external progress change
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker moves to ARROW_HANDLE_MOVES_CONTINUOUSLY and calls the appropriate
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+ verify(sliderStateListener).onProgress(progress)
+ }
+
+ @Test
+ fun onArrowUp_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the external stimulus is released
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+ // THEN the tracker moves to IDLE and no haptics are played
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onStartTrackingTouch_onArrowMovesContinuously_movesToWait() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider starts tracking touch
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+ // THEN the tracker moves to WAIT and the wait job starts.
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+ assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_onArrowMovesContinuously_preservesState() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider changes progress programmatically at the middle
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker stays in the same state and haptics are delivered appropriately
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+ verify(sliderStateListener).onProgress(progress)
+ }
+
+ @Test
+ fun onProgramProgress_atLowerBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider reaches the lower bookend programmatically
+ val progress = config.lowerBookendThreshold - 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes lower bookend haptics before moving to IDLE
+ verify(sliderStateListener).onLowerBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onProgramProgress_atUpperBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider reaches the lower bookend programmatically
+ val progress = config.upperBookendThreshold + 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes upper bookend haptics before moving to IDLE
+ verify(sliderStateListener).onUpperBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
@OptIn(ExperimentalCoroutinesApi::class)
private fun initTracker(
scheduler: TestCoroutineScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 0616a34..027dfa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -109,6 +109,7 @@
)
underTest =
DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
mContext,
FakeDeviceEntryFingerprintAuthRepository(),
primaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
index 80f8cf1..50349be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -58,13 +58,13 @@
testScope.runTest {
val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled)
- secureSettingsRepository.set(
+ secureSettingsRepository.setInt(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
value = 1,
)
assertThat(showNotifs).isEqualTo(true)
- secureSettingsRepository.set(
+ secureSettingsRepository.setInt(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
value = 0,
)
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 7558974..1236fcf 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
@@ -38,6 +38,7 @@
import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
import android.metrics.LogMaker;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -84,6 +85,7 @@
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -218,6 +220,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_notificationsVisible_zenHiding() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true);
initController(/* viewIsAttached= */ true);
@@ -238,6 +241,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_notificationsHidden_zenNotHiding() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -258,6 +262,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_splitShadeMode_alwaysShowEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -285,6 +290,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_bouncerShowing_flagOff_hideEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -306,6 +312,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_bouncerShowing_flagOn_hideEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -327,6 +334,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_bouncerNotShowing_flagOff_showEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -348,6 +356,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_bouncerNotShowing_flagOn_showEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -504,6 +513,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
initController(/* viewIsAttached= */ true);
mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true);
@@ -545,6 +555,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateImportantForAccessibility_noChild_onKeyGuard_notImportantForA11y() {
// GIVEN: Controller is attached, active notifications is empty,
// and mNotificationStackScrollLayout.onKeyguard() is true
@@ -561,6 +572,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateImportantForAccessibility_hasChild_onKeyGuard_importantForA11y() {
// GIVEN: Controller is attached, active notifications is not empty,
// and mNotificationStackScrollLayout.onKeyguard() is true
@@ -584,6 +596,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateImportantForAccessibility_hasChild_notOnKeyGuard_importantForA11y() {
// GIVEN: Controller is attached, active notifications is not empty,
// and mNotificationStackScrollLayout.onKeyguard() is false
@@ -607,6 +620,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateImportantForAccessibility_noChild_notOnKeyGuard_importantForA11y() {
// GIVEN: Controller is attached, active notifications is empty,
// and mNotificationStackScrollLayout.onKeyguard() is false
@@ -623,6 +637,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateEmptyShadeView_onKeyguardTransitionToAod_hidesView() {
initController(/* viewIsAttached= */ true);
mController.onKeyguardTransitionChanged(
@@ -633,6 +648,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateEmptyShadeView_onKeyguardOccludedTransitionToAod_hidesView() {
initController(/* viewIsAttached= */ true);
mController.onKeyguardTransitionChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index ad7dee3..83ba684 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -51,6 +51,8 @@
import android.graphics.Insets;
import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
@@ -81,6 +83,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -191,7 +194,7 @@
mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
mNotificationStackSizeCalculator);
mStackScroller = spy(mStackScrollerInternal);
- mStackScroller.setResetUserExpandedStatesRunnable(()->{});
+ mStackScroller.setResetUserExpandedStatesRunnable(() -> {});
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
when(mStackScrollLayoutController.getNotificationRoundnessManager())
@@ -309,7 +312,9 @@
public void updateEmptyView_dndSuppressing() {
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, true);
+ mStackScroller.updateEmptyShadeView(/* visible = */ true,
+ /* areNotificationsHiddenInShade = */ true,
+ /* hasFilteredOutSeenNotifications = */ false);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
@@ -319,7 +324,9 @@
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, false);
+ mStackScroller.updateEmptyShadeView(/* visible = */ true,
+ /* areNotificationsHiddenInShade = */ false,
+ /* hasFilteredOutSeenNotifications = */ false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
}
@@ -328,10 +335,14 @@
public void updateEmptyView_noNotificationsToDndSuppressing() {
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, false);
+ mStackScroller.updateEmptyShadeView(/* visible = */ true,
+ /* areNotificationsHiddenInShade = */ false,
+ /* hasFilteredOutSeenNotifications = */ false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
- mStackScroller.updateEmptyShadeView(true, true);
+ mStackScroller.updateEmptyShadeView(/* visible = */ true,
+ /* areNotificationsHiddenInShade = */ true,
+ /* hasFilteredOutSeenNotifications = */ false);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
@@ -385,8 +396,8 @@
mStackScroller.setExpandedHeight(100f);
}
-
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void manageNotifications_visible() {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
@@ -399,6 +410,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void clearAll_visible() {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
@@ -411,6 +423,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testInflateFooterView() {
mStackScroller.inflateFooterView();
ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
@@ -444,7 +457,7 @@
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(false, true, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
}
@Test
@@ -459,7 +472,7 @@
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(false, false, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true);
}
@Test
@@ -474,7 +487,7 @@
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(true, true, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, true);
}
@Test
@@ -490,7 +503,7 @@
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(true, true, false);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, false);
}
@Test
@@ -505,7 +518,7 @@
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(false, true, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
}
@Test
@@ -521,7 +534,7 @@
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(true, false, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(true, false, true);
}
@Test
@@ -529,7 +542,8 @@
mStackScroller.setCurrentUserSetup(true);
// add footer
- mStackScroller.inflateFooterView();
+ FooterView view = mock(FooterView.class);
+ mStackScroller.setFooterView(view);
// add notification
ExpandableNotificationRow row = createClearableRow();
@@ -545,6 +559,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testReInflatesFooterViews() {
when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
clearInvocations(mStackScroller);
@@ -554,6 +569,16 @@
}
@Test
+ @EnableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testReInflatesEmptyShadeView() {
+ when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
+ clearInvocations(mStackScroller);
+ mStackScroller.reinflateViews();
+ verify(mStackScroller, never()).setFooterView(any());
+ verify(mStackScroller).setEmptyShadeView(any());
+ }
+
+ @Test
public void testSetIsBeingDraggedResetsExposedMenu() {
mStackScroller.setIsBeingDragged(true);
verify(mNotificationSwipeHelper).resetExposedMenuView(true, true);
@@ -601,6 +626,8 @@
@Test
public void testClearNotifications_clearAllInProgress() {
+ mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
ExpandableNotificationRow row = createClearableRow();
when(row.getEntry().hasFinishedInitialization()).thenReturn(true);
doReturn(true).when(mStackScroller).isVisible(row);
@@ -645,6 +672,8 @@
@Test
public void testAddNotificationUpdatesSpeedBumpIndex() {
+ mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
// initial state calculated == 0
assertEquals(0, mStackScroller.getSpeedBumpIndex());
@@ -661,6 +690,8 @@
@Test
public void testAddAmbientNotificationNoSpeedBumpUpdate() {
+ mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
// initial state calculated == 0
assertEquals(0, mStackScroller.getSpeedBumpIndex());
@@ -677,6 +708,8 @@
@Test
public void testRemoveNotificationUpdatesSpeedBump() {
+ mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
// initial state calculated == 0
assertEquals(0, mStackScroller.getSpeedBumpIndex());
@@ -872,6 +905,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void hasFilteredOutSeenNotifs_updateFooter() {
mStackScroller.setCurrentUserSetup(true);
@@ -887,6 +921,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void hasFilteredOutSeenNotifs_updateEmptyShadeView() {
mStackScroller.setHasFilteredOutSeenNotifications(true);
mStackScroller.updateEmptyShadeView(true, false);
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 7744fca..491ed22 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -55,6 +55,7 @@
android.os.Parcel
android.os.Parcelable
android.os.Process
+android.os.ServiceSpecificException
android.os.SystemClock
android.os.ThreadLocalWorkSource
android.os.TimestampedValue
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index cac2efb..08093c0 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1463,4 +1463,9 @@
*/
@NonNull
public abstract PackageArchiver getPackageArchiver();
+
+ /**
+ * Returns true if the device is upgrading from an SDK version lower than the one specified.
+ */
+ public abstract boolean isUpgradingFromLowerThan(int sdkVersion);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d461643..96b1650 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -303,6 +303,23 @@
@Retention(RetentionPolicy.SOURCE)
@interface FgsStopReason {}
+ /**
+ * Disables foreground service background starts from BOOT_COMPLETED broadcasts for all types
+ * except:
+ * <ul>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_HEALTH}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}</li>
+ * </ul>
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+ @Overridable
+ public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L;
+
final ActivityManagerService mAm;
// Maximum number of services that we allow to start in the background
@@ -1053,6 +1070,20 @@
}
}
+ private boolean shouldAllowBootCompletedStart(ServiceRecord r, int foregroundServiceType) {
+ @PowerExemptionManager.ReasonCode final int fgsStartReasonCode =
+ r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mReasonCode
+ : REASON_DENIED;
+ if (Flags.fgsBootCompleted()
+ && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, r.appInfo.uid)
+ && fgsStartReasonCode == PowerExemptionManager.REASON_BOOT_COMPLETED) {
+ // Filter through types
+ return ((foregroundServiceType & mAm.mConstants.FGS_BOOT_COMPLETED_ALLOWLIST) != 0);
+ }
+ // Not BOOT_COMPLETED
+ return true;
+ }
+
private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
int callingUid, int callingPid, String callingProcessName,
int callingProcessState, boolean fgRequired, boolean callerFg,
@@ -2087,6 +2118,11 @@
// anyway, so we just remove the SHORT_SERVICE type.
foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
}
+ if (!shouldAllowBootCompletedStart(r, foregroundServiceType)) {
+ throw new ForegroundServiceStartNotAllowedException("FGS type "
+ + ServiceInfo.foregroundServiceTypeToLabel(foregroundServiceType)
+ + " not allowed to start from BOOT_COMPLETED!");
+ }
boolean alreadyStartedOp = false;
boolean stopProcStatsOp = false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 1d69905..1c3c21c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -16,6 +16,12 @@
package com.android.server.am;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE;
@@ -73,6 +79,9 @@
= "fgservice_screen_on_before_time";
private static final String KEY_FGSERVICE_SCREEN_ON_AFTER_TIME
= "fgservice_screen_on_after_time";
+
+ private static final String KEY_FGS_BOOT_COMPLETED_ALLOWLIST = "fgs_boot_completed_allowlist";
+
private static final String KEY_CONTENT_PROVIDER_RETAIN_TIME = "content_provider_retain_time";
private static final String KEY_GC_TIMEOUT = "gc_timeout";
private static final String KEY_GC_MIN_INTERVAL = "gc_min_interval";
@@ -166,6 +175,15 @@
private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000;
private static final long DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME = 5*1000;
+
+ private static final int DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST =
+ FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
+ | FOREGROUND_SERVICE_TYPE_HEALTH
+ | FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING
+ | FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
+ | FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+ | FOREGROUND_SERVICE_TYPE_LOCATION;
+
private static final long DEFAULT_CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
private static final long DEFAULT_GC_TIMEOUT = 5*1000;
private static final long DEFAULT_GC_MIN_INTERVAL = 60*1000;
@@ -446,6 +464,9 @@
// on until we will stop reporting it.
public long FGSERVICE_SCREEN_ON_AFTER_TIME = DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME;
+ // Allow-list for FGS types that are allowed to start from BOOT_COMPLETED.
+ public int FGS_BOOT_COMPLETED_ALLOWLIST = DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST;
+
// How long we will retain processes hosting content providers in the "last activity"
// state before allowing them to drop down to the regular cached LRU list. This is
// to avoid thrashing of provider processes under low memory situations.
@@ -1450,6 +1471,8 @@
DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME);
FGSERVICE_SCREEN_ON_AFTER_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME,
DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME);
+ FGS_BOOT_COMPLETED_ALLOWLIST = mParser.getInt(KEY_FGS_BOOT_COMPLETED_ALLOWLIST,
+ DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST);
CONTENT_PROVIDER_RETAIN_TIME = mParser.getLong(KEY_CONTENT_PROVIDER_RETAIN_TIME,
DEFAULT_CONTENT_PROVIDER_RETAIN_TIME);
GC_TIMEOUT = mParser.getLong(KEY_GC_TIMEOUT,
@@ -2091,6 +2114,8 @@
pw.println(FGSERVICE_SCREEN_ON_BEFORE_TIME);
pw.print(" "); pw.print(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME); pw.print("=");
pw.println(FGSERVICE_SCREEN_ON_AFTER_TIME);
+ pw.print(" "); pw.print(KEY_FGS_BOOT_COMPLETED_ALLOWLIST); pw.print("=");
+ pw.println(FGS_BOOT_COMPLETED_ALLOWLIST);
pw.print(" "); pw.print(KEY_CONTENT_PROVIDER_RETAIN_TIME); pw.print("=");
pw.println(CONTENT_PROVIDER_RETAIN_TIME);
pw.print(" "); pw.print(KEY_GC_TIMEOUT); pw.print("=");
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 00dd169..848a2b0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -30,6 +30,7 @@
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.Process.INVALID_UID;
import static android.view.Display.INVALID_DISPLAY;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
@@ -555,6 +556,13 @@
} else if (opt.equals("--dismiss-keyguard-if-insecure")
|| opt.equals("--dismiss-keyguard")) {
mDismissKeyguardIfInsecure = true;
+ } else if (opt.equals("--allow-fgs-start-reason")) {
+ final int reasonCode = Integer.parseInt(getNextArgRequired());
+ mBroadcastOptions = BroadcastOptions.makeBasic();
+ mBroadcastOptions.setTemporaryAppAllowlist(10_000,
+ TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ reasonCode,
+ "");
} else {
return false;
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index cc56110..d0d647c 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -179,6 +179,7 @@
"text",
"threadnetwork",
"tv_system_ui",
+ "usb",
"vibrator",
"virtual_devices",
"wallet_integration",
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 3024dd2..8910b6e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -82,6 +82,7 @@
public static final int AUTO_BRIGHTNESS_MODE_DEFAULT = 0;
public static final int AUTO_BRIGHTNESS_MODE_IDLE = 1;
public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2;
+ public static final int AUTO_BRIGHTNESS_MODE_MAX = AUTO_BRIGHTNESS_MODE_DOZE;
// How long the current sensor reading is assumed to be valid beyond the current time.
// This provides a bit of prediction, as well as ensures that the weight for the last sample is
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e38d08f..bc3f9dd 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4573,8 +4573,10 @@
if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
final DisplayPowerControllerInterface displayPowerController =
mDisplayPowerControllers.get(id);
- ready &= displayPowerController.requestPowerState(request,
- waitForNegativeProximity);
+ if (displayPowerController != null) {
+ ready &= displayPowerController.requestPowerState(request,
+ waitForNegativeProximity);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 4e341a9..a43f93a 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_MAX;
+
import android.annotation.Nullable;
import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
@@ -65,6 +67,22 @@
return true;
}
+ @Override
+ public float[] getAutoBrightnessLevels(int mode) {
+ if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+ throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+ }
+ return mDisplayPowerController.getAutoBrightnessLevels(mode);
+ }
+
+ @Override
+ public float[] getAutoBrightnessLuxLevels(int mode) {
+ if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+ throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+ }
+ return mDisplayPowerController.getAutoBrightnessLuxLevels(mode);
+ }
+
/**
* Start the offload session. The method returns if the session is already active.
* @return Whether the session was started successfully
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 06e5f99..734381b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2214,6 +2214,20 @@
}
@Override
+ public float[] getAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ // The old DPC is no longer supported
+ return null;
+ }
+
+ @Override
+ public float[] getAutoBrightnessLuxLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ // The old DPC is no longer supported
+ return null;
+ }
+
+ @Override
public BrightnessInfo getBrightnessInfo() {
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 519224a..7df6114 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1886,6 +1886,24 @@
}
@Override
+ public float[] getAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+ return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
+ }
+
+ @Override
+ public float[] getAutoBrightnessLuxLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+ return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+ }
+
+ @Override
public BrightnessInfo getBrightnessInfo() {
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index c279184..13acb3f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -237,4 +237,21 @@
* Indicate that boot has been completed and the screen is ready to update.
*/
void onBootCompleted();
+
+ /**
+ * Get the brightness levels used to determine automatic brightness based on lux levels.
+ * @param mode The auto-brightness mode
+ * @return The brightness levels for the specified mode. The values are between
+ * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+ */
+ float[] getAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
+
+ /**
+ * Get the lux levels used to determine automatic brightness.
+ * @param mode The auto-brightness mode
+ * @return The lux levels for the specified mode
+ */
+ float[] getAutoBrightnessLuxLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 27b01a5..9c4225d 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -67,6 +67,7 @@
import android.location.LocationManager;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.flags.Flags;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
@@ -1380,7 +1381,11 @@
location.setExtras(mLocationExtras.getBundle());
- reportLocation(LocationResult.wrap(location).validate());
+ try {
+ reportLocation(LocationResult.wrap(location).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
if (mStarted) {
mGnssMetrics.logReceivedLocationStatus(hasLatLong);
@@ -1751,7 +1756,11 @@
}
}
- reportLocation(LocationResult.wrap(locations).validate());
+ try {
+ reportLocation(LocationResult.wrap(locations).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
}
Runnable[] listeners;
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 91e6a80..7d44aec 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -64,6 +64,7 @@
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.altitude.AltitudeConverter;
import android.location.provider.IProviderRequestListener;
import android.location.provider.ProviderProperties;
@@ -910,7 +911,8 @@
< getRequest().getMinUpdateIntervalMillis() - maxJitterMs) {
if (D) {
Log.v(TAG, mName + " provider registration " + getIdentity()
- + " dropped delivery - too fast");
+ + " dropped delivery - too fast (deltaMs="
+ + deltaMs + ").");
}
return false;
}
@@ -2574,29 +2576,17 @@
@GuardedBy("mMultiplexerLock")
@Nullable
private LocationResult processReportedLocation(LocationResult locationResult) {
- LocationResult processed = locationResult.filter(location -> {
- if (!location.isMock()) {
- if (location.getLatitude() == 0 && location.getLongitude() == 0) {
- Log.e(TAG, "blocking 0,0 location from " + mName + " provider");
- return false;
- }
- }
-
- if (!location.isComplete()) {
- Log.e(TAG, "blocking incomplete location from " + mName + " provider");
- return false;
- }
-
- return true;
- });
- if (processed == null) {
+ try {
+ locationResult.validate();
+ } catch (BadLocationException e) {
+ Log.e(TAG, "Dropping invalid locations: " + e);
return null;
}
// Attempt to add a missing MSL altitude on behalf of the provider.
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_LOCATION,
"enable_location_provider_manager_msl", true)) {
- return processed.map(location -> {
+ return locationResult.map(location -> {
if (!location.hasMslAltitude() && location.hasAltitude()) {
try {
Location locationCopy = new Location(location);
@@ -2626,7 +2616,7 @@
return location;
});
}
- return processed;
+ return locationResult;
}
@GuardedBy("mMultiplexerLock")
diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
index 52b04d4..4efacd7 100644
--- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.location.Location;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
@@ -55,7 +56,11 @@
Location location = new Location(l);
location.setIsFromMockProvider(true);
mLocation = location;
- reportLocation(LocationResult.wrap(location).validate());
+ try {
+ reportLocation(LocationResult.wrap(location).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 05966da..a597edd 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -305,7 +305,7 @@
return;
}
- reportLocation(LocationResult.wrap(location).validate());
+ reportLocation(LocationResult.wrap(location));
}
}
@@ -316,8 +316,7 @@
if (mProxy != this) {
return;
}
-
- reportLocation(LocationResult.wrap(locations).validate());
+ reportLocation(LocationResult.wrap(locations));
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 38f0df4..9088cb9 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2700,11 +2700,8 @@
// session info from them.
sessionInfo = mSystemProvider.getDefaultSessionInfo();
}
- // TODO: b/279555229 - replace with matchingRequest.mRouterRecord.notifySessionCreated.
- notifySessionCreatedToRouter(
- matchingRequest.mRouterRecord,
- toOriginalRequestId(uniqueRequestId),
- sessionInfo);
+ matchingRequest.mRouterRecord.notifySessionCreated(
+ toOriginalRequestId(uniqueRequestId), sessionInfo);
}
private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
@@ -2812,11 +2809,6 @@
return true;
}
- private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord,
- int requestId, @NonNull RoutingSessionInfo sessionInfo) {
- routerRecord.notifySessionCreated(requestId, sessionInfo);
- }
-
private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
int requestId) {
try {
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index b424c20..07b333a 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import android.app.ForegroundServiceDelegationOptions;
import android.media.MediaController2;
import android.media.Session2CommandGroup;
import android.media.Session2Token;
@@ -89,6 +90,12 @@
}
@Override
+ public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+ // TODO: Implement when MediaSession2 knows about its owner pid.
+ return null;
+ }
+
+ @Override
public boolean isSystemPriority() {
// System priority session is currently only allowed for telephony, so it's OK to stick to
// the media1 API at this moment.
@@ -217,7 +224,8 @@
synchronized (mLock) {
service = mService;
}
- service.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
+ service.onSessionPlaybackStateChanged(
+ MediaSession2Record.this, playbackActive, /* playbackState= */ null);
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 994d3ca..cce66e2 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -29,6 +29,7 @@
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
@@ -182,6 +183,8 @@
private final Context mContext;
private final boolean mVolumeAdjustmentForRemoteGroupSessions;
+ private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
+
private final Object mLock = new Object();
private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
mControllerCallbackHolders = new CopyOnWriteArrayList<>();
@@ -244,10 +247,32 @@
mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
+ mForegroundServiceDelegationOptions = createForegroundServiceDelegationOptions();
+
// May throw RemoteException if the session app is killed.
mSessionCb.mCb.asBinder().linkToDeath(this, 0);
}
+ private ForegroundServiceDelegationOptions createForegroundServiceDelegationOptions() {
+ return new ForegroundServiceDelegationOptions.Builder()
+ .setClientPid(mOwnerPid)
+ .setClientUid(getUid())
+ .setClientPackageName(getPackageName())
+ .setClientAppThread(null)
+ .setSticky(false)
+ .setClientInstanceName(
+ "MediaSessionFgsDelegate_"
+ + getUid()
+ + "_"
+ + mOwnerPid
+ + "_"
+ + getPackageName())
+ .setForegroundServiceTypes(0)
+ .setDelegationService(
+ ForegroundServiceDelegationOptions.DELEGATION_SERVICE_MEDIA_PLAYBACK)
+ .build();
+ }
+
/**
* Get the session binder for the {@link MediaSession}.
*
@@ -681,6 +706,11 @@
return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
}
+ @Override
+ public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+ return mForegroundServiceDelegationOptions;
+ }
+
private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
final String callingOpPackageName, final int callingPid, final int callingUid,
final boolean asSystemService, final boolean useSuggested,
@@ -1273,7 +1303,7 @@
final long token = Binder.clearCallingIdentity();
try {
mService.onSessionPlaybackStateChanged(
- MediaSessionRecord.this, shouldUpdatePriority);
+ MediaSessionRecord.this, shouldUpdatePriority, mPlaybackState);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 8f01f02..99c8ea9 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -16,7 +16,9 @@
package com.android.server.media;
+import android.app.ForegroundServiceDelegationOptions;
import android.media.AudioManager;
+import android.media.session.PlaybackState;
import android.os.ResultReceiver;
import android.view.KeyEvent;
@@ -51,6 +53,15 @@
int getUserId();
/**
+ * Get the {@link ForegroundServiceDelegationOptions} needed for notifying activity manager
+ * service with changes in the {@link PlaybackState} for this session.
+ *
+ * @return the {@link ForegroundServiceDelegationOptions} needed for notifying the activity
+ * manager service with changes in the {@link PlaybackState} for this session.
+ */
+ ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions();
+
+ /**
* Check if this session has system priority and should receive media buttons before any other
* sessions.
*
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2c59511..d4666ba 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -29,6 +29,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -59,6 +61,7 @@
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -144,6 +147,7 @@
private AudioManager mAudioManager;
private boolean mHasFeatureLeanback;
private ActivityManagerLocal mActivityManagerLocal;
+ private ActivityManagerInternal mActivityManagerInternal;
// The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
// It's always not null after the MediaSessionService is started.
@@ -229,6 +233,7 @@
mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter);
mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
}
@Override
@@ -285,11 +290,31 @@
}
user.mPriorityStack.onSessionActiveStateChanged(record);
}
-
+ notifyActivityManagerWithActiveStateChanges(record, record.isActive());
mHandler.postSessionsChanged(record);
}
}
+ private void notifyActivityManagerWithActiveStateChanges(
+ MediaSessionRecordImpl record, boolean isActive) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ if (isActive) {
+ mActivityManagerInternal.startForegroundServiceDelegate(
+ foregroundServiceDelegationOptions, /* connection= */ null);
+ } else {
+ mActivityManagerInternal.stopForegroundServiceDelegate(
+ foregroundServiceDelegationOptions);
+ }
+ }
+
// Currently only media1 can become global priority session.
void setGlobalPrioritySession(MediaSessionRecord record) {
synchronized (mLock) {
@@ -371,8 +396,10 @@
}
}
- void onSessionPlaybackStateChanged(MediaSessionRecordImpl record,
- boolean shouldUpdatePriority) {
+ void onSessionPlaybackStateChanged(
+ MediaSessionRecordImpl record,
+ boolean shouldUpdatePriority,
+ @Nullable PlaybackState playbackState) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user == null || !user.mPriorityStack.contains(record)) {
@@ -380,6 +407,27 @@
return;
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
+ notifyActivityManagerWithPlaybackStateChanges(record, playbackState);
+ }
+ }
+
+ private void notifyActivityManagerWithPlaybackStateChanges(
+ MediaSessionRecordImpl record, PlaybackState playbackState) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null || playbackState == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ if (playbackState.shouldAllowServiceToRunInForeground()) {
+ mActivityManagerInternal.startForegroundServiceDelegate(
+ foregroundServiceDelegationOptions, /* connection= */ null);
+ } else {
+ mActivityManagerInternal.stopForegroundServiceDelegate(
+ foregroundServiceDelegationOptions);
}
}
@@ -543,9 +591,23 @@
}
session.close();
+ notifyActivityManagerWithSessionDestroyed(session);
mHandler.postSessionsChanged(session);
}
+ private void notifyActivityManagerWithSessionDestroyed(MediaSessionRecordImpl record) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ mActivityManagerInternal.stopForegroundServiceDelegate(foregroundServiceDelegationOptions);
+ }
+
void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
int callingPid, int callingUid, String callingPackage, String reason) {
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 46e7041..e4e48bd 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4090,6 +4090,7 @@
}
fout.decreaseIndent();
+ fout.println();
fout.println("Admin restricted uids for metered data:");
fout.increaseIndent();
size = mMeteredRestrictedUids.size();
@@ -4099,6 +4100,7 @@
}
fout.decreaseIndent();
+ fout.println();
fout.println("Network to interfaces:");
fout.increaseIndent();
for (int i = 0; i < mNetworkToIfaces.size(); ++i) {
@@ -4108,6 +4110,10 @@
fout.decreaseIndent();
fout.println();
+ fout.print("Active notifications: ");
+ fout.println(mActiveNotifs);
+
+ fout.println();
mStatLogger.dump(fout);
mLogger.dumpLogs(fout);
@@ -6672,7 +6678,7 @@
* Build unique tag that identifies an active {@link NetworkPolicy}
* notification of a specific type, like {@link #TYPE_LIMIT}.
*/
- private String buildNotificationTag(NetworkPolicy policy, int type) {
+ private static String buildNotificationTag(NetworkPolicy policy, int type) {
return TAG + ":" + policy.template.hashCode() + ":" + type;
}
@@ -6683,5 +6689,10 @@
public int getId() {
return mId;
}
+
+ @Override
+ public String toString() {
+ return mTag;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index b96b704..c920ca8 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -829,6 +829,9 @@
int returnCodeOfChild;
for (int childId : childUserIds) {
if (childId == userId) continue;
+ if (mUserManagerInternal.getProfileParentId(childId) != userId) {
+ continue;
+ }
// If package is not present in child then don't attempt to delete.
if (!packageState.getUserStateOrDefault(childId).isInstalled()) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 81d5d81..b7deef0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -607,6 +607,8 @@
private final boolean mIsUpgrade;
private final boolean mIsPreNMR1Upgrade;
private final boolean mIsPreQUpgrade;
+ // If mIsUpgrade == true, contains the prior SDK version, else -1.
+ private final int mPriorSdkVersion;
// Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
// LOCK HELD. Can be called with mInstallLock held.
@@ -1891,6 +1893,7 @@
mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
mIsPreQUpgrade = testParams.isPreQupgrade;
+ mPriorSdkVersion = testParams.priorSdkVersion;
mIsUpgrade = testParams.isUpgrade;
mMetrics = testParams.Metrics;
mModuleInfoProvider = testParams.moduleInfoProvider;
@@ -2230,7 +2233,7 @@
"Upgrading from " + ver.fingerprint + " (" + ver.buildFingerprint + ") to "
+ PackagePartitions.FINGERPRINT + " (" + Build.FINGERPRINT + ")");
}
-
+ mPriorSdkVersion = mIsUpgrade ? ver.sdkVersion : -1;
mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
mInjector.getSystemPartitions());
@@ -7099,6 +7102,12 @@
mPackageMonitorCallbackHelper.notifyPackageMonitorWithIntent(intent, userId,
visibilityAllowList, mHandler);
}
+
+ @Override
+ public boolean isUpgradingFromLowerThan(int sdkVersion) {
+ final boolean isUpgrading = mPriorSdkVersion != -1;
+ return isUpgrading && mPriorSdkVersion < sdkVersion;
+ }
}
private void setEnabledOverlayPackages(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 86d78dc..2d79718 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -65,6 +65,7 @@
public ComponentName instantAppResolverSettingsComponent;
public boolean isPreNmr1Upgrade;
public boolean isPreQupgrade;
+ public int priorSdkVersion = -1;
public boolean isUpgrade;
public LegacyPermissionManagerInternal legacyPermissionManagerInternal;
public DisplayMetrics Metrics;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index edae273..b286b12 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -64,6 +64,7 @@
import android.os.Message;
import android.os.PatternMatcher;
import android.os.PersistableBundle;
+import android.os.Process;
import android.os.SELinux;
import android.os.SystemClock;
import android.os.Trace;
@@ -3189,6 +3190,9 @@
pkg.isScannedAsStoppedSystemApp());
if (!pkg.hasSharedUser()) {
serializer.attributeInt(null, "userId", pkg.getAppId());
+
+ serializer.attributeBoolean(null, "isSdkLibrary",
+ pkg.getAndroidPackage() != null && pkg.getAndroidPackage().isSdkLibrary());
} else {
serializer.attributeInt(null, "sharedUserId", pkg.getAppId());
}
@@ -4039,10 +4043,12 @@
int targetSdkVersion = 0;
byte[] restrictUpdateHash = null;
boolean isScannedAsStoppedSystemApp = false;
+ boolean isSdkLibrary = false;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
appId = parseAppId(parser);
+ isSdkLibrary = parser.getAttributeBoolean(null, "isSdkLibrary", false);
sharedUserAppId = parseSharedUserAppId(parser);
codePathStr = parser.getAttributeValue(null, "codePath");
@@ -4157,7 +4163,8 @@
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: <package> has no codePath at "
+ parser.getPositionDescription());
- } else if (appId > 0) {
+ } else if (appId > 0 || (appId == Process.INVALID_UID && isSdkLibrary
+ && Flags.disallowSdkLibsToBeApps())) {
packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
appId, pkgFlags, pkgPrivateFlags, domainSetId);
if (PackageManagerService.DEBUG_SETTINGS)
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 59e95e7..1185a4e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2734,11 +2734,7 @@
// AdServicesManagerService (PP API service)
t.traceBegin("StartAdServicesManagerService");
- SystemService adServices = mSystemServiceManager
- .startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
- if (adServices instanceof Dumpable) {
- mDumper.addDumpable((Dumpable) adServices);
- }
+ mSystemServiceManager.startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
t.traceEnd();
// OnDevicePersonalizationSystemService
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 293003d..32878b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -67,6 +67,7 @@
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.flags.Flags;
import android.location.provider.IProviderRequestListener;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
@@ -78,8 +79,10 @@
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.Log;
@@ -97,6 +100,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -140,6 +144,9 @@
private static final WorkSource WORK_SOURCE = new WorkSource(IDENTITY.getUid());
private static final String MISSING_PERMISSION = "missing_permission";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private Random mRandom;
@Mock
@@ -1347,6 +1354,24 @@
assertThat(mManager.isVisibleToCaller()).isFalse();
}
+ @Test
+ public void testValidateLocation_futureLocation() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_LOCATION_VALIDATION);
+ Location location = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(location);
+
+ assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+ PERMISSION_FINE)).isEqualTo(location);
+
+ Location futureLocation = createLocation(NAME, mRandom);
+ futureLocation.setElapsedRealtimeNanos(
+ SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos(2));
+ mProvider.setProviderLocation(futureLocation);
+
+ assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+ PERMISSION_FINE)).isEqualTo(location);
+ }
+
@MediumTest
@Test
public void testEnableMsl_expectedBehavior() throws Exception {
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
index 25d208d..5cbb1aa 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -77,7 +77,9 @@
.autoFix()
.build()
- return LintFix.create().composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
+ return LintFix.create()
+ .name(annotateFix.getDisplayName())
+ .composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
}
private val annotation: String