diff options
50 files changed, 812 insertions, 210 deletions
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index fe10b7f8b3f4..27f6a266597c 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -31,6 +31,7 @@ import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; +import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PointF; import android.graphics.Rect; @@ -311,20 +312,27 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW super.onLayout(changed, left, top, right, bottom); } catch (final RuntimeException e) { Log.e(TAG, "Remote provider threw runtime exception, using error view instead.", e); - removeViewInLayout(mView); - View child = getErrorView(); - prepareView(child); - addViewInLayout(child, 0, child.getLayoutParams()); - measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); - child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight, - child.getMeasuredHeight() + mPaddingTop + mPaddingBottom); - mView = child; - mViewMode = VIEW_MODE_ERROR; + handleViewError(); } } /** + * Remove bad view and replace with error message view + */ + private void handleViewError() { + removeViewInLayout(mView); + View child = getErrorView(); + prepareView(child); + addViewInLayout(child, 0, child.getLayoutParams()); + measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); + child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight, + child.getMeasuredHeight() + mPaddingTop + mPaddingBottom); + mView = child; + mViewMode = VIEW_MODE_ERROR; + } + + /** * Provide guidance about the size of this widget to the AppWidgetManager. The widths and * heights should correspond to the full area the AppWidgetHostView is given. Padding added by * the framework will be accounted for automatically. This information gets embedded into the @@ -953,4 +961,15 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW reapplyLastRemoteViews(); } } + + @Override + protected void dispatchDraw(@NonNull Canvas canvas) { + try { + super.dispatchDraw(canvas); + } catch (Exception e) { + // Catch draw exceptions that may be caused by RemoteViews + Log.e(TAG, "Drawing view failed: " + e); + post(this::handleViewError); + } + } } diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 63dc7c7ed661..aef2ae26747d 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -40,6 +40,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.Trace; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -1006,7 +1007,8 @@ public final class DisplayManagerGlobal { @Override public void onDisplayEvent(int displayId, @DisplayEvent int event) { if (DEBUG) { - Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event); + Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + eventToString( + event)); } handleDisplayEvent(displayId, event); } @@ -1040,6 +1042,12 @@ public final class DisplayManagerGlobal { @Override public void handleMessage(Message msg) { + if (DEBUG) { + Trace.beginSection( + "DisplayListenerDelegate(" + eventToString(msg.what) + + ", display=" + msg.arg1 + + ", listener=" + mListener.getClass() + ")"); + } switch (msg.what) { case EVENT_DISPLAY_ADDED: if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) { @@ -1066,6 +1074,9 @@ public final class DisplayManagerGlobal { } break; } + if (DEBUG) { + Trace.endSection(); + } } } @@ -1172,4 +1183,18 @@ public final class DisplayManagerGlobal { updateCallbackIfNeededLocked(); } } + + private static String eventToString(@DisplayEvent int event) { + switch (event) { + case EVENT_DISPLAY_ADDED: + return "ADDED"; + case EVENT_DISPLAY_CHANGED: + return "CHANGED"; + case EVENT_DISPLAY_REMOVED: + return "REMOVED"; + case EVENT_DISPLAY_BRIGHTNESS_CHANGED: + return "BRIGHTNESS_CHANGED"; + } + return "UNKNOWN"; + } } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 52d222b19b6a..f85f9067e347 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1600,6 +1600,21 @@ public final class Display { } /** + * Returns the committed state of the display. + * + * @return The latest committed display state, such as {@link #STATE_ON}. The display state + * {@link Display#getState()} is set as committed only after power state changes finish. + * + * @hide + */ + public int getCommittedState() { + synchronized (mLock) { + updateDisplayInfoLocked(); + return mIsValid ? mDisplayInfo.committedState : STATE_UNKNOWN; + } + } + + /** * Returns true if the specified UID has access to this display. * @hide */ diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 12ce8ee5e0ad..f65a69a8e2bc 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -253,6 +253,12 @@ public final class DisplayInfo implements Parcelable { public int state; /** + * The current committed state of the display. For example, this becomes + * {@link android.view.Display#STATE_ON} only after the power state ON is fully committed. + */ + public int committedState; + + /** * The UID of the application that owns this display, or zero if it is owned by the system. * <p> * If the display is private, then only the owner can use it. @@ -380,6 +386,7 @@ public final class DisplayInfo implements Parcelable { && appVsyncOffsetNanos == other.appVsyncOffsetNanos && presentationDeadlineNanos == other.presentationDeadlineNanos && state == other.state + && committedState == other.committedState && ownerUid == other.ownerUid && Objects.equals(ownerPackageName, other.ownerPackageName) && removeMode == other.removeMode @@ -431,6 +438,7 @@ public final class DisplayInfo implements Parcelable { appVsyncOffsetNanos = other.appVsyncOffsetNanos; presentationDeadlineNanos = other.presentationDeadlineNanos; state = other.state; + committedState = other.committedState; ownerUid = other.ownerUid; ownerPackageName = other.ownerPackageName; removeMode = other.removeMode; @@ -482,6 +490,7 @@ public final class DisplayInfo implements Parcelable { appVsyncOffsetNanos = source.readLong(); presentationDeadlineNanos = source.readLong(); state = source.readInt(); + committedState = source.readInt(); ownerUid = source.readInt(); ownerPackageName = source.readString8(); uniqueId = source.readString8(); @@ -538,6 +547,7 @@ public final class DisplayInfo implements Parcelable { dest.writeLong(appVsyncOffsetNanos); dest.writeLong(presentationDeadlineNanos); dest.writeInt(state); + dest.writeInt(committedState); dest.writeInt(ownerUid); dest.writeString8(ownerPackageName); dest.writeString8(uniqueId); @@ -761,6 +771,8 @@ public final class DisplayInfo implements Parcelable { sb.append(rotation); sb.append(", state "); sb.append(Display.stateToString(state)); + sb.append(", committedState "); + sb.append(Display.stateToString(committedState)); if (Process.myUid() != Process.SYSTEM_UID) { sb.append("}"); diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml index 38a71f0e17f6..bb0f70455e78 100644 --- a/core/res/res/layout/miniresolver.xml +++ b/core/res/res/layout/miniresolver.xml @@ -50,6 +50,7 @@ android:paddingTop="16dp" android:layout_below="@id/icon" android:layout_centerHorizontal="true" + android:fontFamily="@string/config_headlineFontFamily" android:textSize="24sp" android:lineHeight="32sp" android:gravity="center" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index a592e79b9581..f8ecd2e995e6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -103,7 +103,6 @@ import com.android.wm.shell.sysui.UserChangeListener; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; -import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -118,6 +117,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb UserChangeListener { private static final String TAG = "PipController"; + private static final String LAUNCHER_KEEP_CLEAR_AREA_TAG = "hotseat"; + private static final long PIP_KEEP_CLEAR_AREAS_DELAY = SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200); @@ -929,12 +930,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb 0, mPipBoundsState.getDisplayBounds().bottom - height, mPipBoundsState.getDisplayBounds().right, mPipBoundsState.getDisplayBounds().bottom); - Set<Rect> restrictedKeepClearAreas = new HashSet<>( - mPipBoundsState.getRestrictedKeepClearAreas()); - restrictedKeepClearAreas.add(rect); - mPipBoundsState.setKeepClearAreas(restrictedKeepClearAreas, - mPipBoundsState.getUnrestrictedKeepClearAreas()); + mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect); updatePipPositionForKeepClearAreas(); + } else { + mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java index bd9e760acfda..c8bcabff1094 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java @@ -35,6 +35,7 @@ import android.util.Slog; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.function.Predicate; /** * Class for managing services matching a given intent and requesting a given permission. @@ -51,12 +52,13 @@ public class ServiceListing { private final HashSet<ComponentName> mEnabledServices = new HashSet<>(); private final List<ServiceInfo> mServices = new ArrayList<>(); private final List<Callback> mCallbacks = new ArrayList<>(); + private final Predicate mValidator; private boolean mListening; private ServiceListing(Context context, String tag, String setting, String intentAction, String permission, String noun, - boolean addDeviceLockedFlags) { + boolean addDeviceLockedFlags, Predicate validator) { mContentResolver = context.getContentResolver(); mContext = context; mTag = tag; @@ -65,6 +67,7 @@ public class ServiceListing { mPermission = permission; mNoun = noun; mAddDeviceLockedFlags = addDeviceLockedFlags; + mValidator = validator; } public void addCallback(Callback callback) { @@ -137,7 +140,6 @@ public class ServiceListing { final PackageManager pmWrapper = mContext.getPackageManager(); List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser( new Intent(mIntentAction), flags, user); - for (ResolveInfo resolveInfo : installedServices) { ServiceInfo info = resolveInfo.serviceInfo; @@ -148,6 +150,9 @@ public class ServiceListing { + mPermission); continue; } + if (mValidator != null && !mValidator.test(info)) { + continue; + } mServices.add(info); } for (Callback callback : mCallbacks) { @@ -194,6 +199,7 @@ public class ServiceListing { private String mPermission; private String mNoun; private boolean mAddDeviceLockedFlags = false; + private Predicate mValidator; public Builder(Context context) { mContext = context; @@ -224,6 +230,11 @@ public class ServiceListing { return this; } + public Builder setValidator(Predicate<ServiceInfo> validator) { + mValidator = validator; + return this; + } + /** * Set to true to add support for both MATCH_DIRECT_BOOT_AWARE and * MATCH_DIRECT_BOOT_UNAWARE flags when querying PackageManager. Required to get results @@ -236,7 +247,7 @@ public class ServiceListing { public ServiceListing build() { return new ServiceListing(mContext, mTag, mSetting, mIntentAction, mPermission, mNoun, - mAddDeviceLockedFlags); + mAddDeviceLockedFlags, mValidator); } } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java index f7fd25b9fb7d..7ff0988c494d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java @@ -18,20 +18,35 @@ package com.android.settingslib.applications; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.provider.Settings; +import androidx.test.core.app.ApplicationProvider; + +import com.google.common.collect.ImmutableList; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import java.util.List; + @RunWith(RobolectricTestRunner.class) public class ServiceListingTest { @@ -39,16 +54,97 @@ public class ServiceListingTest { private static final String TEST_INTENT = "com.example.intent"; private ServiceListing mServiceListing; + private Context mContext; + private PackageManager mPm; @Before public void setUp() { - mServiceListing = new ServiceListing.Builder(RuntimeEnvironment.application) + mPm = mock(PackageManager.class); + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getPackageManager()).thenReturn(mPm); + + mServiceListing = new ServiceListing.Builder(mContext) + .setTag("testTag") + .setSetting(TEST_SETTING) + .setNoun("testNoun") + .setIntentAction(TEST_INTENT) + .setPermission("testPermission") + .build(); + } + + @Test + public void testValidator() { + ServiceInfo s1 = new ServiceInfo(); + s1.permission = "testPermission"; + s1.packageName = "pkg"; + ServiceInfo s2 = new ServiceInfo(); + s2.permission = "testPermission"; + s2.packageName = "pkg2"; + ResolveInfo r1 = new ResolveInfo(); + r1.serviceInfo = s1; + ResolveInfo r2 = new ResolveInfo(); + r2.serviceInfo = s2; + + when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn( + ImmutableList.of(r1, r2)); + + mServiceListing = new ServiceListing.Builder(mContext) + .setTag("testTag") + .setSetting(TEST_SETTING) + .setNoun("testNoun") + .setIntentAction(TEST_INTENT) + .setValidator(info -> { + if (info.packageName.equals("pkg")) { + return true; + } + return false; + }) + .setPermission("testPermission") + .build(); + ServiceListing.Callback callback = mock(ServiceListing.Callback.class); + mServiceListing.addCallback(callback); + mServiceListing.reload(); + + verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt()); + ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class); + verify(callback, times(1)).onServicesReloaded(captor.capture()); + + assertThat(captor.getValue().size()).isEqualTo(1); + assertThat(captor.getValue().get(0)).isEqualTo(s1); + } + + @Test + public void testNoValidator() { + ServiceInfo s1 = new ServiceInfo(); + s1.permission = "testPermission"; + s1.packageName = "pkg"; + ServiceInfo s2 = new ServiceInfo(); + s2.permission = "testPermission"; + s2.packageName = "pkg2"; + ResolveInfo r1 = new ResolveInfo(); + r1.serviceInfo = s1; + ResolveInfo r2 = new ResolveInfo(); + r2.serviceInfo = s2; + + when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn( + ImmutableList.of(r1, r2)); + + mServiceListing = new ServiceListing.Builder(mContext) .setTag("testTag") .setSetting(TEST_SETTING) .setNoun("testNoun") .setIntentAction(TEST_INTENT) .setPermission("testPermission") .build(); + ServiceListing.Callback callback = mock(ServiceListing.Callback.class); + mServiceListing.addCallback(callback); + mServiceListing.reload(); + + verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt()); + ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class); + verify(callback, times(1)).onServicesReloaded(captor.capture()); + + assertThat(captor.getValue().size()).isEqualTo(2); } @Test diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java index 162bb2c720e8..b8887ae872d5 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java @@ -49,6 +49,7 @@ public class GlobalSettings { Settings.Global.CHARGING_SOUNDS_ENABLED, Settings.Global.USB_MASS_STORAGE_ENABLED, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, + Settings.Global.NETWORK_AVOID_BAD_WIFI, Settings.Global.WIFI_WAKEUP_ENABLED, Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, Settings.Global.USE_OPEN_WIFI_PACKAGE, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index b1522092dbe9..8784d87b1942 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -17,6 +17,9 @@ package android.provider.settings.validators; import static android.media.AudioFormat.SURROUND_SOUND_ENCODING; +import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_AVOID; +import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_IGNORE; +import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_PROMPT; import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR; @@ -95,6 +98,14 @@ public class GlobalSettingsValidators { VALIDATORS.put( Global.NETWORK_RECOMMENDATIONS_ENABLED, new DiscreteValueValidator(new String[] {"-1", "0", "1"})); + VALIDATORS.put( + Global.NETWORK_AVOID_BAD_WIFI, + new DiscreteValueValidator( + new String[] { + String.valueOf(NETWORK_AVOID_BAD_WIFI_IGNORE), + String.valueOf(NETWORK_AVOID_BAD_WIFI_PROMPT), + String.valueOf(NETWORK_AVOID_BAD_WIFI_AVOID), + })); VALIDATORS.put(Global.WIFI_WAKEUP_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, BOOLEAN_VALIDATOR); VALIDATORS.put( @@ -339,4 +350,3 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.Wearable.COOLDOWN_MODE_ON, BOOLEAN_VALIDATOR); } } - diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index fd0fa3ac82fe..3c2aefdaa8fb 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -377,7 +377,6 @@ public class SettingsBackupTest { Settings.Global.NETPOLICY_QUOTA_FRAC_JOBS, Settings.Global.NETPOLICY_QUOTA_FRAC_MULTIPATH, Settings.Global.NETPOLICY_OVERRIDE_ENABLED, - Settings.Global.NETWORK_AVOID_BAD_WIFI, Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES, Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE, Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME, diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 71a82bf92f0c..aadc14061a61 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -878,7 +878,7 @@ android:showForAllUsers="true" android:finishOnTaskLaunch="true" android:launchMode="singleInstance" - android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden" + android:configChanges="screenLayout|keyboard|keyboardHidden|orientation" android:visibleToInstantApps="true"> </activity> @@ -900,7 +900,7 @@ android:showWhenLocked="true" android:showForAllUsers="true" android:finishOnTaskLaunch="true" - android:lockTaskMode="if_whitelisted" + android:lockTaskMode="always" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 9a9f5106b7d8..81241c91d1c6 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2258,7 +2258,7 @@ <!-- Shows in a dialog presented to the user to authorize this app to display a Device controls panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=NONE] --> - <string name="controls_panel_authorization"><xliff:g id="appName" example="My app">%s</xliff:g>can choose which controls and content show here.</string> + <string name="controls_panel_authorization"><xliff:g id="appName" example="My app">%s</xliff:g> can choose which controls and content show here.</string> <!-- Shows in a dialog presented to the user to authorize this app removal from a Device controls panel [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt index bf0a69296dfd..224eb1ca409a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt @@ -20,6 +20,8 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.pm.ActivityInfo +import android.content.res.Configuration import android.os.Bundle import android.os.RemoteException import android.service.dreams.IDreamManager @@ -57,9 +59,11 @@ class ControlsActivity @Inject constructor( private lateinit var parent: ViewGroup private lateinit var broadcastReceiver: BroadcastReceiver private var mExitToDream: Boolean = false + private lateinit var lastConfiguration: Configuration override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + lastConfiguration = resources.configuration if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) { window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY) } @@ -92,6 +96,14 @@ class ControlsActivity @Inject constructor( initBroadcastReceiver() } + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + if (lastConfiguration.diff(newConfig) and ActivityInfo.CONFIG_ORIENTATION != 0 ) { + uiController.onOrientationChange() + } + lastConfiguration = newConfig + } + override fun onStart() { super.onStart() diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt index 0d5311752ab9..3ecf4236656d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -64,6 +64,8 @@ interface ControlsUiController { * This element will be the one that appears when the user first opens the controls activity. */ fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem + + fun onOrientationChange() } sealed class SelectedItem { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 5da86de933e6..163990ec7d4b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -124,6 +124,7 @@ class ControlsUiControllerImpl @Inject constructor ( } private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION + private var selectionItem: SelectionItem? = null private lateinit var allStructures: List<StructureInfo> private val controlsById = mutableMapOf<ControlKey, ControlWithState>() private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>() @@ -230,6 +231,7 @@ class ControlsUiControllerImpl @Inject constructor ( this.overflowMenuAdapter = null hidden = false retainCache = false + selectionItem = null controlActionCoordinator.activityContext = activityContext @@ -272,7 +274,7 @@ class ControlsUiControllerImpl @Inject constructor ( } } - private fun reload(parent: ViewGroup) { + private fun reload(parent: ViewGroup, dismissTaskView: Boolean = true) { if (hidden) return controlsListingController.get().removeCallback(listingCallback) @@ -425,6 +427,7 @@ class ControlsUiControllerImpl @Inject constructor ( } else { Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem") } + this.selectionItem = selectionItem bgExecutor.execute { val intent = Intent(Intent.ACTION_MAIN) @@ -657,6 +660,7 @@ class ControlsUiControllerImpl @Inject constructor ( val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources) val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup + listView.removeAllViews() var lastRow: ViewGroup = createRow(inflater, listView) selectedStructure.controls.forEach { val key = ControlKey(selectedStructure.componentName, it.controlId) @@ -804,6 +808,15 @@ class ControlsUiControllerImpl @Inject constructor ( } } + override fun onOrientationChange() { + selectionItem?.let { + when (selectedItem) { + is SelectedItem.StructureItem -> createListView(it) + is SelectedItem.PanelItem -> taskViewController?.refreshBounds() ?: reload(parent) + } + } ?: reload(parent) + } + private fun createRow(inflater: LayoutInflater, listView: ViewGroup): ViewGroup { val row = inflater.inflate(R.layout.controls_row, listView, false) as ViewGroup listView.addView(row) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt index 78e87cafc4f2..1f89c917186a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt @@ -37,7 +37,7 @@ class PanelTaskViewController( private val activityContext: Context, private val uiExecutor: Executor, private val pendingIntent: PendingIntent, - private val taskView: TaskView, + val taskView: TaskView, private val hide: () -> Unit = {} ) { @@ -108,6 +108,10 @@ class PanelTaskViewController( } } + fun refreshBounds() { + taskView.onLocationChanged() + } + fun dismiss() { taskView.release() } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java index 6808142e98d2..616bd81abe4d 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java @@ -23,6 +23,8 @@ import com.android.systemui.R; import com.android.systemui.dagger.SystemUIBinder; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.complication.ComplicationLayoutParams; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import javax.inject.Named; @@ -47,6 +49,7 @@ public interface RegisteredComplicationsModule { String DREAM_MEDIA_ENTRY_LAYOUT_PARAMS = "media_entry_layout_params"; int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1; + int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT_NO_SMARTSPACE = 2; int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 2; int DREAM_MEDIA_COMPLICATION_WEIGHT = 0; int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 4; @@ -58,7 +61,15 @@ public interface RegisteredComplicationsModule { */ @Provides @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS) - static ComplicationLayoutParams provideClockTimeLayoutParams() { + static ComplicationLayoutParams provideClockTimeLayoutParams(FeatureFlags featureFlags) { + if (featureFlags.isEnabled(Flags.HIDE_SMARTSPACE_ON_DREAM_OVERLAY)) { + return new ComplicationLayoutParams(0, + ViewGroup.LayoutParams.WRAP_CONTENT, + ComplicationLayoutParams.POSITION_BOTTOM + | ComplicationLayoutParams.POSITION_START, + ComplicationLayoutParams.DIRECTION_END, + DREAM_CLOCK_TIME_COMPLICATION_WEIGHT_NO_SMARTSPACE); + } return new ComplicationLayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, ComplicationLayoutParams.POSITION_BOTTOM diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java index 58fe0940b7fb..367a9b9bd4a0 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java @@ -85,9 +85,34 @@ public class FeatureFlagsDebug implements FeatureFlags { private final ServerFlagReader.ChangeListener mOnPropertiesChanged = new ServerFlagReader.ChangeListener() { @Override - public void onChange(Flag<?> flag) { - mRestarter.restartSystemUI( - "Server flag change: " + flag.getNamespace() + "." + flag.getName()); + public void onChange(Flag<?> flag, String value) { + boolean shouldRestart = false; + if (mBooleanFlagCache.containsKey(flag.getName())) { + boolean newValue = value == null ? false : Boolean.parseBoolean(value); + if (mBooleanFlagCache.get(flag.getName()) != newValue) { + shouldRestart = true; + } + } else if (mStringFlagCache.containsKey(flag.getName())) { + String newValue = value == null ? "" : value; + if (mStringFlagCache.get(flag.getName()) != value) { + shouldRestart = true; + } + } else if (mIntFlagCache.containsKey(flag.getName())) { + int newValue = 0; + try { + newValue = value == null ? 0 : Integer.parseInt(value); + } catch (NumberFormatException e) { + } + if (mIntFlagCache.get(flag.getName()) != newValue) { + shouldRestart = true; + } + } + if (shouldRestart) { + mRestarter.restartSystemUI( + "Server flag change: " + flag.getNamespace() + "." + + flag.getName()); + + } } }; diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java index 9859ff6b4917..9d19a7dc4604 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java @@ -53,13 +53,38 @@ public class FeatureFlagsRelease implements FeatureFlags { private final Map<String, Flag<?>> mAllFlags; private final Map<String, Boolean> mBooleanCache = new HashMap<>(); private final Map<String, String> mStringCache = new HashMap<>(); + private final Map<String, Integer> mIntCache = new HashMap<>(); private final ServerFlagReader.ChangeListener mOnPropertiesChanged = new ServerFlagReader.ChangeListener() { @Override - public void onChange(Flag<?> flag) { - mRestarter.restartSystemUI( - "Server flag change: " + flag.getNamespace() + "." + flag.getName()); + public void onChange(Flag<?> flag, String value) { + boolean shouldRestart = false; + if (mBooleanCache.containsKey(flag.getName())) { + boolean newValue = value == null ? false : Boolean.parseBoolean(value); + if (mBooleanCache.get(flag.getName()) != newValue) { + shouldRestart = true; + } + } else if (mStringCache.containsKey(flag.getName())) { + String newValue = value == null ? "" : value; + if (mStringCache.get(flag.getName()) != newValue) { + shouldRestart = true; + } + } else if (mIntCache.containsKey(flag.getName())) { + int newValue = 0; + try { + newValue = value == null ? 0 : Integer.parseInt(value); + } catch (NumberFormatException e) { + } + if (mIntCache.get(flag.getName()) != newValue) { + shouldRestart = true; + } + } + if (shouldRestart) { + mRestarter.restartSystemUI( + "Server flag change: " + flag.getNamespace() + "." + + flag.getName()); + } } }; @@ -97,68 +122,97 @@ public class FeatureFlagsRelease implements FeatureFlags { @Override public boolean isEnabled(@NotNull ReleasedFlag flag) { - return mServerFlagReader.readServerOverride(flag.getNamespace(), flag.getName(), true); + // Fill the cache. + return isEnabledInternal(flag.getName(), + mServerFlagReader.readServerOverride(flag.getNamespace(), flag.getName(), true)); } @Override public boolean isEnabled(ResourceBooleanFlag flag) { - if (!mBooleanCache.containsKey(flag.getName())) { - return isEnabled(flag.getName(), mResources.getBoolean(flag.getResourceId())); - } - - return mBooleanCache.get(flag.getName()); + // Fill the cache. + return isEnabledInternal(flag.getName(), mResources.getBoolean(flag.getResourceId())); } @Override public boolean isEnabled(SysPropBooleanFlag flag) { - if (!mBooleanCache.containsKey(flag.getName())) { - return isEnabled( - flag.getName(), - mSystemProperties.getBoolean(flag.getName(), flag.getDefault())); - } - - return mBooleanCache.get(flag.getName()); + // Fill the cache. + return isEnabledInternal( + flag.getName(), + mSystemProperties.getBoolean(flag.getName(), flag.getDefault())); } - private boolean isEnabled(String name, boolean defaultValue) { - mBooleanCache.put(name, defaultValue); - return defaultValue; + /** + * Checks and fills the boolean cache. This is important, Always call through to this method! + * + * We use the cache as a way to decide if we need to restart the process when server-side + * changes occur. + */ + private boolean isEnabledInternal(String name, boolean defaultValue) { + // Fill the cache. + if (!mBooleanCache.containsKey(name)) { + mBooleanCache.put(name, defaultValue); + } + + return mBooleanCache.get(name); } @NonNull @Override public String getString(@NonNull StringFlag flag) { - return getString(flag.getName(), flag.getDefault()); + // Fill the cache. + return getStringInternal(flag.getName(), flag.getDefault()); } @NonNull @Override public String getString(@NonNull ResourceStringFlag flag) { - if (!mStringCache.containsKey(flag.getName())) { - return getString(flag.getName(), - requireNonNull(mResources.getString(flag.getResourceId()))); - } - - return mStringCache.get(flag.getName()); + // Fill the cache. + return getStringInternal(flag.getName(), + requireNonNull(mResources.getString(flag.getResourceId()))); } - private String getString(String name, String defaultValue) { - mStringCache.put(name, defaultValue); - return defaultValue; + /** + * Checks and fills the String cache. This is important, Always call through to this method! + * + * We use the cache as a way to decide if we need to restart the process when server-side + * changes occur. + */ + private String getStringInternal(String name, String defaultValue) { + if (!mStringCache.containsKey(name)) { + mStringCache.put(name, defaultValue); + } + + return mStringCache.get(name); } @NonNull @Override public int getInt(@NonNull IntFlag flag) { - return flag.getDefault(); + // Fill the cache. + return getIntInternal(flag.getName(), flag.getDefault()); } @NonNull @Override public int getInt(@NonNull ResourceIntFlag flag) { + // Fill the cache. return mResources.getInteger(flag.getResourceId()); } + /** + * Checks and fills the integer cache. This is important, Always call through to this method! + * + * We use the cache as a way to decide if we need to restart the process when server-side + * changes occur. + */ + private int getIntInternal(String name, int defaultValue) { + if (!mIntCache.containsKey(name)) { + mIntCache.put(name, defaultValue); + } + + return mIntCache.get(name); + } + @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println("can override: false"); diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt index 9b748d0a0eb2..eaf5eac155ef 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt @@ -37,7 +37,7 @@ interface ServerFlagReader { fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener) interface ChangeListener { - fun onChange(flag: Flag<*>) + fun onChange(flag: Flag<*>, value: String?) } } @@ -67,7 +67,7 @@ class ServerFlagReaderImpl @Inject constructor( propLoop@ for (propName in properties.keyset) { for (flag in flags) { if (propName == flag.name) { - listener.onChange(flag) + listener.onChange(flag, properties.getString(propName, null)) break@propLoop } } @@ -144,7 +144,7 @@ class ServerFlagReaderFake : ServerFlagReader { for ((listener, flags) in listeners) { flagLoop@ for (flag in flags) { if (name == flag.name) { - listener.onChange(flag) + listener.onChange(flag, if (value) "true" else "false") break@flagLoop } } @@ -159,5 +159,6 @@ class ServerFlagReaderFake : ServerFlagReader { flags: Collection<Flag<*>>, listener: ServerFlagReader.ChangeListener ) { + listeners.add(Pair(listener, flags)) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java index 4ddff530e658..638c4c408bb1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java @@ -518,16 +518,15 @@ public class MediaControlPanel { mLogger.logTapContentView(mUid, mPackageName, mInstanceId); logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT); - // See StatusBarNotificationActivityStarter#onNotificationClicked boolean showOverLockscreen = mKeyguardStateController.isShowing() - && mActivityIntentHelper.wouldShowOverLockscreen(clickIntent.getIntent(), + && mActivityIntentHelper.wouldPendingShowOverLockscreen(clickIntent, mLockscreenUserManager.getCurrentUserId()); - if (showOverLockscreen) { - mActivityStarter.startActivity(clickIntent.getIntent(), - /* dismissShade */ true, - /* animationController */ null, - /* showOverLockscreenWhenLocked */ true); + try { + clickIntent.send(); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Pending intent for " + key + " was cancelled"); + } } else { mActivityStarter.postStartActivityDismissingKeyguard(clickIntent, buildLaunchAnimatorController(mMediaViewHolder.getPlayer())); diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index fab8c068b2a7..78082c3eb3c6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -225,8 +225,10 @@ open class MediaTttChipControllerReceiver @Inject constructor( val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple) val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple) val translationYBy = getTranslationAmount() + // Expand ripple before translating icon container to make sure both views have same bounds. + rippleController.expandToInProgressState(rippleView, iconRippleView) // Make the icon container view starts animation from bottom of the screen. - iconContainerView.translationY += rippleController.getReceiverIconSize() + iconContainerView.translationY = rippleController.getReceiverIconSize().toFloat() animateViewTranslationAndFade( iconContainerView, translationYBy = -1 * translationYBy, @@ -235,7 +237,6 @@ open class MediaTttChipControllerReceiver @Inject constructor( ) { animateBouncingView(iconContainerView, translationYBy * BOUNCE_TRANSLATION_RATIO) } - rippleController.expandToInProgressState(rippleView, iconRippleView) } override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) { @@ -293,7 +294,7 @@ open class MediaTttChipControllerReceiver @Inject constructor( /** Returns the amount that the chip will be translated by in its intro animation. */ private fun getTranslationAmount(): Float { - return rippleController.getRippleSize() * 0.5f + return rippleController.getReceiverIconSize() * 2f } private fun View.getAppIconView(): CachingIconView { diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 8a3ecc6afaac..0748bcbf020c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -21,7 +21,6 @@ import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; - import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY; @@ -713,6 +712,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } public void startConnectionToCurrentUser() { + Log.v(TAG_OPS, "startConnectionToCurrentUser: connection is restarted"); if (mHandler.getLooper() != Looper.myLooper()) { mHandler.post(mConnectionRunnable); } else { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index ca8e10176e7f..02a60ad60fa1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -481,7 +481,6 @@ public class LongScreenshotActivity extends Activity { mCropView.setExtraPadding(extraPadding + mPreview.getPaddingTop(), extraPadding + mPreview.getPaddingBottom()); imageTop += (previewHeight - imageHeight) / 2; - mCropView.setExtraPadding(extraPadding, extraPadding); mCropView.setImageWidth(previewWidth); scale = previewWidth / (float) mPreview.getDrawable().getIntrinsicWidth(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 51c5183ffee9..cac4251bce63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; - import static com.android.systemui.DejankUtils.whitelistIpcs; import android.app.KeyguardManager; @@ -145,7 +144,10 @@ public class NotificationLockscreenUserManagerImpl implements break; case Intent.ACTION_USER_UNLOCKED: // Start the overview connection to the launcher service - mOverviewProxyServiceLazy.get().startConnectionToCurrentUser(); + // Connect if user hasn't connected yet + if (mOverviewProxyServiceLazy.get().getProxy() == null) { + mOverviewProxyServiceLazy.get().startConnectionToCurrentUser(); + } break; case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION: final IntentSender intentSender = intent.getParcelableExtra( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index e28d486dc9fb..11598e0c1f51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -73,6 +73,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.ContrastColorUtil; import com.android.internal.widget.CachingIconView; import com.android.internal.widget.CallLayout; @@ -1702,7 +1703,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView MetricsLogger metricsLogger, SmartReplyConstants smartReplyConstants, SmartReplyController smartReplyController, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + IStatusBarService statusBarService) { mEntry = entry; mAppName = appName; if (mMenuRow == null) { @@ -1731,7 +1733,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPeopleNotificationIdentifier, rivSubcomponentFactory, smartReplyConstants, - smartReplyController); + smartReplyController, + statusBarService); } mOnUserInteractionCallback = onUserInteractionCallback; mBubblesManagerOptional = bubblesManagerOptional; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 9420df3ca26f..b6f0a05661e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -29,6 +29,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; @@ -100,6 +101,7 @@ public class ExpandableNotificationRowController implements NotifViewController private final SmartReplyConstants mSmartReplyConstants; private final SmartReplyController mSmartReplyController; private final ExpandableNotificationRowDragController mDragController; + private final IStatusBarService mStatusBarService; private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback = new ExpandableNotificationRow.ExpandableNotificationRowLogger() { @Override @@ -157,7 +159,8 @@ public class ExpandableNotificationRowController implements NotifViewController FeatureFlags featureFlags, PeopleNotificationIdentifier peopleNotificationIdentifier, Optional<BubblesManager> bubblesManagerOptional, - ExpandableNotificationRowDragController dragController) { + ExpandableNotificationRowDragController dragController, + IStatusBarService statusBarService) { mView = view; mListContainer = listContainer; mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory; @@ -189,6 +192,7 @@ public class ExpandableNotificationRowController implements NotifViewController mLogBufferLogger = logBufferLogger; mSmartReplyConstants = smartReplyConstants; mSmartReplyController = smartReplyController; + mStatusBarService = statusBarService; } /** @@ -220,7 +224,8 @@ public class ExpandableNotificationRowController implements NotifViewController mMetricsLogger, mSmartReplyConstants, mSmartReplyController, - mFeatureFlags + mFeatureFlags, + mStatusBarService ); mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); if (mAllowLongPress) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 4a023c41388e..ef32a08deeae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -21,10 +21,13 @@ import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; +import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.RemoteException; import android.provider.Settings; +import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.AttributeSet; import android.util.IndentingPrintWriter; @@ -39,6 +42,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.R; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.RemoteInputController; @@ -129,6 +133,7 @@ public class NotificationContentView extends FrameLayout implements Notification private Runnable mExpandedVisibleListener; private PeopleNotificationIdentifier mPeopleIdentifier; private RemoteInputViewSubcomponent.Factory mRemoteInputSubcomponentFactory; + private IStatusBarService mStatusBarService; /** * List of listeners for when content views become inactive (i.e. not the showing view). @@ -196,11 +201,13 @@ public class NotificationContentView extends FrameLayout implements Notification PeopleNotificationIdentifier peopleNotificationIdentifier, RemoteInputViewSubcomponent.Factory rivSubcomponentFactory, SmartReplyConstants smartReplyConstants, - SmartReplyController smartReplyController) { + SmartReplyController smartReplyController, + IStatusBarService statusBarService) { mPeopleIdentifier = peopleNotificationIdentifier; mRemoteInputSubcomponentFactory = rivSubcomponentFactory; mSmartReplyConstants = smartReplyConstants; mSmartReplyController = smartReplyController; + mStatusBarService = statusBarService; } public void reinflate() { @@ -2176,4 +2183,36 @@ public class NotificationContentView extends FrameLayout implements Notification protected void setHeadsUpWrapper(NotificationViewWrapper headsUpWrapper) { mHeadsUpWrapper = headsUpWrapper; } + + @Override + protected void dispatchDraw(Canvas canvas) { + try { + super.dispatchDraw(canvas); + } catch (Exception e) { + // Catch draw exceptions that may be caused by RemoteViews + Log.e(TAG, "Drawing view failed: " + e); + cancelNotification(e); + } + } + + private void cancelNotification(Exception exception) { + try { + setVisibility(GONE); + final StatusBarNotification sbn = mNotificationEntry.getSbn(); + if (mStatusBarService != null) { + // report notification inflation errors back up + // to notification delegates + mStatusBarService.onNotificationError( + sbn.getPackageName(), + sbn.getTag(), + sbn.getId(), + sbn.getUid(), + sbn.getInitialPid(), + exception.getMessage(), + sbn.getUser().getIdentifier()); + } + } catch (RemoteException ex) { + Log.e(TAG, "cancelNotification failed: " + ex); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt index 3f61bf75740a..9d5b94be3885 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt @@ -372,6 +372,38 @@ class ControlsUiControllerImplTest : SysuiTestCase() { verify(fakeDialogController.dialog).cancel() } + @Test + fun testOnRotationWithPanelUpdateBoundsCalled() { + mockLayoutInflater() + val packageName = "pkg" + `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName)) + val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls")) + val serviceInfo = setUpPanel(panel) + + underTest.show(parent, {}, context) + + val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>() + + verify(controlsListingController).addCallback(capture(captor)) + captor.value.onServicesUpdated(listOf(serviceInfo)) + FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor) + + val taskViewConsumerCaptor = argumentCaptor<Consumer<TaskView>>() + verify(taskViewFactory).create(eq(context), eq(uiExecutor), capture(taskViewConsumerCaptor)) + + val taskView: TaskView = mock { + `when`(this.post(any())).thenAnswer { + uiExecutor.execute(it.arguments[0] as Runnable) + true + } + } + + taskViewConsumerCaptor.value.accept(taskView) + + underTest.onOrientationChange() + verify(taskView).onLocationChanged() + } + private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo { val activity = ComponentName(context, "activity") preferredPanelRepository.setSelectedComponent( diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt index de04ef810dd0..9df7992f979f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt @@ -152,4 +152,12 @@ class PanelTaskViewControllerTest : SysuiTestCase() { listenerCaptor.value.onTaskRemovalStarted(0) verify(taskView).release() } + + @Test + fun testOnRefreshBounds() { + underTest.launchTaskView() + + underTest.refreshBounds() + verify(taskView).onLocationChanged() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt index 2bcd75b7c707..18f7db18b1f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt @@ -37,6 +37,7 @@ import org.mockito.Mock import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyString import org.mockito.Mockito.inOrder +import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @@ -53,7 +54,7 @@ import org.mockito.Mockito.`when` as whenever */ @SmallTest class FeatureFlagsDebugTest : SysuiTestCase() { - private lateinit var mFeatureFlagsDebug: FeatureFlagsDebug + private lateinit var featureFlagsDebug: FeatureFlagsDebug @Mock private lateinit var flagManager: FlagManager @@ -85,7 +86,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD) flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA) flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB) - mFeatureFlagsDebug = FeatureFlagsDebug( + featureFlagsDebug = FeatureFlagsDebug( flagManager, mockContext, globalSettings, @@ -95,7 +96,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { flagMap, restarter ) - mFeatureFlagsDebug.init() + featureFlagsDebug.init() verify(flagManager).onSettingsChangedAction = any() broadcastReceiver = withArgCaptor { verify(mockContext).registerReceiver( @@ -116,7 +117,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { whenever(flagManager.readFlagValue<Boolean>(eq("4"), any())).thenReturn(false) assertThat( - mFeatureFlagsDebug.isEnabled( + featureFlagsDebug.isEnabled( ReleasedFlag( 2, name = "2", @@ -125,7 +126,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ).isTrue() assertThat( - mFeatureFlagsDebug.isEnabled( + featureFlagsDebug.isEnabled( UnreleasedFlag( 3, name = "3", @@ -134,7 +135,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ).isTrue() assertThat( - mFeatureFlagsDebug.isEnabled( + featureFlagsDebug.isEnabled( ReleasedFlag( 4, name = "4", @@ -143,7 +144,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ).isFalse() assertThat( - mFeatureFlagsDebug.isEnabled( + featureFlagsDebug.isEnabled( UnreleasedFlag( 5, name = "5", @@ -157,8 +158,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() { fun teamFoodFlag_False() { whenever(flagManager.readFlagValue<Boolean>( eq(Flags.TEAMFOOD.name), any())).thenReturn(false) - assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isFalse() - assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue() + assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagA)).isFalse() + assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue() // Regular boolean flags should still test the same. // Only our teamfoodableFlag should change. @@ -169,8 +170,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() { fun teamFoodFlag_True() { whenever(flagManager.readFlagValue<Boolean>( eq(Flags.TEAMFOOD.name), any())).thenReturn(true) - assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue() - assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue() + assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue() + assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue() // Regular boolean flags should still test the same. // Only our teamfoodableFlag should change. @@ -185,8 +186,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() { .thenReturn(false) whenever(flagManager.readFlagValue<Boolean>( eq(Flags.TEAMFOOD.name), any())).thenReturn(true) - assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue() - assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse() + assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue() + assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse() // Regular boolean flags should still test the same. // Only our teamfoodableFlag should change. @@ -205,7 +206,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { whenever(flagManager.readFlagValue<Boolean>(eq("5"), any())).thenReturn(false) assertThat( - mFeatureFlagsDebug.isEnabled( + featureFlagsDebug.isEnabled( ResourceBooleanFlag( 1, "1", @@ -214,16 +215,16 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ) ).isFalse() - assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(2, "2", "test", 1002))).isTrue() - assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(3, "3", "test", 1003))).isTrue() + assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag(2, "2", "test", 1002))).isTrue() + assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag(3, "3", "test", 1003))).isTrue() Assert.assertThrows(NameNotFoundException::class.java) { - mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(4, "4", "test", 1004)) + featureFlagsDebug.isEnabled(ResourceBooleanFlag(4, "4", "test", 1004)) } // Test that resource is loaded (and validated) even when the setting is set. // This prevents developers from not noticing when they reference an invalid resource. Assert.assertThrows(NameNotFoundException::class.java) { - mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(5, "5", "test", 1005)) + featureFlagsDebug.isEnabled(ResourceBooleanFlag(5, "5", "test", 1005)) } } @@ -236,11 +237,11 @@ class FeatureFlagsDebugTest : SysuiTestCase() { return@thenAnswer it.getArgument(1) } - assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a", "test"))).isFalse() - assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b", "test"))).isTrue() - assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", "test", true))).isTrue() + assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a", "test"))).isFalse() + assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b", "test"))).isTrue() + assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", "test", true))).isTrue() assertThat( - mFeatureFlagsDebug.isEnabled( + featureFlagsDebug.isEnabled( SysPropBooleanFlag( 4, "d", @@ -249,17 +250,17 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ) ).isFalse() - assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e", "test"))).isFalse() + assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e", "test"))).isFalse() } @Test fun readStringFlag() { whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("foo") whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("bar") - assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz") - assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz") - assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo") - assertThat(mFeatureFlagsDebug.getString(StringFlag(4, "4", "test", "buz"))).isEqualTo("bar") + assertThat(featureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz") + assertThat(featureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz") + assertThat(featureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo") + assertThat(featureFlagsDebug.getString(StringFlag(4, "4", "test", "buz"))).isEqualTo("bar") } @Test @@ -276,7 +277,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { whenever(flagManager.readFlagValue<String>(eq("6"), any())).thenReturn("override6") assertThat( - mFeatureFlagsDebug.getString( + featureFlagsDebug.getString( ResourceStringFlag( 1, "1", @@ -286,7 +287,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ).isEqualTo("") assertThat( - mFeatureFlagsDebug.getString( + featureFlagsDebug.getString( ResourceStringFlag( 2, "2", @@ -296,7 +297,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) ).isEqualTo("resource2") assertThat( - mFeatureFlagsDebug.getString( + featureFlagsDebug.getString( ResourceStringFlag( 3, "3", @@ -307,15 +308,15 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ).isEqualTo("override3") Assert.assertThrows(NullPointerException::class.java) { - mFeatureFlagsDebug.getString(ResourceStringFlag(4, "4", "test", 1004)) + featureFlagsDebug.getString(ResourceStringFlag(4, "4", "test", 1004)) } Assert.assertThrows(NameNotFoundException::class.java) { - mFeatureFlagsDebug.getString(ResourceStringFlag(5, "5", "test", 1005)) + featureFlagsDebug.getString(ResourceStringFlag(5, "5", "test", 1005)) } // Test that resource is loaded (and validated) even when the setting is set. // This prevents developers from not noticing when they reference an invalid resource. Assert.assertThrows(NameNotFoundException::class.java) { - mFeatureFlagsDebug.getString(ResourceStringFlag(6, "6", "test", 1005)) + featureFlagsDebug.getString(ResourceStringFlag(6, "6", "test", 1005)) } } @@ -323,10 +324,10 @@ class FeatureFlagsDebugTest : SysuiTestCase() { fun readIntFlag() { whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(22) whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(48) - assertThat(mFeatureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12) - assertThat(mFeatureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93) - assertThat(mFeatureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22) - assertThat(mFeatureFlagsDebug.getInt(IntFlag(4, "4", "test", 234))).isEqualTo(48) + assertThat(featureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12) + assertThat(featureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93) + assertThat(featureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22) + assertThat(featureFlagsDebug.getInt(IntFlag(4, "4", "test", 234))).isEqualTo(48) } @Test @@ -342,17 +343,17 @@ class FeatureFlagsDebugTest : SysuiTestCase() { whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(500) whenever(flagManager.readFlagValue<Int>(eq(5), any())).thenReturn(9519) - assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(1, "1", "test", 1001))).isEqualTo(88) - assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(2, "2", "test", 1002))).isEqualTo(61) - assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(3, "3", "test", 1003))).isEqualTo(20) + assertThat(featureFlagsDebug.getInt(ResourceIntFlag(1, "1", "test", 1001))).isEqualTo(88) + assertThat(featureFlagsDebug.getInt(ResourceIntFlag(2, "2", "test", 1002))).isEqualTo(61) + assertThat(featureFlagsDebug.getInt(ResourceIntFlag(3, "3", "test", 1003))).isEqualTo(20) Assert.assertThrows(NotFoundException::class.java) { - mFeatureFlagsDebug.getInt(ResourceIntFlag(4, "4", "test", 1004)) + featureFlagsDebug.getInt(ResourceIntFlag(4, "4", "test", 1004)) } // Test that resource is loaded (and validated) even when the setting is set. // This prevents developers from not noticing when they reference an invalid resource. Assert.assertThrows(NotFoundException::class.java) { - mFeatureFlagsDebug.getInt(ResourceIntFlag(5, "5", "test", 1005)) + featureFlagsDebug.getInt(ResourceIntFlag(5, "5", "test", 1005)) } } @@ -432,11 +433,11 @@ class FeatureFlagsDebugTest : SysuiTestCase() { whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("original") // gets the flag & cache it - assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original") + assertThat(featureFlagsDebug.getString(flag1)).isEqualTo("original") verify(flagManager, times(1)).readFlagValue(eq("1"), eq(StringFlagSerializer)) // hit the cache - assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original") + assertThat(featureFlagsDebug.getString(flag1)).isEqualTo("original") verifyNoMoreInteractions(flagManager) // set the flag @@ -444,7 +445,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { verifyPutData("1", "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2) whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("new") - assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("new") + assertThat(featureFlagsDebug.getString(flag1)).isEqualTo("new") verify(flagManager, times(3)).readFlagValue(eq("1"), eq(StringFlagSerializer)) } @@ -454,7 +455,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { serverFlagReader.setFlagValue(flag.namespace, flag.name, false) - assertThat(mFeatureFlagsDebug.isEnabled(flag)).isFalse() + assertThat(featureFlagsDebug.isEnabled(flag)).isFalse() } @Test @@ -462,7 +463,33 @@ class FeatureFlagsDebugTest : SysuiTestCase() { val flag = UnreleasedFlag(100, name = "100", namespace = "test") serverFlagReader.setFlagValue(flag.namespace, flag.name, true) - assertThat(mFeatureFlagsDebug.isEnabled(flag)).isTrue() + assertThat(featureFlagsDebug.isEnabled(flag)).isTrue() + } + + @Test + fun serverSide_OverrideUncached_NoRestart() { + // No one has read the flag, so it's not in the cache. + serverFlagReader.setFlagValue( + teamfoodableFlagA.namespace, teamfoodableFlagA.name, !teamfoodableFlagA.default) + verify(restarter, never()).restartSystemUI(anyString()) + } + + @Test + fun serverSide_Override_Restarts() { + // Read it to put it in the cache. + featureFlagsDebug.isEnabled(teamfoodableFlagA) + serverFlagReader.setFlagValue( + teamfoodableFlagA.namespace, teamfoodableFlagA.name, !teamfoodableFlagA.default) + verify(restarter).restartSystemUI(anyString()) + } + + @Test + fun serverSide_RedundantOverride_NoRestart() { + // Read it to put it in the cache. + featureFlagsDebug.isEnabled(teamfoodableFlagA) + serverFlagReader.setFlagValue( + teamfoodableFlagA.namespace, teamfoodableFlagA.name, teamfoodableFlagA.default) + verify(restarter, never()).restartSystemUI(anyString()) } @Test @@ -482,13 +509,13 @@ class FeatureFlagsDebugTest : SysuiTestCase() { .thenReturn("override7") // WHEN the flags have been accessed - assertThat(mFeatureFlagsDebug.isEnabled(flag1)).isTrue() - assertThat(mFeatureFlagsDebug.isEnabled(flag2)).isTrue() - assertThat(mFeatureFlagsDebug.isEnabled(flag3)).isFalse() - assertThat(mFeatureFlagsDebug.getString(flag4)).isEmpty() - assertThat(mFeatureFlagsDebug.getString(flag5)).isEqualTo("flag5default") - assertThat(mFeatureFlagsDebug.getString(flag6)).isEqualTo("resource1006") - assertThat(mFeatureFlagsDebug.getString(flag7)).isEqualTo("override7") + assertThat(featureFlagsDebug.isEnabled(flag1)).isTrue() + assertThat(featureFlagsDebug.isEnabled(flag2)).isTrue() + assertThat(featureFlagsDebug.isEnabled(flag3)).isFalse() + assertThat(featureFlagsDebug.getString(flag4)).isEmpty() + assertThat(featureFlagsDebug.getString(flag5)).isEqualTo("flag5default") + assertThat(featureFlagsDebug.getString(flag6)).isEqualTo("resource1006") + assertThat(featureFlagsDebug.getString(flag7)).isEqualTo("override7") // THEN the dump contains the flags and the default values val dump = dumpToString() @@ -527,7 +554,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { private fun dumpToString(): String { val sw = StringWriter() val pw = PrintWriter(sw) - mFeatureFlagsDebug.dump(pw, emptyArray<String>()) + featureFlagsDebug.dump(pw, emptyArray<String>()) pw.flush() return sw.toString() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt index 4c6028c4c9b7..917147b17517 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt @@ -24,6 +24,8 @@ import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.never import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever @@ -33,7 +35,7 @@ import org.mockito.Mockito.`when` as whenever */ @SmallTest class FeatureFlagsReleaseTest : SysuiTestCase() { - private lateinit var mFeatureFlagsRelease: FeatureFlagsRelease + private lateinit var featureFlagsRelease: FeatureFlagsRelease @Mock private lateinit var mResources: Resources @Mock private lateinit var mSystemProperties: SystemPropertiesHelper @@ -41,15 +43,21 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { private val flagMap = mutableMapOf<String, Flag<*>>() private val serverFlagReader = ServerFlagReaderFake() + + private val flagA = ReleasedFlag(501, name = "a", namespace = "test") + @Before fun setup() { MockitoAnnotations.initMocks(this) - mFeatureFlagsRelease = FeatureFlagsRelease( + flagMap.put(flagA.name, flagA) + featureFlagsRelease = FeatureFlagsRelease( mResources, mSystemProperties, serverFlagReader, flagMap, restarter) + + featureFlagsRelease.init() } @Test @@ -60,7 +68,7 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { val flagNamespace = "test" val flag = ResourceBooleanFlag(flagId, flagName, flagNamespace, flagResourceId) whenever(mResources.getBoolean(flagResourceId)).thenReturn(true) - assertThat(mFeatureFlagsRelease.isEnabled(flag)).isTrue() + assertThat(featureFlagsRelease.isEnabled(flag)).isTrue() } @Test @@ -70,16 +78,16 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { whenever(mResources.getString(1003)).thenReturn(null) whenever(mResources.getString(1004)).thenAnswer { throw NameNotFoundException() } - assertThat(mFeatureFlagsRelease.getString( + assertThat(featureFlagsRelease.getString( ResourceStringFlag(1, "1", "test", 1001))).isEqualTo("") - assertThat(mFeatureFlagsRelease.getString( + assertThat(featureFlagsRelease.getString( ResourceStringFlag(2, "2", "test", 1002))).isEqualTo("res2") assertThrows(NullPointerException::class.java) { - mFeatureFlagsRelease.getString(ResourceStringFlag(3, "3", "test", 1003)) + featureFlagsRelease.getString(ResourceStringFlag(3, "3", "test", 1003)) } assertThrows(NameNotFoundException::class.java) { - mFeatureFlagsRelease.getString(ResourceStringFlag(4, "4", "test", 1004)) + featureFlagsRelease.getString(ResourceStringFlag(4, "4", "test", 1004)) } } @@ -92,7 +100,7 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { val flag = SysPropBooleanFlag(flagId, flagName, flagNamespace, flagDefault) whenever(mSystemProperties.getBoolean(flagName, flagDefault)).thenReturn(flagDefault) - assertThat(mFeatureFlagsRelease.isEnabled(flag)).isEqualTo(flagDefault) + assertThat(featureFlagsRelease.isEnabled(flag)).isEqualTo(flagDefault) } @Test @@ -101,7 +109,7 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { serverFlagReader.setFlagValue(flag.namespace, flag.name, false) - assertThat(mFeatureFlagsRelease.isEnabled(flag)).isFalse() + assertThat(featureFlagsRelease.isEnabled(flag)).isFalse() } @Test @@ -110,6 +118,32 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { serverFlagReader.setFlagValue(flag.namespace, flag.name, true) - assertThat(mFeatureFlagsRelease.isEnabled(flag)).isFalse() + assertThat(featureFlagsRelease.isEnabled(flag)).isFalse() + } + + @Test + fun serverSide_OverrideUncached_NoRestart() { + // No one has read the flag, so it's not in the cache. + serverFlagReader.setFlagValue( + flagA.namespace, flagA.name, !flagA.default) + Mockito.verify(restarter, never()).restartSystemUI(Mockito.anyString()) + } + + @Test + fun serverSide_Override_Restarts() { + // Read it to put it in the cache. + featureFlagsRelease.isEnabled(flagA) + serverFlagReader.setFlagValue( + flagA.namespace, flagA.name, !flagA.default) + Mockito.verify(restarter).restartSystemUI(Mockito.anyString()) + } + + @Test + fun serverSide_RedundantOverride_NoRestart() { + // Read it to put it in the cache. + featureFlagsRelease.isEnabled(flagA) + serverFlagReader.setFlagValue( + flagA.namespace, flagA.name, flagA.default) + Mockito.verify(restarter, never()).restartSystemUI(Mockito.anyString()) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt index 2e9800606edf..953b7fb32d56 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt @@ -21,11 +21,13 @@ import android.testing.AndroidTestingRunner import com.android.systemui.SysuiTestCase import com.android.systemui.util.DeviceConfigProxyFake import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any import com.android.systemui.util.time.FakeSystemClock import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.anyString import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -57,18 +59,18 @@ class ServerFlagReaderImplTest : SysuiTestCase() { deviceConfig.setProperty(NAMESPACE, "flag_1", "1", false) executor.runAllReady() - verify(changeListener).onChange(flag) + verify(changeListener).onChange(flag, "1") } @Test fun testChange_ignoresListenersDuringTest() { val serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor, true) - val flag = ReleasedFlag(1, "1", "test") + val flag = ReleasedFlag(1, "1", " test") serverFlagReader.listenForChanges(listOf(flag), changeListener) deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false) executor.runAllReady() - verify(changeListener, never()).onChange(flag) + verify(changeListener, never()).onChange(any(), anyString()) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt index fd353afff7c0..df13fddc5a28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt @@ -94,7 +94,6 @@ import com.android.systemui.util.mockito.KotlinArgumentCaptor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -1763,7 +1762,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun tapContentView_showOverLockscreen_openActivity() { // WHEN we are on lockscreen and this activity can show over lockscreen whenever(keyguardStateController.isShowing).thenReturn(true) - whenever(activityIntentHelper.wouldShowOverLockscreen(any(), any())).thenReturn(true) + whenever(activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())).thenReturn(true) val clickIntent = mock(Intent::class.java) val pendingIntent = mock(PendingIntent::class.java) @@ -1774,16 +1773,20 @@ public class MediaControlPanelTest : SysuiTestCase() { player.bindPlayer(data, KEY) verify(viewHolder.player).setOnClickListener(captor.capture()) - // THEN it shows without dismissing keyguard first + // THEN it sends the PendingIntent without dismissing keyguard first, + // and does not use the Intent directly (see b/271845008) captor.value.onClick(viewHolder.player) - verify(activityStarter).startActivity(eq(clickIntent), eq(true), nullable(), eq(true)) + verify(pendingIntent).send() + verify(pendingIntent, never()).getIntent() + verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any()) } @Test fun tapContentView_noShowOverLockscreen_dismissKeyguard() { // WHEN we are on lockscreen and the activity cannot show over lockscreen whenever(keyguardStateController.isShowing).thenReturn(true) - whenever(activityIntentHelper.wouldShowOverLockscreen(any(), any())).thenReturn(false) + whenever(activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())) + .thenReturn(false) val clickIntent = mock(Intent::class.java) val pendingIntent = mock(PendingIntent::class.java) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt index f5053d945d87..d1645556d733 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt @@ -21,6 +21,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger +import com.android.internal.statusbar.IStatusBarService import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FeatureFlags @@ -93,6 +94,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock() private val bubblesManager: BubblesManager = mock() private val dragController: ExpandableNotificationRowDragController = mock() + private val statusBarService: IStatusBarService = mock() private lateinit var controller: ExpandableNotificationRowController @Before @@ -129,7 +131,8 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { featureFlags, peopleNotificationIdentifier, Optional.of(bubblesManager), - dragController + dragController, + statusBarService ) whenever(view.childrenContainer).thenReturn(childrenContainer) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index 7b2051da4d15..0b90ebec3ec6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -74,7 +74,7 @@ class NotificationContentViewTest : SysuiTestCase() { doReturn(10).whenever(spyRow).intrinsicHeight with(view) { - initialize(mPeopleNotificationIdentifier, mock(), mock(), mock()) + initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock()) setContainingNotification(spyRow) setHeights(/* smallHeight= */ 10, /* headsUpMaxHeight= */ 20, /* maxHeight= */ 30) contractedChild = createViewWithHeight(10) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index aca9c563de04..318497af080a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -48,6 +48,7 @@ import android.view.LayoutInflater; import android.widget.RemoteViews; import com.android.internal.logging.MetricsLogger; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.TestableDependency; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; @@ -548,7 +549,8 @@ public class NotificationTestHelper { mock(MetricsLogger.class), mock(SmartReplyConstants.class), mock(SmartReplyController.class), - mFeatureFlags); + mFeatureFlags, + mock(IStatusBarService.class)); row.setAboveShelfChangedListener(aboveShelf -> { }); mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 521f342455d6..72054fd8c5c0 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1352,8 +1352,8 @@ public class AudioService extends IAudioService.Stub intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); if (mMonitorRotation) { RotationHelper.init(mContext, mAudioHandler, - rotationParam -> onRotationUpdate(rotationParam), - foldParam -> onFoldUpdate(foldParam)); + rotation -> onRotationUpdate(rotation), + foldState -> onFoldStateUpdate(foldState)); } intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); @@ -1502,16 +1502,20 @@ public class AudioService extends IAudioService.Stub //----------------------------------------------------------------- // rotation/fold updates coming from RotationHelper - void onRotationUpdate(String rotationParameter) { + void onRotationUpdate(Integer rotation) { + mSpatializerHelper.setDisplayOrientation((float) (rotation * Math.PI / 180.)); // use REPLACE as only the last rotation matters + final String rotationParameter = "rotation=" + rotation; sendMsg(mAudioHandler, MSG_ROTATION_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0, /*obj*/ rotationParameter, /*delay*/ 0); } - void onFoldUpdate(String foldParameter) { + void onFoldStateUpdate(Boolean foldState) { + mSpatializerHelper.setFoldState(foldState); // use REPLACE as only the last fold state matters + final String foldStateParameter = "device_folded=" + (foldState ? "on" : "off"); sendMsg(mAudioHandler, MSG_FOLD_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0, - /*obj*/ foldParameter, /*delay*/ 0); + /*obj*/ foldStateParameter, /*delay*/ 0); } //----------------------------------------------------------------- @@ -1726,6 +1730,9 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect); + // Restore rotation information. + RotationHelper.forceUpdate(); + onIndicateSystemReady(); // indicate the end of reconfiguration phase to audio HAL AudioSystem.setParameters("restarting=false"); diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java index 5cdf58bdd62f..394e4af30a9e 100644 --- a/services/core/java/com/android/server/audio/RotationHelper.java +++ b/services/core/java/com/android/server/audio/RotationHelper.java @@ -55,14 +55,14 @@ class RotationHelper { private static AudioDisplayListener sDisplayListener; private static FoldStateListener sFoldStateListener; /** callback to send rotation updates to AudioSystem */ - private static Consumer<String> sRotationUpdateCb; + private static Consumer<Integer> sRotationCallback; /** callback to send folded state updates to AudioSystem */ - private static Consumer<String> sFoldUpdateCb; + private static Consumer<Boolean> sFoldStateCallback; private static final Object sRotationLock = new Object(); private static final Object sFoldStateLock = new Object(); - private static int sDeviceRotation = Surface.ROTATION_0; // R/W synchronized on sRotationLock - private static boolean sDeviceFold = true; // R/W synchronized on sFoldStateLock + private static Integer sRotation = null; // R/W synchronized on sRotationLock + private static Boolean sFoldState = null; // R/W synchronized on sFoldStateLock private static Context sContext; private static Handler sHandler; @@ -73,15 +73,15 @@ class RotationHelper { * - sContext != null */ static void init(Context context, Handler handler, - Consumer<String> rotationUpdateCb, Consumer<String> foldUpdateCb) { + Consumer<Integer> rotationCallback, Consumer<Boolean> foldStateCallback) { if (context == null) { throw new IllegalArgumentException("Invalid null context"); } sContext = context; sHandler = handler; sDisplayListener = new AudioDisplayListener(); - sRotationUpdateCb = rotationUpdateCb; - sFoldUpdateCb = foldUpdateCb; + sRotationCallback = rotationCallback; + sFoldStateCallback = foldStateCallback; enable(); } @@ -112,9 +112,9 @@ class RotationHelper { int newRotation = DisplayManagerGlobal.getInstance() .getDisplayInfo(Display.DEFAULT_DISPLAY).rotation; synchronized(sRotationLock) { - if (newRotation != sDeviceRotation) { - sDeviceRotation = newRotation; - publishRotation(sDeviceRotation); + if (sRotation == null || sRotation != newRotation) { + sRotation = newRotation; + publishRotation(sRotation); } } } @@ -123,43 +123,52 @@ class RotationHelper { if (DEBUG_ROTATION) { Log.i(TAG, "publishing device rotation =" + rotation + " (x90deg)"); } - String rotationParam; + int rotationDegrees; switch (rotation) { case Surface.ROTATION_0: - rotationParam = "rotation=0"; + rotationDegrees = 0; break; case Surface.ROTATION_90: - rotationParam = "rotation=90"; + rotationDegrees = 90; break; case Surface.ROTATION_180: - rotationParam = "rotation=180"; + rotationDegrees = 180; break; case Surface.ROTATION_270: - rotationParam = "rotation=270"; + rotationDegrees = 270; break; default: Log.e(TAG, "Unknown device rotation"); - rotationParam = null; + rotationDegrees = -1; } - if (rotationParam != null) { - sRotationUpdateCb.accept(rotationParam); + if (rotationDegrees != -1) { + sRotationCallback.accept(rotationDegrees); } } /** * publish the change of device folded state if any. */ - static void updateFoldState(boolean newFolded) { + static void updateFoldState(boolean foldState) { synchronized (sFoldStateLock) { - if (sDeviceFold != newFolded) { - sDeviceFold = newFolded; - String foldParam; - if (newFolded) { - foldParam = "device_folded=on"; - } else { - foldParam = "device_folded=off"; - } - sFoldUpdateCb.accept(foldParam); + if (sFoldState == null || sFoldState != foldState) { + sFoldState = foldState; + sFoldStateCallback.accept(foldState); + } + } + } + + /** + * forceUpdate is called when audioserver restarts. + */ + static void forceUpdate() { + synchronized (sRotationLock) { + sRotation = null; + } + updateOrientation(); // We will get at least one orientation update now. + synchronized (sFoldStateLock) { + if (sFoldState != null) { + sFoldStateCallback.accept(sFoldState); } } } @@ -185,4 +194,4 @@ class RotationHelper { updateOrientation(); } } -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 8e8fd05bf72e..2b566668a7c7 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -1063,7 +1063,7 @@ public class SpatializerHelper { if (transform.length != 6) { throw new IllegalArgumentException("invalid array size" + transform.length); } - if (!checkSpatForHeadTracking("setGlobalTransform")) { + if (!checkSpatializerForHeadTracking("setGlobalTransform")) { return; } try { @@ -1074,7 +1074,7 @@ public class SpatializerHelper { } synchronized void recenterHeadTracker() { - if (!checkSpatForHeadTracking("recenterHeadTracker")) { + if (!checkSpatializerForHeadTracking("recenterHeadTracker")) { return; } try { @@ -1084,8 +1084,30 @@ public class SpatializerHelper { } } + synchronized void setDisplayOrientation(float displayOrientation) { + if (!checkSpatializer("setDisplayOrientation")) { + return; + } + try { + mSpat.setDisplayOrientation(displayOrientation); + } catch (RemoteException e) { + Log.e(TAG, "Error calling setDisplayOrientation", e); + } + } + + synchronized void setFoldState(boolean folded) { + if (!checkSpatializer("setFoldState")) { + return; + } + try { + mSpat.setFoldState(folded); + } catch (RemoteException e) { + Log.e(TAG, "Error calling setFoldState", e); + } + } + synchronized void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) { - if (!checkSpatForHeadTracking("setDesiredHeadTrackingMode")) { + if (!checkSpatializerForHeadTracking("setDesiredHeadTrackingMode")) { return; } if (mode != Spatializer.HEAD_TRACKING_MODE_DISABLED) { @@ -1178,7 +1200,7 @@ public class SpatializerHelper { return mHeadTrackerAvailable; } - private boolean checkSpatForHeadTracking(String funcName) { + private boolean checkSpatializer(String funcName) { switch (mState) { case STATE_UNINITIALIZED: case STATE_NOT_SUPPORTED: @@ -1189,14 +1211,18 @@ public class SpatializerHelper { case STATE_ENABLED_AVAILABLE: if (mSpat == null) { // try to recover by resetting the native spatializer state - Log.e(TAG, "checkSpatForHeadTracking(): " - + "native spatializer should not be null in state: " + mState); + Log.e(TAG, "checkSpatializer(): called from " + funcName + + "(), native spatializer should not be null in state: " + mState); postReset(); return false; } break; } - return mIsHeadTrackingSupported; + return true; + } + + private boolean checkSpatializerForHeadTracking(String funcName) { + return checkSpatializer(funcName) && mIsHeadTrackingSupported; } private void dispatchActualHeadTrackingMode(int newMode) { diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index dbbd354a2ff0..84dfe860bc84 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -180,7 +180,7 @@ final class DisplayDeviceInfo { public static final int TOUCH_VIRTUAL = 3; /** - * Diff result: The {@link #state} fields differ. + * Diff result: The {@link #state} or {@link #committedState} fields differ. */ public static final int DIFF_STATE = 1 << 0; @@ -342,6 +342,13 @@ final class DisplayDeviceInfo { public int state = Display.STATE_ON; /** + * Display committed state. + * + * This matches {@link DisplayDeviceInfo#state} only after the power state change finishes. + */ + public int committedState = Display.STATE_UNKNOWN; + + /** * The UID of the application that owns this display, or zero if it is owned by the system. * <p> * If the display is private, then only the owner can use it. @@ -394,7 +401,7 @@ final class DisplayDeviceInfo { */ public int diff(DisplayDeviceInfo other) { int diff = 0; - if (state != other.state) { + if (state != other.state || committedState != other.committedState) { diff |= DIFF_STATE; } if (colorMode != other.colorMode) { @@ -468,6 +475,7 @@ final class DisplayDeviceInfo { address = other.address; deviceProductInfo = other.deviceProductInfo; state = other.state; + committedState = other.committedState; ownerUid = other.ownerUid; ownerPackageName = other.ownerPackageName; frameRateOverrides = other.frameRateOverrides; @@ -508,6 +516,7 @@ final class DisplayDeviceInfo { } sb.append(", deviceProductInfo ").append(deviceProductInfo); sb.append(", state ").append(Display.stateToString(state)); + sb.append(", committedState ").append(Display.stateToString(committedState)); if (ownerUid != 0 || ownerPackageName != null) { sb.append(", owner ").append(ownerPackageName); sb.append(" (uid ").append(ownerUid).append(")"); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index efb2cb7a3283..58a182a61e44 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -198,6 +198,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { private DisplayDeviceInfo mInfo; private boolean mHavePendingChanges; private int mState = Display.STATE_UNKNOWN; + private int mCommittedState = Display.STATE_UNKNOWN; + // This is only set in the runnable returned from requestDisplayStateLocked. private float mBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT; private float mSdrBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT; @@ -635,6 +637,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mInfo.appVsyncOffsetNanos = mActiveSfDisplayMode.appVsyncOffsetNanos; mInfo.presentationDeadlineNanos = mActiveSfDisplayMode.presentationDeadlineNanos; mInfo.state = mState; + mInfo.committedState = mCommittedState; mInfo.uniqueId = getUniqueId(); final DisplayAddress.Physical physicalAddress = DisplayAddress.fromPhysicalDisplayId(mPhysicalDisplayId); @@ -822,6 +825,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } + setCommittedState(state); // If we're entering a suspended (but not OFF) power state and we // have a sidekick available, tell it now that it can take control. if (Display.isSuspendedState(state) && state != Display.STATE_OFF @@ -836,6 +840,16 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } + private void setCommittedState(int state) { + // After the display state is set, let's update the committed state. + getHandler().post(() -> { + synchronized (getSyncRoot()) { + mCommittedState = state; + updateDeviceInfoLocked(); + } + }); + } + private void setDisplayBrightness(float brightnessState, float sdrBrightnessState) { // brightnessState includes invalid, off and full range. @@ -1108,6 +1122,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { pw.println("mDefaultModeId=" + mDefaultModeId); pw.println("mUserPreferredModeId=" + mUserPreferredModeId); pw.println("mState=" + Display.stateToString(mState)); + pw.println("mCommittedState=" + Display.stateToString(mCommittedState)); pw.println("mBrightnessState=" + mBrightnessState); pw.println("mBacklightAdapter=" + mBacklightAdapter); pw.println("mAllmSupported=" + mAllmSupported); diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 7dc412ed1cf8..178278e7fd2f 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -390,6 +390,7 @@ final class LogicalDisplay { mBaseDisplayInfo.appVsyncOffsetNanos = deviceInfo.appVsyncOffsetNanos; mBaseDisplayInfo.presentationDeadlineNanos = deviceInfo.presentationDeadlineNanos; mBaseDisplayInfo.state = deviceInfo.state; + mBaseDisplayInfo.committedState = deviceInfo.committedState; mBaseDisplayInfo.smallestNominalAppWidth = maskedWidth; mBaseDisplayInfo.smallestNominalAppHeight = maskedHeight; mBaseDisplayInfo.largestNominalAppWidth = maskedWidth; diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index d9f2b6e4a0a3..b9ac89095112 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -1005,7 +1005,8 @@ final class LetterboxUiController { @VisibleForTesting boolean shouldShowLetterboxUi(WindowState mainWindow) { - return isSurfaceVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed() + return (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow)) + && mainWindow.areAppWindowBoundsLetterboxed() // Check for FLAG_SHOW_WALLPAPER explicitly instead of using // WindowContainer#showWallpaper because the later will return true when this // activity is using blurred wallpaper for letterbox background. @@ -1112,7 +1113,7 @@ final class LetterboxUiController { // for all corners for consistency and pick a minimal bottom one for consistency with a // taskbar rounded corners. int getRoundedCornersRadius(final WindowState mainWindow) { - if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) { + if (!requiresRoundedCorners(mainWindow)) { return 0; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index ddfd5ae2f27d..3461bc27b40c 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3453,8 +3453,10 @@ class Task extends TaskFragment { final boolean isTopActivityResumed = top != null && top.getOrganizedTask() == this && top.isState(RESUMED); - // Whether the direct top activity is in size compat mode on foreground. - info.topActivityInSizeCompat = isTopActivityResumed && top.inSizeCompatMode(); + final boolean isTopActivityVisible = top != null + && top.getOrganizedTask() == this && top.isVisible(); + // Whether the direct top activity is in size compat mode + info.topActivityInSizeCompat = isTopActivityVisible && top.inSizeCompatMode(); if (info.topActivityInSizeCompat && mWmService.mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { // We hide the restart button in case of transparent activities. diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index 82236bfd98e0..5f67b6e0c79d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -576,6 +576,40 @@ public class LocalDisplayAdapterTest { } @Test + public void testAfterDisplayStateChanges_committedSetAfterState() throws Exception { + FakeDisplay display = new FakeDisplay(PORT_A); + setUpDisplay(display); + updateAvailableDisplays(); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + DisplayDevice displayDevice = mListener.addedDisplays.get(0); + + // Turn off. + Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0, + 0); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + assertThat(mListener.changedDisplays.size()).isEqualTo(1); + mListener.changedDisplays.clear(); + assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF); + assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isNotEqualTo( + Display.STATE_OFF); + verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF); + + // Execute powerstate change. + changeStateRunnable.run(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + + // Verify that committed triggered a new change event and is set correctly. + verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF); + assertThat(mListener.changedDisplays.size()).isEqualTo(1); + assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF); + assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isEqualTo( + Display.STATE_OFF); + } + + @Test public void testAfterDisplayChange_GameContentTypeSupportIsUpdated() throws Exception { FakeDisplay display = new FakeDisplay(PORT_A); display.dynamicInfo.gameContentTypeSupported = true; diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 656c07b1acee..93b1df08369e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -453,8 +453,17 @@ public class LetterboxUiControllerTest extends WindowTestsBase { mainWindow.mInvGlobalScale = invGlobalScale; mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius); + doReturn(true).when(mActivity).isInLetterboxAnimation(); assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow)); + doReturn(false).when(mActivity).isInLetterboxAnimation(); + assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow)); + + doReturn(false).when(mainWindow).isOnScreen(); + assertEquals(0, mController.getRoundedCornersRadius(mainWindow)); + + doReturn(true).when(mActivity).isInLetterboxAnimation(); + assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow)); } @Test @@ -489,6 +498,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { doReturn(taskbar).when(insets).peekSource(taskbar.getType()); } doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds(); + doReturn(false).when(mActivity).isInLetterboxAnimation(); doReturn(true).when(mActivity).isVisible(); doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); doReturn(insets).when(mainWindow).getInsetsState(); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index d77b6ada268e..3ab9ea906128 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -51,6 +51,7 @@ import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANG import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; +import static com.android.server.wm.ActivityRecord.State.PAUSED; import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityRecord.State.STOPPED; @@ -3884,6 +3885,24 @@ public class SizeCompatTests extends WindowTestsBase { assertTrue(mActivity.inSizeCompatMode()); } + @Test + public void testTopActivityInSizeCompatMode_pausedAndInSizeCompatMode_returnsTrue() { + setUpDisplaySizeWithApp(1000, 2500); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + spyOn(mActivity); + doReturn(mTask).when(mActivity).getOrganizedTask(); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + mActivity.setState(PAUSED, "test"); + + assertTrue(mActivity.inSizeCompatMode()); + assertEquals(mActivity.getState(), PAUSED); + assertTrue(mActivity.isVisible()); + assertTrue(mTask.getTaskInfo().topActivityInSizeCompat); + } + /** * Tests that all three paths in which aspect ratio logic can be applied yield the same * result, which is that aspect ratio is respected on app bounds. The three paths are diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index fa98537f8909..87c2a9af8c15 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -1485,9 +1485,9 @@ public class WindowOrganizerTests extends WindowTestsBase { assertEquals(rootTask.mTaskId, info.taskId); assertTrue(info.topActivityInSizeCompat); - // Ensure task info show top activity that is not in foreground as not in size compat. + // Ensure task info show top activity that is not visible as not in size compat. clearInvocations(organizer); - doReturn(false).when(activity).isState(RESUMED); + doReturn(false).when(activity).isVisible(); rootTask.onSizeCompatActivityChanged(); mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer).onTaskInfoChanged(infoCaptor.capture()); @@ -1497,7 +1497,7 @@ public class WindowOrganizerTests extends WindowTestsBase { // Ensure task info show non size compat top activity as not in size compat. clearInvocations(organizer); - doReturn(true).when(activity).isState(RESUMED); + doReturn(true).when(activity).isVisible(); doReturn(false).when(activity).inSizeCompatMode(); rootTask.onSizeCompatActivityChanged(); mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); |