Bug 1325155 - 2. Convert text selection events to bundle events; r=sebastian

Convert the "TextSelection:*" events to use BundleEventListener /
GeckoBundle, in both ActionBarTextSelection.java and
FloatingToolbarTextSelection.java. UI events are used because the
listeners require the UI thread. The "TextSelection:Update" listeners
are removed because the event is no longer sent by ActionBarHandler.
This commit is contained in:
Jim Chen 2016-12-28 17:49:29 -05:00
parent 34ae4d687c
commit c3a02d7e8e
4 changed files with 115 additions and 151 deletions

View File

@ -8,7 +8,9 @@ import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.menu.GeckoMenuItem;
import org.mozilla.gecko.util.ResourceDrawableUtils;
import org.mozilla.gecko.text.TextSelection;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.ActionModeCompat.Callback;
@ -16,16 +18,16 @@ import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.MenuItem;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import android.util.Log;
class ActionBarTextSelection implements TextSelection, GeckoEventListener {
class ActionBarTextSelection implements TextSelection, BundleEventListener {
private static final String LOGTAG = "GeckoTextSelection";
private static final int SHUTDOWN_DELAY_MS = 250;
@ -33,9 +35,9 @@ class ActionBarTextSelection implements TextSelection, GeckoEventListener {
private boolean mDraggingHandles;
private String selectionID; // Unique ID provided for each selection action.
private int selectionID; // Unique ID provided for each selection action.
private String mCurrentItems;
private GeckoBundle[] mCurrentItems;
private TextSelectionActionModeCallback mCallback;
@ -65,11 +67,10 @@ class ActionBarTextSelection implements TextSelection, GeckoEventListener {
if (context == null) {
Log.e(LOGTAG, "Failed to initialize text selection because at least one context is null");
} else {
GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
"TextSelection:ActionbarInit",
"TextSelection:ActionbarStatus",
"TextSelection:ActionbarUninit",
"TextSelection:Update");
GeckoApp.getEventDispatcher().registerUiThreadListener(this,
"TextSelection:ActionbarInit",
"TextSelection:ActionbarStatus",
"TextSelection:ActionbarUninit");
}
}
@ -84,66 +85,51 @@ class ActionBarTextSelection implements TextSelection, GeckoEventListener {
if (context == null) {
Log.e(LOGTAG, "Do not unregister TextSelection:* listeners since context is null");
} else {
GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
GeckoApp.getEventDispatcher().unregisterUiThreadListener(this,
"TextSelection:ActionbarInit",
"TextSelection:ActionbarStatus",
"TextSelection:ActionbarUninit",
"TextSelection:Update");
"TextSelection:ActionbarUninit");
}
}
@Override
public void handleMessage(final String event, final JSONObject message) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
try {
if (event.equals("TextSelection:Update")) {
if (mActionModeTimerTask != null)
mActionModeTimerTask.cancel();
showActionMode(message.getJSONArray("actions"));
} else if (event.equals("TextSelection:ActionbarInit")) {
// Init / Open the action bar. Note the current selectionID,
// cancel any pending actionBar close.
Telemetry.sendUIEvent(TelemetryContract.Event.SHOW,
TelemetryContract.Method.CONTENT, "text_selection");
public void handleMessage(final String event, final GeckoBundle message,
final EventCallback callback) {
if ("TextSelection:ActionbarInit".equals(event)) {
// Init / Open the action bar. Note the current selectionID,
// cancel any pending actionBar close.
Telemetry.sendUIEvent(TelemetryContract.Event.SHOW,
TelemetryContract.Method.CONTENT, "text_selection");
selectionID = message.getString("selectionID");
mCurrentItems = null;
if (mActionModeTimerTask != null) {
mActionModeTimerTask.cancel();
}
} else if (event.equals("TextSelection:ActionbarStatus")) {
// Ensure async updates from SearchService for example are valid.
if (selectionID != message.optString("selectionID")) {
return;
}
// Update the actionBar actions as provided by Gecko.
showActionMode(message.getJSONArray("actions"));
} else if (event.equals("TextSelection:ActionbarUninit")) {
// Uninit the actionbar. Schedule a cancellable close
// action to avoid UI jank. (During SelectionAll for ex).
mCurrentItems = null;
mActionModeTimerTask = new ActionModeTimerTask();
mActionModeTimer.schedule(mActionModeTimerTask, SHUTDOWN_DELAY_MS);
}
} catch (JSONException e) {
Log.e(LOGTAG, "JSON exception", e);
}
selectionID = message.getInt("selectionID");
mCurrentItems = null;
if (mActionModeTimerTask != null) {
mActionModeTimerTask.cancel();
}
});
} else if ("TextSelection:ActionbarStatus".equals(event)) {
// Ensure async updates from SearchService for example are valid.
if (selectionID != message.getInt("selectionID")) {
return;
}
// Update the actionBar actions as provided by Gecko.
showActionMode(message.getBundleArray("actions"));
} else if ("TextSelection:ActionbarUninit".equals(event)) {
// Uninit the actionbar. Schedule a cancellable close
// action to avoid UI jank. (During SelectionAll for ex).
mCurrentItems = null;
mActionModeTimerTask = new ActionModeTimerTask();
mActionModeTimer.schedule(mActionModeTimerTask, SHUTDOWN_DELAY_MS);
}
}
private void showActionMode(final JSONArray items) {
String itemsString = items.toString();
if (itemsString.equals(mCurrentItems)) {
private void showActionMode(final GeckoBundle[] items) {
if (Arrays.equals(items, mCurrentItems)) {
return;
}
mCurrentItems = itemsString;
mCurrentItems = items;
if (mCallback != null) {
mCallback.updateItems(items);
@ -167,14 +153,14 @@ class ActionBarTextSelection implements TextSelection, GeckoEventListener {
}
private class TextSelectionActionModeCallback implements Callback {
private JSONArray mItems;
private GeckoBundle[] mItems;
private ActionModeCompat mActionMode;
public TextSelectionActionModeCallback(JSONArray items) {
public TextSelectionActionModeCallback(final GeckoBundle[] items) {
mItems = items;
}
public void updateItems(JSONArray items) {
public void updateItems(final GeckoBundle[] items) {
mItems = items;
if (mActionMode != null) {
mActionMode.invalidate();
@ -189,32 +175,31 @@ class ActionBarTextSelection implements TextSelection, GeckoEventListener {
@Override
public boolean onPrepareActionMode(final ActionModeCompat mode, final GeckoMenu menu) {
// Android would normally expect us to only update the state of menu items here
// To make the js-java interaction a bit simpler, we just wipe out the menu here and recreate all
// the javascript menu items in onPrepare instead. This will be called any time invalidate() is called on the
// action mode.
// Android would normally expect us to only update the state of menu items
// here To make the js-java interaction a bit simpler, we just wipe out the
// menu here and recreate all the javascript menu items in onPrepare instead.
// This will be called any time invalidate() is called on the action mode.
menu.clear();
int length = mItems.length();
final int length = mItems.length;
for (int i = 0; i < length; i++) {
try {
final JSONObject obj = mItems.getJSONObject(i);
final GeckoMenuItem menuitem = (GeckoMenuItem) menu.add(0, i, 0, obj.optString("label"));
final int actionEnum = obj.optBoolean("showAsAction") ? GeckoMenuItem.SHOW_AS_ACTION_ALWAYS : GeckoMenuItem.SHOW_AS_ACTION_NEVER;
menuitem.setShowAsAction(actionEnum, R.attr.menuItemActionModeStyle);
final GeckoBundle obj = mItems[i];
final GeckoMenuItem menuitem = (GeckoMenuItem)
menu.add(0, i, 0, obj.getString("label"));
final int actionEnum = obj.getBoolean("showAsAction") ?
GeckoMenuItem.SHOW_AS_ACTION_ALWAYS : GeckoMenuItem.SHOW_AS_ACTION_NEVER;
menuitem.setShowAsAction(actionEnum, R.attr.menuItemActionModeStyle);
final String iconString = obj.optString("icon");
ResourceDrawableUtils.getDrawable(context, iconString, new ResourceDrawableUtils.BitmapLoader() {
@Override
public void onBitmapFound(Drawable d) {
if (d != null) {
menuitem.setIcon(d);
}
final String iconString = obj.getString("icon");
ResourceDrawableUtils.getDrawable(context, iconString,
new ResourceDrawableUtils.BitmapLoader() {
@Override
public void onBitmapFound(Drawable d) {
if (d != null) {
menuitem.setIcon(d);
}
});
} catch (Exception ex) {
Log.i(LOGTAG, "Exception building menu", ex);
}
}
});
}
return true;
}
@ -227,14 +212,9 @@ class ActionBarTextSelection implements TextSelection, GeckoEventListener {
@Override
public boolean onActionItemClicked(ActionModeCompat mode, MenuItem item) {
try {
final JSONObject obj = mItems.getJSONObject(item.getItemId());
GeckoAppShell.notifyObservers("TextSelection:Action", obj.optString("id"));
return true;
} catch (Exception ex) {
Log.i(LOGTAG, "Exception calling action", ex);
}
return false;
final GeckoBundle obj = mItems[item.getItemId()];
GeckoAppShell.notifyObservers("TextSelection:Action", obj.getString("id"));
return true;
}
// Called when the user exits the action mode

View File

@ -21,18 +21,18 @@ 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.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
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 {
public class FloatingToolbarTextSelection implements TextSelection, BundleEventListener {
private static final String LOGTAG = "GeckoFloatTextSelection";
// This is an additional offset we add to the height of the selection. This will avoid that the
@ -46,7 +46,7 @@ public class FloatingToolbarTextSelection implements TextSelection, GeckoEventLi
private ActionMode actionMode;
private FloatingActionModeCallback actionModeCallback;
private String selectionID;
private int selectionID;
/* package-private */ Rect contentRect;
public FloatingToolbarTextSelection(Activity activity, LayerView layerView) {
@ -69,7 +69,7 @@ public class FloatingToolbarTextSelection implements TextSelection, GeckoEventLi
}
private void endTextSelection() {
if (TextUtils.isEmpty(selectionID)) {
if (selectionID == 0) {
return;
}
@ -95,42 +95,33 @@ public class FloatingToolbarTextSelection implements TextSelection, GeckoEventLi
}
private void registerForEvents() {
GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
GeckoApp.getEventDispatcher().registerUiThreadListener(this,
"TextSelection:ActionbarInit",
"TextSelection:ActionbarStatus",
"TextSelection:ActionbarUninit",
"TextSelection:Update",
"TextSelection:Visibility");
}
private void unregisterFromEvents() {
GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
GeckoApp.getEventDispatcher().unregisterUiThreadListener(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) {
@Override // BundleEventListener
public void handleMessage(final String event, final GeckoBundle message,
final EventCallback callback) {
if ("TextSelection:ActionbarInit".equals(event)) {
Telemetry.sendUIEvent(TelemetryContract.Event.SHOW,
TelemetryContract.Method.CONTENT, "text_selection");
selectionID = message.optString("selectionID");
selectionID = message.getInt("selectionID");
} else if ("TextSelection:ActionbarStatus".equals(event)) {
// Ensure async updates from SearchService for example are valid.
if (selectionID != message.optString("selectionID")) {
if (selectionID != message.getInt("selectionID")) {
return;
}
@ -141,10 +132,10 @@ public class FloatingToolbarTextSelection implements TextSelection, GeckoEventLi
} 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();
}
@ -183,24 +174,20 @@ public class FloatingToolbarTextSelection implements TextSelection, GeckoEventLi
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");
private void updateRect(final GeckoBundle message) {
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);
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);
}
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)));
}
}

View File

@ -10,6 +10,8 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.util.GeckoBundle;
import java.util.ArrayList;
import java.util.List;
@ -26,25 +28,20 @@ public class TextAction {
private TextAction() {}
public static List<TextAction> fromEventMessage(JSONObject message) {
public static List<TextAction> fromEventMessage(final GeckoBundle message) {
final List<TextAction> actions = new ArrayList<>();
final GeckoBundle[] array = message.getBundleArray("actions");
try {
final JSONArray array = message.getJSONArray("actions");
for (int i = 0; i < array.length; i++) {
final GeckoBundle object = array[i];
final TextAction action = new TextAction();
for (int i = 0; i < array.length(); i++) {
final JSONObject object = array.getJSONObject(i);
action.id = object.getString("id");
action.label = object.getString("label");
action.order = object.getInt("order");
action.floatingOrder = object.getInt("floatingOrder", 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);
actions.add(action);
}
return actions;

View File

@ -143,7 +143,7 @@ var ActionBarHandler = {
this._boundingClientRect = boundingClientRect;
// Open the ActionBar, send it's actions list.
Messaging.sendRequest({
WindowEventDispatcher.sendRequest({
type: "TextSelection:ActionbarInit",
selectionID: this._selectionID,
});
@ -156,7 +156,7 @@ var ActionBarHandler = {
* Called when content is scrolled and handles are hidden.
*/
_updateVisibility: function() {
Messaging.sendRequest({
WindowEventDispatcher.sendRequest({
type: "TextSelection:Visibility",
selectionID: this._selectionID,
});
@ -207,7 +207,7 @@ var ActionBarHandler = {
}
// Close the ActionBar.
Messaging.sendRequest({
WindowEventDispatcher.sendRequest({
type: "TextSelection:ActionbarUninit",
});
@ -266,7 +266,7 @@ var ActionBarHandler = {
});
if (sendAlways || !actionsMatch) {
Messaging.sendRequest({
WindowEventDispatcher.sendRequest({
type: "TextSelection:ActionbarStatus",
selectionID: this._selectionID,
actions: actions,