Bug 1322586 - 5. Implement PermissionDelegate for geckoview_example; r=droeh draft
authorJim Chen <nchen@mozilla.com>
Thu, 20 Jul 2017 17:52:14 -0400
changeset 612610 3b4b770c606a9be3326bca3acd87770c5f8bb4fa
parent 612609 7c0a4489b136100ca57740512436bb8cccc62f3a
child 638457 62426c8487ad1e761e4e3e31e67170902cf0da7e
push id69550
push userbmo:nchen@mozilla.com
push dateThu, 20 Jul 2017 21:53:06 +0000
reviewersdroeh
bugs1322586
milestone56.0a1
Bug 1322586 - 5. Implement PermissionDelegate for geckoview_example; r=droeh Add a sample implementation of PermissionDelegate for geckoview_example; Because the prompt code has some existing boilerplate, the actual prompts are implemented in BasicGeckoViewPrompt. MozReview-Commit-ID: EDfmRPn4cjR
mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java
mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
mobile/android/geckoview_example/src/main/res/values/strings.xml
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java
@@ -31,17 +31,17 @@ import android.widget.CheckedTextView;
 import android.widget.CompoundButton;
 import android.widget.DatePicker;
 import android.widget.EditText;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.ScrollView;
-import android.widget.SeekBar;
+import android.widget.Spinner;
 import android.widget.TextView;
 import android.widget.TimePicker;
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
@@ -166,20 +166,21 @@ final class BasicGeckoViewPrompt impleme
         return attr.getDimensionPixelSize(0, 1);
     }
 
     private LinearLayout addStandardLayout(final AlertDialog.Builder builder,
                                            final String title, final String msg,
                                            final AlertCallback callback) {
         final ScrollView scrollView = new ScrollView(builder.getContext());
         final LinearLayout container = new LinearLayout(builder.getContext());
-        final int padding = getViewPadding(builder);
+        final int horizontalPadding = getViewPadding(builder);
+        final int verticalPadding = (msg == null || msg.isEmpty()) ? horizontalPadding : 0;
         container.setOrientation(LinearLayout.VERTICAL);
-        container.setPadding(/* left */ padding, /* top */ 0,
-                             /* right */ padding, /* bottom */ 0);
+        container.setPadding(/* left */ horizontalPadding, /* top */ verticalPadding,
+                             /* right */ horizontalPadding, /* bottom */ verticalPadding);
         scrollView.addView(container);
         builder.setTitle(title)
                .setMessage(msg)
                .setOnDismissListener(new DialogInterface.OnDismissListener() {
                     @Override
                     public void onDismiss(final DialogInterface dialog) {
                         callback.dismiss();
                     }
@@ -783,9 +784,115 @@ final class BasicGeckoViewPrompt impleme
             final int count = clip.getItemCount();
             final ArrayList<Uri> uris = new ArrayList<>(count);
             for (int i = 0; i < count; i++) {
                 uris.add(clip.getItemAt(i).getUri());
             }
             callback.confirm(uris.toArray(new Uri[uris.size()]));
         }
     }
+
+    public void promptForPermission(final GeckoView view, final String title,
+                                    final GeckoView.PermissionDelegate.Callback callback) {
+        final Activity activity = getActivity(view);
+        if (activity == null) {
+            callback.reject();
+            return;
+        }
+        final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setTitle(title)
+               .setOnDismissListener(new DialogInterface.OnDismissListener() {
+                   @Override
+                   public void onDismiss(final DialogInterface dialog) {
+                       callback.reject();
+                   }
+               })
+               .setNegativeButton(android.R.string.cancel, /* onClickListener */ null)
+               .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                   @Override
+                   public void onClick(final DialogInterface dialog, final int which) {
+                       callback.grant();
+                   }
+               })
+               .show();
+    }
+
+    private Spinner addMediaSpinner(final Context context, final ViewGroup container,
+                                    final GeckoBundle[] sources) {
+        final ArrayAdapter<GeckoBundle> adapter = new ArrayAdapter<GeckoBundle>(
+                context, android.R.layout.simple_spinner_item) {
+            private View convertView(final int position, final View view) {
+                if (view != null) {
+                    final GeckoBundle item = getItem(position);
+                    ((TextView) view).setText(item.getString("name"));
+                }
+                return view;
+            }
+
+            @Override
+            public View getView(final int position, View view,
+                                final ViewGroup parent) {
+                return convertView(position, super.getView(position, view, parent));
+            }
+
+            @Override
+            public View getDropDownView(final int position, final View view,
+                                        final ViewGroup parent) {
+                return convertView(position, super.getDropDownView(position, view, parent));
+            }
+        };
+        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        adapter.addAll(sources);
+
+        final Spinner spinner = new Spinner(context);
+        spinner.setAdapter(adapter);
+        spinner.setSelection(0);
+        container.addView(spinner);
+        return spinner;
+    }
+
+    public void promptForMedia(final GeckoView view, final String title,
+                               final GeckoBundle[] video, final GeckoBundle[] audio,
+                               final GeckoView.PermissionDelegate.MediaCallback callback) {
+        final Activity activity = getActivity(view);
+        if (activity == null || (video == null && audio == null)) {
+            callback.reject();
+            return;
+        }
+        final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        final LinearLayout container = addStandardLayout(builder, title, /* msg */ null,
+                                                         /* callback */ null);
+
+        final Spinner videoSpinner;
+        if (video != null) {
+            videoSpinner = addMediaSpinner(builder.getContext(), container, video);
+        } else {
+            videoSpinner = null;
+        }
+
+        final Spinner audioSpinner;
+        if (audio != null) {
+            audioSpinner = addMediaSpinner(builder.getContext(), container, audio);
+        } else {
+            audioSpinner = null;
+        }
+
+        builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
+                    @Override
+                    public void onDismiss(final DialogInterface dialog) {
+                        callback.reject();
+                    }
+                })
+               .setNegativeButton(android.R.string.cancel, /* listener */ null)
+               .setPositiveButton(android.R.string.ok,
+                                  new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(final DialogInterface dialog, final int which) {
+                        final GeckoBundle video = (videoSpinner != null)
+                                ? (GeckoBundle) videoSpinner.getSelectedItem() : null;
+                        final GeckoBundle audio = (audioSpinner != null)
+                                ? (GeckoBundle) audioSpinner.getSelectedItem() : null;
+                        callback.grant(video, audio);
+                    }
+                })
+               .show();
+    }
 }
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
@@ -2,33 +2,38 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.geckoview_example;
 
 import android.app.Activity;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.WindowManager;
 
+import java.util.Locale;
+
 import org.mozilla.gecko.GeckoView;
 import org.mozilla.gecko.GeckoViewSettings;
 import org.mozilla.gecko.util.GeckoBundle;
 
 public class GeckoViewActivity extends Activity {
     private static final String LOGTAG = "GeckoViewActivity";
     private static final String DEFAULT_URL = "https://mozilla.org";
     private static final String USE_MULTIPROCESS_EXTRA = "use_multiprocess";
 
     /* package */ static final int REQUEST_FILE_PICKER = 1;
+    private static final int REQUEST_PERMISSIONS = 2;
 
     private GeckoView mGeckoView;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() +
               " - application start");
@@ -56,16 +61,20 @@ public class GeckoViewActivity extends A
         mGeckoView = (GeckoView) findViewById(R.id.gecko_view);
         mGeckoView.setContentListener(new MyGeckoViewContent());
         mGeckoView.setProgressListener(new MyGeckoViewProgress());
 
         final BasicGeckoViewPrompt prompt = new BasicGeckoViewPrompt();
         prompt.filePickerRequestCode = REQUEST_FILE_PICKER;
         mGeckoView.setPromptDelegate(prompt);
 
+        final MyGeckoViewPermission permission = new MyGeckoViewPermission();
+        permission.androidPermissionRequestCode = REQUEST_PERMISSIONS;
+        mGeckoView.setPermissionDelegate(permission);
+
         loadFromIntent(getIntent());
     }
 
     @Override
     protected void onNewIntent(final Intent intent) {
         super.onNewIntent(intent);
         setIntent(intent);
 
@@ -90,16 +99,29 @@ public class GeckoViewActivity extends A
             final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt)
                     mGeckoView.getPromptDelegate();
             prompt.onFileCallbackResult(resultCode, data);
         } else {
             super.onActivityResult(requestCode, resultCode, data);
         }
     }
 
+    @Override
+    public void onRequestPermissionsResult(final int requestCode,
+                                           final String[] permissions,
+                                           final int[] grantResults) {
+        if (requestCode == REQUEST_PERMISSIONS) {
+            final MyGeckoViewPermission permission = (MyGeckoViewPermission)
+                    mGeckoView.getPermissionDelegate();
+            permission.onRequestPermissionsResult(permissions, grantResults);
+        } else {
+            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        }
+    }
+
     private class MyGeckoViewContent implements GeckoView.ContentListener {
         @Override
         public void onTitleChange(GeckoView view, String title) {
             Log.i(LOGTAG, "Content title changed to " + title);
         }
 
         @Override
         public void onFullScreen(final GeckoView view, final boolean fullScreen) {
@@ -138,9 +160,113 @@ public class GeckoViewActivity extends A
             } else if ((status & STATE_IS_INSECURE) != 0) {
                 statusString = "insecure";
             } else {
                 statusString = "unknown";
             }
             Log.i(LOGTAG, "Security status changed to " + statusString);
         }
     }
+
+    private class MyGeckoViewPermission implements GeckoView.PermissionDelegate {
+
+        public int androidPermissionRequestCode = 1;
+        private Callback mCallback;
+
+        public void onRequestPermissionsResult(final String[] permissions,
+                                               final int[] grantResults) {
+            if (mCallback == null) {
+                return;
+            }
+
+            final Callback cb = mCallback;
+            mCallback = null;
+            for (final int result : grantResults) {
+                if (result != PackageManager.PERMISSION_GRANTED) {
+                    // At least one permission was not granted.
+                    cb.reject();
+                    return;
+                }
+            }
+            cb.grant();
+        }
+
+        @Override
+        public void requestAndroidPermissions(final GeckoView view, final String[] permissions,
+                                              final Callback callback) {
+            if (Build.VERSION.SDK_INT < 23) {
+                // requestPermissions was introduced in API 23.
+                callback.grant();
+                return;
+            }
+            mCallback = callback;
+            requestPermissions(permissions, androidPermissionRequestCode);
+        }
+
+        @Override
+        public void requestContentPermission(final GeckoView view, final String uri,
+                                             final String type, final String access,
+                                             final Callback callback) {
+            final int resId;
+            if ("geolocation".equals(type)) {
+                resId = R.string.request_geolocation;
+            } else if ("desktop-notification".equals(type)) {
+                resId = R.string.request_notification;
+            } else {
+                Log.w(LOGTAG, "Unknown permission: " + type);
+                callback.reject();
+                return;
+            }
+
+            final String title = getString(resId, Uri.parse(uri).getAuthority());
+            final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt)
+                    mGeckoView.getPromptDelegate();
+            prompt.promptForPermission(view, title, callback);
+        }
+
+        private void normalizeMediaName(final GeckoBundle[] sources) {
+            if (sources == null) {
+                return;
+            }
+            for (final GeckoBundle source : sources) {
+                final String mediaSource = source.getString("mediaSource");
+                String name = source.getString("name");
+                if ("camera".equals(mediaSource)) {
+                    if (name.toLowerCase(Locale.ENGLISH).contains("front")) {
+                        name = getString(R.string.media_front_camera);
+                    } else {
+                        name = getString(R.string.media_back_camera);
+                    }
+                } else if (!name.isEmpty()) {
+                    continue;
+                } else if ("microphone".equals(mediaSource)) {
+                    name = getString(R.string.media_microphone);
+                } else {
+                    name = getString(R.string.media_other);
+                }
+                source.putString("name", name);
+            }
+        }
+
+        @Override
+        public void requestMediaPermission(final GeckoView view, final String uri,
+                                           final GeckoBundle[] video,
+                                           final GeckoBundle[] audio,
+                                           final MediaCallback callback) {
+            final String host = Uri.parse(uri).getAuthority();
+            final String title;
+            if (audio == null) {
+                title = getString(R.string.request_video, host);
+            } else if (video == null) {
+                title = getString(R.string.request_audio, host);
+            } else {
+                title = getString(R.string.request_media, host);
+            }
+
+            normalizeMediaName(video);
+            normalizeMediaName(audio);
+
+            final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt)
+                    mGeckoView.getPromptDelegate();
+            prompt.promptForMedia(view, title, video, audio, callback);
+        }
+    }
 }
--- a/mobile/android/geckoview_example/src/main/res/values/strings.xml
+++ b/mobile/android/geckoview_example/src/main/res/values/strings.xml
@@ -1,6 +1,15 @@
 <resources>
     <string name="app_name">geckoview_example</string>
     <string name="username">Username</string>
     <string name="password">Password</string>
     <string name="clear_field">Clear</string>
+    <string name="request_geolocation">Share location with "%1$s"?</string>
+    <string name="request_notification">Allow notifications for "%1$s"?</string>
+    <string name="request_video">Share video with "%1$s"</string>
+    <string name="request_audio">Share audio with "%1$s"</string>
+    <string name="request_media">Share video and audio with "%1$s"</string>
+    <string name="media_back_camera">Back camera</string>
+    <string name="media_front_camera">Front camera</string>
+    <string name="media_microphone">Microphone</string>
+    <string name="media_other">Unknown source</string>
 </resources>