summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaData.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt153
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHost.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt216
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt57
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java10
-rw-r--r--packages/Tethering/jarjar-rules.txt2
-rw-r--r--packages/Tethering/tests/unit/jarjar-rules.txt2
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java7
-rw-r--r--services/core/java/com/android/server/media/BluetoothRouteProvider.java47
-rw-r--r--services/core/java/com/android/server/textclassifier/IconsContentProvider.java66
-rw-r--r--services/core/java/com/android/server/wm/ActivityStackSupervisor.java23
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java59
-rw-r--r--services/core/java/com/android/server/wm/Task.java21
-rw-r--r--services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java28
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java5
37 files changed, 857 insertions, 308 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index cd27fdf9c947..749b537ea364 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -290,6 +290,12 @@ public class BubbleStackView extends FrameLayout
/** Whether we're in the middle of dragging the stack around by touch. */
private boolean mIsDraggingStack = false;
+ /**
+ * The pointer index of the ACTION_DOWN event we received prior to an ACTION_UP. We'll ignore
+ * touches from other pointer indices.
+ */
+ private int mPointerIndexDown = -1;
+
/** Description of current animation controller state. */
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Stack view state:");
@@ -2220,6 +2226,18 @@ public class BubbleStackView extends FrameLayout
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (ev.getAction() != MotionEvent.ACTION_DOWN && ev.getActionIndex() != mPointerIndexDown) {
+ // Ignore touches from additional pointer indices.
+ return false;
+ }
+
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mPointerIndexDown = ev.getActionIndex();
+ } else if (ev.getAction() == MotionEvent.ACTION_UP
+ || ev.getAction() == MotionEvent.ACTION_CANCEL) {
+ mPointerIndexDown = -1;
+ }
+
boolean dispatched = super.dispatchTouchEvent(ev);
// If a new bubble arrives while the collapsed stack is being dragged, it will be positioned
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 1a730c39dcd3..127c5dd54d72 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -39,9 +39,8 @@ class MediaCarouselController @Inject constructor(
private val mediaHostStatesManager: MediaHostStatesManager,
private val activityStarter: ActivityStarter,
@Main executor: DelayableExecutor,
- mediaManager: MediaDataCombineLatest,
+ mediaManager: MediaDataFilter,
configurationController: ConfigurationController,
- mediaDataManager: MediaDataManager,
falsingManager: FalsingManager
) {
/**
@@ -148,7 +147,7 @@ class MediaCarouselController @Inject constructor(
mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
- executor, mediaDataManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
+ executor, mediaManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
falsingManager)
isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
inflateSettingsButton()
@@ -249,6 +248,7 @@ class MediaCarouselController @Inject constructor(
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
existingPlayer.view?.player?.setLayoutParams(lp)
+ existingPlayer.bind(data)
existingPlayer.setListening(currentlyExpanded)
updatePlayerToState(existingPlayer, noAnimation = true)
if (existingPlayer.isPlaying) {
@@ -256,16 +256,18 @@ class MediaCarouselController @Inject constructor(
} else {
mediaContent.addView(existingPlayer.view?.player)
}
- } else if (existingPlayer.isPlaying &&
- mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
- if (visualStabilityManager.isReorderingAllowed) {
- mediaContent.removeView(existingPlayer.view?.player)
- mediaContent.addView(existingPlayer.view?.player, 0)
- } else {
- needsReordering = true
+ } else {
+ existingPlayer.bind(data)
+ if (existingPlayer.isPlaying &&
+ mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
+ if (visualStabilityManager.isReorderingAllowed) {
+ mediaContent.removeView(existingPlayer.view?.player)
+ mediaContent.addView(existingPlayer.view?.player, 0)
+ } else {
+ needsReordering = true
+ }
}
}
- existingPlayer?.bind(data)
updatePageIndicator()
mediaCarouselScrollHandler.onPlayersChanged()
mediaCarousel.requiresRemeasuring = true
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 8c9cb1b240bf..dafc52ad8025 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -23,6 +23,7 @@ import android.media.session.MediaSession
/** State of a media view. */
data class MediaData(
+ val userId: Int,
val initialized: Boolean = false,
val backgroundColor: Int,
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
index 11cbc482459a..0904ebccd414 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
@@ -58,6 +58,17 @@ class MediaDataCombineLatest @Inject constructor(
}
/**
+ * Get a map of all non-null data entries
+ */
+ fun getData(): Map<String, MediaData> {
+ return entries.filter {
+ (key, pair) -> pair.first != null
+ }.mapValues {
+ (key, pair) -> pair.first!!
+ }
+ }
+
+ /**
* Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData].
*/
fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
new file mode 100644
index 000000000000..662831e4a445
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.media
+
+import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+private const val TAG = "MediaDataFilter"
+
+/**
+ * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user
+ * switches (removing entries for the previous user, adding back entries for the current user)
+ *
+ * This is added downstream of [MediaDataManager] since we may still need to handle callbacks from
+ * background users (e.g. timeouts) that UI classes should ignore.
+ * Instead, UI classes should listen to this so they can stay in sync with the current user.
+ */
+@Singleton
+class MediaDataFilter @Inject constructor(
+ private val dataSource: MediaDataCombineLatest,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val mediaResumeListener: MediaResumeListener,
+ private val mediaDataManager: MediaDataManager,
+ private val lockscreenUserManager: NotificationLockscreenUserManager,
+ @Main private val executor: Executor
+) : MediaDataManager.Listener {
+ private val userTracker: CurrentUserTracker
+ private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
+
+ // The filtered mediaEntries, which will be a subset of all mediaEntries in MediaDataManager
+ private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+
+ init {
+ userTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ override fun onUserSwitched(newUserId: Int) {
+ // Post this so we can be sure lockscreenUserManager already got the broadcast
+ executor.execute { handleUserSwitched(newUserId) }
+ }
+ }
+ userTracker.startTracking()
+ dataSource.addListener(this)
+ }
+
+ override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
+ if (!lockscreenUserManager.isCurrentProfile(data.userId)) {
+ return
+ }
+
+ if (oldKey != null) {
+ mediaEntries.remove(oldKey)
+ }
+ mediaEntries.put(key, data)
+
+ // Notify listeners
+ val listenersCopy = listeners.toSet()
+ listenersCopy.forEach {
+ it.onMediaDataLoaded(key, oldKey, data)
+ }
+ }
+
+ override fun onMediaDataRemoved(key: String) {
+ mediaEntries.remove(key)?.let {
+ // Only notify listeners if something actually changed
+ val listenersCopy = listeners.toSet()
+ listenersCopy.forEach {
+ it.onMediaDataRemoved(key)
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal fun handleUserSwitched(id: Int) {
+ // If the user changes, remove all current MediaData objects and inform listeners
+ val listenersCopy = listeners.toSet()
+ val keyCopy = mediaEntries.keys.toMutableList()
+ // Clear the list first, to make sure callbacks from listeners if we have any entries
+ // are up to date
+ mediaEntries.clear()
+ keyCopy.forEach {
+ Log.d(TAG, "Removing $it after user change")
+ listenersCopy.forEach { listener ->
+ listener.onMediaDataRemoved(it)
+ }
+ }
+
+ dataSource.getData().forEach { (key, data) ->
+ if (lockscreenUserManager.isCurrentProfile(data.userId)) {
+ Log.d(TAG, "Re-adding $key after user change")
+ mediaEntries.put(key, data)
+ listenersCopy.forEach { listener ->
+ listener.onMediaDataLoaded(key, null, data)
+ }
+ }
+ }
+ }
+
+ /**
+ * Invoked when the user has dismissed the media carousel
+ */
+ fun onSwipeToDismiss() {
+ val mediaKeys = mediaEntries.keys.toSet()
+ mediaKeys.forEach {
+ mediaDataManager.setTimedOut(it, timedOut = true)
+ }
+ }
+
+ /**
+ * Are there any media notifications active?
+ */
+ fun hasActiveMedia() = mediaEntries.any { it.value.active }
+
+ /**
+ * Are there any media entries we should display?
+ * If resumption is enabled, this will include inactive players
+ * If resumption is disabled, we only want to show active players
+ */
+ fun hasAnyMedia() = if (mediaResumeListener.isResumptionEnabled()) {
+ mediaEntries.isNotEmpty()
+ } else {
+ hasActiveMedia()
+ }
+
+ /**
+ * Add a listener for filtered [MediaData] changes
+ */
+ fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
+
+ /**
+ * Remove a listener that was registered with addListener
+ */
+ fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index d6b6660b778c..8cb93bfc6d4d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -67,7 +67,7 @@ private const val DEFAULT_LUMINOSITY = 0.25f
private const val LUMINOSITY_THRESHOLD = 0.05f
private const val SATURATION_MULTIPLIER = 0.8f
-private val LOADING = MediaData(false, 0, null, null, null, null, null,
+private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
emptyList(), emptyList(), "INVALID", null, null, null, true, null)
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
@@ -116,15 +116,6 @@ class MediaDataManager(
broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
- private val userChangeReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (Intent.ACTION_USER_SWITCHED == intent.action) {
- // Remove all controls, regardless of state
- clearData()
- }
- }
- }
-
private val appChangeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
@@ -152,9 +143,6 @@ class MediaDataManager(
mediaResumeListener.setManager(this)
addListener(mediaResumeListener)
- val userFilter = IntentFilter(Intent.ACTION_USER_SWITCHED)
- broadcastDispatcher.registerReceiver(userChangeReceiver, userFilter, null, UserHandle.ALL)
-
val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED)
broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL)
@@ -169,7 +157,6 @@ class MediaDataManager(
fun destroy() {
context.unregisterReceiver(appChangeReceiver)
- broadcastDispatcher.unregisterReceiver(userChangeReceiver)
}
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
@@ -190,20 +177,6 @@ class MediaDataManager(
}
}
- private fun clearData() {
- // Called on user change. Remove all current MediaData objects and inform listeners
- val listenersCopy = listeners.toSet()
- val keyCopy = mediaEntries.keys.toMutableList()
- // Clear the list first, to make sure callbacks from listeners if we have any entries
- // are up to date
- mediaEntries.clear()
- keyCopy.forEach {
- listenersCopy.forEach { listener ->
- listener.onMediaDataRemoved(it)
- }
- }
- }
-
private fun removeAllForPackage(packageName: String) {
Assert.isMainThread()
val listenersCopy = listeners.toSet()
@@ -224,6 +197,7 @@ class MediaDataManager(
}
fun addResumptionControls(
+ userId: Int,
desc: MediaDescription,
action: Runnable,
token: MediaSession.Token,
@@ -238,7 +212,8 @@ class MediaDataManager(
mediaEntries.put(packageName, resumeData)
}
backgroundExecutor.execute {
- loadMediaDataInBgForResumption(desc, action, token, appName, appIntent, packageName)
+ loadMediaDataInBgForResumption(userId, desc, action, token, appName, appIntent,
+ packageName)
}
}
@@ -282,7 +257,7 @@ class MediaDataManager(
* This will make the player not active anymore, hiding it from QQS and Keyguard.
* @see MediaData.active
*/
- private fun setTimedOut(token: String, timedOut: Boolean) {
+ internal fun setTimedOut(token: String, timedOut: Boolean) {
mediaEntries[token]?.let {
if (it.active == !timedOut) {
return
@@ -293,6 +268,7 @@ class MediaDataManager(
}
private fun loadMediaDataInBgForResumption(
+ userId: Int,
desc: MediaDescription,
resumeAction: Runnable,
token: MediaSession.Token,
@@ -307,7 +283,7 @@ class MediaDataManager(
return
}
- Log.d(TAG, "adding track from browser: $desc")
+ Log.d(TAG, "adding track for $userId from browser: $desc")
// Album art
var artworkBitmap = desc.iconBitmap
@@ -323,7 +299,7 @@ class MediaDataManager(
val mediaAction = getResumeMediaAction(resumeAction)
foregroundExecutor.execute {
- onMediaDataLoaded(packageName, null, MediaData(true, bgColor, appName,
+ onMediaDataLoaded(packageName, null, MediaData(userId, true, bgColor, appName,
null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
packageName, token, appIntent, device = null, active = false,
resumeAction = resumeAction, resumption = true, notificationKey = packageName,
@@ -439,10 +415,11 @@ class MediaDataManager(
val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
val active = mediaEntries[key]?.active ?: true
- onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist,
- song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
- notif.contentIntent, null, active, resumeAction = resumeAction,
- notificationKey = key, hasCheckedForResume = hasCheckedForResume))
+ onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
+ smallIconDrawable, artist, song, artWorkIcon, actionIcons,
+ actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
+ active, resumeAction = resumeAction, notificationKey = key,
+ hasCheckedForResume = hasCheckedForResume))
}
}
@@ -564,18 +541,6 @@ class MediaDataManager(
}
}
- /**
- * Are there any media notifications active?
- */
- fun hasActiveMedia() = mediaEntries.any { it.value.active }
-
- /**
- * Are there any media entries we should display?
- * If resumption is enabled, this will include inactive players
- * If resumption is disabled, we only want to show active players
- */
- fun hasAnyMedia() = if (useMediaResumption) mediaEntries.isNotEmpty() else hasActiveMedia()
-
fun setMediaResumptionEnabled(isEnabled: Boolean) {
if (useMediaResumption == isEnabled) {
return
@@ -596,16 +561,6 @@ class MediaDataManager(
}
}
- /**
- * Invoked when the user has dismissed the media carousel
- */
- fun onSwipeToDismiss() {
- val mediaKeys = mediaEntries.keys.toSet()
- mediaKeys.forEach {
- setTimedOut(it, timedOut = true)
- }
- }
-
interface Listener {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index 570de635ea1d..3598719fcb3a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -14,8 +14,7 @@ import javax.inject.Inject
class MediaHost @Inject constructor(
private val state: MediaHostStateHolder,
private val mediaHierarchyManager: MediaHierarchyManager,
- private val mediaDataManager: MediaDataManager,
- private val mediaDataManagerCombineLatest: MediaDataCombineLatest,
+ private val mediaDataFilter: MediaDataFilter,
private val mediaHostStatesManager: MediaHostStatesManager
) : MediaHostState by state {
lateinit var hostView: UniqueObjectHostView
@@ -80,12 +79,12 @@ class MediaHost @Inject constructor(
// be a delay until the views and the controllers are initialized, leaving us
// with either a blank view or the controllers not yet initialized and the
// measuring wrong
- mediaDataManagerCombineLatest.addListener(listener)
+ mediaDataFilter.addListener(listener)
updateViewVisibility()
}
override fun onViewDetachedFromWindow(v: View?) {
- mediaDataManagerCombineLatest.removeListener(listener)
+ mediaDataFilter.removeListener(listener)
}
})
@@ -114,9 +113,9 @@ class MediaHost @Inject constructor(
private fun updateViewVisibility() {
visible = if (showsOnlyActiveMedia) {
- mediaDataManager.hasActiveMedia()
+ mediaDataFilter.hasActiveMedia()
} else {
- mediaDataManager.hasAnyMedia()
+ mediaDataFilter.hasAnyMedia()
}
val newVisibility = if (visible) View.VISIBLE else View.GONE
if (newVisibility != hostView.visibility) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 0cc1e7bb1b56..4ec746fcb153 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -56,7 +56,7 @@ class MediaResumeListener @Inject constructor(
private lateinit var mediaDataManager: MediaDataManager
private var mediaBrowser: ResumeMediaBrowser? = null
- private var currentUserId: Int
+ private var currentUserId: Int = context.userId
private val userChangeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
@@ -65,7 +65,6 @@ class MediaResumeListener @Inject constructor(
} else if (Intent.ACTION_USER_SWITCHED == intent.action) {
currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
loadSavedComponents()
- loadMediaResumptionControls()
}
}
}
@@ -89,13 +88,12 @@ class MediaResumeListener @Inject constructor(
}
Log.d(TAG, "Adding resume controls $desc")
- mediaDataManager.addResumptionControls(desc, resumeAction, token, appName.toString(),
- appIntent, component.packageName)
+ mediaDataManager.addResumptionControls(currentUserId, desc, resumeAction, token,
+ appName.toString(), appIntent, component.packageName)
}
}
init {
- currentUserId = context.userId
if (useMediaResumption) {
val unlockFilter = IntentFilter()
unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED)
@@ -118,6 +116,8 @@ class MediaResumeListener @Inject constructor(
}, Settings.Secure.MEDIA_CONTROLS_RESUME)
}
+ fun isResumptionEnabled() = useMediaResumption
+
private fun loadSavedComponents() {
// Make sure list is empty (if we switched users)
resumeComponents.clear()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 9a134dbe0264..8662aacfdab2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -54,7 +54,32 @@ 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.
+ 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()
+ if (DEBUG) Log.d(TAG, "migrating key $oldKey to $key, for resumption")
+ } 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) {
@@ -71,7 +96,7 @@ class MediaTimeoutListener @Inject constructor(
) : MediaController.Callback() {
var timedOut = false
- private var playing: Boolean? = null
+ var playing: Boolean? = null
// Resume controls may have null token
private val mediaController = if (data.token != null) {
@@ -83,7 +108,9 @@ class MediaTimeoutListener @Inject constructor(
init {
mediaController?.registerCallback(this)
- onPlaybackStateChanged(mediaController?.playbackState)
+ // 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)
}
fun destroy() {
@@ -91,8 +118,12 @@ class MediaTimeoutListener @Inject constructor(
}
override fun onPlaybackStateChanged(state: PlaybackState?) {
+ processState(state, dispatchEvents = true)
+ }
+
+ private fun processState(state: PlaybackState?, dispatchEvents: Boolean) {
if (DEBUG) {
- Log.v(TAG, "onPlaybackStateChanged: $state")
+ Log.v(TAG, "processState: $state")
}
val isPlaying = state != null && isPlayingState(state.state)
@@ -116,12 +147,16 @@ class MediaTimeoutListener @Inject constructor(
Log.v(TAG, "Execute timeout for $key")
}
timedOut = true
- timeoutCallback(key, timedOut)
+ if (dispatchEvents) {
+ timeoutCallback(key, timedOut)
+ }
}, PAUSED_MEDIA_TIMEOUT)
} else {
expireMediaTimeout(key, "playback started - $state, $key")
timedOut = false
- timeoutCallback(key, timedOut)
+ if (dispatchEvents) {
+ timeoutCallback(key, timedOut)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index 83cfdd5f2699..38817d7b579e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -268,7 +268,6 @@ class MediaViewController @Inject constructor(
fun attach(transitionLayout: TransitionLayout) {
this.transitionLayout = transitionLayout
layoutController.attach(transitionLayout)
- ensureAllMeasurements()
if (currentEndLocation == -1) {
return
}
@@ -414,13 +413,16 @@ class MediaViewController @Inject constructor(
* Clear all existing measurements and refresh the state to match the view.
*/
fun refreshState() {
- if (!firstRefresh) {
- // Let's clear all of our measurements and recreate them!
- viewStates.clear()
- setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress,
- applyImmediately = true)
+ // Let's clear all of our measurements and recreate them!
+ viewStates.clear()
+ if (firstRefresh) {
+ // This is the first bind, let's ensure we pre-cache all measurements. Otherwise
+ // We'll just load these on demand.
+ ensureAllMeasurements()
+ firstRefresh = false
}
- firstRefresh = false
+ setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress,
+ applyImmediately = true)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index 2980f11b3cbc..ead17867844a 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -18,16 +18,16 @@ package com.android.systemui.pip;
import android.animation.AnimationHandler;
import android.animation.Animator;
+import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.content.Context;
import android.graphics.Rect;
import android.view.SurfaceControl;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.Interpolators;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -76,7 +76,6 @@ public class PipAnimationController {
|| direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN;
}
- private final Interpolator mFastOutSlowInInterpolator;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private PipTransitionAnimator mCurrentAnimator;
@@ -90,8 +89,6 @@ public class PipAnimationController {
@Inject
PipAnimationController(Context context, PipSurfaceTransactionHelper helper) {
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_slow_in);
mSurfaceTransactionHelper = helper;
}
@@ -113,10 +110,11 @@ public class PipAnimationController {
}
@SuppressWarnings("unchecked")
- PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds) {
+ PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds,
+ Rect sourceHintRect) {
if (mCurrentAnimator == null) {
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, endBounds));
+ PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect));
} else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
&& mCurrentAnimator.isRunning()) {
// If we are still animating the fade into pip, then just move the surface and ensure
@@ -131,7 +129,7 @@ public class PipAnimationController {
} else {
mCurrentAnimator.cancel();
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, endBounds));
+ PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect));
}
return mCurrentAnimator;
}
@@ -142,7 +140,7 @@ public class PipAnimationController {
private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) {
animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
- animator.setInterpolator(mFastOutSlowInInterpolator);
+ animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
animator.setFloatValues(FRACTION_START, FRACTION_END);
animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
return animator;
@@ -341,6 +339,7 @@ public class PipAnimationController {
@Override
void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
getSurfaceTransactionHelper()
+ .resetScale(tx, leash, getDestinationBounds())
.crop(tx, leash, getDestinationBounds())
.round(tx, leash, shouldApplyCornerRadius());
tx.show(leash);
@@ -356,35 +355,46 @@ public class PipAnimationController {
}
static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
- Rect startValue, Rect endValue) {
+ Rect startValue, Rect endValue, Rect sourceHintRect) {
+ // Just for simplicity we'll interpolate between the source rect hint insets and empty
+ // insets to calculate the window crop
+ final Rect initialStartValue = new Rect(startValue);
+ final Rect sourceHintRectInsets = sourceHintRect != null
+ ? new Rect(sourceHintRect.left - startValue.left,
+ sourceHintRect.top - startValue.top,
+ startValue.right - sourceHintRect.right,
+ startValue.bottom - sourceHintRect.bottom)
+ : null;
+ final Rect sourceInsets = new Rect(0, 0, 0, 0);
+
// construct new Rect instances in case they are recycled
return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS,
endValue, new Rect(startValue), new Rect(endValue)) {
- private final Rect mTmpRect = new Rect();
-
- private int getCastedFractionValue(float start, float end, float fraction) {
- return (int) (start * (1 - fraction) + end * fraction + .5f);
- }
+ private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
+ private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
@Override
void applySurfaceControlTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, float fraction) {
final Rect start = getStartValue();
final Rect end = getEndValue();
- mTmpRect.set(
- getCastedFractionValue(start.left, end.left, fraction),
- getCastedFractionValue(start.top, end.top, fraction),
- getCastedFractionValue(start.right, end.right, fraction),
- getCastedFractionValue(start.bottom, end.bottom, fraction));
- setCurrentValue(mTmpRect);
+ Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
+ setCurrentValue(bounds);
if (inScaleTransition()) {
if (isOutPipDirection(getTransitionDirection())) {
- getSurfaceTransactionHelper().scale(tx, leash, end, mTmpRect);
+ getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
} else {
- getSurfaceTransactionHelper().scale(tx, leash, start, mTmpRect);
+ getSurfaceTransactionHelper().scale(tx, leash, start, bounds);
}
} else {
- getSurfaceTransactionHelper().crop(tx, leash, mTmpRect);
+ if (sourceHintRectInsets != null) {
+ Rect insets = mInsetsEvaluator.evaluate(fraction, sourceInsets,
+ sourceHintRectInsets);
+ getSurfaceTransactionHelper().scaleAndCrop(tx, leash, initialStartValue,
+ bounds, insets);
+ } else {
+ getSurfaceTransactionHelper().scale(tx, leash, start, bounds);
+ }
}
tx.apply();
}
@@ -400,11 +410,11 @@ public class PipAnimationController {
@Override
void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
- if (!inScaleTransition()) return;
// NOTE: intentionally does not apply the transaction here.
// this end transaction should get executed synchronously with the final
// WindowContainerTransaction in task organizer
- getSurfaceTransactionHelper().resetScale(tx, leash, getDestinationBounds())
+ getSurfaceTransactionHelper()
+ .resetScale(tx, leash, getDestinationBounds())
.crop(tx, leash, getDestinationBounds());
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 0d3a16ec1028..8bbd15babf19 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -289,6 +289,24 @@ public class PipBoundsHandler {
}
/**
+ * Updatest the display info and display layout on rotation change. This is needed even when we
+ * aren't in PIP because the rotation layout is used to calculate the proper insets for the
+ * next enter animation into PIP.
+ */
+ public void onDisplayRotationChangedNotInPip(int toRotation) {
+ // Update the display layout, note that we have to do this on every rotation even if we
+ // aren't in PIP since we need to update the display layout to get the right resources
+ mDisplayLayout.rotateTo(mContext.getResources(), toRotation);
+
+ // Populate the new {@link #mDisplayInfo}.
+ // The {@link DisplayInfo} queried from DisplayManager would be the one before rotation,
+ // therefore, the width/height may require a swap first.
+ // Moving forward, we should get the new dimensions after rotation from DisplayLayout.
+ mDisplayInfo.rotation = toRotation;
+ updateDisplayInfoIfNeeded();
+ }
+
+ /**
* Updates the display info, calculating and returning the new stack and movement bounds in the
* new orientation of the device if necessary.
*
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
index fc41d2ea8862..65ea887259be 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
@@ -44,6 +44,7 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf
private final float[] mTmpFloat9 = new float[9];
private final RectF mTmpSourceRectF = new RectF();
private final RectF mTmpDestinationRectF = new RectF();
+ private final Rect mTmpDestinationRect = new Rect();
@Inject
public PipSurfaceTransactionHelper(Context context, ConfigurationController configController) {
@@ -90,7 +91,30 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf
mTmpDestinationRectF.set(destinationBounds);
mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
- .setPosition(leash, destinationBounds.left, destinationBounds.top);
+ .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top);
+ return this;
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, Rect destinationBounds, Rect insets) {
+ mTmpSourceRectF.set(sourceBounds);
+ mTmpDestinationRect.set(sourceBounds);
+ mTmpDestinationRect.inset(insets);
+ // Scale by the shortest edge and offset such that the top/left of the scaled inset source
+ // rect aligns with the top/left of the destination bounds
+ final float scale = sourceBounds.width() <= sourceBounds.height()
+ ? (float) destinationBounds.width() / sourceBounds.width()
+ : (float) destinationBounds.height() / sourceBounds.height();
+ final float left = destinationBounds.left - insets.left * scale;
+ final float top = destinationBounds.top - insets.top * scale;
+ mTmpTransform.setScale(scale, scale);
+ tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+ .setWindowCrop(leash, mTmpDestinationRect)
+ .setPosition(leash, left, top);
return this;
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index c8a1ca02fdfb..0141dee04086 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -143,8 +143,10 @@ public class PipTaskOrganizer extends TaskOrganizer implements
case MSG_RESIZE_ANIMATE: {
Rect currentBounds = (Rect) args.arg2;
Rect toBounds = (Rect) args.arg3;
+ Rect sourceHintRect = (Rect) args.arg4;
int duration = args.argi2;
- animateResizePip(currentBounds, toBounds, args.argi1 /* direction */, duration);
+ animateResizePip(currentBounds, toBounds, sourceHintRect,
+ args.argi1 /* direction */, duration);
if (updateBoundsCallback != null) {
updateBoundsCallback.accept(toBounds);
}
@@ -307,7 +309,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements
public void onTransactionReady(int id, SurfaceControl.Transaction t) {
t.apply();
scheduleAnimateResizePip(mLastReportedBounds, destinationBounds,
- direction, animationDurationMs, null /* updateBoundsCallback */);
+ null /* sourceHintRect */, direction, animationDurationMs,
+ null /* updateBoundsCallback */);
mInPip = false;
}
});
@@ -380,7 +383,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
- scheduleAnimateResizePip(currentBounds, destinationBounds,
+ final Rect sourceHintRect = getValidSourceHintRect(info, currentBounds);
+ scheduleAnimateResizePip(currentBounds, destinationBounds, sourceHintRect,
TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration,
null /* updateBoundsCallback */);
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
@@ -391,6 +395,21 @@ public class PipTaskOrganizer extends TaskOrganizer implements
}
}
+ /**
+ * Returns the source hint rect if it is valid (if provided and is contained by the current
+ * task bounds).
+ */
+ private Rect getValidSourceHintRect(ActivityManager.RunningTaskInfo info, Rect sourceBounds) {
+ final Rect sourceHintRect = info.pictureInPictureParams != null
+ && info.pictureInPictureParams.hasSourceBoundsHint()
+ ? info.pictureInPictureParams.getSourceRectHint()
+ : null;
+ if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) {
+ return sourceHintRect;
+ }
+ return null;
+ }
+
private void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) {
// If we are fading the PIP in, then we should move the pip to the final location as
// soon as possible, but set the alpha immediately since the transaction can take a
@@ -611,13 +630,13 @@ public class PipTaskOrganizer extends TaskOrganizer implements
Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred");
return;
}
- scheduleAnimateResizePip(mLastReportedBounds, toBounds,
+ scheduleAnimateResizePip(mLastReportedBounds, toBounds, null /* sourceHintRect */,
TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback);
}
private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds,
- @PipAnimationController.TransitionDirection int direction, int durationMs,
- Consumer<Rect> updateBoundsCallback) {
+ Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction,
+ int durationMs, Consumer<Rect> updateBoundsCallback) {
if (!mInPip) {
// TODO: tend to use shouldBlockResizeRequest here as well but need to consider
// the fact that when in exitPip, scheduleAnimateResizePip is executed in the window
@@ -629,6 +648,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements
args.arg1 = updateBoundsCallback;
args.arg2 = currentBounds;
args.arg3 = destinationBounds;
+ args.arg4 = sourceHintRect;
args.argi1 = direction;
args.argi2 = durationMs;
mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_ANIMATE, args));
@@ -732,7 +752,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements
}
final Rect destinationBounds = new Rect(originalBounds);
destinationBounds.offset(xOffset, yOffset);
- animateResizePip(originalBounds, destinationBounds, TRANSITION_DIRECTION_SAME, durationMs);
+ animateResizePip(originalBounds, destinationBounds, null /* sourceHintRect */,
+ TRANSITION_DIRECTION_SAME, durationMs);
}
private void resizePip(Rect destinationBounds) {
@@ -838,7 +859,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements
return WINDOWING_MODE_UNDEFINED;
}
- private void animateResizePip(Rect currentBounds, Rect destinationBounds,
+
+ private void animateResizePip(Rect currentBounds, Rect destinationBounds, Rect sourceHintRect,
@PipAnimationController.TransitionDirection int direction, int durationMs) {
if (Looper.myLooper() != mUpdateHandler.getLooper()) {
throw new RuntimeException("Callers should call scheduleAnimateResizePip() instead of "
@@ -850,7 +872,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements
return;
}
mPipAnimationController
- .getAnimator(mLeash, currentBounds, destinationBounds)
+ .getAnimator(mLeash, currentBounds, destinationBounds, sourceHintRect)
.setTransitionDirection(direction)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(durationMs)
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 40a86b78d3ad..7d35416a8d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -36,7 +36,6 @@ import android.util.Log;
import android.util.Pair;
import android.view.DisplayInfo;
import android.view.IPinnedStackController;
-import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
import com.android.systemui.Dependency;
@@ -96,7 +95,9 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
private final DisplayChangeController.OnDisplayChangingListener mRotationController = (
int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
if (!mPipTaskOrganizer.isInPip() || mPipTaskOrganizer.isDeferringEnterPipAnimation()) {
- // Skip if we aren't in PIP or haven't actually entered PIP yet
+ // Skip if we aren't in PIP or haven't actually entered PIP yet. We still need to update
+ // the display layout in the bounds handler in this case.
+ mPipBoundsHandler.onDisplayRotationChangedNotInPip(toRotation);
return;
}
// If there is an animation running (ie. from a shelf offset), then ensure that we calculate
@@ -174,7 +175,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
- if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode()
+ if (task.configuration.windowConfiguration.getWindowingMode()
!= WINDOWING_MODE_PINNED) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
index a4edacecfd91..1ca53f907994 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -55,7 +55,9 @@ import com.android.systemui.pip.PipBoundsHandler;
import com.android.systemui.pip.PipTaskOrganizer;
import com.android.systemui.util.DeviceConfigProxy;
+import java.io.PrintWriter;
import java.util.concurrent.Executor;
+import java.util.function.Function;
import java.util.function.Supplier;
/**
@@ -94,7 +96,7 @@ public class PipResizeGestureHandler {
private final Rect mTmpBottomLeftCorner = new Rect();
private final Rect mTmpBottomRightCorner = new Rect();
private final Rect mDisplayBounds = new Rect();
- private final Supplier<Rect> mMovementBoundsSupplier;
+ private final Function<Rect, Rect> mMovementBoundsSupplier;
private final Runnable mUpdateMovementBoundsRunnable;
private int mDelta;
@@ -113,7 +115,7 @@ public class PipResizeGestureHandler {
public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig,
- PipTaskOrganizer pipTaskOrganizer, Supplier<Rect> movementBoundsSupplier,
+ PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
Runnable updateMovementBoundsRunnable, SysUiState sysUiState) {
mContext = context;
mDisplayId = context.getDisplayId();
@@ -244,10 +246,15 @@ public class PipResizeGestureHandler {
return mTmpRegion.contains(x, y);
}
+ public boolean willStartResizeGesture(MotionEvent ev) {
+ return mEnableUserResize && isInValidSysUiState()
+ && isWithinTouchRegion((int) ev.getRawX(), (int) ev.getRawY());
+ }
+
private void setCtrlType(int x, int y) {
final Rect currentPipBounds = mMotionHelper.getBounds();
- Rect movementBounds = mMovementBoundsSupplier.get();
+ Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
mDisplayBounds.set(movementBounds.left,
movementBounds.top,
movementBounds.right + currentPipBounds.width(),
@@ -353,6 +360,16 @@ public class PipResizeGestureHandler {
mMinSize.set(minX, minY);
}
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mAllowGesture=" + mAllowGesture);
+ pw.println(innerPrefix + "mIsAttached=" + mIsAttached);
+ pw.println(innerPrefix + "mIsEnabled=" + mIsEnabled);
+ pw.println(innerPrefix + "mEnableUserResize=" + mEnableUserResize);
+ pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
+ }
+
class SysUiInputEventReceiver extends BatchedInputEventReceiver {
SysUiInputEventReceiver(InputChannel channel, Looper looper) {
super(channel, looper, Choreographer.getSfInstance());
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 3e06c1eb1ca3..b6e4e1628c20 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -644,12 +644,12 @@ public class PipTouchHandler {
}
MotionEvent ev = (MotionEvent) inputEvent;
- if (!mTouchState.isDragging()
- && !mMagnetizedPip.getObjectStuckToTarget()
- && !mMotionHelper.isAnimating()
- && mPipResizeGestureHandler.isWithinTouchRegion(
- (int) ev.getRawX(), (int) ev.getRawY())) {
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN
+ && mPipResizeGestureHandler.willStartResizeGesture(ev)) {
+ // Initialize the touch state for the gesture, but immediately reset to invalidate the
+ // gesture
mTouchState.onTouchEvent(ev);
+ mTouchState.reset();
return true;
}
@@ -1032,8 +1032,11 @@ public class PipTouchHandler {
isMenuExpanded && willResizeMenu() ? mExpandedShortestEdgeSize : 0);
}
- private Rect getMovementBounds() {
- return mMovementBounds;
+ private Rect getMovementBounds(Rect curBounds) {
+ Rect movementBounds = new Rect();
+ mSnapAlgorithm.getMovementBounds(curBounds, mInsetBounds,
+ movementBounds, mIsImeShowing ? mImeHeight : 0);
+ return movementBounds;
}
/**
@@ -1065,6 +1068,9 @@ public class PipTouchHandler {
pw.println(innerPrefix + "mMovementBoundsExtraOffsets=" + mMovementBoundsExtraOffsets);
mTouchState.dump(pw, innerPrefix);
mMotionHelper.dump(pw, innerPrefix);
+ if (mPipResizeGestureHandler != null) {
+ mPipResizeGestureHandler.dump(pw, innerPrefix);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 628223630af7..4b2c27321035 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -716,7 +716,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
@Override
public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
boolean clearedTask, boolean wasVisible) {
- if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode()
+ if (task.configuration.windowConfiguration.getWindowingMode()
!= WINDOWING_MODE_PINNED) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 5628a24f40ef..739d30c2a707 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -100,12 +100,7 @@ public class NotificationMediaManager implements Dumpable {
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_STOPPED);
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_PAUSED);
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
- }
- private static final HashSet<Integer> INACTIVE_MEDIA_STATES = new HashSet<>();
- static {
- INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_NONE);
- INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_STOPPED);
- INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
+ PAUSED_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING);
}
private final NotificationEntryManager mEntryManager;
@@ -262,15 +257,6 @@ public class NotificationMediaManager implements Dumpable {
return !PAUSED_MEDIA_STATES.contains(state);
}
- /**
- * Check if a state should be considered active (playing or paused)
- * @param state a PlaybackState
- * @return true if active
- */
- public static boolean isActiveState(int state) {
- return !INACTIVE_MEDIA_STATES.contains(state);
- }
-
public void setUpWithPresenter(NotificationPresenter presenter) {
mPresenter = presenter;
}
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 b7f317b38743..c63781cb110a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -64,6 +64,7 @@ private const val DEVICE_NAME = "DEVICE_NAME"
private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
+private const val USER_ID = 0
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -180,7 +181,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindWhenUnattached() {
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, null, null, device, true, null)
player.bind(state)
assertThat(player.isPlaying()).isFalse()
@@ -189,7 +190,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindText() {
player.attach(holder)
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
player.bind(state)
assertThat(appName.getText()).isEqualTo(APP)
@@ -200,7 +201,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindBackgroundColor() {
player.attach(holder)
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
player.bind(state)
val list = ArgumentCaptor.forClass(ColorStateList::class.java)
@@ -211,7 +212,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindDevice() {
player.attach(holder)
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
player.bind(state)
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
@@ -223,7 +224,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
seamless.id = 1
seamlessFallback.id = 2
player.attach(holder)
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
player.bind(state)
verify(expandedSet).setVisibility(seamless.id, View.GONE)
@@ -235,7 +236,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindNullDevice() {
player.attach(holder)
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ 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)
assertThat(seamless.isEnabled()).isTrue()
@@ -246,7 +247,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindDeviceResumptionPlayer() {
player.attach(holder)
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null,
resumption = true)
player.bind(state)
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 9fdd9ad744ff..5d4693d3ccf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -52,6 +52,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
private static final String ARTIST = "ARTIST";
private static final String TITLE = "TITLE";
private static final String DEVICE_NAME = "DEVICE_NAME";
+ private static final int USER_ID = 0;
private MediaDataCombineLatest mManager;
@@ -78,7 +79,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
mManager.addListener(mListener);
- mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null,
+ mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, false,
KEY, false);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
new file mode 100644
index 000000000000..afb64a7649b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -0,0 +1,216 @@
+/*
+ * 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.media
+
+import android.graphics.Color
+import androidx.test.filters.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
+
+private const val KEY = "TEST_KEY"
+private const val KEY_ALT = "TEST_KEY_2"
+private const val USER_MAIN = 0
+private const val USER_GUEST = 10
+private const val APP = "APP"
+private const val BG_COLOR = Color.RED
+private const val PACKAGE = "PKG"
+private const val ARTIST = "ARTIST"
+private const val TITLE = "TITLE"
+private const val DEVICE_NAME = "DEVICE_NAME"
+
+private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+private fun <T> any(): T = Mockito.any()
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class MediaDataFilterTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var combineLatest: MediaDataCombineLatest
+ @Mock
+ private lateinit var listener: MediaDataManager.Listener
+ @Mock
+ private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock
+ private lateinit var mediaResumeListener: MediaResumeListener
+ @Mock
+ private lateinit var mediaDataManager: MediaDataManager
+ @Mock
+ private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
+ @Mock
+ private lateinit var executor: Executor
+
+ private lateinit var mediaDataFilter: MediaDataFilter
+ private lateinit var dataMain: MediaData
+ private lateinit var dataGuest: MediaData
+ private val device = MediaDeviceData(true, null, DEVICE_NAME)
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mediaDataFilter = MediaDataFilter(combineLatest, broadcastDispatcher, mediaResumeListener,
+ mediaDataManager, lockscreenUserManager, executor)
+ mediaDataFilter.addListener(listener)
+
+ // Start all tests as main user
+ setUser(USER_MAIN)
+
+ // Set up test media data
+ dataMain = MediaData(USER_MAIN, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ emptyList(), PACKAGE, null, null, device, true, null)
+
+ dataGuest = MediaData(USER_GUEST, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
+ emptyList(), emptyList(), PACKAGE, null, null, device, true, null)
+ }
+
+ private fun setUser(id: Int) {
+ `when`(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
+ `when`(lockscreenUserManager.isCurrentProfile(eq(id))).thenReturn(true)
+ mediaDataFilter.handleUserSwitched(id)
+ }
+
+ @Test
+ fun testOnDataLoadedForCurrentUser_callsListener() {
+ // GIVEN a media for main user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+
+ // THEN we should tell the listener
+ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain))
+ }
+
+ @Test
+ fun testOnDataLoadedForGuest_doesNotCallListener() {
+ // GIVEN a media for guest user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
+
+ // THEN we should NOT tell the listener
+ verify(listener, never()).onMediaDataLoaded(any(), any(), any())
+ }
+
+ @Test
+ fun testOnRemovedForCurrent_callsListener() {
+ // GIVEN a media was removed for main user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ mediaDataFilter.onMediaDataRemoved(KEY)
+
+ // THEN we should tell the listener
+ verify(listener).onMediaDataRemoved(eq(KEY))
+ }
+
+ @Test
+ fun testOnRemovedForGuest_doesNotCallListener() {
+ // GIVEN a media was removed for guest user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
+ mediaDataFilter.onMediaDataRemoved(KEY)
+
+ // THEN we should NOT tell the listener
+ verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ }
+
+ @Test
+ fun testOnUserSwitched_removesOldUserControls() {
+ // GIVEN that we have a media loaded for main user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+
+ // and we switch to guest user
+ setUser(USER_GUEST)
+
+ // THEN we should remove the main user's media
+ verify(listener).onMediaDataRemoved(eq(KEY))
+ }
+
+ @Test
+ fun testOnUserSwitched_addsNewUserControls() {
+ // GIVEN that we had some media for both users
+ val dataMap = mapOf(KEY to dataMain, KEY_ALT to dataGuest)
+ `when`(combineLatest.getData()).thenReturn(dataMap)
+
+ // and we switch to guest user
+ setUser(USER_GUEST)
+
+ // THEN we should add back the guest user media
+ verify(listener).onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest))
+
+ // but not the main user's
+ verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), eq(dataMain))
+ }
+
+ @Test
+ fun testHasAnyMedia() {
+ assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
+
+ mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
+ assertThat(mediaDataFilter.hasAnyMedia()).isTrue()
+ }
+
+ @Test
+ fun testHasActiveMedia() {
+ assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
+ val data = dataMain.copy(active = true)
+
+ mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
+ assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
+ }
+
+ @Test
+ fun testHasAnyMedia_onlyCurrentUser() {
+ assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
+
+ mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataGuest)
+ assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
+ }
+
+ @Test
+ fun testHasActiveMedia_onlyCurrentUser() {
+ assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
+ val data = dataGuest.copy(active = true)
+
+ mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
+ assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
+ }
+
+ @Test
+ fun testOnNotificationRemoved_doesntHaveMedia() {
+ mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
+ mediaDataFilter.onMediaDataRemoved(KEY)
+ assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
+ }
+
+ @Test
+ fun testOnSwipeToDismiss_setsTimedOut() {
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ mediaDataFilter.onSwipeToDismiss()
+
+ verify(mediaDataManager).setTimedOut(eq(KEY), eq(true))
+ }
+} \ No newline at end of file
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 e56bbabfdc0b..6761b282b26a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -35,6 +35,7 @@ private const val PACKAGE_NAME = "com.android.systemui"
private const val APP_NAME = "SystemUI"
private const val SESSION_ARTIST = "artist"
private const val SESSION_TITLE = "title"
+private const val USER_ID = 0
private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
@@ -91,28 +92,15 @@ class MediaDataManagerTest : SysuiTestCase() {
}
@Test
- fun testHasActiveMedia() {
- assertThat(mediaDataManager.hasActiveMedia()).isFalse()
- val data = mock(MediaData::class.java)
-
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
- assertThat(mediaDataManager.hasActiveMedia()).isFalse()
-
- whenever(data.active).thenReturn(true)
- assertThat(mediaDataManager.hasActiveMedia()).isTrue()
- }
-
- @Test
- fun testOnSwipeToDismiss_deactivatesMedia() {
- val data = MediaData(initialized = true, backgroundColor = 0, app = null, appIcon = null,
- artist = null, song = null, artwork = null, actions = emptyList(),
+ fun testSetTimedOut_deactivatesMedia() {
+ val data = MediaData(userId = USER_ID, initialized = true, backgroundColor = 0, app = null,
+ appIcon = null, artist = null, song = null, artwork = null, actions = emptyList(),
actionsToShowInCompact = emptyList(), packageName = "INVALID", token = null,
clickIntent = null, device = null, active = true, resumeAction = null)
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
- mediaDataManager.onSwipeToDismiss()
+ mediaDataManager.setTimedOut(KEY, timedOut = true)
assertThat(data.active).isFalse()
}
@@ -141,37 +129,6 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
assertThat(listener.data!!.active).isTrue()
-
- // Swiping away makes the notification not active
- mediaDataManager.onSwipeToDismiss()
- assertThat(mediaDataManager.hasActiveMedia()).isFalse()
-
- // And when a notification is updated
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-
- // MediaData should still be inactive
- assertThat(mediaDataManager.hasActiveMedia()).isFalse()
- }
-
- @Test
- fun testHasAnyMedia_whenAddingMedia() {
- assertThat(mediaDataManager.hasAnyMedia()).isFalse()
- val data = mock(MediaData::class.java)
-
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
- assertThat(mediaDataManager.hasAnyMedia()).isTrue()
- }
-
- @Test
- fun testOnNotificationRemoved_doesntHaveMedia() {
- val data = mock(MediaData::class.java)
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
- mediaDataManager.onNotificationRemoved(KEY)
- assertThat(mediaDataManager.hasAnyMedia()).isFalse()
}
@Test
@@ -212,8 +169,8 @@ class MediaDataManagerTest : SysuiTestCase() {
setTitle(SESSION_TITLE)
build()
}
- mediaDataManager.addResumptionControls(desc, Runnable {}, session.sessionToken, APP_NAME,
- pendingIntent, PACKAGE_NAME)
+ mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
+ APP_NAME, pendingIntent, PACKAGE_NAME)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
// THEN the media data indicates that it is for resumption
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 6c7f2e8d7925..fc22eeb3ea68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -58,6 +58,7 @@ private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
private const val DEVICE_NAME = "DEVICE_NAME"
+private const val USER_ID = 0
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
@@ -118,7 +119,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
setSmallIcon(android.R.drawable.ic_media_pause)
setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken()))
}
- mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null,
+ mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
device = null, active = true, resumeAction = null)
}
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 916fd0fe11b7..7a8e4f7e9b85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -32,7 +32,9 @@ import org.junit.Test
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
import org.mockito.Mockito
@@ -48,6 +50,7 @@ private const val PACKAGE = "PKG"
private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
+private const val USER_ID = 0
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
private fun <T> anyObject(): T {
@@ -93,7 +96,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
setPlaybackState(playbackBuilder.build())
}
session.setActive(true)
- mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null,
+ mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
device = null, active = true, resumeAction = null)
}
@@ -118,6 +121,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
verify(executor).executeDelayed(capture(timeoutCaptor), anyLong())
+ verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
}
@Test
@@ -133,6 +137,24 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
}
@Test
+ fun testOnMediaDataLoaded_migratesKeys() {
+ // From not playing
+ mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+ clearInvocations(mediaController)
+
+ // To playing
+ val playingState = mock(android.media.session.PlaybackState::class.java)
+ `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
+ `when`(mediaController.playbackState).thenReturn(playingState)
+ mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData)
+ verify(mediaController).unregisterCallback(anyObject())
+ verify(mediaController).registerCallback(anyObject())
+
+ // Enqueues callback
+ verify(executor).execute(anyObject())
+ }
+
+ @Test
fun testOnPlaybackStateChanged_schedulesTimeout_whenPaused() {
// Assuming we're registered
testOnMediaDataLoaded_registersPlaybackListener()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
index b7a2633d0d36..536cae4380c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
@@ -82,7 +82,7 @@ public class PipAnimationControllerTest extends SysuiTestCase {
@Test
public void getAnimator_withBounds_returnBoundsAnimator() {
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, new Rect(), new Rect());
+ .getAnimator(mLeash, new Rect(), new Rect(), null);
assertEquals("Expect ANIM_TYPE_BOUNDS animation",
animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS);
@@ -94,12 +94,12 @@ public class PipAnimationControllerTest extends SysuiTestCase {
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue1);
+ .getAnimator(mLeash, startValue, endValue1, null);
oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
oldAnimator.start();
final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue2);
+ .getAnimator(mLeash, startValue, endValue2, null);
assertEquals("getAnimator with same type returns same animator",
oldAnimator, newAnimator);
@@ -129,7 +129,7 @@ public class PipAnimationControllerTest extends SysuiTestCase {
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue1);
+ .getAnimator(mLeash, startValue, endValue1, null);
animator.updateEndValue(endValue2);
@@ -141,7 +141,7 @@ public class PipAnimationControllerTest extends SysuiTestCase {
final Rect startValue = new Rect(0, 0, 100, 100);
final Rect endValue = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue);
+ .getAnimator(mLeash, startValue, endValue, null);
animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
animator.setPipAnimationCallback(mPipAnimationCallback);
diff --git a/packages/Tethering/jarjar-rules.txt b/packages/Tethering/jarjar-rules.txt
index 2d3108a0bff1..591861f5b837 100644
--- a/packages/Tethering/jarjar-rules.txt
+++ b/packages/Tethering/jarjar-rules.txt
@@ -3,7 +3,7 @@
# If there are files in that filegroup that are not covered below, the classes in the
# module will be overwritten by the ones in the framework.
rule com.android.internal.util.** com.android.networkstack.tethering.util.@1
-rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
+rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1
rule android.net.shared.Inet4AddressUtils* com.android.networkstack.tethering.shared.Inet4AddressUtils@1
diff --git a/packages/Tethering/tests/unit/jarjar-rules.txt b/packages/Tethering/tests/unit/jarjar-rules.txt
index 1ea56cdf1a3d..ec2d2b02004e 100644
--- a/packages/Tethering/tests/unit/jarjar-rules.txt
+++ b/packages/Tethering/tests/unit/jarjar-rules.txt
@@ -8,4 +8,4 @@ rule com.android.internal.util.State* com.android.networkstack.tethering.util.St
rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1
-rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
+rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index b5c173c91a53..6eab0221b7ab 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1597,7 +1597,8 @@ public class AppOpsService extends IAppOpsService.Stub {
packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
packageUpdateFilter.addDataScheme("package");
- mContext.registerReceiver(mOnPackageUpdatedReceiver, packageUpdateFilter);
+ mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
+ packageUpdateFilter, null, null);
synchronized (this) {
for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
@@ -1640,7 +1641,7 @@ public class AppOpsService extends IAppOpsService.Stub {
final IntentFilter packageSuspendFilter = new IntentFilter();
packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
- mContext.registerReceiver(new BroadcastReceiver() {
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
@@ -1664,7 +1665,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
}
- }, packageSuspendFilter);
+ }, UserHandle.ALL, packageSuspendFilter, null, null);
final IntentFilter packageAddedFilter = new IntentFilter();
packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 25bbfa02fa05..3a4dfaf9bfcd 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -45,6 +45,7 @@ import com.android.internal.R;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -318,16 +319,6 @@ class BluetoothRouteProvider {
btRoute.route = builder.build();
}
- private void clearActiveRoutes() {
- if (DEBUG) {
- Log.d(TAG, "Clearing active routes");
- }
- for (BluetoothRouteInfo btRoute : mActiveRoutes) {
- setRouteConnectionState(btRoute, STATE_DISCONNECTED);
- }
- mActiveRoutes.clear();
- }
-
private void addActiveRoute(BluetoothRouteInfo btRoute) {
if (DEBUG) {
Log.d(TAG, "Adding active route: " + btRoute.route);
@@ -348,18 +339,34 @@ class BluetoothRouteProvider {
}
}
- private void findAndSetActiveHearingAidDevices() {
+ private void clearActiveRoutesWithType(int type) {
if (DEBUG) {
- Log.d(TAG, "Setting active hearing aid devices");
+ Log.d(TAG, "Clearing active routes with type. type=" + type);
+ }
+ Iterator<BluetoothRouteInfo> iter = mActiveRoutes.iterator();
+ while (iter.hasNext()) {
+ BluetoothRouteInfo btRoute = iter.next();
+ if (btRoute.route.getType() == type) {
+ iter.remove();
+ setRouteConnectionState(btRoute, STATE_DISCONNECTED);
+ }
}
+ }
- BluetoothHearingAid hearingAidProfile = mHearingAidProfile;
- if (hearingAidProfile == null) {
- return;
+ private void addActiveHearingAidDevices(BluetoothDevice device) {
+ if (DEBUG) {
+ Log.d(TAG, "Setting active hearing aid devices. device=" + device);
}
- List<BluetoothDevice> activeDevices = hearingAidProfile.getActiveDevices();
+
+ // Let the given device be the first active device
+ BluetoothRouteInfo activeBtRoute = mBluetoothRoutes.get(device.getAddress());
+ addActiveRoute(activeBtRoute);
+
+ // A bluetooth route with the same route ID should be added.
for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
- if (activeDevices.contains(btRoute.btDevice)) {
+ if (TextUtils.equals(btRoute.route.getId(), activeBtRoute.route.getId())
+ && !TextUtils.equals(btRoute.btDevice.getAddress(),
+ activeBtRoute.btDevice.getAddress())) {
addActiveRoute(btRoute);
}
}
@@ -465,16 +472,16 @@ class BluetoothRouteProvider {
public void onReceive(Context context, Intent intent, BluetoothDevice device) {
switch (intent.getAction()) {
case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
- clearActiveRoutes();
+ clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
if (device != null) {
addActiveRoute(mBluetoothRoutes.get(device.getAddress()));
}
notifyBluetoothRoutesUpdated();
break;
case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
- clearActiveDevices();
+ clearActiveRoutesWithType(MediaRoute2Info.TYPE_HEARING_AID);
if (device != null) {
- findAndSetActiveHearingAidDevices();
+ addActiveHearingAidDevices(device);
}
notifyBluetoothRoutesUpdated();
break;
diff --git a/services/core/java/com/android/server/textclassifier/IconsContentProvider.java b/services/core/java/com/android/server/textclassifier/IconsContentProvider.java
index 9b3176d9df67..183e920e4620 100644
--- a/services/core/java/com/android/server/textclassifier/IconsContentProvider.java
+++ b/services/core/java/com/android/server/textclassifier/IconsContentProvider.java
@@ -27,6 +27,7 @@ import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.os.UserHandle;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.textclassifier.IconsUriHelper.ResourceInfo;
@@ -34,6 +35,7 @@ import com.android.server.textclassifier.IconsUriHelper.ResourceInfo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.Arrays;
/**
* A content provider that is used to access icons returned from the TextClassifier service.
@@ -46,32 +48,40 @@ import java.io.OutputStream;
public final class IconsContentProvider extends ContentProvider {
private static final String TAG = "IconsContentProvider";
+ private static final String MIME_TYPE = "image/png";
+
+ private final PipeDataWriter<Pair<ResourceInfo, Integer>> mWriter =
+ (writeSide, uri, mimeType, bundle, args) -> {
+ try (OutputStream out = new AutoCloseOutputStream(writeSide)) {
+ final ResourceInfo res = args.first;
+ final int userId = args.second;
+ final Drawable drawable = Icon.createWithResource(res.packageName, res.id)
+ .loadDrawableAsUser(getContext(), userId);
+ getBitmap(drawable).compress(Bitmap.CompressFormat.PNG, 100, out);
+ } catch (Exception e) {
+ Log.e(TAG, "Error retrieving icon for uri: " + uri, e);
+ }
+ };
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) {
+ final ResourceInfo res = IconsUriHelper.getInstance().getResourceInfo(uri);
+ if (res == null) {
+ Log.e(TAG, "No icon found for uri: " + uri);
+ return null;
+ }
+
try {
- final ResourceInfo res = IconsUriHelper.getInstance().getResourceInfo(uri);
- final Drawable drawable = Icon.createWithResource(res.packageName, res.id)
- .loadDrawableAsUser(getContext(), UserHandle.getCallingUserId());
- final byte[] data = getBitmapData(drawable);
- final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
- final ParcelFileDescriptor readSide = pipe[0];
- final ParcelFileDescriptor writeSide = pipe[1];
- try (OutputStream out = new AutoCloseOutputStream(writeSide)) {
- out.write(data);
- return readSide;
- }
- } catch (IOException | RuntimeException e) {
- Log.e(TAG, "Error retrieving icon for uri: " + uri, e);
+ final Pair<ResourceInfo, Integer> args = new Pair(res, UserHandle.getCallingUserId());
+ return openPipeHelper(uri, MIME_TYPE, /* bundle= */ null, args, mWriter);
+ } catch (IOException e) {
+ Log.e(TAG, "Error opening pipe helper for icon at uri: " + uri, e);
}
+
return null;
}
- /**
- * Returns the bitmap data for the specified drawable.
- */
- @VisibleForTesting
- public static byte[] getBitmapData(Drawable drawable) {
+ private static Bitmap getBitmap(Drawable drawable) {
if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
throw new IllegalStateException("The icon is zero-sized");
}
@@ -85,16 +95,24 @@ public final class IconsContentProvider extends ContentProvider {
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
- final ByteArrayOutputStream stream = new ByteArrayOutputStream();
- bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
- final byte[] byteArray = stream.toByteArray();
- bitmap.recycle();
- return byteArray;
+ return bitmap;
+ }
+
+ /**
+ * Returns true if the drawables are considered the same.
+ */
+ @VisibleForTesting
+ public static boolean sameIcon(Drawable one, Drawable two) {
+ final ByteArrayOutputStream stream1 = new ByteArrayOutputStream();
+ getBitmap(one).compress(Bitmap.CompressFormat.PNG, 100, stream1);
+ final ByteArrayOutputStream stream2 = new ByteArrayOutputStream();
+ getBitmap(two).compress(Bitmap.CompressFormat.PNG, 100, stream2);
+ return Arrays.equals(stream1.toByteArray(), stream2.toByteArray());
}
@Override
public String getType(Uri uri) {
- return "image/png";
+ return MIME_TYPE;
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 407b9fcbca74..6bfcf0c75b83 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -1504,12 +1504,21 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
}
void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason) {
- task.removeTaskActivitiesLocked(reason);
- cleanUpRemovedTaskLocked(task, killProcess, removeFromRecents);
- mService.getLockTaskController().clearLockedTask(task);
- mService.getTaskChangeNotificationController().notifyTaskStackChanged();
- if (task.isPersistable) {
- mService.notifyTaskPersisterLocked(null, true);
+ if (task.mInRemoveTask) {
+ // Prevent recursion.
+ return;
+ }
+ task.mInRemoveTask = true;
+ try {
+ task.performClearTask(reason);
+ cleanUpRemovedTaskLocked(task, killProcess, removeFromRecents);
+ mService.getLockTaskController().clearLockedTask(task);
+ mService.getTaskChangeNotificationController().notifyTaskStackChanged();
+ if (task.isPersistable) {
+ mService.notifyTaskPersisterLocked(null, true);
+ }
+ } finally {
+ task.mInRemoveTask = false;
}
}
@@ -2177,7 +2186,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
// split-screen in split-screen.
mService.getTaskChangeNotificationController()
.notifyActivityDismissingDockedStack();
- taskDisplayArea.onSplitScreenModeDismissed(task.getStack());
+ taskDisplayArea.onSplitScreenModeDismissed((ActivityStack) task);
taskDisplayArea.mDisplayContent.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
true /* notifyClients */);
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 8734b5efa45d..3e88566449fe 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -19,10 +19,13 @@ package com.android.server.wm;
import static android.os.Process.myPid;
import static android.os.Process.myUid;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS;
@@ -477,12 +480,11 @@ final class InputMonitor {
mService.getRecentsAnimationController();
final boolean shouldApplyRecentsInputConsumer = recentsAnimationController != null
&& recentsAnimationController.shouldApplyInputConsumer(w.mActivityRecord);
- if (inputChannel == null || inputWindowHandle == null || w.mRemoved
- || (w.cantReceiveTouchInput() && !shouldApplyRecentsInputConsumer)) {
+ if (inputWindowHandle == null || w.mRemoved) {
if (w.mWinAnimator.hasSurface()) {
mInputTransaction.setInputWindowInfo(
- w.mWinAnimator.mSurfaceController.getClientViewRootSurface(),
- mInvalidInputWindow);
+ w.mWinAnimator.mSurfaceController.getClientViewRootSurface(),
+ mInvalidInputWindow);
}
// Skip this window because it cannot possibly receive input.
return;
@@ -491,9 +493,23 @@ final class InputMonitor {
final int flags = w.mAttrs.flags;
final int privateFlags = w.mAttrs.privateFlags;
final int type = w.mAttrs.type;
- final boolean hasFocus = w.isFocused();
final boolean isVisible = w.isVisibleLw();
+ // Assign an InputInfo with type to the overlay window which can't receive input event.
+ // This is used to omit Surfaces from occlusion detection.
+ if (inputChannel == null
+ || (w.cantReceiveTouchInput() && !shouldApplyRecentsInputConsumer)) {
+ if (!w.mWinAnimator.hasSurface()) {
+ return;
+ }
+ populateOverlayInputInfo(inputWindowHandle, w.getName(), type, isVisible);
+ mInputTransaction.setInputWindowInfo(
+ w.mWinAnimator.mSurfaceController.getClientViewRootSurface(),
+ inputWindowHandle);
+ return;
+ }
+
+ final boolean hasFocus = w.isFocused();
if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
if (recentsAnimationController.updateInputConsumerForApp(
mRecentsAnimationInputConsumer.mWindowHandle, hasFocus)) {
@@ -555,6 +571,28 @@ final class InputMonitor {
}
}
+ // This would reset InputWindowHandle fields to prevent it could be found by input event.
+ // We need to check if any new field of InputWindowHandle could impact the result.
+ private static void populateOverlayInputInfo(final InputWindowHandle inputWindowHandle,
+ final String name, final int type, final boolean isVisible) {
+ inputWindowHandle.name = name;
+ inputWindowHandle.layoutParamsType = type;
+ inputWindowHandle.dispatchingTimeoutNanos =
+ WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+ inputWindowHandle.visible = isVisible;
+ inputWindowHandle.canReceiveKeys = false;
+ inputWindowHandle.hasFocus = false;
+ inputWindowHandle.ownerPid = myPid();
+ inputWindowHandle.ownerUid = myUid();
+ inputWindowHandle.inputFeatures = INPUT_FEATURE_NO_INPUT_CHANNEL;
+ inputWindowHandle.scaleFactor = 1;
+ inputWindowHandle.layoutParamsFlags =
+ FLAG_NOT_TOUCH_MODAL | FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE;
+ inputWindowHandle.portalToDisplayId = INVALID_DISPLAY;
+ inputWindowHandle.touchableRegion.setEmpty();
+ inputWindowHandle.setTouchableRegionCrop(null);
+ }
+
/**
* Helper function to generate an InputInfo with type SECURE_SYSTEM_OVERLAY. This input
* info will not have an input channel or be touchable, but is used to omit Surfaces
@@ -564,16 +602,7 @@ final class InputMonitor {
static void setTrustedOverlayInputInfo(SurfaceControl sc, SurfaceControl.Transaction t,
int displayId, String name) {
InputWindowHandle inputWindowHandle = new InputWindowHandle(null, displayId);
- inputWindowHandle.name = name;
- inputWindowHandle.layoutParamsType = TYPE_SECURE_SYSTEM_OVERLAY;
- inputWindowHandle.dispatchingTimeoutNanos = -1;
- inputWindowHandle.visible = true;
- inputWindowHandle.canReceiveKeys = false;
- inputWindowHandle.hasFocus = false;
- inputWindowHandle.ownerPid = myPid();
- inputWindowHandle.ownerUid = myUid();
- inputWindowHandle.inputFeatures = INPUT_FEATURE_NO_INPUT_CHANNEL;
- inputWindowHandle.scaleFactor = 1;
+ populateOverlayInputInfo(inputWindowHandle, name, TYPE_SECURE_SYSTEM_OVERLAY, true);
t.setInputWindowInfo(sc, inputWindowHandle);
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 748244e1a5c2..c664a841fc1f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -435,6 +435,13 @@ class Task extends WindowContainer<WindowContainer> {
static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
private int mForceHiddenFlags = 0;
+ // TODO(b/160201781): Revisit double invocation issue in Task#removeChild.
+ /**
+ * Skip {@link ActivityStackSupervisor#removeTask(Task, boolean, boolean, String)} execution if
+ * {@code true} to prevent double traversal of {@link #mChildren} in a loop.
+ */
+ boolean mInRemoveTask;
+
// When non-null, this is a transaction that will get applied on the next frame returned after
// a relayout is requested from the client. While this is only valid on a leaf task; since the
// transaction can effect an ancestor task, this also needs to keep track of the ancestor task
@@ -1496,11 +1503,8 @@ class Task extends WindowContainer<WindowContainer> {
return autoRemoveRecents || (!hasChild() && !getHasBeenVisible());
}
- /**
- * Completely remove all activities associated with an existing
- * task starting at a specified index.
- */
- private void performClearTaskAtIndexLocked(String reason) {
+ /** Completely remove all activities associated with an existing task. */
+ void performClearTask(String reason) {
// Broken down into to cases to avoid object create due to capturing mStack.
if (getStack() == null) {
forAllActivities((r) -> {
@@ -1524,7 +1528,7 @@ class Task extends WindowContainer<WindowContainer> {
*/
void performClearTaskLocked() {
mReuseTask = true;
- performClearTaskAtIndexLocked("clear-task-all");
+ performClearTask("clear-task-all");
mReuseTask = false;
}
@@ -1585,11 +1589,6 @@ class Task extends WindowContainer<WindowContainer> {
return false;
}
- void removeTaskActivitiesLocked(String reason) {
- // Just remove the entire task.
- performClearTaskAtIndexLocked(reason);
- }
-
String lockTaskAuthToString() {
switch (mLockTaskAuth) {
case LOCK_TASK_AUTH_DONT_LOCK: return "LOCK_TASK_AUTH_DONT_LOCK";
diff --git a/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java b/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java
index 72580a3b98c2..a787c321fc66 100644
--- a/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java
@@ -50,8 +50,7 @@ public final class IconsContentProviderTest {
final Drawable actual = Icon.createWithContentUri(uri).loadDrawable(context);
assertThat(actual).isNotNull();
- assertThat(IconsContentProvider.getBitmapData(actual))
- .isEqualTo(IconsContentProvider.getBitmapData(expected));
+ assertThat(IconsContentProvider.sameIcon(actual, expected)).isTrue();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 1415c506a1c9..9d88ada5a90c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -28,6 +28,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.clearInvocations;
import android.graphics.Point;
@@ -158,4 +160,30 @@ public class TaskTests extends WindowTestsBase {
assertEquals(activity1, task1.isInTask(activity1));
assertNull(task1.isInTask(activity2));
}
+
+ @Test
+ public void testRemoveChildForOverlayTask() {
+ final Task task = createTaskStackOnDisplay(mDisplayContent);
+ final int taskId = task.mTaskId;
+ final ActivityRecord activity1 =
+ WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+ final ActivityRecord activity2 =
+ WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+ final ActivityRecord activity3 =
+ WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+ activity1.setTaskOverlay(true);
+ activity2.setTaskOverlay(true);
+ activity3.setTaskOverlay(true);
+
+ assertEquals(3, task.getChildCount());
+ assertTrue(task.onlyHasTaskOverlayActivities(true));
+
+ task.removeChild(activity1);
+
+ verify(task.mStackSupervisor).removeTask(any(), anyBoolean(), anyBoolean(), anyString());
+ assertEquals(2, task.getChildCount());
+ task.forAllActivities((r) -> {
+ assertTrue(r.finishing);
+ });
+ }
}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
index 42b0c608822e..314e95229d29 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
@@ -61,11 +61,12 @@ public class NetworkStagedRollbackTest {
private static final TestApp NETWORK_STACK = new TestApp("NetworkStack",
getNetworkStackPackageName(), -1, false, findNetworkStackApk());
- private static File findNetworkStackApk() {
+ private static File[] findNetworkStackApk() {
for (String name : NETWORK_STACK_APK_NAMES) {
final File apk = new File("/system/priv-app/" + name + "/" + name + ".apk");
if (apk.isFile()) {
- return apk;
+ final File dir = new File("/system/priv-app/" + name);
+ return dir.listFiles((d, f) -> f.startsWith(name));
}
}
throw new RuntimeException("Can't find NetworkStackApk");