0

Don't trigger HW acceleration from Toasts on low-end devices.

BUG=520600

Review URL: https://codereview.chromium.org/1276523003

Cr-Commit-Position: refs/heads/master@{#343518}
This commit is contained in:
dskiba
2015-08-14 16:03:29 -07:00
committed by Commit bot
parent b708d49c73
commit 88634f4e2e
23 changed files with 304 additions and 25 deletions

@ -1312,6 +1312,43 @@ def _CheckJavaStyle(input_api, output_api):
black_list=_EXCLUDED_PATHS + input_api.DEFAULT_BLACK_LIST)
def _CheckAndroidToastUsage(input_api, output_api):
"""Checks that code uses org.chromium.ui.widget.Toast instead of
android.widget.Toast (Chromium Toast doesn't force hardware
acceleration on low-end devices, saving memory).
"""
toast_import_pattern = input_api.re.compile(
r'^import android\.widget\.Toast;$')
errors = []
sources = lambda affected_file: input_api.FilterSourceFile(
affected_file,
black_list=(_EXCLUDED_PATHS +
_TEST_CODE_EXCLUDED_PATHS +
input_api.DEFAULT_BLACK_LIST +
(r'^chromecast[\\\/].*',
r'^remoting[\\\/].*')),
white_list=(r'.*\.java$',))
for f in input_api.AffectedSourceFiles(sources):
for line_num, line in f.ChangedContents():
if toast_import_pattern.search(line):
errors.append("%s:%d" % (f.LocalPath(), line_num))
results = []
if errors:
results.append(output_api.PresubmitError(
'android.widget.Toast usage is detected. Android toasts use hardware'
' acceleration, and can be\ncostly on low-end devices. Please use'
' org.chromium.ui.widget.Toast instead.\n'
'Contact dskiba@chromium.org if you have any questions.',
errors))
return results
def _CheckAndroidCrLogUsage(input_api, output_api):
"""Checks that new logs using org.chromium.base.Log:
- Are using 'TAG' as variable name for the tags (warn)
@ -1530,6 +1567,7 @@ def _AndroidSpecificOnUploadChecks(input_api, output_api):
"""Groups checks that target android code."""
results = []
results.extend(_CheckAndroidCrLogUsage(input_api, output_api))
results.extend(_CheckAndroidToastUsage(input_api, output_api))
return results

@ -22,7 +22,6 @@ import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.Toast;
import org.chromium.base.CommandLine;
import org.chromium.base.MemoryPressureListener;
@ -93,6 +92,7 @@ import org.chromium.content.common.ContentSwitches;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.base.PageTransition;
import org.chromium.ui.widget.Toast;
/**
* This is the main activity for ChromeMobile when not running in document mode. All the tabs
@ -221,7 +221,7 @@ public class ChromeTabbedActivity extends ChromeActivity implements ActionBarDel
public void didAddTab(Tab tab, TabLaunchType type) {
if (type == TabLaunchType.FROM_LONGPRESS_BACKGROUND
&& !DeviceClassManager.enableAnimations(getApplicationContext())) {
Toast.makeText(getBaseContext(),
Toast.makeText(ChromeTabbedActivity.this,
R.string.open_in_new_tab_toast,
Toast.LENGTH_SHORT).show();
}

@ -10,7 +10,6 @@ import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Looper;
import android.util.Base64;
import android.widget.Toast;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.VisibleForTesting;
@ -20,6 +19,7 @@ import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.webapps.WebappLauncherActivity;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.common.ScreenOrientationConstants;
import org.chromium.ui.widget.Toast;
import java.io.ByteArrayOutputStream;
import java.util.UUID;

@ -13,7 +13,6 @@ import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
import android.webkit.URLUtil;
import android.widget.Toast;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
@ -26,6 +25,7 @@ import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.content.browser.ContentViewDownloadDelegate;
import org.chromium.content.browser.DownloadInfo;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.widget.Toast;
import java.io.File;

@ -19,7 +19,6 @@ import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.LongSparseArray;
import android.widget.Toast;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
@ -29,6 +28,7 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl;
import org.chromium.content.browser.DownloadController;
import org.chromium.content.browser.DownloadInfo;
import org.chromium.ui.widget.Toast;
import java.io.File;
import java.util.ArrayList;

@ -14,7 +14,6 @@ import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.util.Log;
import android.widget.Toast;
import com.google.android.gms.cast.CastMediaControlIntent;
@ -24,6 +23,7 @@ import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
import org.chromium.ui.widget.Toast;
import java.util.HashSet;
import java.util.Set;

@ -20,7 +20,6 @@ import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.support.v7.media.MediaSessionStatus;
import android.util.Log;
import android.widget.Toast;
import com.google.android.gms.cast.CastMediaControlIntent;
@ -30,6 +29,7 @@ import org.chromium.base.CommandLine;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
import org.chromium.ui.widget.Toast;
import java.net.URI;
import java.net.URISyntaxException;

@ -17,7 +17,6 @@ import android.support.v7.app.MediaRouteChooserDialogFragment;
import android.support.v7.app.MediaRouteControllerDialogFragment;
import android.support.v7.app.MediaRouteDialogFactory;
import android.util.Log;
import android.widget.Toast;
import com.google.android.gms.cast.CastMediaControlIntent;
@ -28,6 +27,7 @@ import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
import org.chromium.ui.widget.Toast;
import java.lang.ref.WeakReference;
import java.util.ArrayList;

@ -14,11 +14,11 @@ import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.widget.Toast;
import org.chromium.base.ThreadUtils;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.ui.widget.Toast;
import java.net.MalformedURLException;
import java.net.URL;

@ -12,13 +12,13 @@ import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.appmenu.ChromeAppMenuPropertiesDelegate;
import org.chromium.chrome.browser.widget.TintedDrawable;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.widget.Toast;
/**
* The toolbar at the bottom of the new tab page. Contains buttons to open the bookmarks and

@ -5,9 +5,9 @@
package org.chromium.chrome.browser.preferences;
import android.content.Context;
import android.widget.Toast;
import org.chromium.chrome.R;
import org.chromium.ui.widget.Toast;
/**
* Utilities and common methods to handle settings managed by policies.

@ -19,7 +19,6 @@ import android.view.View;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;
import android.widget.Toast;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
@ -27,6 +26,7 @@ import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
import org.chromium.chrome.browser.preferences.PreferencesLauncher;
import org.chromium.ui.text.SpanApplier;
import org.chromium.ui.text.SpanApplier.SpanInfo;
import org.chromium.ui.widget.Toast;
/**
* The promo screen encouraging users to enable Data Saver.

@ -23,7 +23,6 @@ import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckedTextView;
import android.widget.TextView;
import android.widget.Toast;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
@ -31,6 +30,7 @@ import org.chromium.chrome.browser.preferences.Preferences;
import org.chromium.chrome.browser.signin.AccountManagementFragment;
import org.chromium.sync.signin.ChromeSigninController;
import org.chromium.ui.text.SpanApplier;
import org.chromium.ui.widget.Toast;
import java.util.EnumSet;

@ -12,7 +12,6 @@ import android.preference.PreferenceFragment;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.help.HelpAndFeedback;
@ -20,6 +19,7 @@ import org.chromium.chrome.browser.preferences.ChromeBaseCheckBoxPreference;
import org.chromium.chrome.browser.preferences.ManagedPreferenceDelegate;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.ui.widget.Toast;
/**
* Fragment to keep track of the translate preferences.

@ -19,7 +19,6 @@ import android.view.MenuItem;
import android.view.inputmethod.EditorInfo;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.preferences.ChromeBaseCheckBoxPreference;
@ -34,6 +33,7 @@ import org.chromium.chrome.browser.preferences.ProtectedContentResetCredentialCo
import org.chromium.chrome.browser.widget.TintedDrawable;
import org.chromium.content.browser.MediaDrmCredentialManager;
import org.chromium.content.browser.MediaDrmCredentialManager.MediaDrmCredentialManagerCallback;
import org.chromium.ui.widget.Toast;
import java.util.ArrayList;
import java.util.Collections;

@ -17,7 +17,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.Toast;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
@ -29,6 +28,7 @@ import org.chromium.chrome.browser.util.ViewUtils;
import org.chromium.chrome.browser.widget.TintedImageButton;
import org.chromium.chrome.browser.widget.ToolbarProgressBar;
import org.chromium.ui.UiUtils;
import org.chromium.ui.widget.Toast;
/**
* Layout class that contains the base shared logic for manipulating the toolbar component. For

@ -10,19 +10,32 @@ import android.test.suitebuilder.annotation.SmallTest;
import org.chromium.base.test.util.Restriction;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeTabbedActivityTestBase;
import org.chromium.chrome.test.util.browser.contextmenu.ContextMenuUtils;
/**
* Tests that ChromeTabbedActivity is hardware accelerated only high-end devices.
*/
public class ChromeTabbedActivityHWATest extends ChromeTabbedActivityTestBase {
private static final String LINKED_URL = UrlUtils.encodeHtmlDataUri(
"<html>"
+ " <head>"
+ " <title>Linked Page</title>"
+ " </head>"
+ " <body style='margin: 0em; background: #ff00ff;'></body>"
+ "</html>");
private static final String LINK_ID = "testLink";
private static final String URL = UrlUtils.encodeHtmlDataUri(
"<html>"
+ " <head>"
+ " <title>Test Page</title>"
+ " </head>"
+ " <body style='margin: 0em; background: #ffff00;'></body>"
+ " <a href='" + LINKED_URL + "' id='" + LINK_ID + "'>Test Link</a>"
+ "</html>");
@Override
@ -40,4 +53,16 @@ public class ChromeTabbedActivityHWATest extends ChromeTabbedActivityTestBase {
public void testNoRenderThread() throws Exception {
Utils.assertNoRenderThread();
}
@Restriction(RESTRICTION_TYPE_LOW_END_DEVICE)
@SmallTest
public void testToastNoRenderThread() throws Exception {
// Open link in new tab (shows 'Tab Opened In Background' toast)
Tab tab = getActivity().getActivityTab();
ContextMenuUtils.selectContextMenuItem(this, tab, LINK_ID,
R.id.contextmenu_open_in_new_tab);
getInstrumentation().waitForIdleSync();
Utils.assertNoRenderThread();
}
}

@ -5,6 +5,7 @@
package org.chromium.chrome.browser.hardware_acceleration;
import android.app.Dialog;
import android.os.Build;
import android.view.View;
import android.view.ViewTreeObserver.OnPreDrawListener;
@ -14,6 +15,7 @@ import org.chromium.base.SysUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.content.browser.test.util.CallbackHelper;
import org.chromium.ui.widget.Toast;
import java.util.HashSet;
import java.util.Set;
@ -31,6 +33,11 @@ public class Utils {
public static void assertHardwareAcceleration(ChromeActivity activity) throws Exception {
assertActivityAcceleration(activity);
assertChildWindowAcceleration(activity);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Toasts are only HW accelerated on LOLLIPOP+
assertToastAcceleration(activity);
}
}
/**
@ -63,12 +70,33 @@ public class Utils {
});
listenerCalled.waitForCallback(0);
assertAcceleration(accelerated);
}
if (SysUtils.isLowEndDevice()) {
Assert.assertFalse(accelerated.get());
} else {
Assert.assertTrue(accelerated.get());
}
private static void assertToastAcceleration(final ChromeActivity activity)
throws Exception {
final AtomicBoolean accelerated = new AtomicBoolean();
final CallbackHelper listenerCalled = new CallbackHelper();
ThreadUtils.postOnUiThread(new Runnable() {
@Override
public void run() {
// We are using Toast.makeText(context, ...) instead of new Toast(context)
// because that Toast constructor is unused and is deleted by proguard.
Toast toast = Toast.makeText(activity, "", Toast.LENGTH_SHORT);
toast.setView(new View(activity) {
@Override
public void onAttachedToWindow() {
accelerated.set(isHardwareAccelerated());
listenerCalled.notifyCalled();
}
});
toast.show();
}
});
listenerCalled.waitForCallback(0);
assertAcceleration(accelerated);
}
private static void assertChildWindowAcceleration(final ChromeActivity activity)
@ -93,7 +121,10 @@ public class Utils {
});
listenerCalled.waitForCallback(0);
assertAcceleration(accelerated);
}
private static void assertAcceleration(AtomicBoolean accelerated) {
if (SysUtils.isLowEndDevice()) {
Assert.assertFalse(accelerated.get());
} else {

@ -18,7 +18,6 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.BaseSwitches;
@ -62,6 +61,7 @@ import org.chromium.sync.signin.AccountManagerHelper;
import org.chromium.sync.signin.ChromeSigninController;
import org.chromium.ui.base.ActivityWindowAndroid;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.widget.Toast;
/**
* The {@link android.app.Activity} component of a basic test shell to test Chrome features.

@ -10,12 +10,12 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
import android.text.TextUtils;
import android.widget.Toast;
import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.content.R;
import org.chromium.ui.widget.Toast;
import java.io.File;
import java.text.SimpleDateFormat;

@ -7,11 +7,11 @@ package org.chromium.ui.base;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.widget.Toast;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.ui.R;
import org.chromium.ui.widget.Toast;
/**
* Simple proxy that provides C++ code with an access pathway to the Android

@ -23,11 +23,11 @@ import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.ui.VSyncMonitor;
import org.chromium.ui.widget.Toast;
import java.lang.ref.WeakReference;
import java.util.HashMap;

@ -0,0 +1,185 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.ui.widget;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.chromium.base.SysUtils;
/**
* Toast wrapper, makes sure toasts don't trigger HW acceleration when created
* from activities that are not HW accelerated.
*
* Can (and should) also be used for Chromium-related additions and extensions.
*/
public class Toast {
public static final int LENGTH_SHORT = android.widget.Toast.LENGTH_SHORT;
public static final int LENGTH_LONG = android.widget.Toast.LENGTH_LONG;
private android.widget.Toast mToast;
private ViewGroup mSWLayout;
public Toast(Context context) {
this(context, new android.widget.Toast(context));
}
private Toast(Context context, android.widget.Toast toast) {
mToast = toast;
if (SysUtils.isLowEndDevice()
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
&& isHWAccelerationDisabled(context)) {
// Don't HW accelerate Toasts. Unfortunately the only way to do it is to make
// toast.getView().getContext().getApplicationInfo().targetSdkVersion return
// something less than LOLLIPOP (see WindowManagerGlobal.addView).
mSWLayout = new FrameLayout(new ContextWrapper(context) {
@Override
public ApplicationInfo getApplicationInfo() {
ApplicationInfo info = new ApplicationInfo(super.getApplicationInfo());
info.targetSdkVersion = Build.VERSION_CODES.KITKAT;
return info;
}
});
setView(toast.getView());
}
}
public android.widget.Toast getAndroidToast() {
return mToast;
}
public void show() {
mToast.show();
}
public void cancel() {
mToast.cancel();
}
public void setView(View view) {
if (mSWLayout != null) {
mSWLayout.removeAllViews();
if (view != null) {
mSWLayout.addView(view, WRAP_CONTENT, WRAP_CONTENT);
mToast.setView(mSWLayout);
} else {
// When null view is set we propagate it to the toast to trigger appropriate
// handling (for example show() throws an exception when view is null).
mToast.setView(null);
}
} else {
mToast.setView(view);
}
}
public View getView() {
if (mToast.getView() == null) {
return null;
}
if (mSWLayout != null) {
return mSWLayout.getChildAt(0);
} else {
return mToast.getView();
}
}
public void setDuration(int duration) {
mToast.setDuration(duration);
}
public int getDuration() {
return mToast.getDuration();
}
public void setMargin(float horizontalMargin, float verticalMargin) {
mToast.setMargin(horizontalMargin, verticalMargin);
}
public float getHorizontalMargin() {
return mToast.getHorizontalMargin();
}
public float getVerticalMargin() {
return mToast.getVerticalMargin();
}
public void setGravity(int gravity, int xOffset, int yOffset) {
mToast.setGravity(gravity, xOffset, yOffset);
}
public int getGravity() {
return mToast.getGravity();
}
public int getXOffset() {
return mToast.getXOffset();
}
public int getYOffset() {
return mToast.getYOffset();
}
public void setText(int resId) {
mToast.setText(resId);
}
public void setText(CharSequence s) {
mToast.setText(s);
}
@SuppressLint("ShowToast")
public static Toast makeText(Context context, CharSequence text, int duration) {
return new Toast(context, android.widget.Toast.makeText(context, text, duration));
}
@SuppressLint("ShowToast")
public static Toast makeText(Context context, int resId, int duration)
throws Resources.NotFoundException {
return new Toast(context, android.widget.Toast.makeText(context, resId, duration));
}
private static Activity getActivity(Context context) {
while (context != null) {
if (context instanceof Activity) {
return (Activity) context;
}
if (!(context instanceof ContextWrapper)) {
break;
}
context = ((ContextWrapper) context).getBaseContext();
}
return null;
}
private static boolean isHWAccelerationDisabled(Context context) {
Activity activity = getActivity(context);
if (activity == null) {
return false;
}
try {
ActivityInfo info = activity.getPackageManager().getActivityInfo(
activity.getComponentName(), 0);
return (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) == 0;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
}