diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /mobile/android/base/java/org/mozilla/gecko/text | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/text')
4 files changed, 356 insertions, 0 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java b/mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java new file mode 100644 index 000000000..07f17590d --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java @@ -0,0 +1,69 @@ +/* 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.gecko.text; + +import android.annotation.TargetApi; +import android.graphics.Rect; +import android.os.Build; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import org.mozilla.gecko.GeckoAppShell; + +import java.util.List; + +@TargetApi(Build.VERSION_CODES.M) +public class FloatingActionModeCallback extends ActionMode.Callback2 { + private FloatingToolbarTextSelection textSelection; + private List<TextAction> actions; + + public FloatingActionModeCallback(FloatingToolbarTextSelection textSelection, List<TextAction> actions) { + this.textSelection = textSelection; + this.actions = actions; + } + + public void updateActions(List<TextAction> actions) { + this.actions = actions; + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + menu.clear(); + + for (int i = 0; i < actions.size(); i++) { + final TextAction action = actions.get(i); + menu.add(Menu.NONE, i, action.getFloatingOrder(), action.getLabel()); + } + + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + final TextAction action = actions.get(item.getItemId()); + + GeckoAppShell.notifyObservers("TextSelection:Action", action.getId()); + + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) {} + + @Override + public void onGetContentRect(ActionMode mode, View view, Rect outRect) { + final Rect contentRect = textSelection.contentRect; + if (contentRect != null) { + outRect.set(contentRect); + } + } +} diff --git a/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java b/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java new file mode 100644 index 000000000..7a09624d4 --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java @@ -0,0 +1,206 @@ +/* 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.gecko.text; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.graphics.Rect; +import android.os.Build; +import android.util.Log; +import android.util.TypedValue; +import android.view.ActionMode; + +import org.json.JSONException; +import org.json.JSONObject; + +import org.mozilla.gecko.EventDispatcher; +import org.mozilla.gecko.GeckoApp; +import org.mozilla.gecko.GeckoAppShell; +import org.mozilla.gecko.Telemetry; +import org.mozilla.gecko.TelemetryContract; +import org.mozilla.gecko.gfx.LayerView; +import org.mozilla.gecko.util.GeckoEventListener; +import org.mozilla.gecko.util.ThreadUtils; + +import java.util.List; + +import ch.boye.httpclientandroidlib.util.TextUtils; + +/** + * Floating toolbar for text selection actions. Only on Android 6+. + */ +@TargetApi(Build.VERSION_CODES.M) +public class FloatingToolbarTextSelection implements TextSelection, GeckoEventListener { + private static final String LOGTAG = "GeckoFloatTextSelection"; + + // This is an additional offset we add to the height of the selection. This will avoid that the + // floating toolbar overlays the bottom handle(s). + private static final int HANDLES_OFFSET_DP = 20; + + private final Activity activity; + private final LayerView layerView; + private final int[] locationInWindow; + private final float handlesOffset; + + private ActionMode actionMode; + private FloatingActionModeCallback actionModeCallback; + private String selectionID; + /* package-private */ Rect contentRect; + + public FloatingToolbarTextSelection(Activity activity, LayerView layerView) { + this.activity = activity; + this.layerView = layerView; + this.locationInWindow = new int[2]; + + this.handlesOffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + HANDLES_OFFSET_DP, activity.getResources().getDisplayMetrics()); + } + + @Override + public boolean dismiss() { + if (finishActionMode()) { + endTextSelection(); + return true; + } + + return false; + } + + private void endTextSelection() { + if (TextUtils.isEmpty(selectionID)) { + return; + } + + final JSONObject args = new JSONObject(); + try { + args.put("selectionID", selectionID); + } catch (JSONException e) { + Log.e(LOGTAG, "Error building JSON arguments for TextSelection:End", e); + return; + } + + GeckoAppShell.notifyObservers("TextSelection:End", args.toString()); + } + + @Override + public void create() { + registerForEvents(); + } + + @Override + public void destroy() { + unregisterFromEvents(); + } + + private void registerForEvents() { + GeckoApp.getEventDispatcher().registerGeckoThreadListener(this, + "TextSelection:ActionbarInit", + "TextSelection:ActionbarStatus", + "TextSelection:ActionbarUninit", + "TextSelection:Update", + "TextSelection:Visibility"); + } + + private void unregisterFromEvents() { + GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this, + "TextSelection:ActionbarInit", + "TextSelection:ActionbarStatus", + "TextSelection:ActionbarUninit", + "TextSelection:Update", + "TextSelection:Visibility"); + } + + @Override + public void handleMessage(final String event, final JSONObject message) { + ThreadUtils.postToUiThread(new Runnable() { + @Override + public void run() { + handleOnMainThread(event, message); + } + }); + } + + private void handleOnMainThread(final String event, final JSONObject message) { + if ("TextSelection:ActionbarInit".equals(event)) { + Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, + TelemetryContract.Method.CONTENT, "text_selection"); + + selectionID = message.optString("selectionID"); + } else if ("TextSelection:ActionbarStatus".equals(event)) { + // Ensure async updates from SearchService for example are valid. + if (selectionID != message.optString("selectionID")) { + return; + } + + updateRect(message); + + if (!isRectVisible()) { + finishActionMode(); + } else { + startActionMode(TextAction.fromEventMessage(message)); + } + } else if ("TextSelection:ActionbarUninit".equals(event)) { + finishActionMode(); + } else if ("TextSelection:Update".equals(event)) { + startActionMode(TextAction.fromEventMessage(message)); + } else if ("TextSelection:Visibility".equals(event)) { + finishActionMode(); + } + } + + private void startActionMode(List<TextAction> actions) { + if (actionMode != null) { + actionModeCallback.updateActions(actions); + actionMode.invalidate(); + return; + } + + actionModeCallback = new FloatingActionModeCallback(this, actions); + actionMode = activity.startActionMode(actionModeCallback, ActionMode.TYPE_FLOATING); + } + + private boolean finishActionMode() { + if (actionMode != null) { + actionMode.finish(); + actionMode = null; + actionModeCallback = null; + return true; + } + + return false; + } + + /** + * If the content rect is a point (left == right and top == bottom) then this means that the + * content rect is not in the currently visible part. + */ + private boolean isRectVisible() { + // There's another case of an empty rect where just left == right but not top == bottom. + // That's the rect for a collapsed selection. While technically this rect isn't visible too + // we are not interested in this case because we do not want to hide the toolbar. + return contentRect.left != contentRect.right || contentRect.top != contentRect.bottom; + } + + private void updateRect(JSONObject message) { + try { + final double x = message.getDouble("x"); + final double y = (int) message.getDouble("y"); + final double width = (int) message.getDouble("width"); + final double height = (int) message.getDouble("height"); + + final float zoomFactor = layerView.getZoomFactor(); + layerView.getLocationInWindow(locationInWindow); + + contentRect = new Rect( + (int) (x * zoomFactor + locationInWindow[0]), + (int) (y * zoomFactor + locationInWindow[1]), + (int) ((x + width) * zoomFactor + locationInWindow[0]), + (int) ((y + height) * zoomFactor + locationInWindow[1] + + (height > 0 ? handlesOffset : 0))); + } catch (JSONException e) { + Log.w(LOGTAG, "Could not calculate content rect", e); + } + } +} diff --git a/mobile/android/base/java/org/mozilla/gecko/text/TextAction.java b/mobile/android/base/java/org/mozilla/gecko/text/TextAction.java new file mode 100644 index 000000000..9fcbce4a4 --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/text/TextAction.java @@ -0,0 +1,68 @@ +/* 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.gecko.text; + +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +/** + * Text selection action like "copy", "paste", .. + */ +public class TextAction { + private static final String LOGTAG = "GeckoTextAction"; + + private String id; + private String label; + private int order; + private int floatingOrder; + + private TextAction() {} + + public static List<TextAction> fromEventMessage(JSONObject message) { + final List<TextAction> actions = new ArrayList<>(); + + try { + final JSONArray array = message.getJSONArray("actions"); + + for (int i = 0; i < array.length(); i++) { + final JSONObject object = array.getJSONObject(i); + + final TextAction action = new TextAction(); + action.id = object.getString("id"); + action.label = object.getString("label"); + action.order = object.getInt("order"); + action.floatingOrder = object.optInt("floatingOrder", i); + + actions.add(action); + } + } catch (JSONException e) { + Log.w(LOGTAG, "Could not parse text actions", e); + } + + return actions; + } + + public String getId() { + return id; + } + + public String getLabel() { + return label; + } + + public int getOrder() { + return order; + } + + public int getFloatingOrder() { + return floatingOrder; + } +} diff --git a/mobile/android/base/java/org/mozilla/gecko/text/TextSelection.java b/mobile/android/base/java/org/mozilla/gecko/text/TextSelection.java new file mode 100644 index 000000000..29e8e43f5 --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/text/TextSelection.java @@ -0,0 +1,13 @@ +/* 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.gecko.text; + +public interface TextSelection { + void create(); + + boolean dismiss(); + + void destroy(); +} |