1 /*
2  * 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.Context
20 import android.content.pm.PackageManager
21 import com.android.wm.shell.common.ShellExecutor
22 
23 class PipAppOpsListener(
24     private val mContext: Context,
25     private val mCallback: Callback,
26     private val mMainExecutor: ShellExecutor
27 ) {
28     private val mAppOpsManager: AppOpsManager = checkNotNull(
29         mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager)
30     private val mAppOpsChangedListener = AppOpsManager.OnOpChangedListener { _, packageName ->
31         try {
32             // Dismiss the PiP once the user disables the app ops setting for that package
33             val topPipActivityInfo = PipUtils.getTopPipActivity(mContext)
34             val componentName = topPipActivityInfo.first ?: return@OnOpChangedListener
35             val userId = topPipActivityInfo.second
36             val appInfo = mContext.packageManager
37                 .getApplicationInfoAsUser(packageName, 0, userId)
38             if (appInfo.packageName == componentName.packageName &&
39                 mAppOpsManager.checkOpNoThrow(
40                     AppOpsManager.OP_PICTURE_IN_PICTURE, appInfo.uid,
41                     packageName
42                 ) != AppOpsManager.MODE_ALLOWED
43             ) {
44                 mMainExecutor.execute { mCallback.dismissPip() }
45             }
46         } catch (e: PackageManager.NameNotFoundException) {
47             // Unregister the listener if the package can't be found
48             unregisterAppOpsListener()
49         }
50     }
51 
52     fun onActivityPinned(packageName: String) {
53         // Register for changes to the app ops setting for this package while it is in PiP
54         registerAppOpsListener(packageName)
55     }
56 
57     fun onActivityUnpinned() {
58         // Unregister for changes to the previously PiP'ed package
59         unregisterAppOpsListener()
60     }
61 
62     private fun registerAppOpsListener(packageName: String) {
63         mAppOpsManager.startWatchingMode(
64             AppOpsManager.OP_PICTURE_IN_PICTURE, packageName,
65             mAppOpsChangedListener
66         )
67     }
68 
69     private fun unregisterAppOpsListener() {
70         mAppOpsManager.stopWatchingMode(mAppOpsChangedListener)
71     }
72 
73     /** Callback for PipAppOpsListener to request changes to the PIP window.  */
74     interface Callback {
75         /** Dismisses the PIP window.  */
76         fun dismissPip()
77     }
78 }