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