• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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