Commit 2149fc5b authored by Tanya Gupta's avatar Tanya Gupta Committed by Commit Bot

[QRCode] Created a open settings sheet which is displayed when

the user denies permission for Chrome to prompt for camera permission.

Screenshot:
https://screenshot.googleplex.com/himQOS9PQrY

Bug: 1046574
Change-Id: I0df3cf870f0f08e5f0bc08e0dd4f8d963f4797ae
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2037860
Commit-Queue: Tanya Gupta <tgupta@chromium.org>
Reviewed-by: default avatarGayane Petrosyan <gayane@chromium.org>
Cr-Commit-Position: refs/heads/master@{#741571}
parent 2b5a142e
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/permission_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<org.chromium.ui.widget.ChromeImageView
android:id="@+id/camera_icon"
android:layout_marginTop="200dp"
android:layout_gravity="center_horizontal"
android:layout_width="125dp"
android:layout_height="125dp"
android:scaleType="center"
android:importantForAccessibility="no"
android:src="@drawable/camera" />
<TextView
android:id="@+id/qrcode_permission_image"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:drawablePadding="24dp"
android:gravity="center_horizontal"
android:layout_gravity="center_horizontal"
android:text="@string/qr_code_open_settings_description"/>
<org.chromium.ui.widget.ButtonCompat
android:id="@+id/open_settings_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="30dp"
android:text="@string/qr_code_open_settings_label"
style="@style/TextButton"/>
</LinearLayout>
\ No newline at end of file
...@@ -24,7 +24,11 @@ import java.util.ArrayList; ...@@ -24,7 +24,11 @@ import java.util.ArrayList;
public class QrCodeDialog extends DialogFragment { public class QrCodeDialog extends DialogFragment {
private ArrayList<QrCodeDialogTab> mTabs; private ArrayList<QrCodeDialogTab> mTabs;
/** The QrCodeDialog constructor. */ /**
* The QrCodeDialog constructor.
* TODO(tgupta): This causes an NPE when the user moves away from Chrome
* and comes back. Fix this issue.
*/
public QrCodeDialog() {} public QrCodeDialog() {}
/** /**
......
...@@ -60,9 +60,7 @@ public class QrCodeScanMediator implements Camera.PreviewCallback { ...@@ -60,9 +60,7 @@ public class QrCodeScanMediator implements Camera.PreviewCallback {
mPropertyModel = propertyModel; mPropertyModel = propertyModel;
mPermissionDelegate = new ActivityAndroidPermissionDelegate( mPermissionDelegate = new ActivityAndroidPermissionDelegate(
new WeakReference<Activity>((Activity) mContext)); new WeakReference<Activity>((Activity) mContext));
mPropertyModel.set(QrCodeScanViewProperties.HAS_CAMERA_PERMISSION, hasCameraPermission()); updatePermissionSettings();
mPropertyModel.set(
QrCodeScanViewProperties.CAN_PROMPT_FOR_PERMISSION, canPromptForPermission());
mDetector = new BarcodeDetector.Builder(context).build(); mDetector = new BarcodeDetector.Builder(context).build();
mNavigationObserver = observer; mNavigationObserver = observer;
mTabCreator = tabCreator; mTabCreator = tabCreator;
...@@ -75,17 +73,31 @@ public class QrCodeScanMediator implements Camera.PreviewCallback { ...@@ -75,17 +73,31 @@ public class QrCodeScanMediator implements Camera.PreviewCallback {
== PackageManager.PERMISSION_GRANTED; == PackageManager.PERMISSION_GRANTED;
} }
/** Returns whether the user has granted camera permissions. */ /** Returns whether the user can be prompted for camera permissions. */
private Boolean canPromptForPermission() { private Boolean canPromptForPermission() {
return mPermissionDelegate.canRequestPermission(permission.CAMERA); return mPermissionDelegate.canRequestPermission(permission.CAMERA);
} }
/** Updates the permission settings with the latest values. */
private void updatePermissionSettings() {
mPropertyModel.set(
QrCodeScanViewProperties.CAN_PROMPT_FOR_PERMISSION, canPromptForPermission());
mPropertyModel.set(QrCodeScanViewProperties.HAS_CAMERA_PERMISSION, hasCameraPermission());
}
/** /**
* Sets whether QrCode UI is on foreground. * Sets whether QrCode UI is on foreground.
* *
* @param isOnForeground Indicates whether this component UI is current on foreground. * @param isOnForeground Indicates whether this component UI is current on foreground.
*/ */
public void setIsOnForeground(boolean isOnForeground) { 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(QrCodeScanViewProperties.IS_ON_FOREGROUND, isOnForeground); mPropertyModel.set(QrCodeScanViewProperties.IS_ON_FOREGROUND, isOnForeground);
} }
...@@ -104,11 +116,14 @@ public class QrCodeScanMediator implements Camera.PreviewCallback { ...@@ -104,11 +116,14 @@ public class QrCodeScanMediator implements Camera.PreviewCallback {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mPropertyModel.set(QrCodeScanViewProperties.HAS_CAMERA_PERMISSION, true); mPropertyModel.set(QrCodeScanViewProperties.HAS_CAMERA_PERMISSION, true);
} else { } else {
mPropertyModel.set(QrCodeScanViewProperties.HAS_CAMERA_PERMISSION, false); // The order in which these fields are important because it causes updates to
// the view. CanPromptForPermission must be updated first so that it doesn't
// cause the view to be updated twice creating a flicker effect.
if (!mPermissionDelegate.canRequestPermission(permission.CAMERA)) { if (!mPermissionDelegate.canRequestPermission(permission.CAMERA)) {
mPropertyModel.set( mPropertyModel.set(
QrCodeScanViewProperties.CAN_PROMPT_FOR_PERMISSION, false); QrCodeScanViewProperties.CAN_PROMPT_FOR_PERMISSION, false);
} }
mPropertyModel.set(QrCodeScanViewProperties.HAS_CAMERA_PERMISSION, false);
} }
} }
}; };
......
...@@ -4,10 +4,14 @@ ...@@ -4,10 +4,14 @@
package org.chromium.chrome.browser.share.qrcode.scan_tab; package org.chromium.chrome.browser.share.qrcode.scan_tab;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.hardware.Camera; import android.hardware.Camera;
import android.hardware.Camera.ErrorCallback; import android.hardware.Camera.ErrorCallback;
import android.hardware.Camera.PreviewCallback; import android.hardware.Camera.PreviewCallback;
import android.net.Uri;
import android.provider.Settings;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
...@@ -22,6 +26,7 @@ import org.chromium.ui.widget.ButtonCompat; ...@@ -22,6 +26,7 @@ import org.chromium.ui.widget.ButtonCompat;
*/ */
class QrCodeScanView { class QrCodeScanView {
public interface PermissionPrompter { void promptForCameraPermission(); } public interface PermissionPrompter { void promptForCameraPermission(); }
public interface PermissionPromptAllowedChecker { Boolean canPromptForPermission(); }
private final Context mContext; private final Context mContext;
private final FrameLayout mView; private final FrameLayout mView;
...@@ -33,6 +38,7 @@ class QrCodeScanView { ...@@ -33,6 +38,7 @@ class QrCodeScanView {
private CameraPreview mCameraPreview; private CameraPreview mCameraPreview;
private View mPermissionsView; private View mPermissionsView;
private View mCameraErrorView; private View mCameraErrorView;
private View mOpenSettingsView;
/** /**
* The QrCodeScanView constructor. * The QrCodeScanView constructor.
...@@ -45,16 +51,13 @@ class QrCodeScanView { ...@@ -45,16 +51,13 @@ class QrCodeScanView {
mContext = context; mContext = context;
mCameraPreviewCallback = cameraCallback; mCameraPreviewCallback = cameraCallback;
mView = new FrameLayout(context); mView = new FrameLayout(context);
mOpenSettingsView = createOpenSettingsView(context);
mView.setLayoutParams( mView.setLayoutParams(
new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mPermissionsView = createPermissionView(context, permissionPrompter); mPermissionsView = createPermissionView(context, permissionPrompter);
mCameraErrorView = createCameraErrorView(context); mCameraErrorView = createCameraErrorView(context);
} }
public View getView() {
return mView;
}
private View createPermissionView(Context context, PermissionPrompter permissionPrompter) { private View createPermissionView(Context context, PermissionPrompter permissionPrompter) {
View permissionView = (View) LayoutInflater.from(context).inflate( View permissionView = (View) LayoutInflater.from(context).inflate(
org.chromium.chrome.browser.share.qrcode.R.layout.qrcode_permission_layout, null, org.chromium.chrome.browser.share.qrcode.R.layout.qrcode_permission_layout, null,
...@@ -106,6 +109,31 @@ class QrCodeScanView { ...@@ -106,6 +109,31 @@ class QrCodeScanView {
} }
}; };
public View getView() {
return mView;
}
/**
* Creates a view that opens the settings page for the app and allows the user to
* to update permissions including give the app camera permission.
*/
private View createOpenSettingsView(Context context) {
View openSettingsView = (View) LayoutInflater.from(context).inflate(
org.chromium.chrome.browser.share.qrcode.R.layout.qrcode_open_settings_layout, null,
false);
ButtonCompat cameraPermissionPrompt = openSettingsView.findViewById(
org.chromium.chrome.browser.share.qrcode.R.id.open_settings_button);
cameraPermissionPrompt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent openSettingsIntent = getAppInfoIntent(context.getPackageName());
((Activity) context).startActivity(openSettingsIntent);
}
});
return openSettingsView;
}
/** /**
* Sets camera if possible. * Sets camera if possible.
* *
...@@ -119,13 +147,30 @@ class QrCodeScanView { ...@@ -119,13 +147,30 @@ class QrCodeScanView {
return; return;
} }
mHasCameraPermission = hasCameraPermission; mHasCameraPermission = hasCameraPermission;
updateView();
}
/**
* Update the view based on the latest environment:
* - app is in the foreground
* - user has given camera permission
* - user can be prompted for camera permission
*/
private void updateView() {
// The scan tab is not in the foreground so don't do any rendering.
if (!mIsOnForeground) {
return;
}
// Check that the camera permission has changed and that it is now set to true. // Check that the camera permission has changed and that it is now set to true.
if (hasCameraPermission) { if (mHasCameraPermission && mCameraPreview == null) {
setCameraPreview(); setCameraPreview();
} else { } else if (mHasCameraPermission && mCameraPreview != null) {
// TODO(tgupta): Check that the user can be prompted. If not, show the error updateCameraPreviewState();
// screen instead. } else if (mCanPromptForPermission) {
displayPermissionDialog(); displayPermissionDialog();
} else {
displayOpenSettingsDialog();
} }
} }
...@@ -133,23 +178,26 @@ class QrCodeScanView { ...@@ -133,23 +178,26 @@ class QrCodeScanView {
* Checks whether Chrome can prompt the user for Camera permission. Updates the view accordingly * Checks whether Chrome can prompt the user for Camera permission. Updates the view accordingly
* to let the user know if the permission has been permanently denied. * to let the user know if the permission has been permanently denied.
* *
* @param canPromptForPermission * @param canPromptForPermission Indicates whether the user can be prompted for camera
* permission
*/ */
public void canPromptForPermissionChanged(Boolean canPromptForPermission) { public void canPromptForPermissionChanged(Boolean canPromptForPermission) {
if (mCanPromptForPermission != canPromptForPermission && !canPromptForPermission) { mCanPromptForPermission = canPromptForPermission;
// User chose the "don't ask again option, display the appropriate message updateView();
// TODO (tgupta): Update this message
}
} }
/** /**
* Applies changes necessary to camera preview. * Applies changes necessary to camera preview.
* *
* @param isOnForeground Indicates whether this component UI is current on foreground. * @param isOnForeground Indicates whether this component UI is currently on foreground.
*/ */
public void onForegroundChanged(Boolean isOnForeground) { public void onForegroundChanged(Boolean isOnForeground) {
mIsOnForeground = isOnForeground; mIsOnForeground = isOnForeground;
updateCameraPreviewState(); if (!mIsOnForeground && mCameraPreview != null) {
mCameraPreview.stopCamera();
} else {
updateView();
}
} }
/** Creates and sets the camera preview. */ /** Creates and sets the camera preview. */
...@@ -176,7 +224,7 @@ class QrCodeScanView { ...@@ -176,7 +224,7 @@ class QrCodeScanView {
return; return;
} }
if (mIsOnForeground) { if (mIsOnForeground && mHasCameraPermission) {
mCameraPreview.startCamera(); mCameraPreview.startCamera();
} else { } else {
mCameraPreview.stopCamera(); mCameraPreview.stopCamera();
...@@ -201,4 +249,20 @@ class QrCodeScanView { ...@@ -201,4 +249,20 @@ class QrCodeScanView {
mView.removeAllViews(); mView.removeAllViews();
mView.addView(mCameraErrorView); mView.addView(mCameraErrorView);
} }
/**
* Displays the open settings dialog.
*/
private void displayOpenSettingsDialog() {
mView.removeAllViews();
mView.addView(mOpenSettingsView);
}
/**
* 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;
}
} }
...@@ -3998,6 +3998,10 @@ The site does NOT gain access to the camera. The camera images are only visible ...@@ -3998,6 +3998,10 @@ The site does NOT gain access to the camera. The camera images are only visible
Please enable your camera to scan a QR code Please enable your camera to scan a QR code
</message> </message>
<message name="IDS_QR_CODE_OPEN_SETTINGS_DESCRIPTION" desc="Text on QR code sharing tab indicating that user needs to open settings to give camera permission.">
To scan a QR code, change your settings so that Chrome can use your camera
</message>
<message name="IDS_QR_CODE_PERMISSION_CONTINUE_LABEL" desc="Text on button on QR code sharing tab triggering camera permission dialog."> <message name="IDS_QR_CODE_PERMISSION_CONTINUE_LABEL" desc="Text on button on QR code sharing tab triggering camera permission dialog.">
Continue Continue
</message> </message>
...@@ -4018,6 +4022,10 @@ The site does NOT gain access to the camera. The camera images are only visible ...@@ -4018,6 +4022,10 @@ The site does NOT gain access to the camera. The camera images are only visible
Can't open your camera. Something went wrong. Can't open your camera. Something went wrong.
</message> </message>
<message name="IDS_QR_CODE_OPEN_SETTINGS_LABEL" desc="Text on button on QR code sharing tab triggering Android settings.">
Open Settings
</message>
<!-- Chime DFM module strings --> <!-- Chime DFM module strings -->
<message name="IDS_CHIME_MODULE_TITLE" desc="Text shown when the chime module is referenced in install start, success, failure UI (e.g. in IDS_MODULE_INSTALL_START_TEXT, which will expand to 'Installing Google Notifications Platform for Chrome…')."> <message name="IDS_CHIME_MODULE_TITLE" desc="Text shown when the chime module is referenced in install start, success, failure UI (e.g. in IDS_MODULE_INSTALL_START_TEXT, which will expand to 'Installing Google Notifications Platform for Chrome…').">
Google Notifications Platform Google Notifications Platform
......
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