diff options
51 files changed, 1560 insertions, 427 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index 5172e592f750..519bbbb9bc39 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -34,6 +34,7 @@ package android { public static final class R.bool { field public static final int config_assistantOnTopOfDream = 17891333; // 0x1110005 field public static final int config_perDisplayFocusEnabled = 17891332; // 0x1110004 + field public static final int config_remoteInsetsControllerControlsSystemBars = 17891334; // 0x1110006 } public static final class R.string { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 79d2a8102358..76226888bf1a 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -4801,7 +4801,6 @@ public class Notification implements Parcelable contentView.setViewVisibility(R.id.time, View.GONE); contentView.setImageViewIcon(R.id.profile_badge, null); contentView.setViewVisibility(R.id.profile_badge, View.GONE); - contentView.setViewVisibility(R.id.alerted_icon, View.GONE); mN.mUsesStandardHeader = false; } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index c52b02bb6a3c..dfd305330b14 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1903,6 +1903,17 @@ public abstract class NotificationListenerService extends Service { /** * @hide */ + public @NonNull Ranking withAudiblyAlertedInfo(@Nullable Ranking previous) { + if (previous != null && previous.mLastAudiblyAlertedMs > 0 + && this.mLastAudiblyAlertedMs <= 0) { + this.mLastAudiblyAlertedMs = previous.mLastAudiblyAlertedMs; + } + return this; + } + + /** + * @hide + */ public void populate(Ranking other) { populate(other.mKey, other.mRank, diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl index 429c3aeba9b3..a0d4a6587bcf 100644 --- a/core/java/android/view/IDisplayWindowInsetsController.aidl +++ b/core/java/android/view/IDisplayWindowInsetsController.aidl @@ -27,6 +27,13 @@ import android.view.InsetsState; oneway interface IDisplayWindowInsetsController { /** + * Called when top focused window changes to determine whether or not to take over insets + * control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false. + * @param packageName: Passes the top package name + */ + void topFocusedWindowChanged(String packageName); + + /** * @see IWindow#insetsChanged */ void insetsChanged(in InsetsState insetsState); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 58597cf3fb6d..00fc67214f75 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -388,16 +388,6 @@ interface IWindowManager oneway void hideTransientBars(int displayId); /** - * When set to {@code true} the system bars will always be shown. This is true even if an app - * requests to be fullscreen by setting the system ui visibility flags. The - * functionality was added for the automotive case as a way to guarantee required content stays - * on screen at all times. - * - * @hide - */ - oneway void setForceShowSystemBars(boolean show); - - /** * Called by System UI to notify of changes to the visibility of Recents. */ oneway void setRecentsVisibility(boolean visible); diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 14cf258f18ab..fbf050ad08f6 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -2640,7 +2640,10 @@ public class ChooserActivity extends ResolverActivity implements } RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView(); ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter(); - if (gridAdapter == null || recyclerView == null) { + // Skip height calculation if recycler view was scrolled to prevent it inaccurately + // calculating the height, as the logic below does not account for the scrolled offset. + if (gridAdapter == null || recyclerView == null + || recyclerView.computeVerticalScrollOffset() != 0) { return; } @@ -3127,6 +3130,10 @@ public class ChooserActivity extends ResolverActivity implements ChooserGridAdapter currentRootAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter(); currentRootAdapter.updateDirectShareExpansion(); + // This fixes an edge case where after performing a variety of gestures, vertical scrolling + // ends up disabled. That's because at some point the old tab's vertical scrolling is + // disabled and the new tab's is enabled. For context, see b/159997845 + setVerticalScrollEnabled(true); } @Override diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java index ffa6041721c6..3a65a324f9d6 100644 --- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java @@ -252,8 +252,10 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd @Override protected void setupContainerPadding(View container) { + int initialBottomPadding = getContext().getResources().getDimensionPixelSize( + R.dimen.resolver_empty_state_container_padding_bottom); container.setPadding(container.getPaddingLeft(), container.getPaddingTop(), - container.getPaddingRight(), container.getPaddingBottom() + mBottomOffset); + container.getPaddingRight(), initialBottomPadding + mBottomOffset); } class ChooserProfileDescriptor extends ProfileDescriptor { diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml index 3615b9e2f9cb..df271f0f2942 100644 --- a/core/res/res/layout/notification_material_action_list.xml +++ b/core/res/res/layout/notification_material_action_list.xml @@ -25,6 +25,7 @@ android:id="@+id/actions_container_layout" android:layout_width="match_parent" android:layout_height="wrap_content" + android:gravity="end" android:orientation="horizontal" android:paddingEnd="@dimen/bubble_gone_padding_end" > diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 9f4b6f2142c0..8bbb2cec8a9d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -712,10 +712,17 @@ case, this can be disabled (set to false). --> <bool name="config_enableCarDockHomeLaunch">true</bool> - <!-- Control whether to force the display of System UI Bars at all times regardless of - System Ui Flags. This can be useful in the Automotive case if there's a requirement for - a UI element to be on screen at all times. --> - <bool name="config_forceShowSystemBars">false</bool> + <!-- Control whether to force apps to give up control over the display of system bars at all + times regardless of System Ui Flags. + In the Automotive case, this is helpful if there's a requirement for an UI element to be on + screen at all times. Setting this to true also gives System UI the ability to override the + visibility controls for the system through the usage of the + "SYSTEM_BAR_VISIBILITY_OVERRIDE" setting. + Ex: Only setting the config to true will force show system bars for the entire system. + Ex: Setting the config to true and the "SYSTEM_BAR_VISIBILITY_OVERRIDE" setting to + "immersive.status=apps" will force show navigation bar for all apps and force hide status + bar for all apps. --> + <bool name="config_remoteInsetsControllerControlsSystemBars">false</bool> <!-- HDMI behavior --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index e2fbbf46608f..e00aff1af37b 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3024,6 +3024,8 @@ <!-- @hide @TestApi --> <public type="bool" name="config_assistantOnTopOfDream" id="0x01110005" /> + <!-- @hide @TestApi --> + <public type="bool" name="config_remoteInsetsControllerControlsSystemBars" id="0x01110006" /> <!-- =============================================================== Resources added in version S of the platform diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 6901eb21d9fe..dcd0b51772a7 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1670,7 +1670,7 @@ <java-symbol type="bool" name="config_enableCarDockHomeLaunch" /> <java-symbol type="bool" name="config_enableLockBeforeUnlockScreen" /> <java-symbol type="bool" name="config_enableLockScreenRotation" /> - <java-symbol type="bool" name="config_forceShowSystemBars" /> + <java-symbol type="bool" name="config_remoteInsetsControllerControlsSystemBars" /> <java-symbol type="bool" name="config_lidControlsScreenLock" /> <java-symbol type="bool" name="config_lidControlsSleep" /> <java-symbol type="bool" name="config_lockDayNightMode" /> @@ -0,0 +1,25 @@ +diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +index fc43882..832dc91 100644 +--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java ++++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +@@ -67,6 +67,7 @@ import java.util.Arrays; + import java.util.HashSet; + import java.util.List; + import java.util.Set; ++import java.util.NoSuchElementException; + + /** + * This class represents an accessibility client - either an AccessibilityService or a UiAutomation. +@@ -978,7 +979,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ + /* ignore */ + } + if (mService != null) { +- mService.unlinkToDeath(this, 0); ++ try { ++ mService.unlinkToDeath(this, 0); ++ }catch(NoSuchElementException e) { ++ Slog.e(LOG_TAG, "Failed unregistering death link"); ++ } + mService = null; + } + diff --git a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml index 1782d2536035..5aab0a172b99 100644 --- a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml +++ b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml @@ -29,13 +29,12 @@ android:orientation="horizontal" app:layout_constraintGuide_begin="@dimen/headsup_scrim_height"/> - <!-- Include a FocusParkingView at the beginning or end. The rotary controller "parks" the - focus here when the user navigates to another window. This is also used to prevent - wrap-around which is why it must be first or last in Tab order. --> + <!-- Include a FocusParkingView at the beginning. The rotary controller "parks" the focus here + when the user navigates to another window. This is also used to prevent wrap-around. --> <com.android.car.ui.FocusParkingView android:layout_width="wrap_content" android:layout_height="wrap_content" - app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/> <View diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java index 5bf989a971b9..496742680893 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java @@ -67,6 +67,8 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.volume.VolumeDialogComponent; +import com.android.systemui.wm.DisplayImeController; +import com.android.systemui.wm.DisplaySystemBarsController; import javax.inject.Named; import javax.inject.Singleton; @@ -97,6 +99,10 @@ public abstract class CarSystemUIModule { groupManager, configurationController); } + @Binds + abstract DisplayImeController bindDisplayImeController( + DisplaySystemBarsController displaySystemBarsController); + @Singleton @Provides @Named(LEAK_REPORT_EMAIL_NAME) diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java index d60bc418ece2..adf8d4d5acf8 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java @@ -148,10 +148,9 @@ public class NavigationBarViewFactory { CarNavigationBarView view = (CarNavigationBarView) View.inflate(mContext, barLayout, /* root= */ null); - // Include a FocusParkingView at the end. The rotary controller "parks" the focus here when - // the user navigates to another window. This is also used to prevent wrap-around which is - // why it must be first or last in Tab order. - view.addView(new FocusParkingView(mContext)); + // Include a FocusParkingView at the beginning. The rotary controller "parks" the focus here + // when the user navigates to another window. This is also used to prevent wrap-around. + view.addView(new FocusParkingView(mContext), 0); mCachedViewMap.put(type, view); return mCachedViewMap.get(type); diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java b/packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java new file mode 100644 index 000000000000..5f9665ff7632 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wm; + +import android.car.settings.CarSettings; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.ArraySet; +import android.util.Slog; +import android.view.WindowInsets; + +import androidx.annotation.VisibleForTesting; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Util class to load PolicyControl and allow for querying if a package matches immersive filters. + * Similar to {@link com.android.server.wm.PolicyControl}, but separate due to CarSystemUI needing + * to set its own policies for system bar visibilities. + * + * This forces immersive mode behavior for one or both system bars (based on a package + * list). + * + * Control by setting {@link Settings.Global#POLICY_CONTROL_AUTO} to one or more name-value pairs. + * e.g. + * to force immersive mode everywhere: + * "immersive.full=*" + * to force hide status bars for com.package1 but not com.package2: + * "immersive.status=com.package1,-com.package2" + * + * Separate multiple name-value pairs with ':' + * e.g. "immersive.status=com.package:immersive.navigation=*" + */ +public class BarControlPolicy { + + private static final String TAG = "BarControlPolicy"; + private static final boolean DEBUG = false; + + private static final String NAME_IMMERSIVE_FULL = "immersive.full"; + private static final String NAME_IMMERSIVE_STATUS = "immersive.status"; + private static final String NAME_IMMERSIVE_NAVIGATION = "immersive.navigation"; + + @VisibleForTesting + static String sSettingValue; + @VisibleForTesting + static Filter sImmersiveStatusFilter; + private static Filter sImmersiveNavigationFilter; + + /** Loads values from the POLICY_CONTROL setting to set filters. */ + static boolean reloadFromSetting(Context context) { + if (DEBUG) Slog.d(TAG, "reloadFromSetting()"); + String value = null; + try { + value = Settings.Global.getStringForUser(context.getContentResolver(), + CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE, + UserHandle.USER_CURRENT); + if (sSettingValue == value || sSettingValue != null && sSettingValue.equals(value)) { + return false; + } + setFilters(value); + sSettingValue = value; + } catch (Throwable t) { + Slog.w(TAG, "Error loading policy control, value=" + value, t); + return false; + } + return true; + } + + /** Used in testing to reset BarControlPolicy. */ + @VisibleForTesting + static void reset() { + sSettingValue = null; + sImmersiveStatusFilter = null; + sImmersiveNavigationFilter = null; + } + + /** + * Registers a content observer to listen to updates to the SYSTEM_BAR_VISIBILITY_OVERRIDE flag. + */ + static void registerContentObserver(Context context, Handler handler, FilterListener listener) { + context.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE), false, + new ContentObserver(handler) { + @Override + public void onChange(boolean selfChange) { + if (reloadFromSetting(context)) { + listener.onFilterUpdated(); + } + } + }, UserHandle.USER_ALL); + } + + /** + * Returns bar visibilities based on POLICY_CONTROL_AUTO filters and window policies. + * @return int[], where the first value is the inset types that should be shown, and the second + * is the inset types that should be hidden. + */ + @WindowInsets.Type.InsetsType + static int[] getBarVisibilities(String packageName) { + int hideTypes = 0; + int showTypes = 0; + if (matchesStatusFilter(packageName)) { + hideTypes |= WindowInsets.Type.statusBars(); + } else { + showTypes |= WindowInsets.Type.statusBars(); + } + if (matchesNavigationFilter(packageName)) { + hideTypes |= WindowInsets.Type.navigationBars(); + } else { + showTypes |= WindowInsets.Type.navigationBars(); + } + + return new int[] {showTypes, hideTypes}; + } + + private static boolean matchesStatusFilter(String packageName) { + return sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(packageName); + } + + private static boolean matchesNavigationFilter(String packageName) { + return sImmersiveNavigationFilter != null + && sImmersiveNavigationFilter.matches(packageName); + } + + private static void setFilters(String value) { + if (DEBUG) Slog.d(TAG, "setFilters: " + value); + sImmersiveStatusFilter = null; + sImmersiveNavigationFilter = null; + if (value != null) { + String[] nvps = value.split(":"); + for (String nvp : nvps) { + int i = nvp.indexOf('='); + if (i == -1) continue; + String n = nvp.substring(0, i); + String v = nvp.substring(i + 1); + if (n.equals(NAME_IMMERSIVE_FULL)) { + Filter f = Filter.parse(v); + sImmersiveStatusFilter = sImmersiveNavigationFilter = f; + } else if (n.equals(NAME_IMMERSIVE_STATUS)) { + Filter f = Filter.parse(v); + sImmersiveStatusFilter = f; + } else if (n.equals(NAME_IMMERSIVE_NAVIGATION)) { + Filter f = Filter.parse(v); + sImmersiveNavigationFilter = f; + } + } + } + if (DEBUG) { + Slog.d(TAG, "immersiveStatusFilter: " + sImmersiveStatusFilter); + Slog.d(TAG, "immersiveNavigationFilter: " + sImmersiveNavigationFilter); + } + } + + private static class Filter { + private static final String ALL = "*"; + + private final ArraySet<String> mWhitelist; + private final ArraySet<String> mBlacklist; + + private Filter(ArraySet<String> whitelist, ArraySet<String> blacklist) { + mWhitelist = whitelist; + mBlacklist = blacklist; + } + + boolean matches(String packageName) { + if (packageName == null) return false; + if (onBlacklist(packageName)) return false; + return onWhitelist(packageName); + } + + private boolean onBlacklist(String packageName) { + return mBlacklist.contains(packageName) || mBlacklist.contains(ALL); + } + + private boolean onWhitelist(String packageName) { + return mWhitelist.contains(ALL) || mWhitelist.contains(packageName); + } + + void dump(PrintWriter pw) { + pw.print("Filter["); + dump("whitelist", mWhitelist, pw); pw.print(','); + dump("blacklist", mBlacklist, pw); pw.print(']'); + } + + private void dump(String name, ArraySet<String> set, PrintWriter pw) { + pw.print(name); pw.print("=("); + int n = set.size(); + for (int i = 0; i < n; i++) { + if (i > 0) pw.print(','); + pw.print(set.valueAt(i)); + } + pw.print(')'); + } + + @Override + public String toString() { + StringWriter sw = new StringWriter(); + dump(new PrintWriter(sw, true)); + return sw.toString(); + } + + // value = comma-delimited list of tokens, where token = (package name|*) + // e.g. "com.package1", or "com.android.systemui, com.android.keyguard" or "*" + static Filter parse(String value) { + if (value == null) return null; + ArraySet<String> whitelist = new ArraySet<String>(); + ArraySet<String> blacklist = new ArraySet<String>(); + for (String token : value.split(",")) { + token = token.trim(); + if (token.startsWith("-") && token.length() > 1) { + token = token.substring(1); + blacklist.add(token); + } else { + whitelist.add(token); + } + } + return new Filter(whitelist, blacklist); + } + } + + /** + * Interface to listen for updates to the filter triggered by the content observer listening to + * the SYSTEM_BAR_VISIBILITY_OVERRIDE flag. + */ + interface FilterListener { + + /** Callback triggered when the content observer updates the filter. */ + void onFilterUpdated(); + } + + private BarControlPolicy() {} +} diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java new file mode 100644 index 000000000000..a831464e7987 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wm; + +import android.os.Handler; +import android.os.RemoteException; +import android.util.ArraySet; +import android.util.Slog; +import android.util.SparseArray; +import android.view.IDisplayWindowInsetsController; +import android.view.InsetsController; +import android.view.InsetsSourceControl; +import android.view.InsetsState; +import android.view.WindowInsets; + +import androidx.annotation.VisibleForTesting; + +import com.android.systemui.TransactionPool; +import com.android.systemui.dagger.qualifiers.Main; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Controller that maps between displays and {@link IDisplayWindowInsetsController} in order to + * give system bar control to SystemUI. + * {@link R.bool#config_remoteInsetsControllerControlsSystemBars} determines whether this controller + * takes control or not. + */ +@Singleton +public class DisplaySystemBarsController extends DisplayImeController { + + private static final String TAG = "DisplaySystemBarsController"; + + private SparseArray<PerDisplay> mPerDisplaySparseArray; + + @Inject + public DisplaySystemBarsController( + SystemWindows syswin, + DisplayController displayController, + @Main Handler mainHandler, + TransactionPool transactionPool) { + super(syswin, displayController, mainHandler, transactionPool); + } + + @Override + public void onDisplayAdded(int displayId) { + PerDisplay pd = new PerDisplay(displayId); + try { + mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to set insets controller on display " + displayId); + } + // Lazy loading policy control filters instead of during boot. + if (mPerDisplaySparseArray == null) { + mPerDisplaySparseArray = new SparseArray<>(); + BarControlPolicy.reloadFromSetting(mSystemWindows.mContext); + BarControlPolicy.registerContentObserver(mSystemWindows.mContext, mHandler, () -> { + int size = mPerDisplaySparseArray.size(); + for (int i = 0; i < size; i++) { + mPerDisplaySparseArray.valueAt(i).modifyDisplayWindowInsets(); + } + }); + } + mPerDisplaySparseArray.put(displayId, pd); + } + + @Override + public void onDisplayRemoved(int displayId) { + try { + mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to remove insets controller on display " + displayId); + } + mPerDisplaySparseArray.remove(displayId); + } + + @VisibleForTesting + class PerDisplay extends IDisplayWindowInsetsController.Stub { + + int mDisplayId; + InsetsController mInsetsController; + InsetsState mInsetsState = new InsetsState(); + String mPackageName; + + PerDisplay(int displayId) { + mDisplayId = displayId; + mInsetsController = new InsetsController( + new DisplaySystemBarsInsetsControllerHost(mHandler, this)); + } + + @Override + public void insetsChanged(InsetsState insetsState) { + if (mInsetsState.equals(insetsState)) { + return; + } + mInsetsState.set(insetsState, true /* copySources */); + mInsetsController.onStateChanged(insetsState); + if (mPackageName != null) { + modifyDisplayWindowInsets(); + } + } + + @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) { + mInsetsController.onControlsChanged(activeControls); + } + + @Override + public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) { + mInsetsController.hide(types); + } + + @Override + public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) { + mInsetsController.show(types); + } + + @Override + public void topFocusedWindowChanged(String packageName) { + // If both package names are null or both package names are equal, return. + if (mPackageName == packageName + || (mPackageName != null && mPackageName.equals(packageName))) { + return; + } + mPackageName = packageName; + modifyDisplayWindowInsets(); + } + + private void modifyDisplayWindowInsets() { + if (mPackageName == null) { + return; + } + int[] barVisibilities = BarControlPolicy.getBarVisibilities(mPackageName); + updateInsetsState(barVisibilities[0], /* visible= */ true); + updateInsetsState(barVisibilities[1], /* visible= */ false); + showInsets(barVisibilities[0], /* fromIme= */ false); + hideInsets(barVisibilities[1], /* fromIme= */ false); + try { + mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to update window manager service."); + } + } + + private void updateInsetsState(@WindowInsets.Type.InsetsType int types, boolean visible) { + ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + mInsetsState.getSource(internalTypes.valueAt(i)).setVisible(visible); + } + } + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsInsetsControllerHost.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsInsetsControllerHost.java new file mode 100644 index 000000000000..2f8da44ba851 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsInsetsControllerHost.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wm; + +import android.annotation.NonNull; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.IDisplayWindowInsetsController; +import android.view.InsetsController; +import android.view.InsetsState; +import android.view.SurfaceControl; +import android.view.SyncRtSurfaceTransactionApplier; +import android.view.WindowInsets; +import android.view.WindowInsetsAnimation; +import android.view.WindowInsetsController; +import android.view.inputmethod.InputMethodManager; + +import java.util.List; + +/** + * Implements {@link InsetsController.Host} for usage by + * {@link DisplaySystemBarsController.PerDisplay} instances in {@link DisplaySystemBarsController}. + * @hide + */ +public class DisplaySystemBarsInsetsControllerHost implements InsetsController.Host { + + private static final String TAG = DisplaySystemBarsInsetsControllerHost.class.getSimpleName(); + + private final Handler mHandler; + private final IDisplayWindowInsetsController mController; + private final float[] mTmpFloat9 = new float[9]; + + public DisplaySystemBarsInsetsControllerHost( + Handler handler, IDisplayWindowInsetsController controller) { + mHandler = handler; + mController = controller; + } + + @Override + public Handler getHandler() { + return mHandler; + } + + @Override + public void notifyInsetsChanged() { + // no-op + } + + @Override + public void dispatchWindowInsetsAnimationPrepare(@NonNull WindowInsetsAnimation animation) { + // no-op + } + + @Override + public WindowInsetsAnimation.Bounds dispatchWindowInsetsAnimationStart( + @NonNull WindowInsetsAnimation animation, + @NonNull WindowInsetsAnimation.Bounds bounds) { + return null; + } + + @Override + public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets, + @NonNull List<WindowInsetsAnimation> runningAnimations) { + return null; + } + + @Override + public void dispatchWindowInsetsAnimationEnd(@NonNull WindowInsetsAnimation animation) { + // no-op + } + + @Override + public void applySurfaceParams(final SyncRtSurfaceTransactionApplier.SurfaceParams... params) { + for (int i = params.length - 1; i >= 0; i--) { + SyncRtSurfaceTransactionApplier.applyParams( + new SurfaceControl.Transaction(), params[i], mTmpFloat9); + } + + } + + @Override + public void updateCompatSysUiVisibility( + @InsetsState.InternalInsetsType int type, boolean visible, boolean hasControl) { + // no-op + } + + @Override + public void onInsetsModified(InsetsState insetsState) { + try { + mController.insetsChanged(insetsState); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send insets to controller"); + } + } + + @Override + public boolean hasAnimationCallbacks() { + return false; + } + + @Override + public void setSystemBarsAppearance( + @WindowInsetsController.Appearance int appearance, + @WindowInsetsController.Appearance int mask) { + // no-op + } + + @Override + public @WindowInsetsController.Appearance int getSystemBarsAppearance() { + return 0; + } + + @Override + public void setSystemBarsBehavior(@WindowInsetsController.Behavior int behavior) { + // no-op + } + + @Override + public @WindowInsetsController.Behavior int getSystemBarsBehavior() { + return 0; + } + + @Override + public void releaseSurfaceControlFromRt(SurfaceControl surfaceControl) { + surfaceControl.release(); + } + + @Override + public void addOnPreDrawRunnable(Runnable r) { + mHandler.post(r); + } + + @Override + public void postInsetsAnimationCallback(Runnable r) { + mHandler.post(r); + } + + @Override + public InputMethodManager getInputMethodManager() { + return null; + } + + @Override + public String getRootViewTitle() { + return null; + } + + @Override + public int dipToPx(int dips) { + return 0; + } + + @Override + public IBinder getWindowToken() { + return null; + } +} diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java index eca51e34995c..232df2eced39 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java @@ -24,6 +24,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadsetClient; import android.content.Intent; import android.os.Handler; @@ -44,14 +46,19 @@ import org.junit.runner.RunWith; public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase { private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + private static final String BLUETOOTH_REMOTE_ADDRESS = "00:11:22:33:44:55"; private ConnectedDeviceVoiceRecognitionNotifier mVoiceRecognitionNotifier; + private TestableLooper mTestableLooper; private Handler mTestHandler; + private BluetoothDevice mBluetoothDevice; @Before public void setUp() throws Exception { - TestableLooper testableLooper = TestableLooper.get(this); - mTestHandler = spy(new Handler(testableLooper.getLooper())); + mTestableLooper = TestableLooper.get(this); + mTestHandler = spy(new Handler(mTestableLooper.getLooper())); + mBluetoothDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( + BLUETOOTH_REMOTE_ADDRESS); mVoiceRecognitionNotifier = new ConnectedDeviceVoiceRecognitionNotifier( mContext, mTestHandler); mVoiceRecognitionNotifier.onBootCompleted(); @@ -61,8 +68,10 @@ public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase { public void testReceiveIntent_started_showToast() { Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT); intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, VOICE_RECOGNITION_STARTED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); - waitForIdleSync(); + mTestableLooper.processAllMessages(); verify(mTestHandler).post(any()); } @@ -71,8 +80,10 @@ public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase { public void testReceiveIntent_invalidExtra_noToast() { Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT); intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, INVALID_VALUE); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); - waitForIdleSync(); + mTestableLooper.processAllMessages(); verify(mTestHandler, never()).post(any()); } @@ -80,8 +91,10 @@ public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase { @Test public void testReceiveIntent_noExtra_noToast() { Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); - waitForIdleSync(); + mTestableLooper.processAllMessages(); verify(mTestHandler, never()).post(any()); } @@ -89,8 +102,10 @@ public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase { @Test public void testReceiveIntent_invalidIntent_noToast() { Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); - waitForIdleSync(); + mTestableLooper.processAllMessages(); verify(mTestHandler, never()).post(any()); } diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/wm/BarControlPolicyTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/wm/BarControlPolicyTest.java new file mode 100644 index 000000000000..da7cb8e4f6ac --- /dev/null +++ b/packages/CarSystemUI/tests/src/com/android/systemui/wm/BarControlPolicyTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wm; + +import static android.view.WindowInsets.Type.navigationBars; +import static android.view.WindowInsets.Type.statusBars; + +import static com.google.common.truth.Truth.assertThat; + +import android.car.settings.CarSettings; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class BarControlPolicyTest extends SysuiTestCase { + + private static final String PACKAGE_NAME = "sample.app"; + + @Before + public void setUp() { + BarControlPolicy.reset(); + } + + @After + public void tearDown() { + Settings.Global.clearProviderForTest(); + } + + @Test + public void reloadFromSetting_notSet_doesNotSetFilters() { + BarControlPolicy.reloadFromSetting(mContext); + + assertThat(BarControlPolicy.sImmersiveStatusFilter).isNull(); + } + + @Test + public void reloadFromSetting_invalidPolicyControlString_doesNotSetFilters() { + String text = "sample text"; + Settings.Global.putString( + mContext.getContentResolver(), + CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE, + text + ); + + BarControlPolicy.reloadFromSetting(mContext); + + assertThat(BarControlPolicy.sImmersiveStatusFilter).isNull(); + } + + @Test + public void reloadFromSetting_validPolicyControlString_setsFilters() { + String text = "immersive.status=" + PACKAGE_NAME; + Settings.Global.putString( + mContext.getContentResolver(), + CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE, + text + ); + + BarControlPolicy.reloadFromSetting(mContext); + + assertThat(BarControlPolicy.sImmersiveStatusFilter).isNotNull(); + } + + @Test + public void reloadFromSetting_filtersSet_doesNotSetFiltersAgain() { + String text = "immersive.status=" + PACKAGE_NAME; + Settings.Global.putString( + mContext.getContentResolver(), + CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE, + text + ); + + BarControlPolicy.reloadFromSetting(mContext); + + assertThat(BarControlPolicy.reloadFromSetting(mContext)).isFalse(); + } + + @Test + public void getBarVisibilities_policyControlNotSet_showsSystemBars() { + int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME); + + assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars()); + assertThat(visibilities[1]).isEqualTo(0); + } + + @Test + public void getBarVisibilities_immersiveStatusForAppAndMatchingApp_hidesStatusBar() { + Settings.Global.putString( + mContext.getContentResolver(), + CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE, + "immersive.status=" + PACKAGE_NAME); + BarControlPolicy.reloadFromSetting(mContext); + + int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME); + + assertThat(visibilities[0]).isEqualTo(navigationBars()); + assertThat(visibilities[1]).isEqualTo(statusBars()); + } + + @Test + public void getBarVisibilities_immersiveStatusForAppAndNonMatchingApp_showsSystemBars() { + Settings.Global.putString( + mContext.getContentResolver(), + CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE, + "immersive.status=" + PACKAGE_NAME); + BarControlPolicy.reloadFromSetting(mContext); + + int[] visibilities = BarControlPolicy.getBarVisibilities("sample2.app"); + + assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars()); + assertThat(visibilities[1]).isEqualTo(0); + } + + @Test + public void getBarVisibilities_immersiveStatusForAppsAndNonApp_showsSystemBars() { + Settings.Global.putString( + mContext.getContentResolver(), + CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE, + "immersive.status=apps"); + BarControlPolicy.reloadFromSetting(mContext); + + int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME); + + assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars()); + assertThat(visibilities[1]).isEqualTo(0); + } + + @Test + public void getBarVisibilities_immersiveFullForAppAndMatchingApp_hidesSystemBars() { + Settings.Global.putString( + mContext.getContentResolver(), + CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE, + "immersive.full=" + PACKAGE_NAME); + BarControlPolicy.reloadFromSetting(mContext); + + int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME); + + assertThat(visibilities[0]).isEqualTo(0); + assertThat(visibilities[1]).isEqualTo(statusBars() | navigationBars()); + } + + @Test + public void getBarVisibilities_immersiveFullForAppAndNonMatchingApp_showsSystemBars() { + Settings.Global.putString( + mContext.getContentResolver(), + CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE, + "immersive.full=" + PACKAGE_NAME); + BarControlPolicy.reloadFromSetting(mContext); + + int[] visibilities = BarControlPolicy.getBarVisibilities("sample2.app"); + + assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars()); + assertThat(visibilities[1]).isEqualTo(0); + } + + @Test + public void getBarVisibilities_immersiveFullForAppsAndNonApp_showsSystemBars() { + Settings.Global.putString( + mContext.getContentResolver(), + CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE, + "immersive.full=apps"); + BarControlPolicy.reloadFromSetting(mContext); + + int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME); + + assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars()); + assertThat(visibilities[1]).isEqualTo(0); + } +} diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java new file mode 100644 index 000000000000..29cc8eec4bc3 --- /dev/null +++ b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wm; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; + +import android.car.settings.CarSettings; +import android.os.Handler; +import android.os.RemoteException; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.IWindowManager; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.TransactionPool; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class DisplaySystemBarsControllerTest extends SysuiTestCase { + + private DisplaySystemBarsController mController; + + private static final int DISPLAY_ID = 1; + + @Mock + private SystemWindows mSystemWindows; + @Mock + private IWindowManager mIWindowManager; + @Mock + private DisplayController mDisplayController; + @Mock + private Handler mHandler; + @Mock + private TransactionPool mTransactionPool; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mSystemWindows.mContext = mContext; + mSystemWindows.mWmService = mIWindowManager; + + mController = new DisplaySystemBarsController( + mSystemWindows, + mDisplayController, + mHandler, + mTransactionPool + ); + } + + @Test + public void onDisplayAdded_setsDisplayWindowInsetsControllerOnWMService() + throws RemoteException { + mController.onDisplayAdded(DISPLAY_ID); + + verify(mIWindowManager).setDisplayWindowInsetsController( + eq(DISPLAY_ID), any(DisplaySystemBarsController.PerDisplay.class)); + } + + @Test + public void onDisplayAdded_loadsBarControlPolicyFilters() { + String text = "sample text"; + Settings.Global.putString( + mContext.getContentResolver(), + CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE, + text + ); + + mController.onDisplayAdded(DISPLAY_ID); + + assertThat(BarControlPolicy.sSettingValue).isEqualTo(text); + } + + @Test + public void onDisplayRemoved_unsetsDisplayWindowInsetsControllerInWMService() + throws RemoteException { + mController.onDisplayAdded(DISPLAY_ID); + + mController.onDisplayRemoved(DISPLAY_ID); + + verify(mIWindowManager).setDisplayWindowInsetsController( + DISPLAY_ID, /* displayWindowInsetsController= */ null); + } +} diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 8212d6159737..a56f6f56836a 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -92,6 +92,8 @@ <item type="id" name="requires_remeasuring"/> + <item type="id" name="secondary_home_handle" /> + <!-- Whether the icon is from a notification for which targetSdk < L --> <item type="id" name="icon_is_pre_L"/> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java index 40a93e1cdc47..57e2362bd511 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java @@ -24,7 +24,6 @@ import android.content.pm.ShortcutInfo; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -95,9 +94,16 @@ public class BubbleIconFactory extends BaseIconFactory { Bitmap badgeAndRing = Bitmap.createBitmap(userBadgedBitmap.getWidth(), userBadgedBitmap.getHeight(), userBadgedBitmap.getConfig()); Canvas c = new Canvas(badgeAndRing); - Rect dest = new Rect((int) ringStrokeWidth, (int) ringStrokeWidth, - c.getHeight() - (int) ringStrokeWidth, c.getWidth() - (int) ringStrokeWidth); - c.drawBitmap(userBadgedBitmap, null, dest, null); + + final int bitmapTop = (int) ringStrokeWidth; + final int bitmapLeft = (int) ringStrokeWidth; + final int bitmapWidth = c.getWidth() - 2 * (int) ringStrokeWidth; + final int bitmapHeight = c.getHeight() - 2 * (int) ringStrokeWidth; + + Bitmap scaledBitmap = Bitmap.createScaledBitmap(userBadgedBitmap, bitmapWidth, + bitmapHeight, /* filter */ true); + c.drawBitmap(scaledBitmap, bitmapTop, bitmapLeft, /* paint */null); + Paint ringPaint = new Paint(); ringPaint.setStyle(Paint.Style.STROKE); ringPaint.setColor(importantConversationColor); @@ -105,6 +111,7 @@ public class BubbleIconFactory extends BaseIconFactory { ringPaint.setStrokeWidth(ringStrokeWidth); c.drawCircle(c.getWidth() / 2, c.getHeight() / 2, c.getWidth() / 2 - ringStrokeWidth, ringPaint); + shadowGenerator.recreateIcon(Bitmap.createBitmap(badgeAndRing), c); return createIconBitmap(badgeAndRing); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 749b537ea364..c61a36c12059 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -1586,6 +1586,11 @@ public class BubbleStackView extends FrameLayout Log.d(TAG, "setSelectedBubble: " + bubbleToSelect); } + if (bubbleToSelect == null) { + mBubbleData.setShowingOverflow(false); + return; + } + // Ignore this new bubble only if it is the exact same bubble object. Otherwise, we'll want // to re-render it even if it has the same key (equals() returns true). If the currently // expanded bubble is removed and instantly re-added, we'll get back a new Bubble instance @@ -1594,10 +1599,11 @@ public class BubbleStackView extends FrameLayout if (mExpandedBubble == bubbleToSelect) { return; } - if (bubbleToSelect == null || bubbleToSelect.getKey() != BubbleOverflow.KEY) { - mBubbleData.setShowingOverflow(false); - } else { + + if (bubbleToSelect.getKey() == BubbleOverflow.KEY) { mBubbleData.setShowingOverflow(true); + } else { + mBubbleData.setShowingOverflow(false); } if (mIsExpanded && mIsExpansionAnimating) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt index e8f0e069c98d..d0642ccf9714 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt @@ -33,23 +33,31 @@ class MediaDataCombineLatest @Inject constructor( init { dataSource.addListener(object : MediaDataManager.Listener { override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { - if (oldKey != null && !oldKey.equals(key)) { - val s = entries[oldKey]?.second - entries[key] = data to entries[oldKey]?.second - entries.remove(oldKey) + if (oldKey != null && oldKey != key && entries.contains(oldKey)) { + entries[key] = data to entries.remove(oldKey)?.second + update(key, oldKey) } else { entries[key] = data to entries[key]?.second + update(key, key) } - update(key, oldKey) } override fun onMediaDataRemoved(key: String) { remove(key) } }) deviceSource.addListener(object : MediaDeviceManager.Listener { - override fun onMediaDeviceChanged(key: String, data: MediaDeviceData?) { - entries[key] = entries[key]?.first to data - update(key, key) + override fun onMediaDeviceChanged( + key: String, + oldKey: String?, + data: MediaDeviceData? + ) { + if (oldKey != null && oldKey != key && entries.contains(oldKey)) { + entries[key] = entries.remove(oldKey)?.first to data + update(key, oldKey) + } else { + entries[key] = entries[key]?.first to data + update(key, key) + } } override fun onKeyRemoved(key: String) { remove(key) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt index 7ae2dc5c0941..143f8496e7aa 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -71,7 +71,8 @@ class MediaDeviceManager @Inject constructor( val controller = data.token?.let { MediaController(context, it) } - entry = Token(key, controller, localMediaManagerFactory.create(data.packageName)) + entry = Token(key, oldKey, controller, + localMediaManagerFactory.create(data.packageName)) entries[key] = entry entry.start() } @@ -98,23 +99,24 @@ class MediaDeviceManager @Inject constructor( } } - private fun processDevice(key: String, device: MediaDevice?) { + private fun processDevice(key: String, oldKey: String?, device: MediaDevice?) { val enabled = device != null val data = MediaDeviceData(enabled, device?.iconWithoutBackground, device?.name) listeners.forEach { - it.onMediaDeviceChanged(key, data) + it.onMediaDeviceChanged(key, oldKey, data) } } interface Listener { /** Called when the route has changed for a given notification. */ - fun onMediaDeviceChanged(key: String, data: MediaDeviceData?) + fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?) /** Called when the notification was removed. */ fun onKeyRemoved(key: String) } private inner class Token( val key: String, + val oldKey: String?, val controller: MediaController?, val localMediaManager: LocalMediaManager ) : LocalMediaManager.DeviceCallback { @@ -125,7 +127,7 @@ class MediaDeviceManager @Inject constructor( set(value) { if (!started || value != field) { field = value - processDevice(key, value) + processDevice(key, oldKey, value) } } fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index d6e1a16bc69e..85444303490d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -214,9 +214,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private Animator mScreenshotAnimation; private Runnable mOnCompleteRunnable; private Animator mDismissAnimation; - private boolean mInDarkMode = false; - private boolean mDirectionLTR = true; - private boolean mOrientationPortrait = true; + private boolean mInDarkMode; + private boolean mDirectionLTR; + private boolean mOrientationPortrait; private float mCornerSizeX; private float mDismissDeltaY; @@ -245,9 +245,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } }; - /** - * @param context everything needs a context :( - */ @Inject public GlobalScreenshot( Context context, @Main Resources resources, @@ -320,6 +317,104 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset inoutInfo.touchableRegion.set(touchRegion); } + void takeScreenshotFullscreen(Consumer<Uri> finisher, Runnable onComplete) { + mOnCompleteRunnable = onComplete; + + mDisplay.getRealMetrics(mDisplayMetrics); + takeScreenshotInternal( + finisher, + new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); + } + + void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, + Insets visibleInsets, int taskId, int userId, ComponentName topComponent, + Consumer<Uri> finisher, Runnable onComplete) { + // TODO: use task Id, userId, topComponent for smart handler + + mOnCompleteRunnable = onComplete; + if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) { + saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false); + } else { + saveScreenshot(screenshot, finisher, + new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE, + true); + } + } + + /** + * Displays a screenshot selector + */ + @SuppressLint("ClickableViewAccessibility") + void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) { + dismissScreenshot("new screenshot requested", true); + mOnCompleteRunnable = onComplete; + + mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); + mScreenshotSelectorView.setOnTouchListener((v, event) -> { + ScreenshotSelectorView view = (ScreenshotSelectorView) v; + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + view.startSelection((int) event.getX(), (int) event.getY()); + return true; + case MotionEvent.ACTION_MOVE: + view.updateSelection((int) event.getX(), (int) event.getY()); + return true; + case MotionEvent.ACTION_UP: + view.setVisibility(View.GONE); + mWindowManager.removeView(mScreenshotLayout); + final Rect rect = view.getSelectionRect(); + if (rect != null) { + if (rect.width() != 0 && rect.height() != 0) { + // Need mScreenshotLayout to handle it after the view disappears + mScreenshotLayout.post(() -> takeScreenshotInternal(finisher, rect)); + } + } + + view.stopSelection(); + return true; + } + + return false; + }); + mScreenshotLayout.post(() -> { + mScreenshotSelectorView.setVisibility(View.VISIBLE); + mScreenshotSelectorView.requestFocus(); + }); + } + + /** + * Cancels screenshot request + */ + void stopScreenshot() { + // If the selector layer still presents on screen, we remove it and resets its state. + if (mScreenshotSelectorView.getSelectionRect() != null) { + mWindowManager.removeView(mScreenshotLayout); + mScreenshotSelectorView.stopSelection(); + } + } + + /** + * Clears current screenshot + */ + void dismissScreenshot(String reason, boolean immediate) { + Log.v(TAG, "clearing screenshot: " + reason); + mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); + mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + if (!immediate) { + mDismissAnimation = createScreenshotDismissAnimation(); + mDismissAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + clearScreenshot(); + } + }); + mDismissAnimation.start(); + } else { + clearScreenshot(); + } + } + private void onConfigChanged(Configuration newConfig) { boolean needsUpdate = false; // dark mode @@ -408,15 +503,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } return mScreenshotLayout.onApplyWindowInsets(insets); }); - mScreenshotLayout.setOnKeyListener(new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) { - dismissScreenshot("back pressed", true); - return true; - } - return false; + mScreenshotLayout.setOnKeyListener((v, keyCode, event) -> { + if (keyCode == KeyEvent.KEYCODE_BACK) { + dismissScreenshot("back pressed", true); + return true; } + return false; }); // Get focus so that the key events go to the layout. mScreenshotLayout.setFocusableInTouchMode(true); @@ -471,59 +563,19 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } /** - * Updates the window focusability. If the window is already showing, then it updates the - * window immediately, otherwise the layout params will be applied when the window is next - * shown. - */ - private void setWindowFocusable(boolean focusable) { - if (focusable) { - mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE; - } else { - mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE; - } - if (mScreenshotLayout.isAttachedToWindow()) { - mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams); - } - } - - /** - * Creates a new worker thread and saves the screenshot to the media store. - */ - private void saveScreenshotInWorkerThread( - Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) { - SaveImageInBackgroundData data = new SaveImageInBackgroundData(); - data.image = mScreenBitmap; - data.finisher = finisher; - data.mActionsReadyListener = actionsReadyListener; - - if (mSaveInBgTask != null) { - // just log success/failure for the pre-existing screenshot - mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() { - @Override - void onActionsReady(SavedImageData imageData) { - logSuccessOnActionsReady(imageData); - } - }); - } - - mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data); - mSaveInBgTask.execute(); - } - - /** * Takes a screenshot of the current display and shows an animation. */ - private void takeScreenshot(Consumer<Uri> finisher, Rect crop) { + private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) { // copy the input Rect, since SurfaceControl.screenshot can mutate it Rect screenRect = new Rect(crop); int rot = mDisplay.getRotation(); int width = crop.width(); int height = crop.height(); - takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect, + saveScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect, Insets.NONE, true); } - private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, + private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, boolean showFlash) { dismissScreenshot("new screenshot requested", true); @@ -561,85 +613,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset startAnimation(finisher, screenRect, screenInsets, showFlash); } - void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) { - mOnCompleteRunnable = onComplete; - - mDisplay.getRealMetrics(mDisplayMetrics); - takeScreenshot( - finisher, - new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); - } - - void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, - Insets visibleInsets, int taskId, int userId, ComponentName topComponent, - Consumer<Uri> finisher, Runnable onComplete) { - // TODO: use task Id, userId, topComponent for smart handler - - mOnCompleteRunnable = onComplete; - if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) { - takeScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false); - } else { - takeScreenshot(screenshot, finisher, - new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE, - true); - } - } - - /** - * Displays a screenshot selector - */ - @SuppressLint("ClickableViewAccessibility") - void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) { - dismissScreenshot("new screenshot requested", true); - mOnCompleteRunnable = onComplete; - - mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); - mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - ScreenshotSelectorView view = (ScreenshotSelectorView) v; - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - view.startSelection((int) event.getX(), (int) event.getY()); - return true; - case MotionEvent.ACTION_MOVE: - view.updateSelection((int) event.getX(), (int) event.getY()); - return true; - case MotionEvent.ACTION_UP: - view.setVisibility(View.GONE); - mWindowManager.removeView(mScreenshotLayout); - final Rect rect = view.getSelectionRect(); - if (rect != null) { - if (rect.width() != 0 && rect.height() != 0) { - // Need mScreenshotLayout to handle it after the view disappears - mScreenshotLayout.post(() -> takeScreenshot(finisher, rect)); - } - } - - view.stopSelection(); - return true; - } - - return false; - } - }); - mScreenshotLayout.post(() -> { - mScreenshotSelectorView.setVisibility(View.VISIBLE); - mScreenshotSelectorView.requestFocus(); - }); - } - - /** - * Cancels screenshot request - */ - void stopScreenshot() { - // If the selector layer still presents on screen, we remove it and resets its state. - if (mScreenshotSelectorView.getSelectionRect() != null) { - mWindowManager.removeView(mScreenshotLayout); - mScreenshotSelectorView.stopSelection(); - } - } - /** * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on * failure). @@ -670,55 +643,78 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset }); } - private boolean isUserSetupComplete() { - return Settings.Secure.getInt(mContext.getContentResolver(), - SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; + /** + * Starts the animation after taking the screenshot + */ + private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, + boolean showFlash) { + + // If power save is on, show a toast so there is some visual indication that a + // screenshot has been taken. + PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + if (powerManager.isPowerSaveMode()) { + Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show(); + } + + mScreenshotHandler.post(() -> { + if (!mScreenshotLayout.isAttachedToWindow()) { + mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); + } + mScreenshotAnimatedView.setImageDrawable( + createScreenDrawable(mScreenBitmap, screenInsets)); + setAnimatedViewSize(screenRect.width(), screenRect.height()); + // Show when the animation starts + mScreenshotAnimatedView.setVisibility(View.GONE); + + mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets)); + // make static preview invisible (from gone) so we can query its location on screen + mScreenshotPreview.setVisibility(View.INVISIBLE); + + mScreenshotHandler.post(() -> { + mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); + + mScreenshotAnimation = + createScreenshotDropInAnimation(screenRect, showFlash); + + saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { + @Override + void onActionsReady(SavedImageData imageData) { + showUiOnActionsReady(imageData); + } + }); + + // Play the shutter sound to notify that we've taken a screenshot + mCameraSound.play(MediaActionSound.SHUTTER_CLICK); + + mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mScreenshotPreview.buildLayer(); + mScreenshotAnimation.start(); + }); + }); } /** - * Clears current screenshot + * Creates a new worker thread and saves the screenshot to the media store. */ - void dismissScreenshot(String reason, boolean immediate) { - Log.v(TAG, "clearing screenshot: " + reason); - mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); - mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); - if (!immediate) { - mDismissAnimation = createScreenshotDismissAnimation(); - mDismissAnimation.addListener(new AnimatorListenerAdapter() { + private void saveScreenshotInWorkerThread( + Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) { + SaveImageInBackgroundData data = new SaveImageInBackgroundData(); + data.image = mScreenBitmap; + data.finisher = finisher; + data.mActionsReadyListener = actionsReadyListener; + + if (mSaveInBgTask != null) { + // just log success/failure for the pre-existing screenshot + mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() { @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - clearScreenshot(); + void onActionsReady(SavedImageData imageData) { + logSuccessOnActionsReady(imageData); } }); - mDismissAnimation.start(); - } else { - clearScreenshot(); } - } - private void clearScreenshot() { - if (mScreenshotLayout.isAttachedToWindow()) { - mWindowManager.removeView(mScreenshotLayout); - } - - // Clear any references to the bitmap - mScreenshotPreview.setImageDrawable(null); - mScreenshotAnimatedView.setImageDrawable(null); - mScreenshotAnimatedView.setVisibility(View.GONE); - mActionsContainerBackground.setVisibility(View.GONE); - mActionsContainer.setVisibility(View.GONE); - mBackgroundProtection.setAlpha(0f); - mDismissButton.setVisibility(View.GONE); - mScreenshotPreview.setVisibility(View.GONE); - mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null); - mScreenshotPreview.setContentDescription( - mContext.getResources().getString(R.string.screenshot_preview_description)); - mScreenshotLayout.setAlpha(1); - mDismissButton.setTranslationY(0); - mActionsContainer.setTranslationY(0); - mActionsContainerBackground.setTranslationY(0); - mScreenshotPreview.setTranslationY(0); + mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data); + mSaveInBgTask.execute(); } /** @@ -768,56 +764,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } } - /** - * Starts the animation after taking the screenshot - */ - private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, - boolean showFlash) { - - // If power save is on, show a toast so there is some visual indication that a - // screenshot has been taken. - PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - if (powerManager.isPowerSaveMode()) { - Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show(); - } - - mScreenshotHandler.post(() -> { - if (!mScreenshotLayout.isAttachedToWindow()) { - mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); - } - mScreenshotAnimatedView.setImageDrawable( - createScreenDrawable(mScreenBitmap, screenInsets)); - setAnimatedViewSize(screenRect.width(), screenRect.height()); - // Show when the animation starts - mScreenshotAnimatedView.setVisibility(View.GONE); - - mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets)); - // make static preview invisible (from gone) so we can query its location on screen - mScreenshotPreview.setVisibility(View.INVISIBLE); - - mScreenshotHandler.post(() -> { - mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); - - mScreenshotAnimation = - createScreenshotDropInAnimation(screenRect, showFlash); - - saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { - @Override - void onActionsReady(SavedImageData imageData) { - showUiOnActionsReady(imageData); - } - }); - - // Play the shutter sound to notify that we've taken a screenshot - mCameraSound.play(MediaActionSound.SHUTTER_CLICK); - - mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null); - mScreenshotPreview.buildLayer(); - mScreenshotAnimation.start(); - }); - }); - } - private AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) { Rect previewBounds = new Rect(); mScreenshotPreview.getBoundsOnScreen(previewBounds); @@ -1070,6 +1016,30 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset return animSet; } + private void clearScreenshot() { + if (mScreenshotLayout.isAttachedToWindow()) { + mWindowManager.removeView(mScreenshotLayout); + } + + // Clear any references to the bitmap + mScreenshotPreview.setImageDrawable(null); + mScreenshotAnimatedView.setImageDrawable(null); + mScreenshotAnimatedView.setVisibility(View.GONE); + mActionsContainerBackground.setVisibility(View.GONE); + mActionsContainer.setVisibility(View.GONE); + mBackgroundProtection.setAlpha(0f); + mDismissButton.setVisibility(View.GONE); + mScreenshotPreview.setVisibility(View.GONE); + mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null); + mScreenshotPreview.setContentDescription( + mContext.getResources().getString(R.string.screenshot_preview_description)); + mScreenshotLayout.setAlpha(1); + mDismissButton.setTranslationY(0); + mActionsContainer.setTranslationY(0); + mActionsContainerBackground.setTranslationY(0); + mScreenshotPreview.setTranslationY(0); + } + private void setAnimatedViewSize(int width, int height) { ViewGroup.LayoutParams layoutParams = mScreenshotAnimatedView.getLayoutParams(); layoutParams.width = width; @@ -1077,6 +1047,27 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotAnimatedView.setLayoutParams(layoutParams); } + /** + * Updates the window focusability. If the window is already showing, then it updates the + * window immediately, otherwise the layout params will be applied when the window is next + * shown. + */ + private void setWindowFocusable(boolean focusable) { + if (focusable) { + mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE; + } else { + mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE; + } + if (mScreenshotLayout.isAttachedToWindow()) { + mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams); + } + } + + private boolean isUserSetupComplete() { + return Settings.Secure.getInt(mContext.getContentResolver(), + SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; + } + /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ private boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, Rect screenBounds) { int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right; @@ -1128,7 +1119,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) { // Are any of the insets negative, meaning the bitmap is smaller than the bounds so need // to fill in the background of the drawable. - return new LayerDrawable(new Drawable[] { + return new LayerDrawable(new Drawable[]{ new ColorDrawable(Color.BLACK), insetDrawable}); } else { return insetDrawable; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 9f8a9bb4a432..6f143da5405a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -102,7 +102,7 @@ public class TakeScreenshotService extends Service { switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: - mScreenshot.takeScreenshot(uriConsumer, onComplete); + mScreenshot.takeScreenshotFullscreen(uriConsumer, onComplete); break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: mScreenshot.takeScreenshotPartial(uriConsumer, onComplete); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 423f85f2ddd0..bd65ef06f3a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -282,7 +282,7 @@ public final class NotificationEntry extends ListEntry { + " doesn't match existing key " + mKey); } - mRanking = ranking; + mRanking = ranking.withAudiblyAlertedInfo(mRanking); } /* diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 582e3e5b6c34..a7d83b3b2774 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -37,6 +37,8 @@ import android.widget.RemoteViews; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ImageMessageConsumer; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.media.MediaDataManagerKt; +import com.android.systemui.media.MediaFeatureFlag; import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.SmartReplyController; @@ -71,6 +73,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder public static final String TAG = "NotifContentInflater"; private boolean mInflateSynchronously = false; + private final boolean mIsMediaInQS; private final NotificationRemoteInputManager mRemoteInputManager; private final NotifRemoteViewCache mRemoteViewCache; private final Lazy<SmartReplyConstants> mSmartReplyConstants; @@ -85,12 +88,14 @@ public class NotificationContentInflater implements NotificationRowContentBinder Lazy<SmartReplyConstants> smartReplyConstants, Lazy<SmartReplyController> smartReplyController, ConversationNotificationProcessor conversationProcessor, + MediaFeatureFlag mediaFeatureFlag, @Background Executor bgExecutor) { mRemoteViewCache = remoteViewCache; mRemoteInputManager = remoteInputManager; mSmartReplyConstants = smartReplyConstants; mSmartReplyController = smartReplyController; mConversationProcessor = conversationProcessor; + mIsMediaInQS = mediaFeatureFlag.getEnabled(); mBgExecutor = bgExecutor; } @@ -135,7 +140,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder bindParams.usesIncreasedHeight, bindParams.usesIncreasedHeadsUpHeight, callback, - mRemoteInputManager.getRemoteViewsOnClickHandler()); + mRemoteInputManager.getRemoteViewsOnClickHandler(), + mIsMediaInQS); if (mInflateSynchronously) { task.onPostExecute(task.doInBackground()); } else { @@ -711,6 +717,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private RemoteViews.OnClickHandler mRemoteViewClickHandler; private CancellationSignal mCancellationSignal; private final ConversationNotificationProcessor mConversationProcessor; + private final boolean mIsMediaInQS; private AsyncInflationTask( Executor bgExecutor, @@ -726,7 +733,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, InflationCallback callback, - RemoteViews.OnClickHandler remoteViewClickHandler) { + RemoteViews.OnClickHandler remoteViewClickHandler, + boolean isMediaFlagEnabled) { mEntry = entry; mRow = row; mSmartReplyConstants = smartReplyConstants; @@ -742,6 +750,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mRemoteViewClickHandler = remoteViewClickHandler; mCallback = callback; mConversationProcessor = conversationProcessor; + mIsMediaInQS = isMediaFlagEnabled; entry.setInflationTask(this); } @@ -765,7 +774,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder packageContext = new RtlEnabledContext(packageContext); } Notification notification = sbn.getNotification(); - if (notification.isMediaNotification()) { + if (notification.isMediaNotification() && !(mIsMediaInQS + && MediaDataManagerKt.isMediaNotification(sbn))) { MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext, packageContext); processor.processNotification(notification, recoveredBuilder); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 27daf8615a31..837543c9bdae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -591,6 +591,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback .registerDisplayListener(this, new Handler(Looper.getMainLooper())); mOrientationHandle = new QuickswitchOrientedNavHandle(getContext()); + mOrientationHandle.setId(R.id.secondary_home_handle); getBarTransitions().addDarkIntensityListener(mOrientationHandleIntensityListener); mOrientationParams = new WindowManager.LayoutParams(0, 0, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index 812ce1c62677..6dd96f92b344 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -82,6 +82,7 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C private boolean mClockVisibleByUser = true; private boolean mAttached; + private boolean mScreenReceiverRegistered; private Calendar mCalendar; private String mClockFormatString; private SimpleDateFormat mClockFormat; @@ -213,6 +214,14 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); + if (mScreenReceiverRegistered) { + mScreenReceiverRegistered = false; + mBroadcastDispatcher.unregisterReceiver(mScreenReceiver); + if (mSecondsHandler != null) { + mSecondsHandler.removeCallbacks(mSecondTick); + mSecondsHandler = null; + } + } if (mAttached) { mBroadcastDispatcher.unregisterReceiver(mIntentReceiver); mAttached = false; @@ -363,12 +372,14 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C mSecondsHandler.postAtTime(mSecondTick, SystemClock.uptimeMillis() / 1000 * 1000 + 1000); } + mScreenReceiverRegistered = true; IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); mBroadcastDispatcher.registerReceiver(mScreenReceiver, filter); } } else { if (mSecondsHandler != null) { + mScreenReceiverRegistered = false; mBroadcastDispatcher.unregisterReceiver(mScreenReceiver); mSecondsHandler.removeCallbacks(mSecondTick); mSecondsHandler = null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index 07de388598b7..c43ad36d4462 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -106,9 +106,9 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { public void updateNotification(@NonNull String key, boolean alert) { super.updateNotification(key, alert); - AlertEntry alertEntry = getHeadsUpEntry(key); - if (alert && alertEntry != null) { - setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(alertEntry.mEntry)); + HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); + if (alert && headsUpEntry != null) { + setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry)); } } diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java index 1ce98eb152c8..3402a52f056a 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -253,6 +253,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged mHandler.post(() -> startAnimation(false /* show */, false /* forceRestart */)); } + @Override + public void topFocusedWindowChanged(String packageName) { + // no-op + } + /** * Sends the local visibility state back to window manager. Needed for legacy adjustForIme. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java index dbc5596d9f4e..492b33e3c4a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import android.graphics.Color; @@ -47,6 +48,7 @@ import java.util.Map; public class MediaDataCombineLatestTest extends SysuiTestCase { private static final String KEY = "TEST_KEY"; + private static final String OLD_KEY = "TEST_KEY_OLD"; private static final String APP = "APP"; private static final String PACKAGE = "PKG"; private static final int BG_COLOR = Color.RED; @@ -97,7 +99,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void eventNotEmittedWithoutMedia() { // WHEN device source emits an event without media data - mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); + mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData); // THEN an event isn't emitted verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any()); } @@ -105,7 +107,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void emitEventAfterDeviceFirst() { // GIVEN that a device event has already been received - mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); + mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData); // WHEN media event is received mDataListener.onMediaDataLoaded(KEY, null, mMediaData); // THEN the listener receives a combined event @@ -119,7 +121,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { // GIVEN that media event has already been received mDataListener.onMediaDataLoaded(KEY, null, mMediaData); // WHEN device event is received - mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); + mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData); // THEN the listener receives a combined event ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture()); @@ -127,6 +129,64 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { } @Test + public void migrateKeyMediaFirst() { + // GIVEN that media and device info has already been received + mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData); + mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); + reset(mListener); + // WHEN a key migration event is received + mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData); + // THEN the listener receives a combined event + ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); + verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture()); + assertThat(captor.getValue().getDevice()).isNotNull(); + } + + @Test + public void migrateKeyDeviceFirst() { + // GIVEN that media and device info has already been received + mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData); + mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); + reset(mListener); + // WHEN a key migration event is received + mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData); + // THEN the listener receives a combined event + ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); + verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture()); + assertThat(captor.getValue().getDevice()).isNotNull(); + } + + @Test + public void migrateKeyMediaAfter() { + // GIVEN that media and device info has already been received + mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData); + mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); + mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData); + reset(mListener); + // WHEN a second key migration event is received for media + mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData); + // THEN the key has already been migrated + ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); + verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture()); + assertThat(captor.getValue().getDevice()).isNotNull(); + } + + @Test + public void migrateKeyDeviceAfter() { + // GIVEN that media and device info has already been received + mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData); + mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); + mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData); + reset(mListener); + // WHEN a second key migration event is received for the device + mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData); + // THEN the key has already be migrated + ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); + verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture()); + assertThat(captor.getValue().getDevice()).isNotNull(); + } + + @Test public void mediaDataRemoved() { // WHEN media data is removed without first receiving device or data mDataListener.onMediaDataRemoved(KEY); @@ -143,7 +203,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void mediaDataRemovedAfterDeviceEvent() { - mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); + mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData); mDataListener.onMediaDataRemoved(KEY); verify(mListener).onMediaDataRemoved(eq(KEY)); } @@ -152,7 +212,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { public void mediaDataKeyUpdated() { // GIVEN that device and media events have already been received mDataListener.onMediaDataLoaded(KEY, null, mMediaData); - mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); + mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData); // WHEN the key is changed mDataListener.onMediaDataLoaded("NEW_KEY", KEY, mMediaData); // THEN the listener gets a load event with the correct keys @@ -163,7 +223,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void getDataIncludesDevice() { // GIVEN that device and media events have been received - mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); + mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData); mDataListener.onMediaDataLoaded(KEY, null, mMediaData); // THEN the result of getData includes device info diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index fc22eeb3ea68..3c6e19f0ec6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -166,7 +166,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { // THEN the listener for the old key should removed. verify(lmm).unregisterCallback(any()) // AND a new device event emitted - val data = captureDeviceData(KEY) + val data = captureDeviceData(KEY, KEY_OLD) assertThat(data.enabled).isTrue() assertThat(data.name).isEqualTo(DEVICE_NAME) } @@ -179,13 +179,14 @@ public class MediaDeviceManagerTest : SysuiTestCase() { // WHEN the new key is the same as the old key manager.onMediaDataLoaded(KEY, KEY, mediaData) // THEN no event should be emitted - verify(listener, never()).onMediaDeviceChanged(eq(KEY), any()) + verify(listener, never()).onMediaDeviceChanged(eq(KEY), eq(null), any()) } @Test fun unknownOldKey() { - manager.onMediaDataLoaded(KEY, "unknown", mediaData) - verify(listener).onMediaDeviceChanged(eq(KEY), any()) + val oldKey = "unknown" + manager.onMediaDataLoaded(KEY, oldKey, mediaData) + verify(listener).onMediaDeviceChanged(eq(KEY), eq(oldKey), any()) } @Test @@ -223,7 +224,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { manager.removeListener(listener) // THEN it doesn't receive device events manager.onMediaDataLoaded(KEY, null, mediaData) - verify(listener, never()).onMediaDeviceChanged(eq(KEY), any()) + verify(listener, never()).onMediaDeviceChanged(eq(KEY), eq(null), any()) } @Test @@ -318,9 +319,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() { return captor.getValue() } - fun captureDeviceData(key: String): MediaDeviceData { + fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java) - verify(listener).onMediaDeviceChanged(eq(key), captor.capture()) + verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture()) return captor.getValue() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index 25da74137a90..3718cd720659 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -51,6 +51,7 @@ import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; import com.android.systemui.SysuiTestCase; +import com.android.systemui.media.MediaFeatureFlag; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.ConversationNotificationProcessor; @@ -110,6 +111,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { () -> smartReplyConstants, () -> smartReplyController, mConversationNotificationProcessor, + mock(MediaFeatureFlag.class), mock(Executor.class)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java index 7dfead7575a9..3ea0e5639c71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java @@ -43,6 +43,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.util.NotificationMessagingUtil; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.media.MediaFeatureFlag; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.plugins.PluginManager; @@ -197,6 +198,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { () -> mock(SmartReplyConstants.class), () -> mock(SmartReplyController.class), mock(ConversationNotificationProcessor.class), + mock(MediaFeatureFlag.class), mBgExecutor); mRowContentBindStage = new RowContentBindStage( binder, 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 b9eb4d1e29c2..0c6409b38d21 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 @@ -46,6 +46,7 @@ import android.widget.RemoteViews; import com.android.systemui.TestableDependency; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.bubbles.BubblesTestActivity; +import com.android.systemui.media.MediaFeatureFlag; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationMediaManager; @@ -133,6 +134,7 @@ public class NotificationTestHelper { () -> mock(SmartReplyConstants.class), () -> mock(SmartReplyController.class), mock(ConversationNotificationProcessor.class), + mock(MediaFeatureFlag.class), mock(Executor.class)); contentBinder.setInflateSynchronously(true); mBindStage = new RowContentBindStage(contentBinder, diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 6b852adce0f1..40a2816ee7de 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -90,6 +90,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.NoSuchElementException; import java.util.Set; /** @@ -1179,7 +1180,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ /* ignore */ } if (mService != null) { - mService.unlinkToDeath(this, 0); + try { + mService.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Slog.e(LOG_TAG, "Failed unregistering death link"); + } mService = null; } diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java index 5e10916c4491..a4e58a1faeac 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java @@ -649,16 +649,33 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { mReporter.onStartPackageBackup(PM_PACKAGE); mCurrentPackage = new PackageInfo(); mCurrentPackage.packageName = PM_PACKAGE; - try { - extractPmAgentData(mCurrentPackage); + // If we can't even extractPmAgentData(), then we treat the local state as + // compromised, just in case. This means that we will clear data and will + // start from a clean slate in the next attempt. It's not clear whether that's + // the right thing to do, but matches what we have historically done. + try { + extractPmAgentData(mCurrentPackage); + } catch (TaskException e) { + throw TaskException.stateCompromised(e); // force stateCompromised + } + // During sendDataToTransport, we generally trust any thrown TaskException + // about whether stateCompromised because those are likely transient; + // clearing state for those would have the potential to lead to cascading + // failures, as discussed in http://b/144030477. + // For specific status codes (e.g. TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED), + // cleanUpAgentForTransportStatus() or theoretically handleTransportStatus() + // still have the opportunity to perform additional clean-up tasks. int status = sendDataToTransport(mCurrentPackage); cleanUpAgentForTransportStatus(status); } catch (AgentException | TaskException e) { mReporter.onExtractPmAgentDataError(e); cleanUpAgentForError(e); - // PM agent failure is task failure. - throw TaskException.stateCompromised(e); + if (e instanceof TaskException) { + throw (TaskException) e; + } else { + throw TaskException.stateCompromised(e); // PM agent failure is task failure. + } } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 5d3f3c0401ef..cdbc7d2f4861 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5774,6 +5774,19 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mRemoteInsetsController = controller; } + /** + * Notifies the remote insets controller that the top focused window has changed. + * + * @param packageName The name of the package that is open in the top focused window. + */ + void topFocusedWindowChanged(String packageName) { + try { + mRemoteInsetsController.topFocusedWindowChanged(packageName); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to deliver package in top focused window change", e); + } + } + void notifyInsetsChanged() { try { mRemoteInsetsController.insetsChanged( diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 68051ab59599..5ae6f80b262d 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -386,11 +386,6 @@ public class DisplayPolicy { private int mForcingShowNavBarLayer; private boolean mForceShowSystemBars; - /** - * Force the display of system bars regardless of other settings. - */ - private boolean mForceShowSystemBarsFromExternal; - private boolean mShowingDream; private boolean mLastShowingDream; private boolean mDreamingLockscreen; @@ -480,7 +475,6 @@ public class DisplayPolicy { final Resources r = mContext.getResources(); mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer); mDeskDockEnablesAccelerometer = r.getBoolean(R.bool.config_deskDockEnablesAccelerometer); - mForceShowSystemBarsFromExternal = r.getBoolean(R.bool.config_forceShowSystemBars); mAccessibilityManager = (AccessibilityManager) mContext.getSystemService( Context.ACCESSIBILITY_SERVICE); @@ -698,17 +692,6 @@ public class DisplayPolicy { return mDockMode; } - /** - * @see WindowManagerService.setForceShowSystemBars - */ - void setForceShowSystemBars(boolean forceShowSystemBars) { - mForceShowSystemBarsFromExternal = forceShowSystemBars; - } - - boolean getForceShowSystemBars() { - return mForceShowSystemBarsFromExternal; - } - public boolean hasNavigationBar() { return mHasNavigationBar; } @@ -3540,8 +3523,7 @@ public class DisplayPolicy { // We need to force system bars when the docked stack is visible, when the freeform stack // is focused but also when we are resizing for the transitions when docked stack // visibility changes. - mForceShowSystemBars = dockedStackVisible || win.inFreeformWindowingMode() || resizing - || mForceShowSystemBarsFromExternal; + mForceShowSystemBars = dockedStackVisible || win.inFreeformWindowingMode() || resizing; final boolean forceOpaqueStatusBar = mForceShowSystemBars && !isKeyguardShowing(); // apply translucent bar vis flags @@ -3916,8 +3898,8 @@ public class DisplayPolicy { } pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen); pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar); - pw.print(prefix); pw.print("mForceShowSystemBarsFromExternal="); - pw.print(mForceShowSystemBarsFromExternal); + pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars"); + pw.print(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars()); pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn); mStatusBarController.dump(pw, prefix); mNavigationBarController.dump(pw, prefix); diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 254356d673e3..82e7555c62ac 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -46,7 +46,9 @@ import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation; import android.view.WindowInsetsAnimation.Bounds; import android.view.WindowInsetsAnimationControlListener; +import android.view.WindowManager; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.server.DisplayThread; @@ -97,12 +99,31 @@ class InsetsPolicy { private BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR); private BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR); private boolean mAnimatingShown; + + /** + * Let remote insets controller control system bars regardless of other settings. + */ + private boolean mRemoteInsetsControllerControlsSystemBars; private final float[] mTmpFloat9 = new float[9]; InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) { mStateController = stateController; mDisplayContent = displayContent; mPolicy = displayContent.getDisplayPolicy(); + mRemoteInsetsControllerControlsSystemBars = mPolicy.getContext().getResources().getBoolean( + R.bool.config_remoteInsetsControllerControlsSystemBars); + } + + boolean getRemoteInsetsControllerControlsSystemBars() { + return mRemoteInsetsControllerControlsSystemBars; + } + + /** + * Used only for testing. + */ + @VisibleForTesting + void setRemoteInsetsControllerControlsSystemBars(boolean controlsSystemBars) { + mRemoteInsetsControllerControlsSystemBars = controlsSystemBars; } /** Updates the target which can control system bars. */ @@ -256,6 +277,11 @@ class InsetsPolicy { // Notification shade has control anyways, no reason to force anything. return focusedWin; } + if (remoteInsetsControllerControlsSystemBars(focusedWin)) { + mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged( + focusedWin.mAttrs.packageName); + return mDisplayContent.mRemoteInsetsControlTarget; + } if (forceShowsSystemBarsForWindowingMode) { // Status bar is forcibly shown for the windowing mode which is a steady state. // We don't want the client to control the status bar, and we will dispatch the real @@ -285,6 +311,11 @@ class InsetsPolicy { // Notification shade has control anyways, no reason to force anything. return focusedWin; } + if (remoteInsetsControllerControlsSystemBars(focusedWin)) { + mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged( + focusedWin.mAttrs.packageName); + return mDisplayContent.mRemoteInsetsControlTarget; + } if (forceShowsSystemBarsForWindowingMode) { // Navigation bar is forcibly shown for the windowing mode which is a steady state. // We don't want the client to control the navigation bar, and we will dispatch the real @@ -300,6 +331,28 @@ class InsetsPolicy { return focusedWin; } + /** + * Determines whether the remote insets controller should take control of system bars for all + * windows. + */ + boolean remoteInsetsControllerControlsSystemBars(@Nullable WindowState focusedWin) { + if (focusedWin == null) { + return false; + } + if (!mRemoteInsetsControllerControlsSystemBars) { + return false; + } + if (mDisplayContent == null || mDisplayContent.mRemoteInsetsControlTarget == null) { + // No remote insets control target to take control of insets. + return false; + } + // If necessary, auto can control application windows when + // config_remoteInsetsControllerControlsSystemBars is set to true. This is useful in cases + // where we want to dictate system bar inset state for applications. + return focusedWin.getAttrs().type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW + && focusedWin.getAttrs().type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; + } + private boolean forceShowsStatusBarTransiently() { final WindowState win = mPolicy.getStatusBar(); return win != null && (win.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR) != 0; @@ -321,10 +374,7 @@ class InsetsPolicy { // We need to force system bars when the docked stack is visible, when the freeform stack // is visible but also when we are resizing for the transitions when docked stack // visibility changes. - return isDockedStackVisible - || isFreeformStackVisible - || isResizing - || mPolicy.getForceShowSystemBars(); + return isDockedStackVisible || isFreeformStackVisible || isResizing; } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index ef81c0a5d206..371d6b56f687 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5827,27 +5827,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - @Override - public void setForceShowSystemBars(boolean show) { - boolean isAutomotive = mContext.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_AUTOMOTIVE); - if (!isAutomotive) { - throw new UnsupportedOperationException("Force showing system bars is only supported" - + "for Automotive use cases."); - } - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Caller does not hold permission " - + android.Manifest.permission.STATUS_BAR); - } - synchronized (mGlobalLock) { - final PooledConsumer c = PooledLambda.obtainConsumer( - DisplayPolicy::setForceShowSystemBars, PooledLambda.__(), show); - mRoot.forAllDisplayPolicies(c); - c.recycle(); - } - } - public void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR) != PackageManager.PERMISSION_GRANTED) { diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index ec56e1ebc8e0..b5c9375fcc0d 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -122,6 +122,7 @@ import com.android.server.testing.shadows.ShadowBackupDataOutput; import com.android.server.testing.shadows.ShadowEventLog; import com.android.server.testing.shadows.ShadowSystemServiceRegistry; +import com.google.common.base.Charsets; import com.google.common.truth.IterableSubject; import org.junit.After; @@ -1910,7 +1911,8 @@ public class KeyValueBackupTaskTest { } @Test - public void testRunTask_whenTransportReturnsError_updatesFilesAndCleansUp() throws Exception { + public void testRunTask_whenTransportReturnsErrorForGenericPackage_updatesFilesAndCleansUp() + throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); when(transportMock.transport.performBackup( argThat(packageInfo(PACKAGE_1)), any(), anyInt())) @@ -1926,6 +1928,39 @@ public class KeyValueBackupTaskTest { assertCleansUpFilesAndAgent(mTransport, PACKAGE_1); } + /** + * Checks that TRANSPORT_ERROR during @pm@ backup keeps the state file untouched. + * http://b/144030477 + */ + @Test + public void testRunTask_whenTransportReturnsErrorForPm_updatesFilesAndCleansUp() + throws Exception { + // TODO(tobiast): Refactor this method to share code with + // testRunTask_whenTransportReturnsErrorForGenericPackage_updatesFilesAndCleansUp + // See patchset 7 of http://ag/11762961 + final PackageData packageData = PM_PACKAGE; + TransportMock transportMock = setUpInitializedTransport(mTransport); + when(transportMock.transport.performBackup( + argThat(packageInfo(packageData)), any(), anyInt())) + .thenReturn(BackupTransport.TRANSPORT_ERROR); + + byte[] pmStateBytes = "fake @pm@ state for testing".getBytes(Charsets.UTF_8); + + Path pmStatePath = createPmStateFile(pmStateBytes.clone()); + PackageManagerBackupAgent pmAgent = spy(createPmAgent()); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, packageData); + runTask(task); + verify(pmAgent, never()).onBackup(any(), any(), any()); + + assertThat(Files.readAllBytes(pmStatePath)).isEqualTo(pmStateBytes.clone()); + + boolean existed = deletePmStateFile(); + assertThat(existed).isTrue(); + // unbindAgent() is skipped for @pm@. Comment in KeyValueBackupTask.java: + // "For PM metadata (for which applicationInfo is null) there is no agent-bound state." + assertCleansUpFiles(mTransport, packageData); + } + @Test public void testRunTask_whenTransportGetBackupQuotaThrowsForPm() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); @@ -2707,21 +2742,29 @@ public class KeyValueBackupTaskTest { * </ul> * </ul> */ - private void createPmStateFile() throws IOException { - createPmStateFile(mTransport); + private Path createPmStateFile() throws IOException { + return createPmStateFile("pmState".getBytes()); + } + + private Path createPmStateFile(byte[] bytes) throws IOException { + return createPmStateFile(bytes, mTransport); + } + + private Path createPmStateFile(TransportData transport) throws IOException { + return createPmStateFile("pmState".getBytes(), mTransport); } - /** @see #createPmStateFile() */ - private void createPmStateFile(TransportData transport) throws IOException { - Files.write(getStateFile(transport, PM_PACKAGE), "pmState".getBytes()); + /** @see #createPmStateFile(byte[]) */ + private Path createPmStateFile(byte[] bytes, TransportData transport) throws IOException { + return Files.write(getStateFile(transport, PM_PACKAGE), bytes); } /** * Forces transport initialization and call to {@link * UserBackupManagerService#resetBackupState(File)} */ - private void deletePmStateFile() throws IOException { - Files.deleteIfExists(getStateFile(mTransport, PM_PACKAGE)); + private boolean deletePmStateFile() throws IOException { + return Files.deleteIfExists(getStateFile(mTransport, PM_PACKAGE)); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 94acd776fcb3..b4f2a6c9c6c3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -96,13 +96,10 @@ import android.platform.test.annotations.Presubmit; import android.util.DisplayMetrics; import android.view.DisplayCutout; import android.view.Gravity; -import android.view.IDisplayWindowInsetsController; import android.view.IDisplayWindowRotationCallback; import android.view.IDisplayWindowRotationController; import android.view.ISystemGestureExclusionListener; import android.view.IWindowManager; -import android.view.InsetsSourceControl; -import android.view.InsetsState; import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceControl.Transaction; @@ -939,28 +936,6 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(mAppWindow, mDisplayContent.computeImeControlTarget()); } - private IDisplayWindowInsetsController createDisplayWindowInsetsController() { - return new IDisplayWindowInsetsController.Stub() { - - @Override - public void insetsChanged(InsetsState insetsState) throws RemoteException { - } - - @Override - public void insetsControlChanged(InsetsState insetsState, - InsetsSourceControl[] insetsSourceControls) throws RemoteException { - } - - @Override - public void showInsets(int i, boolean b) throws RemoteException { - } - - @Override - public void hideInsets(int i, boolean b) throws RemoteException { - } - }; - } - @Test public void testUpdateSystemGestureExclusion() throws Exception { final DisplayContent dc = createNewDisplay(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 1922351ac1eb..278de56dfea0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -67,7 +67,6 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; -import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.utils.WmDisplayCutout; import org.junit.Before; @@ -807,27 +806,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - public void forceShowSystemBars_clearsSystemUIFlags() { - mDisplayPolicy.mLastSystemUiFlags |= SYSTEM_UI_FLAG_FULLSCREEN; - mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - mWindow.mAttrs.flags = - FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - mWindow.mSystemUiVisibility = SYSTEM_UI_FLAG_FULLSCREEN; - mDisplayPolicy.setForceShowSystemBars(true); - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - // triggers updateSystemUiVisibilityLw which will reset the flags as needed - int finishPostLayoutPolicyLw = mDisplayPolicy.focusChangedLw(mWindow, mWindow); - - assertEquals(WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT, finishPostLayoutPolicyLw); - assertEquals(0, mDisplayPolicy.mLastSystemUiFlags); - assertEquals(0, mWindow.mAttrs.systemUiVisibility); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - } - - @Test public void testScreenDecorWindows() { final WindowState decorWindow = createWindow(null, TYPE_APPLICATION_OVERLAY, "decorWindow"); mWindow.mAttrs.flags = FLAG_NOT_FOCUSABLE | FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index c794e1a3b328..87bc7f1bf781 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -172,8 +172,9 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test - public void testControlsForDispatch_forceShowSystemBarsFromExternal_appHasNoControl() { - mDisplayContent.getDisplayPolicy().setForceShowSystemBars(true); + public void testControlsForDispatch_remoteInsetsControllerControlsBars_appHasNoControl() { + mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController()); + mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(true); addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index f52905ef6ae9..499bf668decf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -60,24 +60,6 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Rule public ExpectedException mExpectedException = ExpectedException.none(); - @Test - public void testForceShowSystemBarsThrowsExceptionForNonAutomotive() { - if (!isAutomotive()) { - mExpectedException.expect(UnsupportedOperationException.class); - - mWm.setForceShowSystemBars(true); - } - } - - @Test - public void testForceShowSystemBarsDoesNotThrowExceptionForAutomotiveWithStatusBarPermission() { - if (isAutomotive()) { - mExpectedException.none(); - - mWm.setForceShowSystemBars(true); - } - } - private boolean isAutomotive() { return getInstrumentation().getTargetContext().getPackageManager().hasSystemFeature( PackageManager.FEATURE_AUTOMOTIVE); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index a1e5b80eb2ed..156298c86d41 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -41,11 +41,15 @@ import static org.mockito.Mockito.mock; import android.content.Context; import android.content.Intent; +import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; import android.view.Display; import android.view.DisplayInfo; +import android.view.IDisplayWindowInsetsController; import android.view.IWindow; +import android.view.InsetsSourceControl; +import android.view.InsetsState; import android.view.SurfaceControl.Transaction; import android.view.View; import android.view.WindowManager; @@ -123,7 +127,7 @@ class WindowTestsBase extends SystemServiceTestsBase { mChildAppWindowBelow = createCommonWindow(mAppWindow, TYPE_APPLICATION_MEDIA_OVERLAY, "mChildAppWindowBelow"); - mDisplayContent.getDisplayPolicy().setForceShowSystemBars(false); + mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(false); // Adding a display will cause freezing the display. Make sure to wait until it's // unfrozen to not run into race conditions with the tests. @@ -344,6 +348,32 @@ class WindowTestsBase extends SystemServiceTestsBase { return createNewDisplay(displayInfo, false /* supportIme */); } + IDisplayWindowInsetsController createDisplayWindowInsetsController() { + return new IDisplayWindowInsetsController.Stub() { + + @Override + public void insetsChanged(InsetsState insetsState) throws RemoteException { + } + + @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] insetsSourceControls) throws RemoteException { + } + + @Override + public void showInsets(int i, boolean b) throws RemoteException { + } + + @Override + public void hideInsets(int i, boolean b) throws RemoteException { + } + + @Override + public void topFocusedWindowChanged(String packageName) { + } + }; + } + /** Sets the default minimum task size to 1 so that tests can use small task sizes */ void removeGlobalMinSizeRestriction() { mWm.mAtmService.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp = 1; |