/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.permissioncontroller.incident; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnDismissListener; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.IncidentManager; import android.provider.Settings; import android.text.Spannable; import android.text.SpannableString; import android.text.style.BulletSpan; import android.util.Log; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.android.permissioncontroller.R; import java.util.ArrayList; /** * Confirmation dialog for approving an incident or bug report for sharing off the device. */ public class ConfirmationActivity extends Activity implements OnClickListener, OnDismissListener { private static final String TAG = "ConfirmationActivity"; /** * Currently displaying activity. */ private static ConfirmationActivity sCurrentActivity; /** * Currently displaying uri. */ private static Uri sCurrentUri; /** * If this activity is running in the current process, call finish() on it. */ public static void finishCurrent() { if (sCurrentActivity != null) { sCurrentActivity.finish(); } } /** * If the activity is in the resumed state, then record the Uri for the current * one, so PendingList can skip re-showing the same one. */ public static Uri getCurrentUri() { return sCurrentUri; } /** * Create the activity. */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Formatting formatting = new Formatting(this); final Uri uri = getIntent().getData(); Log.d(TAG, "uri=" + uri); if (uri == null) { Log.w(TAG, "No uri in intent: " + getIntent()); finish(); return; } final IncidentManager.PendingReport pending = new IncidentManager.PendingReport(uri); final String appLabel = formatting.getAppLabel(pending.getRequestingPackage()); final Resources res = getResources(); ReportDetails details; try { details = ReportDetails.parseIncidentReport(this, uri); } catch (ReportDetails.ParseException ex) { Log.w("Rejecting report because it couldn't be parsed", ex); // If there was an error in the input we will just summarily reject the upload, // since we can't get proper approval. (Zero-length images or reasons means that // we will proceed with the imageless consent dialog). final IncidentManager incidentManager = getSystemService(IncidentManager.class); incidentManager.denyReport(getIntent().getData()); // Show a message to the user saying... nevermind. new AlertDialog.Builder(this) .setTitle(R.string.incident_report_dialog_title) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }) .setMessage(getString(R.string.incident_report_error_dialog_text, appLabel)) .setOnDismissListener(this) .show(); return; } final View content = getLayoutInflater().inflate(R.layout.incident_confirmation, null); final ArrayList reasons = details.getReasons(); final int reasonsSize = reasons.size(); if (reasonsSize > 0) { content.findViewById(R.id.reasonIntro).setVisibility(View.VISIBLE); final TextView reasonTextView = (TextView) content.findViewById(R.id.reasons); reasonTextView.setVisibility(View.VISIBLE); final int bulletSize = (int) (res.getDimension(R.dimen.incident_reason_bullet_size) + 0.5f); final int bulletIndent = (int) (res.getDimension(R.dimen.incident_reason_bullet_indent) + 0.5f); final int bulletColor = getColor(R.color.incident_reason_bullet_color); final StringBuilder text = new StringBuilder(); for (int i = 0; i < reasonsSize; i++) { text.append(reasons.get(i)); if (i != reasonsSize - 1) { text.append("\n"); } } final SpannableString spannable = new SpannableString(text.toString()); int spanStart = 0; for (int i = 0; i < reasonsSize; i++) { final int length = reasons.get(i).length(); spannable.setSpan(new BulletSpan(bulletIndent, bulletColor, bulletSize), spanStart, spanStart + length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); spanStart += length + 1; } reasonTextView.setText(spannable); } final String message = getString(R.string.incident_report_dialog_text, appLabel, formatting.getDate(pending.getTimestamp()), formatting.getTime(pending.getTimestamp()), appLabel); ((TextView) content.findViewById(R.id.message)).setText(message); final ArrayList images = details.getImages(); final int imagesSize = images.size(); if (imagesSize > 0) { content.findViewById(R.id.imageScrollView).setVisibility(View.VISIBLE); final LinearLayout imageList = (LinearLayout) content.findViewById(R.id.imageList); final int width = res.getDimensionPixelSize(R.dimen.incident_image_width); final int height = res.getDimensionPixelSize(R.dimen.incident_image_height); for (int i = 0; i < imagesSize; i++) { final Drawable drawable = images.get(i); final ImageView imageView = new ImageView(this); imageView.setImageDrawable(images.get(i)); imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); imageList.addView(imageView, new LinearLayout.LayoutParams(width, height)); } } final AlertDialog dialog = new AlertDialog.Builder(this) .setTitle(R.string.incident_report_dialog_title) .setPositiveButton(R.string.incident_report_dialog_allow_label, this) .setNegativeButton(R.string.incident_report_dialog_deny_label, this) .setOnDismissListener(this) .setView(content) .create(); if (Settings.canDrawOverlays(this)) { final Window w = dialog.getWindow(); w.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); } dialog.show(); } /** * Activity lifecycle callback. Now visible. */ @Override protected void onStart() { super.onStart(); sCurrentActivity = this; sCurrentUri = getIntent().getData(); } /** * Activity lifecycle callback. Now not visible. */ @Override protected void onStop() { super.onStop(); sCurrentActivity = null; sCurrentUri = null; } /** * Dialog canceled. */ @Override public void onDismiss(DialogInterface dialog) { finish(); } /** * Explicit button click. */ @Override public void onClick(DialogInterface dialog, int which) { final IncidentManager incidentManager = getSystemService(IncidentManager.class); switch (which) { case DialogInterface.BUTTON_POSITIVE: incidentManager.approveReport(getIntent().getData()); PendingList.getInstance().updateState(this, 0); break; case DialogInterface.BUTTON_NEGATIVE: incidentManager.denyReport(getIntent().getData()); PendingList.getInstance().updateState(this, 0); break; } finish(); } }