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 android.app.Activity; 22 import android.app.AlertDialog; 23 import android.content.DialogInterface; 24 import android.content.Intent; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.PackageManager; 27 import android.graphics.Typeface; 28 import android.media.projection.IMediaProjection; 29 import android.media.projection.IMediaProjectionManager; 30 import android.media.projection.MediaProjectionManager; 31 import android.os.Bundle; 32 import android.os.IBinder; 33 import android.os.RemoteException; 34 import android.os.ServiceManager; 35 import android.text.BidiFormatter; 36 import android.text.SpannableString; 37 import android.text.TextPaint; 38 import android.text.TextUtils; 39 import android.text.style.StyleSpan; 40 import android.util.Log; 41 import android.view.View; 42 import android.view.Window; 43 import android.view.WindowManager; 44 import android.widget.TextView; 45 46 import com.android.systemui.R; 47 import com.android.systemui.util.Utils; 48 49 public class MediaProjectionPermissionActivity extends Activity 50 implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener { 51 private static final String TAG = "MediaProjectionPermissionActivity"; 52 private static final float MAX_APP_NAME_SIZE_PX = 500f; 53 private static final String ELLIPSIS = "\u2026"; 54 55 private String mPackageName; 56 private int mUid; 57 private IMediaProjectionManager mService; 58 59 private AlertDialog mDialog; 60 61 @Override onCreate(Bundle icicle)62 public void onCreate(Bundle icicle) { 63 super.onCreate(icicle); 64 65 mPackageName = getCallingPackage(); 66 IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE); 67 mService = IMediaProjectionManager.Stub.asInterface(b); 68 69 if (mPackageName == null) { 70 finish(); 71 return; 72 } 73 74 PackageManager packageManager = getPackageManager(); 75 ApplicationInfo aInfo; 76 try { 77 aInfo = packageManager.getApplicationInfo(mPackageName, 0); 78 mUid = aInfo.uid; 79 } catch (PackageManager.NameNotFoundException e) { 80 Log.e(TAG, "unable to look up package name", e); 81 finish(); 82 return; 83 } 84 85 try { 86 if (mService.hasProjectionPermission(mUid, mPackageName)) { 87 setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName)); 88 finish(); 89 return; 90 } 91 } catch (RemoteException e) { 92 Log.e(TAG, "Error checking projection permissions", e); 93 finish(); 94 return; 95 } 96 97 TextPaint paint = new TextPaint(); 98 paint.setTextSize(42); 99 100 CharSequence dialogText = null; 101 if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) { 102 dialogText = getString(R.string.media_projection_dialog_service_text); 103 } else { 104 String label = aInfo.loadLabel(packageManager).toString(); 105 106 // If the label contains new line characters it may push the security 107 // message below the fold of the dialog. Labels shouldn't have new line 108 // characters anyways, so just truncate the message the first time one 109 // is seen. 110 final int labelLength = label.length(); 111 int offset = 0; 112 while (offset < labelLength) { 113 final int codePoint = label.codePointAt(offset); 114 final int type = Character.getType(codePoint); 115 if (type == Character.LINE_SEPARATOR 116 || type == Character.CONTROL 117 || type == Character.PARAGRAPH_SEPARATOR) { 118 label = label.substring(0, offset) + ELLIPSIS; 119 break; 120 } 121 offset += Character.charCount(codePoint); 122 } 123 124 if (label.isEmpty()) { 125 label = mPackageName; 126 } 127 128 String unsanitizedAppName = TextUtils.ellipsize(label, 129 paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString(); 130 String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName); 131 132 String actionText = getString(R.string.media_projection_dialog_text, appName); 133 SpannableString message = new SpannableString(actionText); 134 135 int appNameIndex = actionText.indexOf(appName); 136 if (appNameIndex >= 0) { 137 message.setSpan(new StyleSpan(Typeface.BOLD), 138 appNameIndex, appNameIndex + appName.length(), 0); 139 } 140 dialogText = message; 141 } 142 143 String dialogTitle = getString(R.string.media_projection_dialog_title); 144 145 View dialogTitleView = View.inflate(this, R.layout.media_projection_dialog_title, null); 146 TextView titleText = (TextView) dialogTitleView.findViewById(R.id.dialog_title); 147 titleText.setText(dialogTitle); 148 149 mDialog = new AlertDialog.Builder(this) 150 .setCustomTitle(dialogTitleView) 151 .setMessage(dialogText) 152 .setPositiveButton(R.string.media_projection_action_text, this) 153 .setNegativeButton(android.R.string.cancel, this) 154 .setOnCancelListener(this) 155 .create(); 156 157 mDialog.create(); 158 mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true); 159 160 final Window w = mDialog.getWindow(); 161 w.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 162 w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 163 164 mDialog.show(); 165 } 166 167 @Override onDestroy()168 protected void onDestroy() { 169 super.onDestroy(); 170 if (mDialog != null) { 171 mDialog.dismiss(); 172 } 173 } 174 175 @Override onClick(DialogInterface dialog, int which)176 public void onClick(DialogInterface dialog, int which) { 177 try { 178 if (which == AlertDialog.BUTTON_POSITIVE) { 179 setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName)); 180 } 181 } catch (RemoteException e) { 182 Log.e(TAG, "Error granting projection permission", e); 183 setResult(RESULT_CANCELED); 184 } finally { 185 if (mDialog != null) { 186 mDialog.dismiss(); 187 } 188 finish(); 189 } 190 } 191 getMediaProjectionIntent(int uid, String packageName)192 private Intent getMediaProjectionIntent(int uid, String packageName) 193 throws RemoteException { 194 IMediaProjection projection = mService.createProjection(uid, packageName, 195 MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */); 196 Intent intent = new Intent(); 197 intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder()); 198 return intent; 199 } 200 201 @Override onCancel(DialogInterface dialog)202 public void onCancel(DialogInterface dialog) { 203 finish(); 204 } 205 } 206