Bug 1322586 - 5. Implement PermissionDelegate for geckoview_example; r=droeh
authorJim Chen <nchen@mozilla.com>
Thu, 20 Jul 2017 17:52:14 -0400
changeset 418785 942cdcab7f794b27ee1537670971a296f3bacdbd
parent 418784 44b8d2f6c838ccb74fb8a50629ef61b7885cb75f
child 418786 0ee08a9803fc13145128cfa0625a63bd464ccd32
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdroeh
bugs1322586
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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>