From 4ce02f59968283046dccf70ea58099cf5f7f40e2 Mon Sep 17 00:00:00 2001 From: arangelov Date: Mon, 29 Jun 2020 17:43:24 +0100 Subject: Force enable vertical scrolling on profile tab change 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. Fixes: 160086572 Test: manually played with scrolling and swiping Test: atest ChoserActivityTest Test: atest ResolverActivityTest Change-Id: Id241bef19af88a48bf43217627d636403a514568 (cherry picked from commit 61d251fb66e6f4cc3de735ad8ee9e1c0e697f4f9) --- core/java/com/android/internal/app/ChooserActivity.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 14cf258f18ab..87d35092a245 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -3127,6 +3127,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 -- cgit v1.2.3 From 56a33b5af9a1a28281bfe1f916a5956381c5c616 Mon Sep 17 00:00:00 2001 From: arangelov Date: Wed, 8 Jul 2020 15:53:26 +0100 Subject: Add bottom offset to the intial padding rather than accumulating it. This prevents the padding from growing on screen rotations. Fixes: 160005616 Test: manual Test: atest ChooserActivityTest Change-Id: I87fcfede3cf9cfad63adeb07b67ed68df403453f (cherry picked from commit 96078b10521676bd60ffff60f14619c1dcdaead6) --- .../com/android/internal/app/ChooserMultiProfilePagerAdapter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 { -- cgit v1.2.3 From 4290693f5d7a996311f435cca2babee4212ec22f Mon Sep 17 00:00:00 2001 From: arangelov Date: Tue, 30 Jun 2020 18:52:34 +0100 Subject: Don't update height if recycler view was scrolled Fixes: 159999176 Fixes: 159997845 Test: manual Test: atest ChooserActivityTest Change-Id: Ie867ee419b3595023195022b0f9f12e5da214938 (cherry picked from commit b85215ff62262b29c8686adc7924765676283ca1) --- core/java/com/android/internal/app/ChooserActivity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 87d35092a245..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; } -- cgit v1.2.3 From 6ef16e3840bf5d3c90ab335bcb24daaba212f075 Mon Sep 17 00:00:00 2001 From: Hall Liu Date: Wed, 11 Mar 2020 15:15:42 -0700 Subject: Allow empty tokens in strict grammar In SQLiteQueryBuilder, allow empty tokens when checking for invalid tokens during the strict grammar check. Fixes: 151151800 Fixes: 161769478 Test: atest SQLiteQueryBuilderTest#testStrictQueryEmptyToken Change-Id: Iac1cdd643253fd186a164b863d65d6e92698fd38 (cherry picked from commit e409ec2492f47be9a0f3917bbbb324624eec7b5e) (cherry picked from commit b7cffcab3228c7de9d6450f746cd5066dcfbb621) --- core/java/android/database/sqlite/SQLiteQueryBuilder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index 669d0466fdf2..e9c59f55a418 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -798,6 +798,7 @@ public class SQLiteQueryBuilder { } private void enforceStrictToken(@NonNull String token) { + if (TextUtils.isEmpty(token)) return; if (isTableOrColumn(token)) return; if (SQLiteTokenizer.isFunction(token)) return; if (SQLiteTokenizer.isType(token)) return; -- cgit v1.2.3 From bb980bb03a12cff3bb21537c01428d43f3705a48 Mon Sep 17 00:00:00 2001 From: Roshan Pius Date: Wed, 29 Jan 2020 15:39:52 +0000 Subject: Revert "Revert "SystemServer: Dynamically load wifi-service" Revert submission 10144846-revert-10103769-load_wifi_service_dynamically-XZNQJHXURR Reason for revert: Changes in build system to store uncompressed and aligned DEX files for java libs in APEXes. Reverted Changes: Icc0b680c2: Revert "SystemServer: Dynamically load wifi-servic... I83a2f93e3: Revert "product: Remove wifi-service off SystemSer... Bug: 148099857 Bug: 162371380 Bug: 162746981 Test: Compiles Change-Id: I6c3f36873cae476dc76fd353d20fb6bd2c177f0b (cherry picked from commit 2c17b6690f84c3eabfdc7ef6301571d860448d92) --- services/java/com/android/server/SystemServer.java | 23 +++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 2a200fb0ae2c..88e698121617 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -232,8 +232,14 @@ public final class SystemServer { "com.android.server.usb.UsbService$Lifecycle"; private static final String MIDI_SERVICE_CLASS = "com.android.server.midi.MidiService$Lifecycle"; + private static final String WIFI_APEX_SERVICE_JAR_PATH = + "/apex/com.android.wifi/javalib/service-wifi.jar"; private static final String WIFI_SERVICE_CLASS = "com.android.server.wifi.WifiService"; + private static final String WIFI_SCANNING_SERVICE_CLASS = + "com.android.server.wifi.scanner.WifiScanningService"; + private static final String WIFI_RTT_SERVICE_CLASS = + "com.android.server.wifi.rtt.RttService"; private static final String WIFI_AWARE_SERVICE_CLASS = "com.android.server.wifi.aware.WifiAwareService"; private static final String WIFI_P2P_SERVICE_CLASS = @@ -1483,33 +1489,36 @@ public final class SystemServer { PackageManager.FEATURE_WIFI)) { // Wifi Service must be started first for wifi-related services. t.traceBegin("StartWifi"); - mSystemServiceManager.startService(WIFI_SERVICE_CLASS); + mSystemServiceManager.startServiceFromJar( + WIFI_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH); t.traceEnd(); t.traceBegin("StartWifiScanning"); - mSystemServiceManager.startService( - "com.android.server.wifi.scanner.WifiScanningService"); + mSystemServiceManager.startServiceFromJar( + WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH); t.traceEnd(); } if (context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_WIFI_RTT)) { t.traceBegin("StartRttService"); - mSystemServiceManager.startService( - "com.android.server.wifi.rtt.RttService"); + mSystemServiceManager.startServiceFromJar( + WIFI_RTT_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH); t.traceEnd(); } if (context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_WIFI_AWARE)) { t.traceBegin("StartWifiAware"); - mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS); + mSystemServiceManager.startServiceFromJar( + WIFI_AWARE_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH); t.traceEnd(); } if (context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_WIFI_DIRECT)) { t.traceBegin("StartWifiP2P"); - mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS); + mSystemServiceManager.startServiceFromJar( + WIFI_P2P_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH); t.traceEnd(); } -- cgit v1.2.3 From 5a171406df5c9a3c0d87ac60c6d4b895668eff42 Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Wed, 29 Jul 2020 16:08:43 -0400 Subject: Add guts to media player on long press Adds the following: * Button for accessing settings * Button for dismissing player (similar path to when a package is uninstalled) Guts will close automatically if: * QS is collapsed * Media carousel changes pages Also, flattened the view hierarchy to support animations between states. Test: manual Test: atest com.android.systemui.media Bug: 156036025 Change-Id: I340e0b37393573f81a3bf12d5e453eccf5982473 Merged-In: I340e0b37393573f81a3bf12d5e453eccf5982473 (cherry picked from commit 429360fb399f4aa2bb51cbcdf76deb0f319e130f) (cherry picked from commit 2e0f351d3508c9d4dadc206d5cc056afa8adb1ce) --- packages/SystemUI/res/layout/media_view.xml | 98 +++++++++++++++++++- .../SystemUI/res/layout/qs_media_panel_options.xml | 61 ------------- packages/SystemUI/res/values/strings.xml | 2 +- .../systemui/media/MediaCarouselController.kt | 8 +- .../systemui/media/MediaCarouselScrollHandler.kt | 2 + .../android/systemui/media/MediaControlPanel.java | 58 +++++++++++- .../com/android/systemui/media/MediaDataManager.kt | 22 +++-- .../systemui/media/MediaHierarchyManager.kt | 7 ++ .../android/systemui/media/MediaViewController.kt | 69 +++++++++++++- .../com/android/systemui/media/PlayerViewHolder.kt | 35 ++++++- .../phone/NotificationPanelViewController.java | 1 + .../systemui/media/MediaControlPanelTest.kt | 101 ++++++++++++++++++++- .../android/systemui/media/MediaDataManagerTest.kt | 14 +++ .../systemui/media/MediaHierarchyManagerTest.kt | 7 ++ 14 files changed, 400 insertions(+), 85 deletions(-) delete mode 100644 packages/SystemUI/res/layout/qs_media_panel_options.xml diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml index 3c641afea0d6..ed870f8bb2ef 100644 --- a/packages/SystemUI/res/layout/media_view.xml +++ b/packages/SystemUI/res/layout/media_view.xml @@ -210,8 +210,98 @@ android:layout_width="@dimen/qs_media_icon_size" android:layout_height="@dimen/qs_media_icon_size" /> - - + + + + + + + + + + + + + + + + + + + diff --git a/packages/SystemUI/res/layout/qs_media_panel_options.xml b/packages/SystemUI/res/layout/qs_media_panel_options.xml deleted file mode 100644 index e72c0e85fb26..000000000000 --- a/packages/SystemUI/res/layout/qs_media_panel_options.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index db45a60ab7c0..5f353a4e1897 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2792,7 +2792,7 @@ Hide the current session. - Hide + Dismiss Resume diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index e12b7dd259a5..f7caa039a9d3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -150,7 +150,7 @@ class MediaCarouselController @Inject constructor( pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator) mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator, executor, mediaManager::onSwipeToDismiss, this::updatePageIndicatorLocation, - falsingManager) + this::closeGuts, falsingManager) isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL inflateSettingsButton() mediaContent = mediaCarousel.requireViewById(R.id.media_carousel) @@ -469,6 +469,12 @@ class MediaCarouselController @Inject constructor( } } + fun closeGuts() { + mediaPlayers.values.forEach { + it.closeGuts(true) + } + } + /** * Update the size of the carousel, remeasuring it if necessary. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt index 3096908aca21..77cac5023db3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt @@ -56,6 +56,7 @@ class MediaCarouselScrollHandler( private val mainExecutor: DelayableExecutor, private val dismissCallback: () -> Unit, private var translationChangedListener: () -> Unit, + private val closeGuts: () -> Unit, private val falsingManager: FalsingManager ) { /** @@ -452,6 +453,7 @@ class MediaCarouselScrollHandler( val nowScrolledIn = scrollIntoCurrentMedia != 0 if (newIndex != activeMediaIndex || wasScrolledIn != nowScrolledIn) { activeMediaIndex = newIndex + closeGuts() updatePlayerVisibilities() } val relativeLocation = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 3fc162ead6d1..e55678dc986b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -16,6 +16,8 @@ package com.android.systemui.media; +import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS; + import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -45,6 +47,7 @@ import com.android.settingslib.widget.AdaptiveIcon; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.util.animation.TransitionLayout; import java.util.List; @@ -52,6 +55,8 @@ import java.util.concurrent.Executor; import javax.inject.Inject; +import dagger.Lazy; + /** * A view controller used for Media Playback. */ @@ -59,6 +64,8 @@ public class MediaControlPanel { private static final String TAG = "MediaControlPanel"; private static final float DISABLED_ALPHA = 0.38f; + private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS); + // Button IDs for QS controls static final int[] ACTION_IDS = { R.id.action0, @@ -78,6 +85,8 @@ public class MediaControlPanel { private MediaViewController mMediaViewController; private MediaSession.Token mToken; private MediaController mController; + private KeyguardDismissUtil mKeyguardDismissUtil; + private Lazy mMediaDataManagerLazy; private int mBackgroundColor; private int mAlbumArtSize; private int mAlbumArtRadius; @@ -93,12 +102,15 @@ public class MediaControlPanel { @Inject public MediaControlPanel(Context context, @Background Executor backgroundExecutor, ActivityStarter activityStarter, MediaViewController mediaViewController, - SeekBarViewModel seekBarViewModel) { + SeekBarViewModel seekBarViewModel, Lazy lazyMediaDataManager, + KeyguardDismissUtil keyguardDismissUtil) { mContext = context; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; mSeekBarViewModel = seekBarViewModel; mMediaViewController = mediaViewController; + mMediaDataManagerLazy = lazyMediaDataManager; + mKeyguardDismissUtil = keyguardDismissUtil; loadDimens(); mViewOutlineProvider = new ViewOutlineProvider() { @@ -174,6 +186,21 @@ public class MediaControlPanel { mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver); mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar()); mMediaViewController.attach(player); + + mViewHolder.getPlayer().setOnLongClickListener(v -> { + if (!mMediaViewController.isGutsVisible()) { + mMediaViewController.openGuts(); + return true; + } else { + return false; + } + }); + mViewHolder.getCancel().setOnClickListener(v -> { + closeGuts(); + }); + mViewHolder.getSettings().setOnClickListener(v -> { + mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */); + }); } /** @@ -205,6 +232,7 @@ public class MediaControlPanel { PendingIntent clickIntent = data.getClickIntent(); if (clickIntent != null) { mViewHolder.getPlayer().setOnClickListener(v -> { + if (mMediaViewController.isGutsVisible()) return; mActivityStarter.postStartActivityDismissingKeyguard(clickIntent); }); } @@ -329,14 +357,38 @@ public class MediaControlPanel { final MediaController controller = getController(); mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller)); - // Set up long press menu - // TODO: b/156036025 bring back media guts + // Dismiss + mViewHolder.getDismiss().setOnClickListener(v -> { + if (data.getNotificationKey() != null) { + closeGuts(); + mKeyguardDismissUtil.executeWhenUnlocked(() -> { + mMediaDataManagerLazy.get().dismissMediaData(data.getNotificationKey(), + MediaViewController.GUTS_ANIMATION_DURATION + 100); + return true; + }, /* requiresShadeOpen */ true); + } else { + Log.w(TAG, "Dismiss media with null notification. Token uid=" + + data.getToken().getUid()); + } + }); // TODO: We don't need to refresh this state constantly, only if the state actually changed // to something which might impact the measurement mMediaViewController.refreshState(); } + /** + * Close the guts for this player. + * @param immediate {@code true} if it should be closed without animation + */ + public void closeGuts(boolean immediate) { + mMediaViewController.closeGuts(immediate); + } + + private void closeGuts() { + closeGuts(false); + } + @UiThread private Drawable scaleDrawable(Icon icon) { if (icon == null) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 299ae5b50aa9..3c4ae1ee5278 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.notification.MediaNotificationProcessor import com.android.systemui.statusbar.notification.row.HybridGroupManager import com.android.systemui.util.Assert import com.android.systemui.util.Utils +import com.android.systemui.util.concurrency.DelayableExecutor import java.io.FileDescriptor import java.io.IOException import java.io.PrintWriter @@ -89,7 +90,7 @@ fun isMediaNotification(sbn: StatusBarNotification): Boolean { class MediaDataManager( private val context: Context, @Background private val backgroundExecutor: Executor, - @Main private val foregroundExecutor: Executor, + @Main private val foregroundExecutor: DelayableExecutor, private val mediaControllerFactory: MediaControllerFactory, private val broadcastDispatcher: BroadcastDispatcher, dumpManager: DumpManager, @@ -106,7 +107,7 @@ class MediaDataManager( constructor( context: Context, @Background backgroundExecutor: Executor, - @Main foregroundExecutor: Executor, + @Main foregroundExecutor: DelayableExecutor, mediaControllerFactory: MediaControllerFactory, dumpManager: DumpManager, broadcastDispatcher: BroadcastDispatcher, @@ -182,10 +183,7 @@ class MediaDataManager( val listenersCopy = listeners.toSet() val toRemove = mediaEntries.filter { it.value.packageName == packageName } toRemove.forEach { - mediaEntries.remove(it.key) - listenersCopy.forEach { listener -> - listener.onMediaDataRemoved(it.key) - } + removeEntry(it.key, listenersCopy) } } @@ -267,6 +265,18 @@ class MediaDataManager( } } + private fun removeEntry(key: String, listenersCopy: Set) { + mediaEntries.remove(key) + listenersCopy.forEach { + it.onMediaDataRemoved(key) + } + } + + fun dismissMediaData(key: String, delay: Long) { + val listenersCopy = listeners.toSet() + foregroundExecutor.executeDelayed({ removeEntry(key, listenersCopy) }, delay) + } + private fun loadMediaDataInBgForResumption( userId: Int, desc: MediaDescription, diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index fc33391a9ad1..70f01d576a9c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -293,6 +293,13 @@ class MediaHierarchyManager @Inject constructor( return viewHost } + /** + * Close the guts in all players in [MediaCarouselController]. + */ + fun closeGuts() { + mediaCarouselController.closeGuts() + } + private fun createUniqueObjectHost(): UniqueObjectHostView { val viewHost = UniqueObjectHostView(context) viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt index 38817d7b579e..92eeed46388d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt @@ -37,6 +37,11 @@ class MediaViewController @Inject constructor( private val mediaHostStatesManager: MediaHostStatesManager ) { + companion object { + @JvmField + val GUTS_ANIMATION_DURATION = 500L + } + /** * A listener when the current dimensions of the player change */ @@ -169,6 +174,12 @@ class MediaViewController @Inject constructor( */ val expandedLayout = ConstraintSet() + /** + * Whether the guts are visible for the associated player. + */ + var isGutsVisible = false + private set + init { collapsedLayout.load(context, R.xml.media_collapsed) expandedLayout.load(context, R.xml.media_expanded) @@ -189,6 +200,37 @@ class MediaViewController @Inject constructor( configurationController.removeCallback(configurationListener) } + /** + * Show guts with an animated transition. + */ + fun openGuts() { + if (isGutsVisible) return + isGutsVisible = true + animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L) + setCurrentState(currentStartLocation, + currentEndLocation, + currentTransitionProgress, + applyImmediately = false) + } + + /** + * Close the guts for the associated player. + * + * @param immediate if `false`, it will animate the transition. + */ + @JvmOverloads + fun closeGuts(immediate: Boolean = false) { + if (!isGutsVisible) return + isGutsVisible = false + if (!immediate) { + animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L) + } + setCurrentState(currentStartLocation, + currentEndLocation, + currentTransitionProgress, + applyImmediately = immediate) + } + private fun ensureAllMeasurements() { val mediaStates = mediaHostStatesManager.mediaHostStates for (entry in mediaStates) { @@ -202,6 +244,24 @@ class MediaViewController @Inject constructor( private fun constraintSetForExpansion(expansion: Float): ConstraintSet = if (expansion > 0) expandedLayout else collapsedLayout + /** + * Set the views to be showing/hidden based on the [isGutsVisible] for a given + * [TransitionViewState]. + */ + private fun setGutsViewState(viewState: TransitionViewState) { + PlayerViewHolder.controlsIds.forEach { id -> + viewState.widgetStates.get(id)?.let { state -> + // Make sure to use the unmodified state if guts are not visible + state.alpha = if (isGutsVisible) 0f else state.alpha + state.gone = if (isGutsVisible) true else state.gone + } + } + PlayerViewHolder.gutsIds.forEach { id -> + viewState.widgetStates.get(id)?.alpha = if (isGutsVisible) 1f else 0f + viewState.widgetStates.get(id)?.gone = !isGutsVisible + } + } + /** * Obtain a new viewState for a given media state. This usually returns a cached state, but if * it's not available, it will recreate one by measuring, which may be expensive. @@ -211,7 +271,7 @@ class MediaViewController @Inject constructor( return null } // Only a subset of the state is relevant to get a valid viewState. Let's get the cachekey - var cacheKey = getKey(state, tmpKey) + var cacheKey = getKey(state, isGutsVisible, tmpKey) val viewState = viewStates[cacheKey] if (viewState != null) { // we already have cached this measurement, let's continue @@ -228,6 +288,7 @@ class MediaViewController @Inject constructor( constraintSetForExpansion(state.expansion), TransitionViewState()) + setGutsViewState(result) // We don't want to cache interpolated or null states as this could quickly fill up // our cache. We only cache the start and the end states since the interpolation // is cheap @@ -252,11 +313,12 @@ class MediaViewController @Inject constructor( return result } - private fun getKey(state: MediaHostState, result: CacheKey): CacheKey { + private fun getKey(state: MediaHostState, guts: Boolean, result: CacheKey): CacheKey { result.apply { heightMeasureSpec = state.measurementInput?.heightMeasureSpec ?: 0 widthMeasureSpec = state.measurementInput?.widthMeasureSpec ?: 0 expansion = state.expansion + gutsVisible = guts } return result } @@ -432,5 +494,6 @@ class MediaViewController @Inject constructor( private data class CacheKey( var widthMeasureSpec: Int = -1, var heightMeasureSpec: Int = -1, - var expansion: Float = 0.0f + var expansion: Float = 0.0f, + var gutsVisible: Boolean = false ) diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt index 600fdc27ef89..666a6038a8b6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt @@ -59,6 +59,11 @@ class PlayerViewHolder private constructor(itemView: View) { val action3 = itemView.requireViewById(R.id.action3) val action4 = itemView.requireViewById(R.id.action4) + // Settings screen + val cancel = itemView.requireViewById(R.id.cancel) + val dismiss = itemView.requireViewById(R.id.dismiss) + val settings = itemView.requireViewById(R.id.settings) + init { (player.background as IlluminationDrawable).let { it.registerLightSource(seamless) @@ -67,6 +72,9 @@ class PlayerViewHolder private constructor(itemView: View) { it.registerLightSource(action2) it.registerLightSource(action3) it.registerLightSource(action4) + it.registerLightSource(cancel) + it.registerLightSource(dismiss) + it.registerLightSource(settings) } } @@ -83,9 +91,6 @@ class PlayerViewHolder private constructor(itemView: View) { } } - // Settings screen - val options = itemView.requireViewById(R.id.qs_media_controls_options) - companion object { /** * Creates a PlayerViewHolder. @@ -105,5 +110,29 @@ class PlayerViewHolder private constructor(itemView: View) { progressTimes.layoutDirection = View.LAYOUT_DIRECTION_LTR } } + + val controlsIds = setOf( + R.id.icon, + R.id.app_name, + R.id.album_art, + R.id.header_title, + R.id.header_artist, + R.id.media_seamless, + R.id.notification_media_progress_time, + R.id.media_progress_bar, + R.id.action0, + R.id.action1, + R.id.action2, + R.id.action3, + R.id.action4, + R.id.icon + ) + val gutsIds = setOf( + R.id.media_text, + R.id.remove_text, + R.id.cancel, + R.id.dismiss, + R.id.settings + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 375af6b099c2..5fec37cea2f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -2612,6 +2612,7 @@ public class NotificationPanelViewController extends PanelViewController { super.onClosingFinished(); resetHorizontalPanelPosition(); setClosingWithAlphaFadeout(false); + mMediaHierarchyManager.closeGuts(); } private void setClosingWithAlphaFadeout(boolean closing) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index c63781cb110a..8a30b00e609d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.media +import android.content.Intent import android.content.res.ColorStateList import android.graphics.Color import android.graphics.drawable.GradientDrawable @@ -23,6 +24,7 @@ import android.graphics.drawable.RippleDrawable import android.media.MediaMetadata import android.media.session.MediaSession import android.media.session.PlaybackState +import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -35,24 +37,31 @@ import android.widget.TextView import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.LiveData import androidx.test.filters.SmallTest -import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.any import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import dagger.Lazy import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock +import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.mock +import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever private const val KEY = "TEST_KEY" private const val APP = "APP" @@ -81,6 +90,8 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var seekBarViewModel: SeekBarViewModel @Mock private lateinit var seekBarData: LiveData @Mock private lateinit var mediaViewController: MediaViewController + @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil + @Mock private lateinit var mediaDataManager: MediaDataManager @Mock private lateinit var expandedSet: ConstraintSet @Mock private lateinit var collapsedSet: ConstraintSet private lateinit var appIcon: ImageView @@ -100,6 +111,9 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var action2: ImageButton private lateinit var action3: ImageButton private lateinit var action4: ImageButton + private lateinit var settings: View + private lateinit var cancel: View + private lateinit var dismiss: View private lateinit var session: MediaSession private val device = MediaDeviceData(true, null, DEVICE_NAME) @@ -114,7 +128,7 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet) player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController, - seekBarViewModel) + seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil) whenever(seekBarViewModel.progress).thenReturn(seekBarData) // Mock out a view holder for the player to attach to. @@ -156,6 +170,12 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(holder.action3).thenReturn(action3) action4 = ImageButton(context) whenever(holder.action4).thenReturn(action4) + settings = View(context) + whenever(holder.settings).thenReturn(settings) + cancel = View(context) + whenever(holder.cancel).thenReturn(cancel) + dismiss = View(context) + whenever(holder.dismiss).thenReturn(dismiss) // Create media session val metadataBuilder = MediaMetadata.Builder().apply { @@ -254,4 +274,79 @@ public class MediaControlPanelTest : SysuiTestCase() { assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME) assertThat(seamless.isEnabled()).isFalse() } + + @Test + fun longClick_gutsClosed() { + player.attach(holder) + whenever(mediaViewController.isGutsVisible).thenReturn(false) + + val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) + verify(holder.player).setOnLongClickListener(captor.capture()) + + captor.value.onLongClick(holder.player) + verify(mediaViewController).openGuts() + } + + @Test + fun longClick_gutsOpen() { + player.attach(holder) + whenever(mediaViewController.isGutsVisible).thenReturn(true) + + val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) + verify(holder.player).setOnLongClickListener(captor.capture()) + + captor.value.onLongClick(holder.player) + verify(mediaViewController, never()).openGuts() + } + + @Test + fun cancelButtonClick_animation() { + player.attach(holder) + + cancel.callOnClick() + + verify(mediaViewController).closeGuts(false) + } + + @Test + fun settingsButtonClick() { + player.attach(holder) + + settings.callOnClick() + + val captor = ArgumentCaptor.forClass(Intent::class.java) + verify(activityStarter).startActivity(captor.capture(), eq(true)) + + assertThat(captor.value.action).isEqualTo(ACTION_MEDIA_CONTROLS_SETTINGS) + } + + @Test + fun dismissButtonClick() { + player.attach(holder) + val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), + emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null, + notificationKey = KEY) + player.bind(state) + + dismiss.callOnClick() + val captor = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction::class.java) + verify(keyguardDismissUtil).executeWhenUnlocked(captor.capture(), anyBoolean()) + + captor.value.onDismiss() + verify(mediaDataManager).dismissMediaData(eq(KEY), anyLong()) + } + + @Test + fun dismissButtonClick_nullNotificationKey() { + player.attach(holder) + val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), + emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null) + player.bind(state) + + verify(keyguardDismissUtil, never()) + .executeWhenUnlocked( + any(ActivityStarter.OnDismissAction::class.java), + ArgumentMatchers.anyBoolean() + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 59c2d0e86c56..3789e6ef1f65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -217,6 +217,20 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(data.actions).hasSize(1) } + @Test + fun testDismissMedia_listenerCalled() { + val listener = mock(MediaDataManager.Listener::class.java) + mediaDataManager.addListener(listener) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java)) + mediaDataManager.dismissMediaData(KEY, 0L) + + foregroundExecutor.advanceClockToLast() + foregroundExecutor.runAllReady() + + verify(listener).onMediaDataRemoved(eq(KEY)) + } + /** * Simple implementation of [MediaDataManager.Listener] for the test. * diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt index 91c5ff8ee627..d86dfa5fa5f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt @@ -142,4 +142,11 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(), any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) } + + @Test + fun testCloseGutsRelayToCarousel() { + mediaHiearchyManager.closeGuts() + + verify(mediaCarouselController).closeGuts() + } } \ No newline at end of file -- cgit v1.2.3 From 593e53f9814fc01249eb031836d6baad2c310b92 Mon Sep 17 00:00:00 2001 From: Jeff Chang Date: Fri, 7 Aug 2020 16:08:15 +0800 Subject: [RESTRICT AUTOMERGE] Update the visibility of activities on sleeping display Activity with showWhenLocked flag is visible when screen is off and leads to activity restart called. This CL update the visibility condition for showWhenLocked and dismisskeyguard activities and refer the keyguard visibility to ensure the function works as expected. Bug: 161036653 Test: atest ActivityRecordTests atest CtsWindowManagerDeviceTestCases:KeyguardTests atest CtsWindowManagerDeviceTestCases:KeyguardLockedTests Change-Id: I9d56e40de964e9d11193fec7008f8d880028ac50 (cherry picked from commit 73d6c7926d2cdc39de617f7213fc85f303501f37) (cherry picked from commit 31fc73a70719ed9a07a9a865c71b602e2fd0c53e) (cherry picked from commit 1e8e7a94872de70aa6e661fe960854e01447b1c0) --- .../java/com/android/server/wm/ActivityRecord.java | 15 +++++------- .../com/android/server/wm/ActivityRecordTests.java | 28 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 3e9377ed0664..41e48b8ec9db 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4578,15 +4578,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return false; } - // Check if the activity is on a sleeping display, and if it can turn it ON. - if (getDisplay().isSleeping()) { - final boolean canTurnScreenOn = !mSetToSleep || canTurnScreenOn() - || canShowWhenLocked() || containsDismissKeyguardWindow(); - if (!canTurnScreenOn) { - return false; - } - } - // Now check whether it's really visible depending on Keyguard state, and update // {@link ActivityStack} internal states. // Inform the method if this activity is the top activity of this stack, but exclude the @@ -4597,6 +4588,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean visibleIgnoringDisplayStatus = stack.checkKeyguardVisibility(this, visibleIgnoringKeyguard, isTop && isTopNotPinnedStack); + // Check if the activity is on a sleeping display, and if it can turn it ON. + // TODO(b/163993448): Do not make activity visible before display awake. + if (visibleIgnoringDisplayStatus && getDisplay().isSleeping()) { + return !mSetToSleep || canTurnScreenOn(); + } + return visibleIgnoringDisplayStatus; } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index c7b45efb2de1..6ab0697206e3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -1099,6 +1099,34 @@ public class ActivityRecordTests extends ActivityTestsBase { verify(topActivity).destroyIfPossible(anyString()); } + /** + * Verify the visibility of a show-when-locked and dismiss keyguard activity on sleeping + * display. + */ + @Test + public void testDisplaySleeping_activityInvisible() { + final KeyguardController keyguardController = + mActivity.mStackSupervisor.getKeyguardController(); + doReturn(true).when(keyguardController).isKeyguardLocked(); + final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); + topActivity.mVisibleRequested = true; + topActivity.nowVisible = true; + topActivity.setState(RESUMED, "test" /*reason*/); + doReturn(true).when(topActivity).containsDismissKeyguardWindow(); + doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible( + any() /* starting */, anyInt() /* configChanges */, + anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */); + topActivity.setShowWhenLocked(true); + + // Verify the top activity is occluded keyguard. + assertEquals(topActivity, mStack.topRunningActivity()); + assertTrue(mStack.topActivityOccludesKeyguard()); + + final DisplayContent display = mActivity.mDisplayContent; + doReturn(true).when(display).isSleeping(); + assertFalse(topActivity.shouldBeVisible()); + } + /** * Verify that complete finish request for a show-when-locked activity must ensure the * keyguard occluded state being updated. -- cgit v1.2.3 From 6b12ae0ec8caf3b117fb87e38cbfde3bd7439153 Mon Sep 17 00:00:00 2001 From: Tiger Huang Date: Thu, 13 Aug 2020 22:43:52 +0800 Subject: Update requested state after applying pending frames When there is an insets animation, we will stop updating insets source frames until the animation is done. The previous logic didn't update the frames within the requested state while the animation is done. And the frames was relied by InsetsPolicy while playing transient bar animation. If the frames don't match the display, the insets would be wrong, and the animation wouldn't be played correctly. Fix: 161134197 Test: atest InsetsControllerTest Merged-In: Id8f3c1956fbfe3ad16f167ff76297dde6c634e81 Change-Id: Id8f3c1956fbfe3ad16f167ff76297dde6c634e81 (cherry picked from commit 23c75281ef0de8fb2b44fb93d8cf36ecf86454c7) (cherry picked from commit dfc8abb1ffc7bf6c7d7b47cf2e9e14bf6422f95c) --- core/java/android/view/InsetsController.java | 9 ++++++--- .../coretests/src/android/view/InsetsControllerTest.java | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index c383bc7a4d70..985829f6c885 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1134,15 +1134,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (invokeCallback) { control.cancel(); } + boolean stateChanged = false; for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { RunningAnimation runningAnimation = mRunningAnimations.get(i); if (runningAnimation.runner == control) { mRunningAnimations.remove(i); ArraySet types = toInternalType(control.getTypes()); for (int j = types.size() - 1; j >= 0; j--) { - if (getSourceConsumer(types.valueAt(j)).notifyAnimationFinished()) { - mHost.notifyInsetsChanged(); - } + stateChanged |= getSourceConsumer(types.valueAt(j)).notifyAnimationFinished(); } if (invokeCallback && runningAnimation.startDispatched) { dispatchAnimationEnd(runningAnimation.runner.getAnimation()); @@ -1150,6 +1149,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation break; } } + if (stateChanged) { + mHost.notifyInsetsChanged(); + updateRequestedState(); + } } private void applyLocalVisibilityOverride() { diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 801cd4ddb94e..48695aa5a329 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -27,6 +27,7 @@ import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; @@ -742,6 +743,20 @@ public class InsetsControllerTest { mController.onControlsChanged(createSingletonControl(ITYPE_IME)); assertEquals(newState.getSource(ITYPE_IME), mTestHost.getModifiedState().peekSource(ITYPE_IME)); + + // The modified frames cannot be updated if there is an animation. + mController.onControlsChanged(createSingletonControl(ITYPE_NAVIGATION_BAR)); + mController.hide(navigationBars()); + newState = new InsetsState(mController.getState(), true /* copySource */); + newState.getSource(ITYPE_NAVIGATION_BAR).getFrame().top--; + mController.onStateChanged(newState); + assertNotEquals(newState.getSource(ITYPE_NAVIGATION_BAR), + mTestHost.getModifiedState().peekSource(ITYPE_NAVIGATION_BAR)); + + // The modified frames can be updated while the animation is done. + mController.cancelExistingAnimations(); + assertEquals(newState.getSource(ITYPE_NAVIGATION_BAR), + mTestHost.getModifiedState().peekSource(ITYPE_NAVIGATION_BAR)); }); } -- cgit v1.2.3 From 90ac770ae9be0f206e968cb59fba7ce6b57b38da Mon Sep 17 00:00:00 2001 From: Beverly Date: Tue, 15 Sep 2020 09:23:54 -0400 Subject: Add telecom to priorityOnlyDndExemptPackages telecom plays dialer's ringtone, so we allow them to play sounds when DND is on in priority mode. We trust that telecom will abide by the DND rules and exceptions (ie: checking for starred contacts) Test: manual Bug: 163525045 Change-Id: I798f1d968821e7d1b0a97b9b034c1ef0726f8242 Merged-In: I798f1d968821e7d1b0a97b9b034c1ef0726f8242 (cherry picked from commit c384c930fd64e8c8e02b7bc28314b7f692ef8c9f) --- core/res/res/values/config.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 39d20bbff3ba..85062fc03c61 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3477,6 +3477,7 @@ mode --> com.android.dialer + com.android.server.telecom com.android.systemui android -- cgit v1.2.3 From 2788239dd69c71b8e4f64373a03f73e48ea884d1 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Wed, 2 Sep 2020 16:32:09 -0400 Subject: Add screenshot back to power menu for some devices Test: atest Bug: 155251236 Change-Id: I190a03385b9136748aa75dbd26ed4556cf81599a (cherry picked from commit 858b4ae2b2ff6870e739192f4046a3ed2f914407) (cherry picked from commit 238de217662d27688c644ddcd523bf12b3404d9e) --- core/res/res/values/config.xml | 1 + .../globalactions/GlobalActionsDialog.java | 22 ++++++++++++++++++++- .../globalactions/GlobalActionsDialogTest.java | 23 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 85062fc03c61..46f2fdeb5e51 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2774,6 +2774,7 @@ power restart logout + screenshot bugreport diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index b2e91643bed2..016ad45f7d75 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -19,6 +19,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; @@ -547,7 +548,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { return false; } - return true; + return action.shouldShow(); } /** @@ -962,6 +963,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @VisibleForTesting class ScreenshotAction extends SinglePressAction implements LongPressAction { + final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons"; + public ScreenshotAction() { super(R.drawable.ic_screenshot, R.string.global_action_screenshot); } @@ -993,6 +996,19 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return false; } + @Override + public boolean shouldShow() { + // Include screenshot in power menu for legacy nav because it is not accessible + // through Recents in that mode + return is2ButtonNavigationEnabled(); + } + + boolean is2ButtonNavigationEnabled() { + return NAV_BAR_MODE_2BUTTON == mContext.getResources().getInteger( + com.android.internal.R.integer.config_navBarInteractionMode); + } + + @Override public boolean onLongPress() { if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SCREENRECORD_LONG_PRESS)) { @@ -1616,6 +1632,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, * @return */ CharSequence getMessage(); + + default boolean shouldShow() { + return true; + } } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index ac8c6710e041..5e2e7c075043 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -49,6 +49,7 @@ import android.testing.TestableLooper; import android.util.FeatureFlagUtils; import android.view.IWindowManager; import android.view.View; +import android.view.WindowManagerPolicyConstants; import android.widget.FrameLayout; import androidx.test.filters.SmallTest; @@ -241,6 +242,28 @@ public class GlobalActionsDialogTest extends SysuiTestCase { verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SCREENSHOT_LONG_PRESS); } + @Test + public void testShouldShowScreenshot() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.integer.config_navBarInteractionMode, + WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON); + + GlobalActionsDialog.ScreenshotAction screenshotAction = + mGlobalActionsDialog.makeScreenshotActionForTesting(); + assertThat(screenshotAction.shouldShow()).isTrue(); + } + + @Test + public void testShouldNotShowScreenshot() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.integer.config_navBarInteractionMode, + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON); + + GlobalActionsDialog.ScreenshotAction screenshotAction = + mGlobalActionsDialog.makeScreenshotActionForTesting(); + assertThat(screenshotAction.shouldShow()).isFalse(); + } + private void verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent event) { mTestableLooper.processAllMessages(); verify(mUiEventLogger, times(1)) -- cgit v1.2.3 From 7e93f15670c8c942d843fb7a999f60cceddac4e1 Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Tue, 30 Jun 2020 17:12:12 -0700 Subject: Update listener instead of creating a new one This way we'll avoid cancelling and recreating timers. Bug: 160036959 Test: atest MediaTimeoutListenerTest Change-Id: Ic150156cc6078cd6477f48f05c8195d98a6d4fa4 (cherry picked from commit f53329235d5c1e9efdf051838f6a25c6dc5b5cfa) Merged-In: Ic150156cc6078cd6477f48f05c8195d98a6d4fa4 (cherry picked from commit 5d04490f318d68fcc5437bcea87a14225ae99e38) --- .../android/systemui/media/MediaTimeoutListener.kt | 65 +++++++++++++--------- .../systemui/media/MediaTimeoutListenerTest.kt | 16 ++++++ 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt index 8662aacfdab2..6eef131e3a30 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt @@ -54,32 +54,35 @@ class MediaTimeoutListener @Inject constructor( if (mediaListeners.containsKey(key)) { return } - // Having an old key means that we're migrating from/to resumption. We should invalidate - // the old listener and create a new one. + // Having an old key means that we're migrating from/to resumption. We should update + // the old listener to make sure that events will be dispatched to the new location. val migrating = oldKey != null && key != oldKey var wasPlaying = false if (migrating) { - if (mediaListeners.containsKey(oldKey)) { - val oldListener = mediaListeners.remove(oldKey) - wasPlaying = oldListener?.playing ?: false - oldListener?.destroy() + val reusedListener = mediaListeners.remove(oldKey) + if (reusedListener != null) { + wasPlaying = reusedListener.playing ?: false if (DEBUG) Log.d(TAG, "migrating key $oldKey to $key, for resumption") + reusedListener.mediaData = data + reusedListener.key = key + mediaListeners[key] = reusedListener + if (wasPlaying != reusedListener.playing) { + // If a player becomes active because of a migration, we'll need to broadcast + // its state. Doing it now would lead to reentrant callbacks, so let's wait + // until we're done. + mainExecutor.execute { + if (mediaListeners[key]?.playing == true) { + if (DEBUG) Log.d(TAG, "deliver delayed playback state for $key") + timeoutCallback.invoke(key, false /* timedOut */) + } + } + } + return } else { Log.w(TAG, "Old key $oldKey for player $key doesn't exist. Continuing...") } } mediaListeners[key] = PlaybackStateListener(key, data) - - // If a player becomes active because of a migration, we'll need to broadcast its state. - // Doing it now would lead to reentrant callbacks, so let's wait until we're done. - if (migrating && mediaListeners[key]?.playing != wasPlaying) { - mainExecutor.execute { - if (mediaListeners[key]?.playing == true) { - if (DEBUG) Log.d(TAG, "deliver delayed playback state for $key") - timeoutCallback.invoke(key, false /* timedOut */) - } - } - } } override fun onMediaDataRemoved(key: String) { @@ -91,26 +94,34 @@ class MediaTimeoutListener @Inject constructor( } private inner class PlaybackStateListener( - private val key: String, + var key: String, data: MediaData ) : MediaController.Callback() { var timedOut = false var playing: Boolean? = null + var mediaData: MediaData = data + set(value) { + mediaController?.unregisterCallback(this) + field = value + mediaController = if (field.token != null) { + mediaControllerFactory.create(field.token) + } else { + null + } + mediaController?.registerCallback(this) + // Let's register the cancellations, but not dispatch events now. + // Timeouts didn't happen yet and reentrant events are troublesome. + processState(mediaController?.playbackState, dispatchEvents = false) + } + // Resume controls may have null token - private val mediaController = if (data.token != null) { - mediaControllerFactory.create(data.token) - } else { - null - } + private var mediaController: MediaController? = null private var cancellation: Runnable? = null init { - mediaController?.registerCallback(this) - // Let's register the cancellations, but not dispatch events now. - // Timeouts didn't happen yet and reentrant events are troublesome. - processState(mediaController?.playbackState, dispatchEvents = false) + mediaData = data } fun destroy() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt index 7a8e4f7e9b85..f38524369b46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt @@ -154,6 +154,22 @@ class MediaTimeoutListenerTest : SysuiTestCase() { verify(executor).execute(anyObject()) } + @Test + fun testOnMediaDataLoaded_migratesKeys_noTimeoutExtension() { + // From not playing + mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + clearInvocations(mediaController) + + // Migrate, still not playing + val playingState = mock(android.media.session.PlaybackState::class.java) + `when`(playingState.state).thenReturn(PlaybackState.STATE_PAUSED) + `when`(mediaController.playbackState).thenReturn(playingState) + mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData) + + // Never cancels callback, or schedule another one + verify(cancellationRunnable, never()).run() + } + @Test fun testOnPlaybackStateChanged_schedulesTimeout_whenPaused() { // Assuming we're registered -- cgit v1.2.3 From 652f2f0fa19783d1966a1c3962a4c5192571cea9 Mon Sep 17 00:00:00 2001 From: Robert Snoeberger Date: Wed, 23 Sep 2020 12:57:41 -0400 Subject: Cancel pending timeouts when media data is removed Bug: 160944177 Test: manual - verified repro steps in b/160944177#comment32 Change-Id: I2925814132c712b2482681f699f647bf5707e664 (cherry picked from commit f5e463fa86ac5ce4789ecf92f0ddb868cd32c866) Merged-In: I2925814132c712b2482681f699f647bf5707e664 (cherry picked from commit 283a2e1d6e850ac80c7fb3b88e67ad23eb9e816e) --- .../android/systemui/media/MediaTimeoutListener.kt | 3 +- .../systemui/media/MediaTimeoutListenerTest.kt | 42 ++++++++++++++-------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt index 6eef131e3a30..dcb7767a680a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt @@ -126,6 +126,7 @@ class MediaTimeoutListener @Inject constructor( fun destroy() { mediaController?.unregisterCallback(this) + cancellation?.run() } override fun onPlaybackStateChanged(state: PlaybackState?) { @@ -182,4 +183,4 @@ class MediaTimeoutListener @Inject constructor( cancellation = null } } -} \ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt index f38524369b46..f3979592c894 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt @@ -23,8 +23,9 @@ import android.media.session.PlaybackState import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.capture +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule @@ -33,7 +34,6 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyString import org.mockito.Captor import org.mockito.Mock @@ -63,10 +63,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Mock private lateinit var mediaControllerFactory: MediaControllerFactory @Mock private lateinit var mediaController: MediaController - @Mock private lateinit var executor: DelayableExecutor + private lateinit var executor: FakeExecutor @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit - @Mock private lateinit var cancellationRunnable: Runnable - @Captor private lateinit var timeoutCaptor: ArgumentCaptor @Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor @JvmField @Rule val mockito = MockitoJUnit.rule() private lateinit var metadataBuilder: MediaMetadata.Builder @@ -78,7 +76,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Before fun setup() { `when`(mediaControllerFactory.create(any())).thenReturn(mediaController) - `when`(executor.executeDelayed(any(), anyLong())).thenReturn(cancellationRunnable) + executor = FakeExecutor(FakeSystemClock()) mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor) mediaTimeoutListener.timeoutCallback = timeoutCallback @@ -120,7 +118,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { fun testOnMediaDataLoaded_registersTimeout_whenPaused() { mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) verify(mediaController).registerCallback(capture(mediaCallbackCaptor)) - verify(executor).executeDelayed(capture(timeoutCaptor), anyLong()) + assertThat(executor.numPending()).isEqualTo(1) verify(timeoutCallback, never()).invoke(anyString(), anyBoolean()) } @@ -136,6 +134,17 @@ class MediaTimeoutListenerTest : SysuiTestCase() { verify(mediaController, never()).unregisterCallback(anyObject()) } + @Test + fun testOnMediaDataRemoved_clearsTimeout() { + // GIVEN media that is paused + mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + assertThat(executor.numPending()).isEqualTo(1) + // WHEN the media is removed + mediaTimeoutListener.onMediaDataRemoved(KEY) + // THEN the timeout runnable is cancelled + assertThat(executor.numPending()).isEqualTo(0) + } + @Test fun testOnMediaDataLoaded_migratesKeys() { // From not playing @@ -151,7 +160,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { verify(mediaController).registerCallback(anyObject()) // Enqueues callback - verify(executor).execute(anyObject()) + assertThat(executor.numPending()).isEqualTo(1) } @Test @@ -166,8 +175,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() { `when`(mediaController.playbackState).thenReturn(playingState) mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData) - // Never cancels callback, or schedule another one - verify(cancellationRunnable, never()).run() + // The number of queued timeout tasks remains the same. The timeout task isn't cancelled nor + // is another scheduled + assertThat(executor.numPending()).isEqualTo(1) } @Test @@ -177,7 +187,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder() .setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()) - verify(executor).executeDelayed(capture(timeoutCaptor), anyLong()) + assertThat(executor.numPending()).isEqualTo(1) } @Test @@ -187,7 +197,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder() .setState(PlaybackState.STATE_PLAYING, 0L, 0f).build()) - verify(cancellationRunnable).run() + assertThat(executor.numPending()).isEqualTo(0) } @Test @@ -195,10 +205,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // Assuming we have a pending timeout testOnPlaybackStateChanged_schedulesTimeout_whenPaused() - clearInvocations(cancellationRunnable) mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder() .setState(PlaybackState.STATE_STOPPED, 0L, 0f).build()) - verify(cancellationRunnable, never()).run() + assertThat(executor.numPending()).isEqualTo(1) } @Test @@ -206,7 +215,10 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // Assuming we're have a pending timeout testOnPlaybackStateChanged_schedulesTimeout_whenPaused() - timeoutCaptor.value.run() + with(executor) { + advanceClockToNext() + runAllReady() + } verify(timeoutCallback).invoke(eq(KEY), eq(true)) } -- cgit v1.2.3 From 639fe4c90e9338cddeecaaef64058df4e0d42720 Mon Sep 17 00:00:00 2001 From: Louis Chang Date: Tue, 22 Sep 2020 16:37:47 +0800 Subject: RESTRICT AUTOMERGE Make task visible if activity launched behind Activities were unable to be started while dream started and keyguard locked because the started activities were not on the top-most focused tasks (it was the dream task on top). Even the activities were set as launched behind, the visibilities of the belonging tasks were still invisible and aborted the launches. Making the task visible if the top activity is launched behind and also starting activities on all visible tasks for attached application. Bug: 168326273 Test: making phone call on Auto while Dream started on Phone Test: atest DreamManagerServiceTests Test: atest ActivityRecordTests Change-Id: I199435bcfc7593e6c58488cd6de7c2289d5d0e20 (cherry picked from commit 0f26f573f5f79970a99b46c0b55b8c1d4e106ae5) --- .../com/android/server/wm/RootWindowContainer.java | 38 +++++++++++++--------- services/core/java/com/android/server/wm/Task.java | 12 +++++++ .../com/android/server/wm/ActivityRecordTests.java | 2 +- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 4700864c03bc..edb1984aea92 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -48,6 +48,7 @@ import static com.android.server.wm.ActivityStack.ActivityState.PAUSED; import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPING; +import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_INVISIBLE; import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME; import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList; @@ -1927,24 +1928,29 @@ class RootWindowContainer extends WindowContainer } boolean attachApplication(WindowProcessController app) throws RemoteException { - final String processName = app.mName; boolean didSomething = false; for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) { - final DisplayContent display = getChildAt(displayNdx); - final ActivityStack stack = display.getFocusedStack(); - if (stack == null) { - continue; - } - mTmpRemoteException = null; mTmpBoolean = false; // Set to true if an activity was started. - final PooledFunction c = PooledLambda.obtainFunction( - RootWindowContainer::startActivityForAttachedApplicationIfNeeded, this, - PooledLambda.__(ActivityRecord.class), app, stack.topRunningActivity()); - stack.forAllActivities(c); - c.recycle(); - if (mTmpRemoteException != null) { - throw mTmpRemoteException; + + final DisplayContent display = getChildAt(displayNdx); + for (int areaNdx = display.getTaskDisplayAreaCount() - 1; areaNdx >= 0; --areaNdx) { + final TaskDisplayArea taskDisplayArea = display.getTaskDisplayAreaAt(areaNdx); + for (int taskNdx = taskDisplayArea.getStackCount() - 1; taskNdx >= 0; --taskNdx) { + final ActivityStack rootTask = taskDisplayArea.getStackAt(taskNdx); + if (rootTask.getVisibility(null /*starting*/) == STACK_VISIBILITY_INVISIBLE) { + break; + } + final PooledFunction c = PooledLambda.obtainFunction( + RootWindowContainer::startActivityForAttachedApplicationIfNeeded, this, + PooledLambda.__(ActivityRecord.class), app, + rootTask.topRunningActivity()); + rootTask.forAllActivities(c); + c.recycle(); + if (mTmpRemoteException != null) { + throw mTmpRemoteException; + } + } } didSomething |= mTmpBoolean; } @@ -1962,8 +1968,8 @@ class RootWindowContainer extends WindowContainer } try { - if (mStackSupervisor.realStartActivityLocked(r, app, top == r /*andResume*/, - true /*checkConfig*/)) { + if (mStackSupervisor.realStartActivityLocked(r, app, + top == r && r.isFocusable() /*andResume*/, true /*checkConfig*/)) { mTmpBoolean = true; } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 6785127d5953..32230cdbdca6 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3649,6 +3649,10 @@ class Task extends WindowContainer { return STACK_VISIBILITY_INVISIBLE; } + if (isTopActivityLaunchedBehind()) { + return STACK_VISIBILITY_VISIBLE; + } + boolean gotSplitScreenStack = false; boolean gotOpaqueSplitScreenPrimary = false; boolean gotOpaqueSplitScreenSecondary = false; @@ -3766,6 +3770,14 @@ class Task extends WindowContainer { : STACK_VISIBILITY_VISIBLE; } + private boolean isTopActivityLaunchedBehind() { + final ActivityRecord top = topRunningActivity(); + if (top != null && top.mLaunchTaskBehind) { + return true; + } + return false; + } + ActivityRecord isInTask(ActivityRecord r) { if (r == null) { return null; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 6ab0697206e3..c6d788b10056 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -543,7 +543,7 @@ public class ActivityRecordTests extends ActivityTestsBase { final ActivityStack stack = new StackBuilder(mRootWindowContainer).build(); try { doReturn(false).when(stack).isTranslucent(any()); - assertFalse(mStack.shouldBeVisible(null /* starting */)); + assertTrue(mStack.shouldBeVisible(null /* starting */)); mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), mActivity.getConfiguration())); -- cgit v1.2.3 From ef56e21b1082cd22094404a087e6848208fab7bf Mon Sep 17 00:00:00 2001 From: Sudheer Shanka Date: Thu, 1 Oct 2020 03:19:00 -0700 Subject: Update INTERNET perm state cache to use uids instead of appIds. It really shouldn't have mattered whether we use uids or appIds for this cache but given the way NetworkPolicy iterates over all apps on the device (all_apps * all_users), it is possible that we end up checking the permission state of apps which are not installed on a user which will always be DENIED and we end up caching this. So, we could end up treating an app as not having INTERNET permission on a user even though it has. Also, update the cache stragety to always check with PackageManager when the permission state is denied in the cache just to be safe, until NetworkPolicy iteration of apps is fixed. Bug: 168299219 Test: atest cts/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java Test: manual Change-Id: I6f2a60695a519a972c96ec8e053d3be5dc732461 Merged-In: I6f2a60695a519a972c96ec8e053d3be5dc732461 (cherry picked from commit 97c29c74766cd6bee5d36792e52ba769b77b1406) --- .../server/net/NetworkPolicyManagerService.java | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index d6557f6410ec..d7dbfa09aac9 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -578,7 +578,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private final NetworkPolicyLogger mLogger = new NetworkPolicyLogger(); - /** List of apps indexed by appId and whether they have the internet permission */ + /** List of apps indexed by uid and whether they have the internet permission */ @GuardedBy("mUidRulesFirstLock") private final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray(); @@ -964,7 +964,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (LOGV) Slog.v(TAG, "ACTION_PACKAGE_ADDED for uid=" + uid); // Clear the cache for the app synchronized (mUidRulesFirstLock) { - mInternetPermissionMap.delete(UserHandle.getAppId(uid)); + mInternetPermissionMap.delete(uid); updateRestrictionRulesForUidUL(uid); } } @@ -4170,16 +4170,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @GuardedBy("mUidRulesFirstLock") private boolean hasInternetPermissionUL(int uid) { try { - final int appId = UserHandle.getAppId(uid); - final boolean hasPermission; - if (mInternetPermissionMap.indexOfKey(appId) < 0) { - hasPermission = - mIPm.checkUidPermission(Manifest.permission.INTERNET, uid) - == PackageManager.PERMISSION_GRANTED; - mInternetPermissionMap.put(appId, hasPermission); - } else { - hasPermission = mInternetPermissionMap.get(appId); + if (mInternetPermissionMap.get(uid)) { + return true; } + // If the cache shows that uid doesn't have internet permission, + // then always re-check with PackageManager just to be safe. + final boolean hasPermission = mIPm.checkUidPermission(Manifest.permission.INTERNET, + uid) == PackageManager.PERMISSION_GRANTED; + mInternetPermissionMap.put(uid, hasPermission); return hasPermission; } catch (RemoteException e) { } -- cgit v1.2.3