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 
17 package com.android.systemui.mediaprojection
18 
19 import android.content.Context
20 import android.media.projection.IMediaProjection
21 import android.media.projection.IMediaProjectionManager
22 import android.media.projection.MediaProjectionManager
23 import android.media.projection.ReviewGrantedConsentResult
24 import android.os.RemoteException
25 import android.os.ServiceManager
26 import android.util.Log
27 import android.window.WindowContainerToken
28 import javax.inject.Inject
29 
30 /**
31  * Helper class that handles the media projection service related actions. It simplifies invoking
32  * the MediaProjectionManagerService and updating the permission consent.
33  */
34 class MediaProjectionServiceHelper @Inject constructor() {
35     companion object {
36         private const val TAG = "MediaProjectionServiceHelper"
37         private val service =
38             IMediaProjectionManager.Stub.asInterface(
39                 ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)
40             )
41 
42         @JvmStatic
43         @Throws(RemoteException::class)
hasProjectionPermissionnull44         fun hasProjectionPermission(uid: Int, packageName: String) =
45             service.hasProjectionPermission(uid, packageName)
46 
47         @JvmStatic
48         @Throws(RemoteException::class)
49         fun createOrReuseProjection(
50             uid: Int,
51             packageName: String,
52             reviewGrantedConsentRequired: Boolean
53         ): IMediaProjection {
54             val existingProjection =
55                 if (reviewGrantedConsentRequired) service.getProjection(uid, packageName) else null
56             return existingProjection
57                 ?: service.createProjection(
58                     uid,
59                     packageName,
60                     MediaProjectionManager.TYPE_SCREEN_CAPTURE,
61                     false /* permanentGrant */
62                 )
63         }
64 
65         /**
66          * This method is called when a host app reuses the consent token. If the token is being
67          * used more than once, ask the user to review their consent and send the reviewed result.
68          *
69          * @param consentResult consent result to update
70          * @param reviewGrantedConsentRequired if user must review already-granted consent that the
71          *   host app is attempting to reuse
72          * @param projection projection token associated with the consent result, or null if the
73          *   result is for cancelling.
74          */
75         @JvmStatic
setReviewedConsentIfNeedednull76         fun setReviewedConsentIfNeeded(
77             @ReviewGrantedConsentResult consentResult: Int,
78             reviewGrantedConsentRequired: Boolean,
79             projection: IMediaProjection?
80         ) {
81             // Only send the result to the server, when the user needed to review the re-used
82             // consent token.
83             if (
84                 reviewGrantedConsentRequired && consentResult != ReviewGrantedConsentResult.UNKNOWN
85             ) {
86                 try {
87                     service.setUserReviewGrantedConsentResult(consentResult, projection)
88                 } catch (e: RemoteException) {
89                     // If we are unable to pass back the result, capture continues with blank frames
90                     Log.e(TAG, "Unable to set required consent result for token re-use", e)
91                 }
92             }
93         }
94     }
95 
96     /** Updates the projected task to the task that has a matching [WindowContainerToken]. */
updateTaskRecordingSessionnull97     fun updateTaskRecordingSession(token: WindowContainerToken): Boolean {
98         return try {
99             true
100             // TODO: actually call the service once it is implemented
101             // service.updateTaskRecordingSession(token)
102         } catch (e: RemoteException) {
103             Log.e(TAG, "Unable to updateTaskRecordingSession", e)
104             false
105         }
106     }
107 }
108