1 /* 2 * Copyright (C) 2018 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.permissioncontroller.incident; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.content.DialogInterface; 22 import android.content.DialogInterface.OnClickListener; 23 import android.content.DialogInterface.OnDismissListener; 24 import android.content.res.Resources; 25 import android.graphics.drawable.Drawable; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.os.IncidentManager; 29 import android.provider.Settings; 30 import android.text.Spannable; 31 import android.text.SpannableString; 32 import android.text.style.BulletSpan; 33 import android.util.Log; 34 import android.view.View; 35 import android.view.Window; 36 import android.view.WindowManager; 37 import android.widget.ImageView; 38 import android.widget.LinearLayout; 39 import android.widget.TextView; 40 41 import com.android.modules.utils.build.SdkLevel; 42 import com.android.permissioncontroller.R; 43 44 import java.util.ArrayList; 45 46 /** 47 * Confirmation dialog for approving an incident or bug report for sharing off the device. 48 */ 49 public class ConfirmationActivity extends Activity implements OnClickListener, OnDismissListener { 50 private static final String TAG = "ConfirmationActivity"; 51 52 /** 53 * Currently displaying activity. 54 */ 55 private static ConfirmationActivity sCurrentActivity; 56 57 /** 58 * Currently displaying uri. 59 */ 60 private static Uri sCurrentUri; 61 62 /** 63 * If this activity is running in the current process, call finish() on it. 64 */ finishCurrent()65 public static void finishCurrent() { 66 if (sCurrentActivity != null) { 67 sCurrentActivity.finish(); 68 } 69 } 70 71 /** 72 * If the activity is in the resumed state, then record the Uri for the current 73 * one, so PendingList can skip re-showing the same one. 74 */ getCurrentUri()75 public static Uri getCurrentUri() { 76 return sCurrentUri; 77 } 78 79 /** 80 * Create the activity. 81 */ 82 @Override onCreate(Bundle savedInstanceState)83 protected void onCreate(Bundle savedInstanceState) { 84 super.onCreate(savedInstanceState); 85 86 final Formatting formatting = new Formatting(this); 87 88 final Uri uri = getIntent().getData(); 89 Log.d(TAG, "uri=" + uri); 90 if (uri == null) { 91 Log.w(TAG, "No uri in intent: " + getIntent()); 92 finish(); 93 return; 94 } 95 96 final IncidentManager.PendingReport pending = new IncidentManager.PendingReport(uri); 97 final String appLabel = formatting.getAppLabel(pending.getRequestingPackage()); 98 99 final Resources res = getResources(); 100 101 ReportDetails details; 102 try { 103 details = ReportDetails.parseIncidentReport(this, uri); 104 } catch (ReportDetails.ParseException ex) { 105 Log.w("Rejecting report because it couldn't be parsed", ex); 106 // If there was an error in the input we will just summarily reject the upload, 107 // since we can't get proper approval. (Zero-length images or reasons means that 108 // we will proceed with the imageless consent dialog). 109 final IncidentManager incidentManager = getSystemService(IncidentManager.class); 110 incidentManager.denyReport(getIntent().getData()); 111 112 // Show a message to the user saying... nevermind. 113 new AlertDialog.Builder(this) 114 .setTitle(R.string.incident_report_dialog_title) 115 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 116 @Override 117 public void onClick(DialogInterface dialog, int which) { 118 finish(); 119 } 120 }) 121 .setMessage(getString(R.string.incident_report_error_dialog_text, appLabel)) 122 .setOnDismissListener(this) 123 .show(); 124 return; 125 126 } 127 128 final View content = getLayoutInflater().inflate(R.layout.incident_confirmation, 129 null); 130 131 final ArrayList<String> reasons = details.getReasons(); 132 final int reasonsSize = reasons.size(); 133 if (reasonsSize > 0) { 134 content.findViewById(R.id.reasonIntro).setVisibility(View.VISIBLE); 135 136 final TextView reasonTextView = (TextView) content.findViewById(R.id.reasons); 137 reasonTextView.setVisibility(View.VISIBLE); 138 139 final int bulletSize = 140 (int) (res.getDimension(R.dimen.incident_reason_bullet_size) + 0.5f); 141 final int bulletIndent = 142 (int) (res.getDimension(R.dimen.incident_reason_bullet_indent) + 0.5f); 143 final int bulletColor = 144 getColor(R.color.incident_reason_bullet_color); 145 146 final StringBuilder text = new StringBuilder(); 147 for (int i = 0; i < reasonsSize; i++) { 148 text.append(reasons.get(i)); 149 if (i != reasonsSize - 1) { 150 text.append("\n"); 151 } 152 } 153 final SpannableString spannable = new SpannableString(text.toString()); 154 int spanStart = 0; 155 for (int i = 0; i < reasonsSize; i++) { 156 final int length = reasons.get(i).length(); 157 spannable.setSpan(new BulletSpan(bulletIndent, bulletColor, bulletSize), 158 spanStart, spanStart + length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 159 spanStart += length + 1; 160 } 161 162 reasonTextView.setText(spannable); 163 } 164 165 final String message = getString(R.string.incident_report_dialog_text, 166 appLabel, 167 formatting.getDate(pending.getTimestamp()), 168 formatting.getTime(pending.getTimestamp()), 169 appLabel); 170 ((TextView) content.findViewById(R.id.message)).setText(message); 171 172 final ArrayList<Drawable> images = details.getImages(); 173 final int imagesSize = images.size(); 174 if (imagesSize > 0) { 175 content.findViewById(R.id.imageScrollView).setVisibility(View.VISIBLE); 176 177 final LinearLayout imageList = (LinearLayout) content.findViewById(R.id.imageList); 178 179 final int width = res.getDimensionPixelSize(R.dimen.incident_image_width); 180 final int height = res.getDimensionPixelSize(R.dimen.incident_image_height); 181 182 for (int i = 0; i < imagesSize; i++) { 183 final Drawable drawable = images.get(i); 184 final ImageView imageView = new ImageView(this); 185 imageView.setImageDrawable(images.get(i)); 186 imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 187 188 imageList.addView(imageView, new LinearLayout.LayoutParams(width, height)); 189 } 190 } 191 192 final AlertDialog dialog = new AlertDialog.Builder(this) 193 .setTitle(R.string.incident_report_dialog_title) 194 .setPositiveButton(R.string.incident_report_dialog_allow_label, this) 195 .setNegativeButton(R.string.incident_report_dialog_deny_label, this) 196 .setOnDismissListener(this) 197 .setView(content) 198 .create(); 199 if (Settings.canDrawOverlays(this)) { 200 final Window w = dialog.getWindow(); 201 if (SdkLevel.isAtLeastT()) { 202 WindowManager.LayoutParams lpm = new WindowManager.LayoutParams(); 203 lpm.setSystemApplicationOverlay(true); 204 w.setAttributes(lpm); 205 } 206 w.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); 207 } 208 dialog.show(); 209 } 210 211 /** 212 * Activity lifecycle callback. Now visible. 213 */ 214 @Override onStart()215 protected void onStart() { 216 super.onStart(); 217 sCurrentActivity = this; 218 sCurrentUri = getIntent().getData(); 219 } 220 221 /** 222 * Activity lifecycle callback. Now not visible. 223 */ 224 @Override onStop()225 protected void onStop() { 226 super.onStop(); 227 sCurrentActivity = null; 228 sCurrentUri = null; 229 } 230 231 /** 232 * Dialog canceled. 233 */ 234 @Override onDismiss(DialogInterface dialog)235 public void onDismiss(DialogInterface dialog) { 236 finish(); 237 } 238 239 /** 240 * Explicit button click. 241 */ 242 @Override onClick(DialogInterface dialog, int which)243 public void onClick(DialogInterface dialog, int which) { 244 final IncidentManager incidentManager = getSystemService(IncidentManager.class); 245 246 switch (which) { 247 case DialogInterface.BUTTON_POSITIVE: 248 incidentManager.approveReport(getIntent().getData()); 249 PendingList.getInstance().updateState(this, 0); 250 break; 251 case DialogInterface.BUTTON_NEGATIVE: 252 incidentManager.denyReport(getIntent().getData()); 253 PendingList.getInstance().updateState(this, 0); 254 break; 255 } 256 finish(); 257 } 258 } 259 260