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 CharSequence dialogTitle = null; 102 if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) { 103 dialogText = getString(R.string.media_projection_dialog_service_text); 104 dialogTitle = getString(R.string.media_projection_dialog_service_title); 105 } else { 106 String label = aInfo.loadLabel(packageManager).toString(); 107 108 // If the label contains new line characters it may push the security 109 // message below the fold of the dialog. Labels shouldn't have new line 110 // characters anyways, so just truncate the message the first time one 111 // is seen. 112 final int labelLength = label.length(); 113 int offset = 0; 114 while (offset < labelLength) { 115 final int codePoint = label.codePointAt(offset); 116 final int type = Character.getType(codePoint); 117 if (type == Character.LINE_SEPARATOR 118 || type == Character.CONTROL 119 || type == Character.PARAGRAPH_SEPARATOR) { 120 label = label.substring(0, offset) + ELLIPSIS; 121 break; 122 } 123 offset += Character.charCount(codePoint); 124 } 125 126 if (label.isEmpty()) { 127 label = mPackageName; 128 } 129 130 String unsanitizedAppName = TextUtils.ellipsize(label, 131 paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString(); 132 String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName); 133 134 String actionText = getString(R.string.media_projection_dialog_text, appName); 135 SpannableString message = new SpannableString(actionText); 136 137 int appNameIndex = actionText.indexOf(appName); 138 if (appNameIndex >= 0) { 139 message.setSpan(new StyleSpan(Typeface.BOLD), 140 appNameIndex, appNameIndex + appName.length(), 0); 141 } 142 dialogText = message; 143 dialogTitle = getString(R.string.media_projection_dialog_title, appName); 144 } 145 146 View dialogTitleView = View.inflate(this, R.layout.media_projection_dialog_title, null); 147 TextView titleText = (TextView) dialogTitleView.findViewById(R.id.dialog_title); 148 titleText.setText(dialogTitle); 149 150 mDialog = new AlertDialog.Builder(this) 151 .setCustomTitle(dialogTitleView) 152 .setMessage(dialogText) 153 .setPositiveButton(R.string.media_projection_action_text, this) 154 .setNegativeButton(android.R.string.cancel, this) 155 .setOnCancelListener(this) 156 .create(); 157 158 mDialog.create(); 159 mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true); 160 161 final Window w = mDialog.getWindow(); 162 w.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 163 w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 164 165 mDialog.show(); 166 } 167 168 @Override onDestroy()169 protected void onDestroy() { 170 super.onDestroy(); 171 if (mDialog != null) { 172 mDialog.dismiss(); 173 } 174 } 175 176 @Override onClick(DialogInterface dialog, int which)177 public void onClick(DialogInterface dialog, int which) { 178 try { 179 if (which == AlertDialog.BUTTON_POSITIVE) { 180 setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName)); 181 } 182 } catch (RemoteException e) { 183 Log.e(TAG, "Error granting projection permission", e); 184 setResult(RESULT_CANCELED); 185 } finally { 186 if (mDialog != null) { 187 mDialog.dismiss(); 188 } 189 finish(); 190 } 191 } 192 getMediaProjectionIntent(int uid, String packageName)193 private Intent getMediaProjectionIntent(int uid, String packageName) 194 throws RemoteException { 195 IMediaProjection projection = mService.createProjection(uid, packageName, 196 MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */); 197 Intent intent = new Intent(); 198 intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder()); 199 return intent; 200 } 201 202 @Override onCancel(DialogInterface dialog)203 public void onCancel(DialogInterface dialog) { 204 finish(); 205 } 206 } 207