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
--- 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>