// Copyright 2016 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.
package org.chromium.chrome.browser.infobar;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.SwitchCompat;
import android.util.SparseArray;
import android.view.View;
import android.widget.TextView;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ContentSettingsType;
import org.chromium.chrome.browser.ResourceId;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.base.WindowAndroid.PermissionCallback;
/**
* An infobar used for prompting the user to grant a web API permission.
*
*/
public class PermissionInfoBar extends ConfirmInfoBar {
/** Whether or not to show a toggle for opting out of persisting the decision. */
private boolean mShowPersistenceToggle;
private WindowAndroid mWindowAndroid;
/**
* Mapping between the required {@link ContentSettingsType}s and their associated Android
* runtime permissions. Only {@link ContentSettingsType}s that are associated with runtime
* permissions will be included in this list while all others will be excluded.
*/
private SparseArray<String> mContentSettingsToPermissionsMap;
protected PermissionInfoBar(int iconDrawableId, Bitmap iconBitmap, String message,
String linkText, String primaryButtonText, String secondaryButtonText,
boolean showPersistenceToggle) {
super(iconDrawableId, iconBitmap, message, linkText, primaryButtonText,
secondaryButtonText);
mShowPersistenceToggle = showPersistenceToggle;
}
@Override
public void createContent(InfoBarLayout layout) {
super.createContent(layout);
if (mShowPersistenceToggle) {
InfoBarControlLayout controlLayout = layout.addControlLayout();
String description =
layout.getContext().getString(R.string.permission_infobar_persist_text);
controlLayout.addSwitch(
0, 0, description, R.id.permission_infobar_persist_toggle, true);
}
}
@Override
public void onTabReparented(Tab tab) {
mWindowAndroid = tab.getWindowAndroid();
}
/**
* Specifies the {@link ContentSettingsType}s that are controlled by this InfoBar.
*
* @param windowAndroid The WindowAndroid that will be used to check for the necessary
* permissions.
* @param contentSettings The list of {@link ContentSettingsType}s whose access is guarded
* by this InfoBar.
*/
protected void setContentSettings(
WindowAndroid windowAndroid, int[] contentSettings) {
mWindowAndroid = windowAndroid;
assert windowAndroid != null
: "A WindowAndroid must be specified to request access to content settings";
mContentSettingsToPermissionsMap = generatePermissionsMapping(contentSettings);
}
private SparseArray<String> generatePermissionsMapping(int[] contentSettings) {
SparseArray<String> permissionsToRequest = new SparseArray<String>();
for (int i = 0; i < contentSettings.length; i++) {
String permission = PrefServiceBridge.getAndroidPermissionForContentSetting(
contentSettings[i]);
if (permission != null && !mWindowAndroid.hasPermission(permission)) {
permissionsToRequest.append(contentSettings[i], permission);
}
}
return permissionsToRequest;
}
private int getDeniedPermissionResourceId(String permission) {
int contentSettingsType = 0;
// SparseArray#indexOfValue uses == instead of .equals, so we need to manually iterate
// over the list.
for (int i = 0; i < mContentSettingsToPermissionsMap.size(); i++) {
if (permission.equals(mContentSettingsToPermissionsMap.valueAt(i))) {
contentSettingsType = mContentSettingsToPermissionsMap.keyAt(i);
}
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_GEOLOCATION) {
return R.string.infobar_missing_location_permission_text;
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC) {
return R.string.infobar_missing_microphone_permission_text;
}
if (contentSettingsType == ContentSettingsType.CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA) {
return R.string.infobar_missing_camera_permission_text;
}
assert false : "Unexpected content setting type received: " + contentSettingsType;
return R.string.infobar_missing_multiple_permissions_text;
}
@Override
public void onButtonClicked(final boolean isPrimaryButton) {
if (mWindowAndroid == null || !isPrimaryButton || getContext() == null
|| mContentSettingsToPermissionsMap == null
|| mContentSettingsToPermissionsMap.size() == 0) {
onButtonClickedInternal(isPrimaryButton);
return;
}
requestAndroidPermissions();
}
private void requestAndroidPermissions() {
PermissionCallback callback = new PermissionCallback() {
@Override
public void onRequestPermissionsResult(
String[] permissions, int[] grantResults) {
int deniedCount = 0;
int requestableCount = 0;
int deniedStringId = R.string.infobar_missing_multiple_permissions_text;
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
deniedCount++;
if (deniedCount > 1) {
deniedStringId = R.string.infobar_missing_multiple_permissions_text;
} else {
deniedStringId = getDeniedPermissionResourceId(permissions[i]);
}
if (mWindowAndroid.canRequestPermission(permissions[i])) {
requestableCount++;
}
}
}
Activity activity = mWindowAndroid.getActivity().get();
if (deniedCount > 0 && requestableCount > 0 && activity != null) {
View view = activity.getLayoutInflater().inflate(
R.layout.update_permissions_dialog, null);
TextView dialogText = (TextView) view.findViewById(R.id.text);
dialogText.setText(deniedStringId);
AlertDialog.Builder builder =
new AlertDialog.Builder(activity, R.style.AlertDialogTheme)
.setView(view)
.setPositiveButton(R.string.infobar_update_permissions_button_text,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
requestAndroidPermissions();
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
onCloseButtonClicked();
}
});
builder.create().show();
} else if (deniedCount > 0) {
onCloseButtonClicked();
} else {
onButtonClickedInternal(true);
}
}
};
String[] permissionsToRequest = new String[mContentSettingsToPermissionsMap.size()];
for (int i = 0; i < mContentSettingsToPermissionsMap.size(); i++) {
permissionsToRequest[i] = mContentSettingsToPermissionsMap.valueAt(i);
}
mWindowAndroid.requestPermissions(permissionsToRequest, callback);
}
private void onButtonClickedInternal(boolean isPrimaryButton) {
super.onButtonClicked(isPrimaryButton);
}
/**
* Returns true if the persist switch exists and is toggled on.
*/
@CalledByNative
protected boolean isPersistSwitchOn() {
SwitchCompat persistSwitch = (SwitchCompat) getView().findViewById(
R.id.permission_infobar_persist_toggle);
if (mShowPersistenceToggle && persistSwitch != null) {
return persistSwitch.isChecked();
}
return false;
}
/**
* Creates and begins the process for showing a PermissionInfoBar.
* @param windowAndroid The owning window for the infobar.
* @param enumeratedIconId ID corresponding to the icon that will be shown for the infobar.
* The ID must have been mapped using the ResourceMapper class before
* passing it to this function.
* @param iconBitmap Bitmap to use if there is no equivalent Java resource for
* enumeratedIconId.
* @param message Message to display to the user indicating what the infobar is for.
* @param linkText Link text to display in addition to the message.
* @param buttonOk String to display on the OK button.
* @param buttonCancel String to display on the Cancel button.
* @param contentSettings The list of ContentSettingTypes being requested by this infobar.
* @param showPersistenceToggle Whether or not a toggle to opt-out of persisting a decision
* should be displayed.
*/
@CalledByNative
private static PermissionInfoBar create(WindowAndroid windowAndroid, int enumeratedIconId,
Bitmap iconBitmap, String message, String linkText, String buttonOk,
String buttonCancel, int[] contentSettings, boolean showPersistenceToggle) {
int drawableId = ResourceId.mapToDrawableId(enumeratedIconId);
PermissionInfoBar infoBar = new PermissionInfoBar(drawableId, iconBitmap, message, linkText,
buttonOk, buttonCancel, showPersistenceToggle);
infoBar.setContentSettings(windowAndroid, contentSettings);
return infoBar;
}
}