Bug 1480031 - Truncate floating menu labels if they overflow screen width; r=VladBaicu a=RyanVM
authorPetru Lingurar <petru.lingurar@softvision.ro>
Fri, 12 Jul 2019 14:30:48 +0300
changeset 541479 43030692b97baac6e41b94f3a59829209fdd3aaa
parent 541478 07d0601ce1716b9f1d425a9d1b469eddd5410165
child 541480 50dcd65bee401205c50299cbe6fa5cc8dc67a5fa
push id11620
push userrmaries@mozilla.com
push dateMon, 22 Jul 2019 19:45:05 +0000
treeherdermozilla-beta@50dcd65bee40 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersVladBaicu, RyanVM
bugs1480031
milestone69.0
Bug 1480031 - Truncate floating menu labels if they overflow screen width; r=VladBaicu a=RyanVM There's currently a bug in Android's framework that manifests by placing the floating menu off-screen if a menu label overflows the menu's width. https://issuetracker.google.com/issues/137169336 To overcome this we'll manually check and truncate any menu label that could cause issues based on the floating menu style declared upstream. Differential Revision: https://phabricator.services.mozilla.com//D37684
mobile/android/app/src/main/res/values/dimens.xml
mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java
mobile/android/base/java/org/mozilla/gecko/util/WindowUtils.java
--- a/mobile/android/app/src/main/res/values/dimens.xml
+++ b/mobile/android/app/src/main/res/values/dimens.xml
@@ -145,16 +145,20 @@
     <dimen name="menu_item_textsize">16sp</dimen>
     <dimen name="menu_item_state_icon">18dp</dimen>
     <!-- This is chosen to match Android's listPreferredItemHeight.
          TODO: We should inherit these from the system.
          http://androidxref.com/4.2.2_r1/xref/frameworks/base/core/res/res/values/themes.xml#123 -->
     <dimen name="menu_item_row_height">64dip</dimen>
     <dimen name="menu_item_row_width">240dp</dimen>
     <dimen name="menu_popup_width">256dp</dimen>
+    <!-- https://android.googlesource.com/platform/frameworks/base/+/eb101a7/core/res/res/values/dimens.xml#384 -->
+    <dimen name="floating_toolbar_horizontal_margin">16dp</dimen>
+    <dimen name="floating_toolbar_menu_button_side_padding">18dp</dimen>
+    <dimen name="floating_toolbar_text_size">14sp</dimen>
     <dimen name="nav_button_border_width">1dp</dimen>
     <dimen name="prompt_service_group_padding_size">32dp</dimen>
     <dimen name="prompt_service_icon_size">36dp</dimen>
     <dimen name="prompt_service_icon_text_padding">10dp</dimen>
     <dimen name="prompt_service_inputs_padding">16dp</dimen>
     <dimen name="prompt_service_left_right_text_with_icon_padding">10dp</dimen>
     <dimen name="prompt_service_top_bottom_text_with_icon_padding">8dp</dimen>
     <dimen name="tabs_panel_indicator_width">60dp</dimen>
--- a/mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java
+++ b/mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java
@@ -1,24 +1,31 @@
 /* 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.content.Context;
+import android.content.res.Resources;
 import android.graphics.Rect;
+import android.graphics.Typeface;
 import android.os.Build;
+import android.support.annotation.NonNull;
+import android.text.TextPaint;
 import android.view.ActionMode;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 
-import org.mozilla.gecko.GeckoApp;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.R;
 import org.mozilla.gecko.util.GeckoBundle;
+import org.mozilla.gecko.util.WindowUtils;
 import org.mozilla.geckoview.GeckoViewBridge;
 
 import java.util.List;
 
 @TargetApi(Build.VERSION_CODES.M)
 public class FloatingActionModeCallback extends ActionMode.Callback2 {
     private FloatingToolbarTextSelection textSelection;
     private List<TextAction> actions;
@@ -38,17 +45,18 @@ public class FloatingActionModeCallback 
     }
 
     @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());
+            final String actionLabel = getOneLinerMenuText(action.getLabel());
+            menu.add(Menu.NONE, i, action.getFloatingOrder(), actionLabel);
         }
 
         return true;
     }
 
     @Override
     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
         final TextAction action = actions.get(item.getItemId());
@@ -65,9 +73,43 @@ public class FloatingActionModeCallback 
 
     @Override
     public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
         final Rect contentRect = textSelection.contentRect;
         if (contentRect != null) {
             outRect.set(contentRect);
         }
     }
+
+    // There's currently a bug in Android's framework that manifests by placing the floating menu
+    // off-screen if a menu label overflows the menu's width.
+    // https://issuetracker.google.com/issues/137169336
+    // To overcome this we'll manually check and truncate any menu label that could cause issues.
+    private static @NonNull String getOneLinerMenuText(@NonNull final String text) {
+        final int textLength = text.length();
+
+        // Avoid heavy unneeded calculations if the text is small
+        if (textLength < 30) {
+            return text;
+        }
+
+        // Simulate as best as possible the floating menu button style used in Android framework
+        // https://android.googlesource.com/platform/frameworks/base/+/eb101a7/core/java/com/android/internal/widget/FloatingToolbar.java
+        final Context context = GeckoAppShell.getApplicationContext();
+        final Resources resources = context.getResources();
+        final TextPaint textPaint = new TextPaint();
+        textPaint.setTextSize(resources.getDimensionPixelSize(R.dimen.floating_toolbar_text_size));
+        textPaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL));
+        final int screenWidth = WindowUtils.getScreenWidth(context);
+        final int menuWidth = screenWidth
+                - (2 * resources.getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin))
+                - (2 * resources.getDimensionPixelSize(R.dimen.floating_toolbar_menu_button_side_padding));
+
+        // If the text cannot fit on one line ellipsize it manually
+        final int charactersThatFit = textPaint.breakText(text, 0, textLength, true, menuWidth, null);
+        final boolean shouldEllipsize = textLength > charactersThatFit;
+        if (shouldEllipsize) {
+            return text.substring(0, charactersThatFit - 3) + "...";
+        } else {
+            return text;
+        }
+    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/util/WindowUtils.java
+++ b/mobile/android/base/java/org/mozilla/gecko/util/WindowUtils.java
@@ -2,16 +2,17 @@
  * 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.util;
 
 import android.content.Context;
 import android.os.Build;
+import android.support.annotation.NonNull;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display;
 import android.view.WindowManager;
 
 import java.lang.reflect.Method;
 
 public class WindowUtils {
@@ -47,12 +48,33 @@ public class WindowUtils {
             } catch (Exception e) {
                 // This is the best we can do.
                 tempWidth = display.getWidth();
                 tempHeight = display.getHeight();
                 Log.w(LOGTAG, "Couldn't use reflection to get the real display metrics.");
             }
 
             return Math.max(tempWidth, tempHeight);
+        }
+    }
 
+    public static int getScreenWidth(@NonNull final Context context) {
+        final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
+
+        if (Build.VERSION.SDK_INT >= 17) {
+            final DisplayMetrics realMetrics = new DisplayMetrics();
+            display.getRealMetrics(realMetrics);
+            return realMetrics.widthPixels;
+        } else {
+            int tempWidth;
+            try {
+                final Method getRawW = Display.class.getMethod("getRawWidth");
+                tempWidth = (Integer) getRawW.invoke(display);
+            } catch (Exception e) {
+                // This is the best we can do.
+                tempWidth = display.getWidth();
+                Log.w(LOGTAG, "Couldn't use reflection to get the real display metrics.");
+            }
+
+            return tempWidth;
         }
     }
 }