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.media
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 
28 /**
29  * Helper class that handles the media projection service related actions. It simplifies invoking
30  * the MediaProjectionManagerService and updating the permission consent.
31  */
32 class MediaProjectionServiceHelper {
33     companion object {
34         private const val TAG = "MediaProjectionServiceHelper"
35         private val service =
36             IMediaProjectionManager.Stub.asInterface(
37                 ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)
38             )
39 
40         @JvmStatic
41         @Throws(RemoteException::class)
42         fun hasProjectionPermission(uid: Int, packageName: String) =
43             service.hasProjectionPermission(uid, packageName)
44 
45         @JvmStatic
46         @Throws(RemoteException::class)
47         fun createOrReuseProjection(
48             uid: Int,
49             packageName: String,
50             reviewGrantedConsentRequired: Boolean
51         ): IMediaProjection {
52             val existingProjection =
53                 if (reviewGrantedConsentRequired) service.getProjection(uid, packageName) else null
54             return existingProjection
55                 ?: service.createProjection(
56                     uid,
57                     packageName,
58                     MediaProjectionManager.TYPE_SCREEN_CAPTURE,
59                     false /* permanentGrant */
60                 )
61         }
62 
63         /**
64          * This method is called when a host app reuses the consent token. If the token is being
65          * used more than once, ask the user to review their consent and send the reviewed result.
66          *
67          * @param consentResult consent result to update
68          * @param reviewGrantedConsentRequired if user must review already-granted consent that the
69          *   host app is attempting to reuse
70          * @param projection projection token associated with the consent result, or null if the
71          *   result is for cancelling.
72          */
73         @JvmStatic
74         fun setReviewedConsentIfNeeded(
75             @ReviewGrantedConsentResult consentResult: Int,
76             reviewGrantedConsentRequired: Boolean,
77             projection: IMediaProjection?
78         ) {
79             // Only send the result to the server, when the user needed to review the re-used
80             // consent token.
81             if (
82                 reviewGrantedConsentRequired && consentResult != ReviewGrantedConsentResult.UNKNOWN
83             ) {
84                 try {
85                     service.setUserReviewGrantedConsentResult(consentResult, projection)
86                 } catch (e: RemoteException) {
87                     // If we are unable to pass back the result, capture continues with blank frames
88                     Log.e(TAG, "Unable to set required consent result for token re-use", e)
89                 }
90             }
91         }
92     }
93 }
94