• 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.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