• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
20 
21 import static com.android.systemui.screenrecord.ScreenShareOptionKt.ENTIRE_SCREEN;
22 import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
23 
24 import android.app.Activity;
25 import android.app.ActivityManager;
26 import android.app.AlertDialog;
27 import android.content.DialogInterface;
28 import android.content.Intent;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.PackageManager;
31 import android.graphics.Typeface;
32 import android.media.projection.IMediaProjection;
33 import android.media.projection.IMediaProjectionManager;
34 import android.media.projection.MediaProjectionManager;
35 import android.os.Bundle;
36 import android.os.IBinder;
37 import android.os.RemoteException;
38 import android.os.ServiceManager;
39 import android.os.UserHandle;
40 import android.text.BidiFormatter;
41 import android.text.SpannableString;
42 import android.text.TextPaint;
43 import android.text.TextUtils;
44 import android.text.style.StyleSpan;
45 import android.util.Log;
46 import android.view.Window;
47 
48 import com.android.systemui.R;
49 import com.android.systemui.flags.FeatureFlags;
50 import com.android.systemui.flags.Flags;
51 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
52 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
53 import com.android.systemui.screenrecord.MediaProjectionPermissionDialog;
54 import com.android.systemui.screenrecord.ScreenShareOption;
55 import com.android.systemui.statusbar.phone.SystemUIDialog;
56 import com.android.systemui.util.Utils;
57 
58 import javax.inject.Inject;
59 
60 import dagger.Lazy;
61 
62 public class MediaProjectionPermissionActivity extends Activity
63         implements DialogInterface.OnClickListener {
64     private static final String TAG = "MediaProjectionPermissionActivity";
65     private static final float MAX_APP_NAME_SIZE_PX = 500f;
66     private static final String ELLIPSIS = "\u2026";
67 
68     private final FeatureFlags mFeatureFlags;
69     private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
70 
71     private String mPackageName;
72     private int mUid;
73     private IMediaProjectionManager mService;
74 
75     private AlertDialog mDialog;
76 
77     @Inject
MediaProjectionPermissionActivity(FeatureFlags featureFlags, Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver)78     public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
79             Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
80         mFeatureFlags = featureFlags;
81         mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
82     }
83 
84     @Override
onCreate(Bundle icicle)85     public void onCreate(Bundle icicle) {
86         super.onCreate(icicle);
87 
88         mPackageName = getCallingPackage();
89         IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
90         mService = IMediaProjectionManager.Stub.asInterface(b);
91 
92         if (mPackageName == null) {
93             finish();
94             return;
95         }
96 
97         PackageManager packageManager = getPackageManager();
98         ApplicationInfo aInfo;
99         try {
100             aInfo = packageManager.getApplicationInfo(mPackageName, 0);
101             mUid = aInfo.uid;
102         } catch (PackageManager.NameNotFoundException e) {
103             Log.e(TAG, "unable to look up package name", e);
104             finish();
105             return;
106         }
107 
108         try {
109             if (mService.hasProjectionPermission(mUid, mPackageName)) {
110                 setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
111                 finish();
112                 return;
113             }
114         } catch (RemoteException e) {
115             Log.e(TAG, "Error checking projection permissions", e);
116             finish();
117             return;
118         }
119 
120         if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
121             if (showScreenCaptureDisabledDialogIfNeeded()) {
122                 return;
123             }
124         }
125 
126         TextPaint paint = new TextPaint();
127         paint.setTextSize(42);
128 
129         CharSequence dialogText = null;
130         CharSequence dialogTitle = null;
131         String appName = null;
132         if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
133             dialogText = getString(R.string.media_projection_dialog_service_text);
134             dialogTitle = getString(R.string.media_projection_dialog_service_title);
135         } else {
136             String label = aInfo.loadLabel(packageManager).toString();
137 
138             // If the label contains new line characters it may push the security
139             // message below the fold of the dialog. Labels shouldn't have new line
140             // characters anyways, so just truncate the message the first time one
141             // is seen.
142             final int labelLength = label.length();
143             int offset = 0;
144             while (offset < labelLength) {
145                 final int codePoint = label.codePointAt(offset);
146                 final int type = Character.getType(codePoint);
147                 if (type == Character.LINE_SEPARATOR
148                         || type == Character.CONTROL
149                         || type == Character.PARAGRAPH_SEPARATOR) {
150                     label = label.substring(0, offset) + ELLIPSIS;
151                     break;
152                 }
153                 offset += Character.charCount(codePoint);
154             }
155 
156             if (label.isEmpty()) {
157                 label = mPackageName;
158             }
159 
160             String unsanitizedAppName = TextUtils.ellipsize(label,
161                     paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
162             appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
163 
164             String actionText = getString(R.string.media_projection_dialog_text, appName);
165             SpannableString message = new SpannableString(actionText);
166 
167             int appNameIndex = actionText.indexOf(appName);
168             if (appNameIndex >= 0) {
169                 message.setSpan(new StyleSpan(Typeface.BOLD),
170                         appNameIndex, appNameIndex + appName.length(), 0);
171             }
172             dialogText = message;
173             dialogTitle = getString(R.string.media_projection_dialog_title, appName);
174         }
175 
176         if (isPartialScreenSharingEnabled()) {
177             mDialog = new MediaProjectionPermissionDialog(this, () -> {
178                 ScreenShareOption selectedOption =
179                         ((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption();
180                 grantMediaProjectionPermission(selectedOption.getMode());
181             }, appName);
182         } else {
183             AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
184                     R.style.Theme_SystemUI_Dialog)
185                     .setTitle(dialogTitle)
186                     .setIcon(R.drawable.ic_media_projection_permission)
187                     .setMessage(dialogText)
188                     .setPositiveButton(R.string.media_projection_action_text, this)
189                     .setNeutralButton(android.R.string.cancel, this);
190             mDialog = dialogBuilder.create();
191         }
192 
193         setUpDialog(mDialog);
194 
195         mDialog.show();
196     }
197 
198     @Override
onDestroy()199     protected void onDestroy() {
200         super.onDestroy();
201         if (mDialog != null) {
202             mDialog.dismiss();
203         }
204     }
205 
206     @Override
onClick(DialogInterface dialog, int which)207     public void onClick(DialogInterface dialog, int which) {
208         if (which == AlertDialog.BUTTON_POSITIVE) {
209             grantMediaProjectionPermission(ENTIRE_SCREEN);
210         }
211     }
212 
setUpDialog(AlertDialog dialog)213     private void setUpDialog(AlertDialog dialog) {
214         SystemUIDialog.registerDismissListener(dialog);
215         SystemUIDialog.applyFlags(dialog);
216         SystemUIDialog.setDialogSize(dialog);
217 
218         dialog.setOnCancelListener(this::onDialogDismissedOrCancelled);
219         dialog.setOnDismissListener(this::onDialogDismissedOrCancelled);
220         dialog.create();
221         dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
222 
223         final Window w = dialog.getWindow();
224         w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
225     }
226 
showScreenCaptureDisabledDialogIfNeeded()227     private boolean showScreenCaptureDisabledDialogIfNeeded() {
228         final UserHandle hostUserHandle = getHostUserHandle();
229         if (mScreenCaptureDevicePolicyResolver.get()
230                 .isScreenCaptureCompletelyDisabled(hostUserHandle)) {
231             AlertDialog dialog = new ScreenCaptureDisabledDialog(this);
232             setUpDialog(dialog);
233             dialog.show();
234             return true;
235         }
236 
237         return false;
238     }
239 
grantMediaProjectionPermission(int screenShareMode)240     private void grantMediaProjectionPermission(int screenShareMode) {
241         try {
242             if (screenShareMode == ENTIRE_SCREEN) {
243                 setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
244             }
245             if (isPartialScreenSharingEnabled() && screenShareMode == SINGLE_APP) {
246                 IMediaProjection projection = createProjection(mUid, mPackageName);
247                 final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
248                 intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
249                         projection.asBinder());
250                 intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
251                         getHostUserHandle());
252                 intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
253 
254                 // Start activity from the current foreground user to avoid creating a separate
255                 // SystemUI process without access to recent tasks because it won't have
256                 // WM Shell running inside.
257                 startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
258             }
259         } catch (RemoteException e) {
260             Log.e(TAG, "Error granting projection permission", e);
261             setResult(RESULT_CANCELED);
262         } finally {
263             if (mDialog != null) {
264                 mDialog.dismiss();
265             }
266             finish();
267         }
268     }
269 
getHostUserHandle()270     private UserHandle getHostUserHandle() {
271         return UserHandle.getUserHandleForUid(getLaunchedFromUid());
272     }
273 
createProjection(int uid, String packageName)274     private IMediaProjection createProjection(int uid, String packageName) throws RemoteException {
275         return mService.createProjection(uid, packageName,
276                 MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
277     }
278 
getMediaProjectionIntent(int uid, String packageName)279     private Intent getMediaProjectionIntent(int uid, String packageName)
280             throws RemoteException {
281         IMediaProjection projection = createProjection(uid, packageName);
282         Intent intent = new Intent();
283         intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
284         return intent;
285     }
286 
onDialogDismissedOrCancelled(DialogInterface dialogInterface)287     private void onDialogDismissedOrCancelled(DialogInterface dialogInterface) {
288         if (!isFinishing()) {
289             finish();
290         }
291     }
292 
isPartialScreenSharingEnabled()293     private boolean isPartialScreenSharingEnabled() {
294         return mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING);
295     }
296 }
297