Chromium Code Reviews| Index: chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java b/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4725d05dec0a0ae6ca6ba1ee8e5dd921e276ae14 |
| --- /dev/null |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java |
| @@ -0,0 +1,186 @@ |
| +// 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.permissions; |
| + |
| +import android.app.Activity; |
| +import android.content.DialogInterface; |
| +import android.support.v7.app.AlertDialog; |
| +import android.support.v7.widget.SwitchCompat; |
| +import android.text.SpannableStringBuilder; |
| +import android.text.Spanned; |
| +import android.text.TextUtils; |
| +import android.text.method.LinkMovementMethod; |
| +import android.text.style.ClickableSpan; |
| +import android.view.LayoutInflater; |
| +import android.view.View; |
| +import android.widget.TextView; |
| + |
| +import org.chromium.base.VisibleForTesting; |
| +import org.chromium.base.annotations.CalledByNative; |
| +import org.chromium.chrome.R; |
| + |
| +import java.util.LinkedList; |
| + |
| +/** |
| + * Singleton instance which controls the display of modal permission dialogs. This class is lazily |
| + * initiated when getInstance() is first called. Implemented like this as modal permission dialogs |
| + * are disabled by default. |
| + * |
| + * Unlike permission infobars, which stack on top of each other, only one permission dialog may be |
| + * visible on the screen at once. Any additional request for a modal permissions dialog is queued, |
| + * and will be displayed once the user responds to the current dialog. |
| + */ |
| +public class PermissionDialogController { |
| + private AlertDialog mDialog; |
| + private SwitchCompat mSwitchView; |
| + private LinkedList<PermissionDialogDelegate> mRequestQueue; |
|
gone
2016/10/27 20:33:35
Go abstract:
private List<PermissionDialogDelegate
dominickn
2016/10/27 23:37:13
Done.
|
| + |
| + // Static holder to ensure safe initialization of the singleton instance. |
| + private static class Holder { |
| + private static final PermissionDialogController sInstance = |
| + new PermissionDialogController(); |
| + } |
| + |
| + protected PermissionDialogController() { |
| + mRequestQueue = new LinkedList<PermissionDialogDelegate>(); |
|
gone
2016/10/27 20:33:35
Does this work?
mRequestQueue = new LinkedList<>()
dominickn
2016/10/27 23:37:12
Done.
|
| + } |
| + |
| + public static PermissionDialogController getInstance() { |
| + return Holder.sInstance; |
| + } |
| + |
| + @CalledByNative |
| + public static void createDialog(PermissionDialogDelegate delegate) { |
|
gone
2016/10/27 20:33:35
1) @CalledByNative doesn't care if the method is p
dominickn
2016/10/27 23:37:12
Done. I couldn't think of a better name, so I adde
|
| + PermissionDialogController.getInstance().queueDialog(delegate); |
| + } |
| + |
| + /** |
| + * Queues a modal permission dialog for display. If there are currently no dialogs on screen, it |
| + * will be displayed immediately. Otherwise, it will be displayed as soon as the user responds |
| + * to the current dialog. |
| + * @param context The context to use to get the dialog layout. |
| + * @param delegate The wrapper for the native-side permission delegate. |
| + */ |
| + private void queueDialog(PermissionDialogDelegate delegate) { |
| + mDialog = null; |
| + mRequestQueue.add(delegate); |
| + scheduleDisplay(); |
| + } |
| + |
| + public void scheduleDisplay() { |
|
gone
2016/10/27 20:33:35
private void
dominickn
2016/10/27 23:37:12
Done.
|
| + if (mDialog == null && !mRequestQueue.isEmpty()) showDialog(); |
| + } |
| + |
| + @VisibleForTesting |
| + public AlertDialog getCurrentDialogForTesting() { |
| + return mDialog; |
| + } |
| + |
| + /** |
| + * Shows the dialog asking the user for a web API permission. |
| + */ |
| + public void showDialog() { |
| + if (mRequestQueue.isEmpty()) return; |
| + |
| + final PermissionDialogDelegate delegate = mRequestQueue.remove(); |
| + Activity activity = delegate.getActivity(); |
| + LayoutInflater inflator = LayoutInflater.from(activity); |
|
gone
2016/10/27 20:33:35
inflater for spelling consistency?
dominickn
2016/10/27 23:37:12
Funny story, I initially had the type as LayoutInf
gone
2016/10/27 23:52:19
I'll spare you from the stream of jokes I was abou
|
| + View view = inflator.inflate(R.layout.permission_dialog, null); |
| + AlertDialog.Builder builder = new AlertDialog.Builder(activity, R.style.AlertDialogTheme); |
| + |
| + mDialog = builder.create(); |
| + mDialog.getDelegate().setHandleNativeActionModesEnabled(false); |
| + |
| + TextView messageTextView = (TextView) view.findViewById(R.id.text); |
| + messageTextView.setText(prepareMainMessageString(delegate)); |
| + messageTextView.setVisibility(View.VISIBLE); |
| + messageTextView.announceForAccessibility(delegate.getMessageText()); |
| + messageTextView.setCompoundDrawablesWithIntrinsicBounds(delegate.getDrawableId(), 0, 0, 0); |
| + messageTextView.setMovementMethod(LinkMovementMethod.getInstance()); |
| + |
| + mSwitchView = (SwitchCompat) view.findViewById(R.id.permission_dialog_persist_toggle); |
| + mSwitchView.setChecked(true); |
| + TextView toggleTextView = |
| + (TextView) view.findViewById(R.id.permission_dialog_persist_message); |
| + if (delegate.shouldShowPersistenceToggle()) { |
| + mSwitchView.setVisibility(View.VISIBLE); |
| + String toggleMessage = |
| + mDialog.getContext().getString(R.string.permission_prompt_persist_text); |
| + toggleTextView.setText(toggleMessage); |
| + toggleTextView.setVisibility(View.VISIBLE); |
| + toggleTextView.announceForAccessibility(toggleMessage); |
| + |
| + // If we have a persistence toggle, make the dialog fully modal. |
| + mDialog.setCanceledOnTouchOutside(false); |
| + |
| + } else { |
| + mSwitchView.setVisibility(View.GONE); |
| + toggleTextView.setVisibility(View.GONE); |
| + |
| + // There is no way for the user to make a once-off decision, so allow them to dismiss |
|
gone
2016/10/27 20:33:35
Can you clarify this comment? I guess the toggle
dominickn
2016/10/27 23:37:12
Done.
|
| + // the prompt by tapping outside the dialog. |
| + mDialog.setCanceledOnTouchOutside(true); |
| + } |
| + |
| + mDialog.setView(view); |
| + |
| + // Set the buttons to call the appropriate delegate methods. When the dialog is dismissed, |
| + // the delegate's native pointers are freed, and the next queued dialog (if any) is |
| + // displayed. |
| + mDialog.setButton(DialogInterface.BUTTON_POSITIVE, |
| + delegate.getPrimaryButtonText(), |
| + new DialogInterface.OnClickListener() { |
| + @Override |
| + public void onClick(DialogInterface dialog, int id) { |
| + delegate.onAccept(mSwitchView.isChecked()); |
| + } |
| + }); |
| + |
| + mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, delegate.getSecondaryButtonText(), |
| + new DialogInterface.OnClickListener() { |
| + @Override |
| + public void onClick(DialogInterface dialog, int id) { |
| + delegate.onCancel(mSwitchView.isChecked()); |
| + } |
| + }); |
| + |
| + mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { |
| + @Override |
| + public void onDismiss(DialogInterface dialog) { |
| + mDialog = null; |
| + delegate.onDismiss(); |
| + scheduleDisplay(); |
| + } |
| + }); |
| + |
| + mDialog.show(); |
| + } |
| + |
| + private CharSequence prepareMainMessageString(final PermissionDialogDelegate delegate) { |
| + SpannableStringBuilder fullString = new SpannableStringBuilder(); |
| + |
| + String messageText = delegate.getMessageText(); |
| + String linkText = delegate.getLinkText(); |
| + if (messageText != null) fullString.append(messageText); |
|
gone
2016/10/27 20:33:35
TextUtils.isEmpty
dominickn
2016/10/27 23:37:12
Done.
|
| + |
| + // If the linkText exists, then wrap it in a clickable span and concatenate it with the main |
| + // dialog message. |
| + if (!TextUtils.isEmpty(linkText)) { |
| + if (fullString.length() > 0) fullString.append(" "); |
| + int spanStart = fullString.length(); |
| + |
| + fullString.append(linkText); |
| + fullString.setSpan(new ClickableSpan() { |
| + @Override |
| + public void onClick(View view) { |
| + delegate.onLinkClicked(); |
| + if (mDialog != null) mDialog.dismiss(); |
| + } |
| + }, spanStart, fullString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); |
| + } |
| + |
| + return fullString; |
| + } |
| +} |