Commit d5f1d692 authored by Kyle Milka's avatar Kyle Milka Committed by Commit Bot

[QRCode] Prompt for storage permission

If the user hasn't granted storage permission prompt them for it
before downloading.  If the user has asked to "don't ask again",
replace the Download button with a link to open settings.

Bug: 1099426
Change-Id: Ib0e998b9c85ff6b15e764d90d923e062b4068406
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2276641Reviewed-by: default avatarTravis Skare <skare@chromium.org>
Reviewed-by: default avatarMin Qin <qinmin@chromium.org>
Commit-Queue: Kyle Milka <kmilka@chromium.org>
Cr-Commit-Position: refs/heads/master@{#786845}
parent 7bf93f10
......@@ -6,6 +6,7 @@ include_rules = [
"+chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java",
"+chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java",
"+chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java",
"+chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java",
"+chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java",
"+chrome/android/java/src/org/chromium/chrome/browser/media/MediaViewerUtils.java",
"+chrome/android/java/src/org/chromium/chrome/browser/modules/ModuleInstallUi.java",
......
......@@ -42,4 +42,13 @@
android:layout_marginTop="30dp"
android:text="@string/qr_code_download"
style="@style/TextButton"/>
</LinearLayout>
\ No newline at end of file
<org.chromium.ui.widget.ButtonCompat
android:id="@+id/settings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="30dp"
android:text="@string/open_settings_button"
style="@style/TextButton"/>
</LinearLayout>
......@@ -17,12 +17,12 @@ import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
*/
public class QrCodeShareCoordinator implements QrCodeDialogTab {
private final QrCodeShareView mShareView;
private final QrCodeShareMediator mMediator;
public QrCodeShareCoordinator(Context context, Runnable closeDialog, String url) {
PropertyModel shareViewModel = new PropertyModel(QrCodeShareViewProperties.ALL_KEYS);
QrCodeShareMediator shareViewMediator =
new QrCodeShareMediator(context, shareViewModel, closeDialog, url);
mShareView = new QrCodeShareView(context, shareViewMediator::downloadQrCode);
mMediator = new QrCodeShareMediator(context, shareViewModel, closeDialog, url);
mShareView = new QrCodeShareView(context, mMediator::downloadQrCode);
PropertyModelChangeProcessor.create(
shareViewModel, mShareView, new QrCodeShareViewBinder());
}
......@@ -35,12 +35,15 @@ public class QrCodeShareCoordinator implements QrCodeDialogTab {
@Override
public void onResume() {
mMediator.setIsOnForeground(true);
RecordUserAction.record("SharingQRCode.TabVisible.Share");
}
@Override
public void onPause() {}
public void onPause() {
mMediator.setIsOnForeground(false);
}
@Override
public void onDestroy() {}
}
\ No newline at end of file
}
......@@ -4,9 +4,13 @@
package org.chromium.chrome.browser.share.qrcode.share_tab;
import android.Manifest.permission;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Process;
import android.text.DynamicLayout;
import android.text.Layout.Alignment;
import android.text.TextPaint;
......@@ -15,17 +19,23 @@ import android.view.View;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.download.DownloadController;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.share.BitmapDownloadRequest;
import org.chromium.chrome.browser.share.qrcode.QRCodeGenerationRequest;
import org.chromium.ui.base.ActivityAndroidPermissionDelegate;
import org.chromium.ui.base.AndroidPermissionDelegate;
import org.chromium.ui.modelutil.PropertyModel;
import java.lang.ref.WeakReference;
/**
* QrCodeShareMediator is in charge of calculating and setting values for QrCodeShareViewProperties.
*/
class QrCodeShareMediator {
private final Context mContext;
private final PropertyModel mPropertyModel;
private final AndroidPermissionDelegate mPermissionDelegate;
// The number of times the user has attempted to download the QR code in this dialog.
private int mNumDownloads;
......@@ -50,6 +60,9 @@ class QrCodeShareMediator {
mUrl = url;
ChromeBrowserInitializer.getInstance().runNowOrAfterFullBrowserStarted(
() -> refreshQrCode(mUrl));
mPermissionDelegate = new ActivityAndroidPermissionDelegate(
new WeakReference<Activity>((Activity) mContext));
updatePermissionSettings();
}
/**
......@@ -72,15 +85,60 @@ class QrCodeShareMediator {
/** Triggers download for the generated QR code bitmap if available. */
protected void downloadQrCode(View view) {
logDownload();
Bitmap qrcodeBitmap = mPropertyModel.get(QrCodeShareViewProperties.QRCODE_BITMAP);
if (qrcodeBitmap != null && !mIsDownloadInProgress) {
DownloadController.requestFileAccessPermission(this::finishDownloadWithPermission);
return;
}
}
private void finishDownloadWithPermission(boolean granted) {
if (granted) {
updatePermissionSettings();
Bitmap qrcodeBitmap = mPropertyModel.get(QrCodeShareViewProperties.QRCODE_BITMAP);
String fileName = mContext.getString(
R.string.qr_code_filename_prefix, String.valueOf(System.currentTimeMillis()));
mIsDownloadInProgress = true;
BitmapDownloadRequest.downloadBitmap(fileName, addUrlToBitmap(qrcodeBitmap, mUrl));
mCloseDialog.run();
}
logDownload();
mCloseDialog.run();
}
/** Returns whether the user has granted storage permissions. */
private Boolean hasStoragePermission() {
return mContext.checkPermission(
permission.WRITE_EXTERNAL_STORAGE, Process.myPid(), Process.myUid())
== PackageManager.PERMISSION_GRANTED;
}
/** Returns whether the user can be prompted for storage permissions. */
private Boolean canPromptForPermission() {
return mPermissionDelegate.canRequestPermission(permission.WRITE_EXTERNAL_STORAGE);
}
/** Updates the permission settings with the latest values. */
private void updatePermissionSettings() {
mPropertyModel.set(
QrCodeShareViewProperties.CAN_PROMPT_FOR_PERMISSION, canPromptForPermission());
mPropertyModel.set(
QrCodeShareViewProperties.HAS_STORAGE_PERMISSION, hasStoragePermission());
}
/**
* Sets whether QrCode UI is on foreground.
*
* @param isOnForeground Indicates whether this component UI is current on foreground.
*/
public void setIsOnForeground(boolean isOnForeground) {
// If the app is in the foreground, the permissions need to be checked again to ensure
// the user is seeing the right thing.
if (isOnForeground) {
updatePermissionSettings();
}
// This is intentionally done last so that the view is updated according to the latest
// permissions.
mPropertyModel.set(QrCodeShareViewProperties.IS_ON_FOREGROUND, isOnForeground);
}
/** Logs user actions when attempting to download a QR code. */
......
......@@ -4,12 +4,17 @@
package org.chromium.chrome.browser.share.qrcode.share_tab;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import org.chromium.chrome.browser.share.R;
......@@ -22,6 +27,10 @@ class QrCodeShareView {
private final Context mContext;
private final View mView;
private boolean mHasStoragePermission;
private boolean mCanPromptForPermission;
private boolean mIsOnForeground;
public QrCodeShareView(Context context, View.OnClickListener listener) {
mContext = context;
......@@ -30,6 +39,15 @@ class QrCodeShareView {
Button downloadButton = (Button) mView.findViewById(R.id.download);
downloadButton.setOnClickListener(listener);
Button settingsButton = (Button) mView.findViewById(R.id.settings);
settingsButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent openSettingsIntent = getAppInfoIntent(mContext.getPackageName());
((Activity) mContext).startActivity(openSettingsIntent);
}
});
updateView();
}
public View getView() {
......@@ -46,4 +64,57 @@ class QrCodeShareView {
Drawable drawable = new BitmapDrawable(bitmap);
qrcodeImageView.setImageDrawable(drawable);
}
public void storagePermissionsChanged(Boolean hasStoragePermission) {
if (mHasStoragePermission && hasStoragePermission) {
return;
}
mHasStoragePermission = hasStoragePermission;
updateView();
}
public void canPromptForPermissionChanged(Boolean canPromptForPermission) {
mCanPromptForPermission = canPromptForPermission;
updateView();
}
/**
* Applies changes necessary changes to the available button.
*
* @param isOnForeground Indicates whether this component UI is currently on foreground.
*/
public void onForegroundChanged(Boolean isOnForeground) {
mIsOnForeground = isOnForeground;
if (mIsOnForeground) {
updateView();
}
}
private void updateView() {
if (!mIsOnForeground) {
return;
}
Button downloadButton = (Button) mView.findViewById(R.id.download);
Button settingsButton = (Button) mView.findViewById(R.id.settings);
if (mHasStoragePermission) {
downloadButton.setVisibility(View.VISIBLE);
settingsButton.setVisibility(View.GONE);
} else if (mCanPromptForPermission) {
downloadButton.setVisibility(View.VISIBLE);
settingsButton.setVisibility(View.GONE);
} else {
downloadButton.setVisibility(View.GONE);
settingsButton.setVisibility(View.VISIBLE);
}
}
/**
* Returns an Intent to show the App Info page for the current app.
*/
private Intent getAppInfoIntent(String packageName) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(new Uri.Builder().scheme("package").opaquePart(packageName).build());
return intent;
}
}
......@@ -13,6 +13,14 @@ class QrCodeShareViewBinder implements ViewBinder<PropertyModel, QrCodeShareView
public void bind(PropertyModel model, QrCodeShareView view, PropertyKey propertyKey) {
if (QrCodeShareViewProperties.QRCODE_BITMAP == propertyKey) {
view.updateQrCodeBitmap(model.get(QrCodeShareViewProperties.QRCODE_BITMAP));
} else if (QrCodeShareViewProperties.HAS_STORAGE_PERMISSION == propertyKey) {
view.storagePermissionsChanged(
model.get(QrCodeShareViewProperties.HAS_STORAGE_PERMISSION));
} else if (QrCodeShareViewProperties.CAN_PROMPT_FOR_PERMISSION == propertyKey) {
view.canPromptForPermissionChanged(
model.get(QrCodeShareViewProperties.CAN_PROMPT_FOR_PERMISSION));
} else if (QrCodeShareViewProperties.IS_ON_FOREGROUND == propertyKey) {
view.onForegroundChanged(model.get(QrCodeShareViewProperties.IS_ON_FOREGROUND));
}
}
}
......@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.share.qrcode.share_tab;
import android.graphics.Bitmap;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
class QrCodeShareViewProperties {
......@@ -14,5 +15,15 @@ class QrCodeShareViewProperties {
public static final WritableObjectPropertyKey<Bitmap> QRCODE_BITMAP =
new WritableObjectPropertyKey<>();
public static final PropertyKey[] ALL_KEYS = {QRCODE_BITMAP};
public static final WritableBooleanPropertyKey HAS_STORAGE_PERMISSION =
new WritableBooleanPropertyKey();
public static final WritableBooleanPropertyKey CAN_PROMPT_FOR_PERMISSION =
new WritableBooleanPropertyKey();
public static final WritableBooleanPropertyKey IS_ON_FOREGROUND =
new WritableBooleanPropertyKey();
public static final PropertyKey[] ALL_KEYS = {
QRCODE_BITMAP, HAS_STORAGE_PERMISSION, CAN_PROMPT_FOR_PERMISSION, IS_ON_FOREGROUND};
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment