• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.wm.shell.common.pip
17 
18 import android.app.AppOpsManager
19 import android.content.ComponentName
20 import android.content.Context
21 import android.content.pm.PackageManager
22 import android.util.Pair
23 import com.android.internal.annotations.VisibleForTesting
24 import com.android.wm.shell.common.ShellExecutor
25 
26 class PipAppOpsListener(
27     private val mContext: Context,
28     private val mMainExecutor: ShellExecutor
29 ) {
30     private val mAppOpsManager: AppOpsManager = checkNotNull(
31         mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager)
32     private var mTopPipActivityInfoSupplier: (Context) -> Pair<ComponentName?, Int> =
33         PipUtils::getTopPipActivity
34     private val mAppOpsChangedListener = AppOpsManager.OnOpChangedListener { _, packageName ->
35         try {
36             // Dismiss the PiP once the user disables the app ops setting for that package
37             val topPipActivityInfo = mTopPipActivityInfoSupplier.invoke(mContext)
38             val componentName = topPipActivityInfo.first ?: return@OnOpChangedListener
39             val userId = topPipActivityInfo.second
40             val appInfo = mContext.packageManager
41                 .getApplicationInfoAsUser(packageName, 0, userId)
42             if (appInfo.packageName == componentName.packageName &&
43                 mAppOpsManager.checkOpNoThrow(
44                     AppOpsManager.OP_PICTURE_IN_PICTURE, appInfo.uid,
45                     packageName
46                 ) != AppOpsManager.MODE_ALLOWED
47             ) {
48                 mCallback?.let {
49                     mMainExecutor.execute { it.dismissPip() }
50                 }
51             }
52         } catch (e: PackageManager.NameNotFoundException) {
53             // Unregister the listener if the package can't be found
54             unregisterAppOpsListener()
55         }
56     }
57 
58     private var mCallback: Callback? = null
59 
60     fun setCallback(callback: Callback) {
61         mCallback = callback
62     }
63 
64     fun onActivityPinned(packageName: String) {
65         // Register for changes to the app ops setting for this package while it is in PiP
66         registerAppOpsListener(packageName)
67     }
68 
69     fun onActivityUnpinned() {
70         // Unregister for changes to the previously PiP'ed package
71         unregisterAppOpsListener()
72     }
73 
74     private fun registerAppOpsListener(packageName: String) {
75         mAppOpsManager.startWatchingMode(
76             AppOpsManager.OP_PICTURE_IN_PICTURE, packageName,
77             mAppOpsChangedListener
78         )
79     }
80 
81     private fun unregisterAppOpsListener() {
82         mAppOpsManager.stopWatchingMode(mAppOpsChangedListener)
83     }
84 
85     /** Callback for PipAppOpsListener to request changes to the PIP window.  */
86     interface Callback {
87         /** Dismisses the PIP window.  */
88         fun dismissPip()
89     }
90 
91     @VisibleForTesting
92     fun setTopPipActivityInfoSupplier(supplier: (Context) -> Pair<ComponentName?, Int>) {
93         mTopPipActivityInfoSupplier = supplier
94     }
95 }