2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui.media;
19 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
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.Window;
42 import android.view.WindowManager;
43 import android.widget.CheckBox;
44 import android.widget.CompoundButton;
46 import com.android.systemui.R;
48 public class MediaProjectionPermissionActivity extends Activity
49 implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener,
50 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";
55 private boolean mPermanentGrant;
56 private String mPackageName;
58 private IMediaProjectionManager mService;
60 private AlertDialog mDialog;
63 public void onCreate(Bundle icicle) {
64 super.onCreate(icicle);
66 mPackageName = getCallingPackage();
67 IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
68 mService = IMediaProjectionManager.Stub.asInterface(b);
70 if (mPackageName == null) {
75 PackageManager packageManager = getPackageManager();
76 ApplicationInfo aInfo;
78 aInfo = packageManager.getApplicationInfo(mPackageName, 0);
80 } catch (PackageManager.NameNotFoundException e) {
81 Log.e(TAG, "unable to look up package name", e);
87 if (mService.hasProjectionPermission(mUid, mPackageName)) {
88 setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName,
89 false /*permanentGrant*/));
93 } catch (RemoteException e) {
94 Log.e(TAG, "Error checking projection permissions", e);
99 TextPaint paint = new TextPaint();
100 paint.setTextSize(42);
102 String label = aInfo.loadLabel(packageManager).toString();
104 // If the label contains new line characters it may push the security
105 // message below the fold of the dialog. Labels shouldn't have new line
106 // characters anyways, so just truncate the message the first time one
108 final int labelLength = label.length();
110 while (offset < labelLength) {
111 final int codePoint = label.codePointAt(offset);
112 final int type = Character.getType(codePoint);
113 if (type == Character.LINE_SEPARATOR
114 || type == Character.CONTROL
115 || type == Character.PARAGRAPH_SEPARATOR) {
116 label = label.substring(0, offset) + ELLIPSIS;
119 offset += Character.charCount(codePoint);
122 if (label.isEmpty()) {
123 label = mPackageName;
126 String unsanitizedAppName = TextUtils.ellipsize(label,
127 paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
128 String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
130 String actionText = getString(R.string.media_projection_dialog_text, appName);
131 SpannableString message = new SpannableString(actionText);
133 int appNameIndex = actionText.indexOf(appName);
134 if (appNameIndex >= 0) {
135 message.setSpan(new StyleSpan(Typeface.BOLD),
136 appNameIndex, appNameIndex + appName.length(), 0);
139 mDialog = new AlertDialog.Builder(this)
140 .setIcon(aInfo.loadIcon(packageManager))
142 .setPositiveButton(R.string.media_projection_action_text, this)
143 .setNegativeButton(android.R.string.cancel, this)
144 .setView(R.layout.remember_permission_checkbox)
145 .setOnCancelListener(this)
149 mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
151 ((CheckBox) mDialog.findViewById(R.id.remember)).setOnCheckedChangeListener(this);
152 final Window w = mDialog.getWindow();
153 w.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
154 w.addPrivateFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
160 protected void onDestroy() {
162 if (mDialog != null) {
168 public void onClick(DialogInterface dialog, int which) {
170 if (which == AlertDialog.BUTTON_POSITIVE) {
171 setResult(RESULT_OK, getMediaProjectionIntent(
172 mUid, mPackageName, mPermanentGrant));
174 } catch (RemoteException e) {
175 Log.e(TAG, "Error granting projection permission", e);
176 setResult(RESULT_CANCELED);
178 if (mDialog != null) {
186 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
187 mPermanentGrant = isChecked;
190 private Intent getMediaProjectionIntent(int uid, String packageName, boolean permanentGrant)
191 throws RemoteException {
192 IMediaProjection projection = mService.createProjection(uid, packageName,
193 MediaProjectionManager.TYPE_SCREEN_CAPTURE, permanentGrant);
194 Intent intent = new Intent();
195 intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
200 public void onCancel(DialogInterface dialog) {