зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 10 changesets (bug 1697844) for causing mpu failures in test_visualmetrics.py
CLOSED TREE Backed out changeset 63b3268b0d2d (bug 1697844) Backed out changeset 875b2aa342d9 (bug 1697844) Backed out changeset 4fb528aaf7d5 (bug 1697844) Backed out changeset 6ef5e1c9ca21 (bug 1697844) Backed out changeset 430d6c940eb9 (bug 1697844) Backed out changeset 7a306f28dc64 (bug 1697844) Backed out changeset 871a40e2fc00 (bug 1697844) Backed out changeset 083e9ce71d14 (bug 1697844) Backed out changeset b53930a3f065 (bug 1697844) Backed out changeset 24326d04dd37 (bug 1697844)
This commit is contained in:
Родитель
79f333bade
Коммит
ced948f22d
|
@ -1290,6 +1290,13 @@ set_config("MOZ_APP_VERSION_DISPLAY", milestone.app_version_display)
|
|||
add_old_configure_assignment("MOZ_APP_VERSION", milestone.app_version)
|
||||
|
||||
|
||||
# Dummy function for availability in toolkit/moz.configure. Overridden in
|
||||
# mobile/android/moz.configure.
|
||||
@depends(milestone.is_nightly)
|
||||
def fennec_nightly(is_nightly):
|
||||
return is_nightly
|
||||
|
||||
|
||||
# The app update channel is 'default' when not supplied. The value is used in
|
||||
# the application's confvars.sh (and is made available to a project specific
|
||||
# moz.configure).
|
||||
|
|
|
@ -7,6 +7,8 @@ ac_add_options --enable-debug
|
|||
ac_add_options --with-android-min-sdk=21
|
||||
ac_add_options --target=aarch64-linux-android
|
||||
|
||||
export FENNEC_NIGHTLY=1
|
||||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
ac_add_options --with-branding=mobile/android/branding/nightly
|
||||
|
|
|
@ -6,6 +6,8 @@ ac_add_options --target=aarch64-linux-android
|
|||
|
||||
ac_add_options --with-branding=mobile/android/branding/nightly
|
||||
|
||||
export FENNEC_NIGHTLY=1
|
||||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
. "$topsrcdir/mobile/android/config/mozconfigs/common.override"
|
||||
|
|
|
@ -29,6 +29,8 @@ ac_add_options --disable-tests
|
|||
ac_add_options --with-android-min-sdk=16
|
||||
ac_add_options --target=arm-linux-androideabi
|
||||
|
||||
export FENNEC_NIGHTLY=1
|
||||
|
||||
ac_add_options --with-branding=mobile/android/branding/nightly
|
||||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
|
|
@ -12,6 +12,8 @@ ac_add_options --enable-debug
|
|||
ac_add_options --with-android-min-sdk=16
|
||||
ac_add_options --target=arm-linux-androideabi
|
||||
|
||||
export FENNEC_NIGHTLY=1
|
||||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
ac_add_options --with-branding=mobile/android/branding/nightly
|
||||
|
|
|
@ -12,6 +12,8 @@ ac_add_options --enable-debug
|
|||
ac_add_options --with-android-min-sdk=16
|
||||
ac_add_options --target=arm-linux-androideabi
|
||||
|
||||
export FENNEC_NIGHTLY=1
|
||||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
ac_add_options --with-branding=mobile/android/branding/nightly
|
||||
|
|
|
@ -11,6 +11,8 @@ ac_add_options --target=arm-linux-androideabi
|
|||
|
||||
ac_add_options --with-branding=mobile/android/branding/nightly
|
||||
|
||||
export FENNEC_NIGHTLY=1
|
||||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
. "$topsrcdir/mobile/android/config/mozconfigs/common.override"
|
||||
|
|
|
@ -24,6 +24,8 @@ ac_add_options --disable-tests
|
|||
ac_add_options --with-android-min-sdk=16
|
||||
ac_add_options --target=arm-linux-androideabi
|
||||
|
||||
export FENNEC_NIGHTLY=1
|
||||
|
||||
ac_add_options --with-branding=mobile/android/branding/nightly
|
||||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
|
|
@ -12,6 +12,8 @@ ac_add_options --enable-debug
|
|||
ac_add_options --with-android-min-sdk=16
|
||||
ac_add_options --target=i686-linux-android
|
||||
|
||||
export FENNEC_NIGHTLY=1
|
||||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
ac_add_options --with-branding=mobile/android/branding/nightly
|
||||
|
|
|
@ -11,6 +11,8 @@ ac_add_options --target=i686-linux-android
|
|||
|
||||
ac_add_options --with-branding=mobile/android/branding/nightly
|
||||
|
||||
export FENNEC_NIGHTLY=1
|
||||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
. "$topsrcdir/mobile/android/config/mozconfigs/common.override"
|
||||
|
|
|
@ -7,6 +7,8 @@ ac_add_options --enable-debug
|
|||
ac_add_options --with-android-min-sdk=21
|
||||
ac_add_options --target=x86_64-linux-android
|
||||
|
||||
export FENNEC_NIGHTLY=1
|
||||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
ac_add_options --with-branding=mobile/android/branding/nightly
|
||||
|
|
|
@ -6,6 +6,8 @@ ac_add_options --target=x86_64-linux-android
|
|||
|
||||
ac_add_options --with-branding=mobile/android/branding/nightly
|
||||
|
||||
export FENNEC_NIGHTLY=1
|
||||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
. "$topsrcdir/mobile/android/config/mozconfigs/common.override"
|
||||
|
|
|
@ -0,0 +1,348 @@
|
|||
.. -*- Mode: rst; fill-column: 80; -*-
|
||||
|
||||
==============
|
||||
UI Telemetry
|
||||
==============
|
||||
|
||||
Fennec records UI events using a telemetry framework called UITelemetry.
|
||||
|
||||
Some links:
|
||||
|
||||
- `Project page <https://wiki.mozilla.org/Mobile/Projects/Telemetry_probes_for_Fennec_UI_elements>`_
|
||||
- `Wiki page <https://wiki.mozilla.org/Mobile/Fennec/Android/UITelemetry>`_
|
||||
- `User research notes <https://wiki.mozilla.org/Mobile/User_Experience/Research>`_
|
||||
|
||||
Sessions
|
||||
========
|
||||
|
||||
**Sessions** are essentially scopes. They are meant to provide context to
|
||||
events; this allows events to be simpler and more reusable. Sessions are
|
||||
usually bound to some component of the UI, some user action with a duration, or
|
||||
some transient state.
|
||||
|
||||
For example, a session might be begun when a user begins interacting with a
|
||||
menu, and stopped when the interaction ends. Or a session might encapsulate
|
||||
period of no network connectivity, the first five seconds after the browser
|
||||
launched, the time spent with an active download, or a guest mode session.
|
||||
|
||||
Sessions implicitly record the duration of the interaction.
|
||||
|
||||
A simple use-case for sessions is the bookmarks panel in about:home. We start a
|
||||
session when the user swipes into the panel, and stop it when they swipe away.
|
||||
This bookmarks session does two things: firstly, it gives scope to any generic
|
||||
event that may occur within the panel (*e.g.*, loading a URL). Secondly, it
|
||||
allows us to figure out how much time users are spending in the bookmarks
|
||||
panel.
|
||||
|
||||
To start a session, call ``Telemetry.startUISession(String sessionName)``.
|
||||
|
||||
``sessionName``
|
||||
The name of the session. Session names should be brief, lowercase, and should describe which UI
|
||||
component the user is interacting with. In certain cases where the UI component is dynamic, they could include an ID, essential to identifying that component. An example of this is dynamic home panels: we use session names of the format ``homepanel:<panel_id>`` to identify home panel sessions.
|
||||
|
||||
To stop a session, call ``Telemetry.stopUISession(String sessionName, String reason)``.
|
||||
|
||||
``sessionName``
|
||||
The name of the open session
|
||||
|
||||
``reason`` (Optional)
|
||||
A descriptive cause for ending the session. It should be brief, lowercase, and generic so it can be reused in different places. Examples reasons are:
|
||||
|
||||
``switched``
|
||||
The user transitioned to a UI element of equal level.
|
||||
|
||||
``exit``
|
||||
The user left for an entirely different element.
|
||||
|
||||
Experiments
|
||||
===========
|
||||
|
||||
**Experiment** is a special type of a session. Experiments denote an "experiment scope".
|
||||
Just like sessions, multiple experiments might be active at the same time. Experiments are recorded
|
||||
by tagging events with an ``experiment.1:varying_experiment_name`` session. However, on the data
|
||||
pipeline side, experiment's name is parsed and stored separately (in the ``experiments`` column).
|
||||
Original ``experiment.1`` substring of the session name is stripped away.
|
||||
|
||||
For example, the following telemetry data:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
...
|
||||
sessions: ["awesomebar.1", "experiment.1:onboarding_a"]
|
||||
...
|
||||
|
||||
Will be stored as:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
...
|
||||
experiments: ["onboarding_a"],
|
||||
sessions: ["awesomebar.1"]
|
||||
...
|
||||
|
||||
Consider a case of A/B testing different variations of Activity Stream. We might want to run two
|
||||
experimental cohorts, each of them seeing a unique variation of the functionality. Depending on which
|
||||
cohort the user is in, their events while interacting with the experiments will be tagged accordingly
|
||||
with a current experiment name, while also retaining session information.
|
||||
On the analytics side it's then possible to compare performance of various metrics between cohorts.
|
||||
|
||||
Events
|
||||
======
|
||||
|
||||
Events capture key occurrences. They should be brief and simple, and should not contain sensitive or excess information. Context for events should come from the session (scope). An event can be created with four fields (via ``Telemetry.sendUIEvent``): ``action``, ``method``, ``extras``, and ``timestamp``.
|
||||
|
||||
``action``
|
||||
The name of the event. Should be brief and lowercase. If needed, you can make use of namespacing with a '``.``' separator. Example event names: ``panel.switch``, ``panel.enable``, ``panel.disable``, ``panel.install``.
|
||||
|
||||
``method`` (Optional)
|
||||
Used for user actions that can be performed in many ways. This field specifies the method by which the action was performed. For example, users can add an item to their reading list either by long-tapping the reader icon in the address bar, or from within reader mode. We would use the same event name for both user actions but specify two methods: ``addressbar`` and ``readermode``.
|
||||
|
||||
``extras`` (Optional)
|
||||
For extra information that may be useful in understanding the event (such as a short string, a json blob, etc).
|
||||
|
||||
``timestamp`` (Optional)
|
||||
The time at which the event occurred. If not specified, this field defaults to the current value of the realtime clock.
|
||||
|
||||
Versioning
|
||||
==========
|
||||
|
||||
As a we improve on our Telemetry methods, it is foreseeable that our probes will change over time. Different versions of a probe could carry different data or have different interpretations on the server-side. To make it easier for the server to handle these changes, you should add version numbers to your event and session names. An example of a versioned session is ``homepanel.1``; this is version 1 of the ``homepanel`` session. This approach should also be applied to event names, an example being: ``panel.enable.1`` and ``panel.enable.2``.
|
||||
|
||||
|
||||
Clock
|
||||
=====
|
||||
|
||||
Times are relative to either elapsed realtime (an arbitrary monotonically increasing clock that continues to tick when the device is asleep), or elapsed uptime (which doesn't tick when the device is in deep sleep). We default to elapsed realtime.
|
||||
|
||||
See the documentation in `the source <https://searchfox.org/mozilla-central/source/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/RuntimeTelemetry.java>`_ for more details.
|
||||
|
||||
Dictionary
|
||||
==========
|
||||
|
||||
Events
|
||||
------
|
||||
``action.1``
|
||||
Generic action, usually for tracking menu and toolbar actions.
|
||||
|
||||
``cancel.1``
|
||||
Cancel a state, action, etc.
|
||||
|
||||
``cast.1``
|
||||
Start casting a video.
|
||||
|
||||
``edit.1``
|
||||
Sent when the user edits a top site.
|
||||
|
||||
``launch.1``
|
||||
Launching (opening) an external application.
|
||||
Note: Only used in JavaScript for now.
|
||||
|
||||
``loadurl.1``
|
||||
Loading a URL.
|
||||
|
||||
``locale.browser.reset.1``
|
||||
When the user chooses "System default" in the browser locale picker.
|
||||
|
||||
``locale.browser.selected.1``
|
||||
When the user chooses a locale in the browser locale picker. The selected
|
||||
locale is provided as the extra.
|
||||
|
||||
``locale.browser.unselected.1``
|
||||
When the user chose a different locale in the browser locale picker, this
|
||||
event is fired with the previous locale as the extra. If the previous locale
|
||||
could not be determined, "unknown" is provided.
|
||||
|
||||
``neterror.1``
|
||||
When the user performs actions on the in-content network error page. This should probably be a ``Session``, but it's difficult to start and stop the session reliably.
|
||||
|
||||
``panel.hide.1``
|
||||
Hide a built-in home panel.
|
||||
|
||||
``panel.move.1``
|
||||
Move a home panel up or down.
|
||||
|
||||
``panel.remove.1``
|
||||
Remove a custom home panel.
|
||||
|
||||
``panel.setdefault.1``
|
||||
Set default home panel.
|
||||
|
||||
``panel.show.1``
|
||||
Show a hidden built-in home panel.
|
||||
|
||||
``pin.1``, ``unpin.1``
|
||||
Sent when the user pinned or unpinned a top site.
|
||||
|
||||
``policynotification.success.1:true``
|
||||
Sent when a user has accepted the data notification policy. Can be ``false``
|
||||
instead of ``true`` if an error occurs.
|
||||
|
||||
``pwa.1``
|
||||
When the user interacts with a Progressive Web Application
|
||||
|
||||
``sanitize.1``
|
||||
Sent when the user chooses to clear private data.
|
||||
|
||||
``save.1``, ``unsave.1``
|
||||
Saving or unsaving a resource (reader, bookmark, etc.) for viewing later.
|
||||
|
||||
``search.1``
|
||||
Sent when the user performs a search. Currently used in the search activity.
|
||||
|
||||
``search.remove.1``
|
||||
Sent when the user removes a search engine.
|
||||
|
||||
``search.restore.1``
|
||||
Sent when the user restores the search engine configuration back to the built-in configuration.
|
||||
|
||||
``search.setdefault.1``
|
||||
Sent when the user sets a search engine to be the default.
|
||||
|
||||
``search.widget.1``
|
||||
Sent when the user initiates a search through the widget.
|
||||
|
||||
``share.1``
|
||||
Sharing content.
|
||||
|
||||
``show.1``
|
||||
Sent when a contextual UI element is shown to the user.
|
||||
|
||||
``undo.1``
|
||||
Sent when performing an undo-style action, like undoing a closed tab.
|
||||
|
||||
Methods
|
||||
-------
|
||||
``actionbar``
|
||||
Action triggered from an ActionBar UI.
|
||||
|
||||
``back``
|
||||
Action triggered from the back button.
|
||||
|
||||
``banner``
|
||||
Action triggered from a banner (such as HomeBanner).
|
||||
|
||||
``button``
|
||||
Action triggered from a button.
|
||||
Note: Only used in JavaScript for now.
|
||||
|
||||
``content``
|
||||
Action triggered from a content page.
|
||||
|
||||
``contextmenu``
|
||||
Action triggered from a contextmenu. Could be from chrome or content.
|
||||
|
||||
``dialog``
|
||||
Action triggered from a dialog.
|
||||
|
||||
``doorhanger``
|
||||
Action triggered from a doorhanger popup prompt.
|
||||
|
||||
``griditem``
|
||||
Action triggered from a griditem, such as those used in Top Sites panel.
|
||||
|
||||
``homescreen``
|
||||
Action triggered from a homescreen shortcut icon.
|
||||
|
||||
``intent``
|
||||
Action triggered from a system Intent, usually sent from the OS.
|
||||
|
||||
``list``
|
||||
Action triggered from an unmanaged list of items, usually provided by the OS.
|
||||
|
||||
``listitem``
|
||||
Action triggered from a listitem.
|
||||
|
||||
``menu``
|
||||
Action triggered from the main menu.
|
||||
|
||||
``notification``
|
||||
Action triggered from a system notification.
|
||||
|
||||
``pageaction``
|
||||
Action triggered from a pageaction, displayed in the URL bar.
|
||||
|
||||
``service``
|
||||
Action triggered from an automatic system making a decision.
|
||||
|
||||
``settings``
|
||||
Action triggered from a content page.
|
||||
|
||||
``shareoverlay``
|
||||
Action triggered from a content page.
|
||||
|
||||
``suggestion``
|
||||
Action triggered from a suggested result, like those from search engines or default tiles.
|
||||
|
||||
``system``
|
||||
Action triggered from an OS level action, like application foreground / background.
|
||||
|
||||
``toast``
|
||||
Action triggered from an unobtrusive, temporary notification.
|
||||
|
||||
``widget``
|
||||
Action triggered from a widget placed on the homescreen.
|
||||
|
||||
Buttons
|
||||
-------
|
||||
``preferences``
|
||||
Button action triggered by the "Sign in" Settings menu item.
|
||||
|
||||
``notification``
|
||||
Button action triggered by signing in from a Leanplum notification.
|
||||
|
||||
``firstrun-welcome``
|
||||
Button action triggered by the Onboarding "WELCOME" panel's "Sign up" button.
|
||||
|
||||
``firstrun-sync``
|
||||
Button action triggered by the Onboarding "SYNC" panel's "Sign up" button.
|
||||
|
||||
``firstrun-sendtab``
|
||||
Button action triggered by the Onboarding "SEND TAB" panel's "Sign up" button.
|
||||
|
||||
``awesomescreen-signin``
|
||||
Button action triggered by the Awesomescreen's "Sign in" link.
|
||||
|
||||
``awesomescreen-signup``
|
||||
Button action triggered by the Awesomescreen's "Sign up" button.
|
||||
|
||||
``awesomescreen-signup-dismiss``
|
||||
Button action triggered by dismissing the Awesomescreen's "Welcome to Firefox" panel
|
||||
|
||||
``firefox_promo_open``
|
||||
Button action triggered by the opening the promo webpage from Awesomescreen's Firefox promo banner.
|
||||
|
||||
``firefox_promo_dismiss``
|
||||
Button action triggered by dismissing the Awesomescreen's Firefox promo banner.
|
||||
|
||||
Sessions
|
||||
--------
|
||||
``activitystream.1``
|
||||
Activity Stream is active.
|
||||
|
||||
``awesomescreen.1``
|
||||
Awesomescreen (including frecency search) is active.
|
||||
|
||||
``experiment.1``
|
||||
Special, non-recorded session which is used to denote experiments. See ``Experiments`` section above.
|
||||
Must be used in conjunction with an experiment's name, e.g. ``experiment.1:varying_experiment_name``.
|
||||
|
||||
``firstrun.1``
|
||||
Started the very first time we believe the application has been launched.
|
||||
|
||||
``frecency.1``
|
||||
Awesomescreen frecency search is active.
|
||||
|
||||
``homepanel.1``
|
||||
Started when a user enters a given home panel.
|
||||
Session name is dynamic, encoded as "homepanel.1:<panel_id>"
|
||||
Built-in home panels have fixed IDs
|
||||
|
||||
``reader.1``
|
||||
Reader viewer becomes active in the foreground.
|
||||
|
||||
``searchactivity.1``
|
||||
Started when the user launches the search activity (onStart) and stopped
|
||||
when they leave the search activity.
|
||||
|
||||
``settings.1``
|
||||
Settings activity is active.
|
|
@ -8,6 +8,6 @@
|
|||
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
|
||||
|
||||
<suppressions>
|
||||
<suppress id="checkstyle:javadocmethod"
|
||||
<suppress checks="JavadocMethod"
|
||||
files="org[/\\]mozilla[/\\]gecko[/\\]"/>
|
||||
</suppressions>
|
||||
|
|
|
@ -24,7 +24,6 @@ import org.mozilla.geckoview.BuildConfig;
|
|||
import org.mozilla.geckoview.R;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
@ -78,24 +77,9 @@ import android.webkit.MimeTypeMap;
|
|||
public class GeckoAppShell {
|
||||
private static final String LOGTAG = "GeckoAppShell";
|
||||
|
||||
/*
|
||||
* Keep these values consistent with |SensorType| in HalSensor.h
|
||||
*/
|
||||
public static final int SENSOR_ORIENTATION = 0;
|
||||
public static final int SENSOR_ACCELERATION = 1;
|
||||
public static final int SENSOR_PROXIMITY = 2;
|
||||
public static final int SENSOR_LINEAR_ACCELERATION = 3;
|
||||
public static final int SENSOR_GYROSCOPE = 4;
|
||||
public static final int SENSOR_LIGHT = 5;
|
||||
public static final int SENSOR_ROTATION_VECTOR = 6;
|
||||
public static final int SENSOR_GAME_ROTATION_VECTOR = 7;
|
||||
|
||||
// We have static members only.
|
||||
private GeckoAppShell() { }
|
||||
|
||||
// Name for app-scoped prefs
|
||||
public static final String APP_PREFS_NAME = "GeckoApp";
|
||||
|
||||
private static class GeckoCrashHandler extends CrashHandler {
|
||||
|
||||
public GeckoCrashHandler(final Class<? extends Service> handlerService) {
|
||||
|
@ -121,7 +105,7 @@ public class GeckoAppShell {
|
|||
try {
|
||||
if (exc instanceof OutOfMemoryError) {
|
||||
final SharedPreferences prefs =
|
||||
getApplicationContext().getSharedPreferences(APP_PREFS_NAME, 0);
|
||||
GeckoSharedPrefs.forApp(getApplicationContext());
|
||||
final SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(PREFS_OOM_EXCEPTION, true);
|
||||
|
||||
|
@ -398,11 +382,11 @@ public class GeckoAppShell {
|
|||
case Sensor.TYPE_LINEAR_ACCELERATION:
|
||||
case Sensor.TYPE_ORIENTATION:
|
||||
if (sensorType == Sensor.TYPE_ACCELEROMETER) {
|
||||
halType = SENSOR_ACCELERATION;
|
||||
halType = GeckoHalDefines.SENSOR_ACCELERATION;
|
||||
} else if (sensorType == Sensor.TYPE_LINEAR_ACCELERATION) {
|
||||
halType = SENSOR_LINEAR_ACCELERATION;
|
||||
halType = GeckoHalDefines.SENSOR_LINEAR_ACCELERATION;
|
||||
} else {
|
||||
halType = SENSOR_ORIENTATION;
|
||||
halType = GeckoHalDefines.SENSOR_ORIENTATION;
|
||||
}
|
||||
x = s.values[0];
|
||||
y = s.values[1];
|
||||
|
@ -410,22 +394,22 @@ public class GeckoAppShell {
|
|||
break;
|
||||
|
||||
case Sensor.TYPE_GYROSCOPE:
|
||||
halType = SENSOR_GYROSCOPE;
|
||||
halType = GeckoHalDefines.SENSOR_GYROSCOPE;
|
||||
x = (float) Math.toDegrees(s.values[0]);
|
||||
y = (float) Math.toDegrees(s.values[1]);
|
||||
z = (float) Math.toDegrees(s.values[2]);
|
||||
break;
|
||||
|
||||
case Sensor.TYPE_LIGHT:
|
||||
halType = SENSOR_LIGHT;
|
||||
halType = GeckoHalDefines.SENSOR_LIGHT;
|
||||
x = s.values[0];
|
||||
break;
|
||||
|
||||
case Sensor.TYPE_ROTATION_VECTOR:
|
||||
case Sensor.TYPE_GAME_ROTATION_VECTOR: // API >= 18
|
||||
halType = (sensorType == Sensor.TYPE_ROTATION_VECTOR ?
|
||||
SENSOR_ROTATION_VECTOR :
|
||||
SENSOR_GAME_ROTATION_VECTOR);
|
||||
GeckoHalDefines.SENSOR_ROTATION_VECTOR :
|
||||
GeckoHalDefines.SENSOR_GAME_ROTATION_VECTOR);
|
||||
x = s.values[0];
|
||||
y = s.values[1];
|
||||
z = s.values[2];
|
||||
|
@ -573,7 +557,7 @@ public class GeckoAppShell {
|
|||
getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
|
||||
|
||||
switch (aSensortype) {
|
||||
case SENSOR_GAME_ROTATION_VECTOR:
|
||||
case GeckoHalDefines.SENSOR_GAME_ROTATION_VECTOR:
|
||||
if (gGameRotationVectorSensor == null) {
|
||||
gGameRotationVectorSensor = sm.getDefaultSensor(
|
||||
Sensor.TYPE_GAME_ROTATION_VECTOR);
|
||||
|
@ -588,7 +572,7 @@ public class GeckoAppShell {
|
|||
}
|
||||
// Fallthrough
|
||||
|
||||
case SENSOR_ROTATION_VECTOR:
|
||||
case GeckoHalDefines.SENSOR_ROTATION_VECTOR:
|
||||
if (gRotationVectorSensor == null) {
|
||||
gRotationVectorSensor = sm.getDefaultSensor(
|
||||
Sensor.TYPE_ROTATION_VECTOR);
|
||||
|
@ -603,7 +587,7 @@ public class GeckoAppShell {
|
|||
}
|
||||
// Fallthrough
|
||||
|
||||
case SENSOR_ORIENTATION:
|
||||
case GeckoHalDefines.SENSOR_ORIENTATION:
|
||||
if (gOrientationSensor == null) {
|
||||
gOrientationSensor = sm.getDefaultSensor(
|
||||
Sensor.TYPE_ORIENTATION);
|
||||
|
@ -615,7 +599,7 @@ public class GeckoAppShell {
|
|||
}
|
||||
break;
|
||||
|
||||
case SENSOR_ACCELERATION:
|
||||
case GeckoHalDefines.SENSOR_ACCELERATION:
|
||||
if (gAccelerometerSensor == null) {
|
||||
gAccelerometerSensor = sm.getDefaultSensor(
|
||||
Sensor.TYPE_ACCELEROMETER);
|
||||
|
@ -627,7 +611,7 @@ public class GeckoAppShell {
|
|||
}
|
||||
break;
|
||||
|
||||
case SENSOR_LIGHT:
|
||||
case GeckoHalDefines.SENSOR_LIGHT:
|
||||
if (gLightSensor == null) {
|
||||
gLightSensor = sm.getDefaultSensor(Sensor.TYPE_LIGHT);
|
||||
}
|
||||
|
@ -638,7 +622,7 @@ public class GeckoAppShell {
|
|||
}
|
||||
break;
|
||||
|
||||
case SENSOR_LINEAR_ACCELERATION:
|
||||
case GeckoHalDefines.SENSOR_LINEAR_ACCELERATION:
|
||||
if (gLinearAccelerometerSensor == null) {
|
||||
gLinearAccelerometerSensor = sm.getDefaultSensor(
|
||||
Sensor.TYPE_LINEAR_ACCELERATION);
|
||||
|
@ -650,7 +634,7 @@ public class GeckoAppShell {
|
|||
}
|
||||
break;
|
||||
|
||||
case SENSOR_GYROSCOPE:
|
||||
case GeckoHalDefines.SENSOR_GYROSCOPE:
|
||||
if (gGyroscopeSensor == null) {
|
||||
gGyroscopeSensor = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
|
||||
}
|
||||
|
@ -674,45 +658,45 @@ public class GeckoAppShell {
|
|||
getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
|
||||
|
||||
switch (aSensortype) {
|
||||
case SENSOR_GAME_ROTATION_VECTOR:
|
||||
case GeckoHalDefines.SENSOR_GAME_ROTATION_VECTOR:
|
||||
if (gGameRotationVectorSensor != null) {
|
||||
sm.unregisterListener(sAndroidListeners, gGameRotationVectorSensor);
|
||||
break;
|
||||
}
|
||||
// Fallthrough
|
||||
|
||||
case SENSOR_ROTATION_VECTOR:
|
||||
case GeckoHalDefines.SENSOR_ROTATION_VECTOR:
|
||||
if (gRotationVectorSensor != null) {
|
||||
sm.unregisterListener(sAndroidListeners, gRotationVectorSensor);
|
||||
break;
|
||||
}
|
||||
// Fallthrough
|
||||
|
||||
case SENSOR_ORIENTATION:
|
||||
case GeckoHalDefines.SENSOR_ORIENTATION:
|
||||
if (gOrientationSensor != null) {
|
||||
sm.unregisterListener(sAndroidListeners, gOrientationSensor);
|
||||
}
|
||||
break;
|
||||
|
||||
case SENSOR_ACCELERATION:
|
||||
case GeckoHalDefines.SENSOR_ACCELERATION:
|
||||
if (gAccelerometerSensor != null) {
|
||||
sm.unregisterListener(sAndroidListeners, gAccelerometerSensor);
|
||||
}
|
||||
break;
|
||||
|
||||
case SENSOR_LIGHT:
|
||||
case GeckoHalDefines.SENSOR_LIGHT:
|
||||
if (gLightSensor != null) {
|
||||
sm.unregisterListener(sAndroidListeners, gLightSensor);
|
||||
}
|
||||
break;
|
||||
|
||||
case SENSOR_LINEAR_ACCELERATION:
|
||||
case GeckoHalDefines.SENSOR_LINEAR_ACCELERATION:
|
||||
if (gLinearAccelerometerSensor != null) {
|
||||
sm.unregisterListener(sAndroidListeners, gLinearAccelerometerSensor);
|
||||
}
|
||||
break;
|
||||
|
||||
case SENSOR_GYROSCOPE:
|
||||
case GeckoHalDefines.SENSOR_GYROSCOPE:
|
||||
if (gGyroscopeSensor != null) {
|
||||
sm.unregisterListener(sAndroidListeners, gGyroscopeSensor);
|
||||
}
|
||||
|
@ -829,23 +813,8 @@ public class GeckoAppShell {
|
|||
return sDensity;
|
||||
}
|
||||
|
||||
private static int sTotalRam;
|
||||
|
||||
private static int getTotalRam(final Context context) {
|
||||
if (sTotalRam == 0) {
|
||||
final ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
|
||||
final ActivityManager am = (ActivityManager) context
|
||||
.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
am.getMemoryInfo(memInfo); // `getMemoryInfo()` returns a value in B. Convert to MB.
|
||||
sTotalRam = (int) (memInfo.totalMem / (1024 * 1024));
|
||||
Log.d(LOGTAG, "System memory: " + sTotalRam + "MB.");
|
||||
}
|
||||
|
||||
return sTotalRam;
|
||||
}
|
||||
|
||||
private static boolean isHighMemoryDevice(final Context context) {
|
||||
return getTotalRam(context) > HIGH_MEMORY_DEVICE_THRESHOLD_MB;
|
||||
return SysInfo.getMemSize(context) > HIGH_MEMORY_DEVICE_THRESHOLD_MB;
|
||||
}
|
||||
|
||||
public static synchronized void useMaxScreenDepth(final boolean enable) {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* 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;
|
||||
|
||||
public class GeckoHalDefines {
|
||||
/*
|
||||
* Keep these values consistent with |SensorType| in HalSensor.h
|
||||
*/
|
||||
public static final int SENSOR_ORIENTATION = 0;
|
||||
public static final int SENSOR_ACCELERATION = 1;
|
||||
public static final int SENSOR_LINEAR_ACCELERATION = 3;
|
||||
public static final int SENSOR_GYROSCOPE = 4;
|
||||
public static final int SENSOR_LIGHT = 5;
|
||||
public static final int SENSOR_ROTATION_VECTOR = 6;
|
||||
public static final int SENSOR_GAME_ROTATION_VECTOR = 7;
|
||||
};
|
|
@ -0,0 +1,308 @@
|
|||
/* 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;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mozilla.gecko.annotation.RobocopTarget;
|
||||
import org.mozilla.gecko.util.StrictModeContext;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* {@code GeckoSharedPrefs} provides scoped SharedPreferences instances.
|
||||
* You should use this API instead of using Context.getSharedPreferences()
|
||||
* directly. There are four methods to get scoped SharedPreferences instances:
|
||||
*
|
||||
* forApp()
|
||||
* Use it for app-wide, cross-profile pref keys.
|
||||
* forCrashReporter()
|
||||
* For the crash reporter, which runs in its own process.
|
||||
* forProfile()
|
||||
* Use it to fetch and store keys for the current profile.
|
||||
* forProfileName()
|
||||
* Use it to fetch and store keys from/for a specific profile.
|
||||
*
|
||||
* {@code GeckoSharedPrefs} has a notion of migrations. Migrations can used to
|
||||
* migrate keys from one scope to another. You can trigger a new migration by
|
||||
* incrementing PREFS_VERSION and updating migrateIfNecessary() accordingly.
|
||||
*
|
||||
* Migration history:
|
||||
* 1: Move all PreferenceManager keys to app/profile scopes
|
||||
* 2: Move the crash reporter's private preferences into their own scope
|
||||
*/
|
||||
@RobocopTarget
|
||||
public final class GeckoSharedPrefs {
|
||||
private static final String LOGTAG = "GeckoSharedPrefs";
|
||||
|
||||
// Increment it to trigger a new migration
|
||||
public static final int PREFS_VERSION = 2;
|
||||
|
||||
// Name for app-scoped prefs
|
||||
public static final String APP_PREFS_NAME = "GeckoApp";
|
||||
|
||||
// Name for crash reporter prefs
|
||||
public static final String CRASH_PREFS_NAME = "CrashReporter";
|
||||
|
||||
// Used when fetching profile-scoped prefs.
|
||||
public static final String PROFILE_PREFS_NAME_PREFIX = "GeckoProfile-";
|
||||
|
||||
// The prefs key that holds the current migration
|
||||
private static final String PREFS_VERSION_KEY = "gecko_shared_prefs_migration";
|
||||
|
||||
// For disabling migration when getting a SharedPreferences instance
|
||||
private static final EnumSet<Flags> disableMigrations = EnumSet.of(Flags.DISABLE_MIGRATIONS);
|
||||
|
||||
// The keys that have to be moved from ProfileManager's default
|
||||
// shared prefs to the profile from version 0 to 1.
|
||||
private static final String[] PROFILE_MIGRATIONS_0_TO_1 = {
|
||||
"home_panels",
|
||||
"home_locale"
|
||||
};
|
||||
|
||||
// The keys that have to be moved from the app prefs
|
||||
// into the crash reporter's own prefs.
|
||||
private static final String[] PROFILE_MIGRATIONS_1_TO_2 = {
|
||||
"sendReport",
|
||||
"includeUrl",
|
||||
"allowContact",
|
||||
"contactEmail"
|
||||
};
|
||||
|
||||
// For optimizing the migration check in subsequent get() calls
|
||||
private static volatile boolean migrationDone;
|
||||
|
||||
public enum Flags {
|
||||
DISABLE_MIGRATIONS
|
||||
}
|
||||
|
||||
public static SharedPreferences forApp(final Context context) {
|
||||
return forApp(context, EnumSet.noneOf(Flags.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an app-scoped SharedPreferences instance. You can disable
|
||||
* migrations by using the DISABLE_MIGRATIONS flag.
|
||||
*/
|
||||
public static SharedPreferences forApp(final Context context, final EnumSet<Flags> flags) {
|
||||
if (flags != null && !flags.contains(Flags.DISABLE_MIGRATIONS)) {
|
||||
migrateIfNecessary(context);
|
||||
}
|
||||
|
||||
return context.getSharedPreferences(APP_PREFS_NAME, 0);
|
||||
}
|
||||
|
||||
public static SharedPreferences forCrashReporter(final Context context) {
|
||||
return forCrashReporter(context, EnumSet.noneOf(Flags.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a crash-reporter-scoped SharedPreferences instance. You can disable
|
||||
* migrations by using the DISABLE_MIGRATIONS flag.
|
||||
*/
|
||||
public static SharedPreferences forCrashReporter(final Context context,
|
||||
final EnumSet<Flags> flags) {
|
||||
if (flags != null && !flags.contains(Flags.DISABLE_MIGRATIONS)) {
|
||||
migrateIfNecessary(context);
|
||||
}
|
||||
|
||||
return context.getSharedPreferences(CRASH_PREFS_NAME, 0);
|
||||
}
|
||||
|
||||
public static SharedPreferences forProfileName(final Context context,
|
||||
final String profileName) {
|
||||
return forProfileName(context, profileName, EnumSet.noneOf(Flags.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an SharedPreferences instance scoped to the given profile name.
|
||||
* You can disable migrations by using the DISABLE_MIGRATION flag.
|
||||
*/
|
||||
public static SharedPreferences forProfileName(final Context context, final String profileName,
|
||||
final EnumSet<Flags> flags) {
|
||||
if (flags != null && !flags.contains(Flags.DISABLE_MIGRATIONS)) {
|
||||
migrateIfNecessary(context);
|
||||
}
|
||||
|
||||
final String prefsName = PROFILE_PREFS_NAME_PREFIX + profileName;
|
||||
return context.getSharedPreferences(prefsName, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current version of the prefs.
|
||||
*/
|
||||
public static int getVersion(final Context context) {
|
||||
return forApp(context, disableMigrations).getInt(PREFS_VERSION_KEY, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets migration flag. Should only be used in tests.
|
||||
*/
|
||||
public static synchronized void reset() {
|
||||
migrationDone = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs all prefs migrations in the background thread to avoid StrictMode
|
||||
* exceptions from reading/writing in the UI thread. This method will block
|
||||
* the current thread until the migration is finished.
|
||||
*/
|
||||
@SuppressWarnings("try")
|
||||
private static synchronized void migrateIfNecessary(final Context context) {
|
||||
// FIXME(emilio): What do we want to do about this?
|
||||
if (true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (migrationDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We deliberately perform the migration in the current thread (which
|
||||
// is likely the UI thread) as this is actually cheaper than enforcing a
|
||||
// context switch to another thread (see bug 940575).
|
||||
// Avoid strict mode warnings when doing so.
|
||||
try (final StrictModeContext unused = StrictModeContext.allowDiskWrites()) {
|
||||
performMigration(context);
|
||||
}
|
||||
|
||||
migrationDone = true;
|
||||
}
|
||||
|
||||
private static void performMigration(final Context context) {
|
||||
final SharedPreferences appPrefs = forApp(context, disableMigrations);
|
||||
|
||||
final int currentVersion = appPrefs.getInt(PREFS_VERSION_KEY, 0);
|
||||
Log.d(LOGTAG, "Current version = " + currentVersion + ", prefs version = " + PREFS_VERSION);
|
||||
|
||||
if (currentVersion == PREFS_VERSION) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(LOGTAG, "Performing migration");
|
||||
|
||||
final Editor appEditor = appPrefs.edit();
|
||||
|
||||
// The migration always moves prefs to the default profile, not
|
||||
// the current one. We might have to revisit this if we ever support
|
||||
// multiple profiles.
|
||||
final String defaultProfileName;
|
||||
try {
|
||||
defaultProfileName = GeckoProfile.getDefaultProfileName(context);
|
||||
} catch (final Exception e) {
|
||||
throw new IllegalStateException("Failed to get default profile name for migration");
|
||||
}
|
||||
|
||||
final Editor profileEditor = forProfileName(context, defaultProfileName, disableMigrations).edit();
|
||||
final Editor crashEditor = forCrashReporter(context, disableMigrations).edit();
|
||||
|
||||
List<String> profileKeys;
|
||||
Editor pmEditor = null;
|
||||
|
||||
for (int v = currentVersion + 1; v <= PREFS_VERSION; v++) {
|
||||
Log.d(LOGTAG, "Migrating to version = " + v);
|
||||
|
||||
switch (v) {
|
||||
case 1:
|
||||
profileKeys = Arrays.asList(PROFILE_MIGRATIONS_0_TO_1);
|
||||
pmEditor = migrateFromPreferenceManager(context, appEditor, profileEditor, profileKeys);
|
||||
break;
|
||||
case 2:
|
||||
profileKeys = Arrays.asList(PROFILE_MIGRATIONS_1_TO_2);
|
||||
migrateCrashReporterSettings(appPrefs, appEditor, crashEditor, profileKeys);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update prefs version accordingly.
|
||||
appEditor.putInt(PREFS_VERSION_KEY, PREFS_VERSION);
|
||||
|
||||
appEditor.apply();
|
||||
profileEditor.apply();
|
||||
crashEditor.apply();
|
||||
if (pmEditor != null) {
|
||||
pmEditor.apply();
|
||||
}
|
||||
|
||||
Log.d(LOGTAG, "All keys have been migrated");
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves all preferences stored in PreferenceManager's default prefs
|
||||
* to either app or profile scopes. The profile-scoped keys are defined
|
||||
* in given profileKeys list, all other keys are moved to the app scope.
|
||||
*/
|
||||
private static Editor migrateFromPreferenceManager(final Context context,
|
||||
final Editor appEditor,
|
||||
final Editor profileEditor,
|
||||
final List<String> profileKeys) {
|
||||
Log.d(LOGTAG, "Migrating from PreferenceManager");
|
||||
|
||||
final SharedPreferences pmPrefs =
|
||||
PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
for (final Map.Entry<String, ?> entry : pmPrefs.getAll().entrySet()) {
|
||||
final String key = entry.getKey();
|
||||
|
||||
final Editor to;
|
||||
if (profileKeys.contains(key)) {
|
||||
to = profileEditor;
|
||||
} else {
|
||||
to = appEditor;
|
||||
}
|
||||
|
||||
putEntry(to, key, entry.getValue());
|
||||
}
|
||||
|
||||
// Clear PreferenceManager's prefs once we're done
|
||||
// and return the Editor to be committed.
|
||||
return pmPrefs.edit().clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the crash reporter's preferences from the app-wide prefs
|
||||
* into its own shared prefs to avoid cross-process pref accesses.
|
||||
*/
|
||||
private static void migrateCrashReporterSettings(final SharedPreferences appPrefs,
|
||||
final Editor appEditor,
|
||||
final Editor crashEditor,
|
||||
final List<String> profileKeys) {
|
||||
Log.d(LOGTAG, "Migrating crash reporter settings");
|
||||
|
||||
for (final Map.Entry<String, ?> entry : appPrefs.getAll().entrySet()) {
|
||||
final String key = entry.getKey();
|
||||
|
||||
if (profileKeys.contains(key)) {
|
||||
putEntry(crashEditor, key, entry.getValue());
|
||||
appEditor.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void putEntry(final Editor to, final String key, final Object value) {
|
||||
Log.d(LOGTAG, "Migrating key = " + key + " with value = " + value);
|
||||
|
||||
if (value instanceof String) {
|
||||
to.putString(key, (String) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
to.putBoolean(key, (Boolean) value);
|
||||
} else if (value instanceof Long) {
|
||||
to.putLong(key, (Long) value);
|
||||
} else if (value instanceof Float) {
|
||||
to.putFloat(key, (Float) value);
|
||||
} else if (value instanceof Integer) {
|
||||
to.putInt(key, (Integer) value);
|
||||
} else {
|
||||
throw new IllegalStateException("Unrecognized value type for key: " + key);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* 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;
|
||||
|
||||
public interface NotificationListener {
|
||||
void showNotification(String name, String cookie, String title, String text,
|
||||
String host, String imageUrl);
|
||||
|
||||
void showPersistentNotification(String name, String cookie, String title, String text,
|
||||
String host, String imageUrl, String data);
|
||||
|
||||
void closeNotification(String name);
|
||||
}
|
|
@ -10,6 +10,13 @@ import org.mozilla.gecko.annotation.WrapForJNI;
|
|||
|
||||
class ScreenManagerHelper {
|
||||
|
||||
/**
|
||||
* The following display types use the same definition in nsIScreen.idl
|
||||
*/
|
||||
final static int DISPLAY_PRIMARY = 0; // primary screen
|
||||
final static int DISPLAY_EXTERNAL = 1; // wired displays, such as HDMI, DisplayPort, etc.
|
||||
final static int DISPLAY_VIRTUAL = 2; // wireless displays, such as Chromecast, WiFi-Display, etc.
|
||||
|
||||
/**
|
||||
* Trigger a refresh of the cached screen information held by Gecko.
|
||||
*/
|
||||
|
@ -23,4 +30,28 @@ class ScreenManagerHelper {
|
|||
|
||||
@WrapForJNI(stubName = "RefreshScreenInfo", dispatchTo = "gecko")
|
||||
private native static void nativeRefreshScreenInfo();
|
||||
|
||||
/**
|
||||
* Add a new nsScreen when a new display in Android is available.
|
||||
*
|
||||
* @param displayType the display type of the nsScreen would be added
|
||||
* @param width the width of the new nsScreen
|
||||
* @param height the height of the new nsScreen
|
||||
* @param density the density of the new nsScreen
|
||||
*
|
||||
* @return return the ID of the added nsScreen
|
||||
*/
|
||||
@WrapForJNI
|
||||
public native static int addDisplay(int displayType,
|
||||
int width,
|
||||
int height,
|
||||
float density);
|
||||
|
||||
/**
|
||||
* Remove the nsScreen by the specific screen ID.
|
||||
*
|
||||
* @param screenId the ID of the screen would be removed.
|
||||
*/
|
||||
@WrapForJNI
|
||||
public native static void removeDisplay(int screenId);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.MemoryInfo;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.util.StrictModeContext;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A collection of system info values, broadly mirroring a subset of
|
||||
* nsSystemInfo. See also the constants in org.mozilla.geckoview.BuildConfig,
|
||||
* which reflect much of nsIXULAppInfo.
|
||||
*/
|
||||
// Normally, we'd annotate with @RobocopTarget. Since SysInfo is compiled
|
||||
// before RobocopTarget, we instead add o.m.g.SysInfo directly to the Proguard
|
||||
// configuration.
|
||||
public final class SysInfo {
|
||||
private static final String LOG_TAG = "GeckoSysInfo";
|
||||
|
||||
// Number of bytes of /proc/meminfo to read in one go.
|
||||
private static final int MEMINFO_BUFFER_SIZE_BYTES = 256;
|
||||
|
||||
// We don't mind an instant of possible duplicate work, we only wish to
|
||||
// avoid inconsistency, so we don't bother with synchronization for
|
||||
// these.
|
||||
private static volatile int cpuCount = -1;
|
||||
|
||||
private static volatile int totalRAM = -1;
|
||||
|
||||
/**
|
||||
* Get the number of cores on the device.
|
||||
*
|
||||
* We can't use a nice tidy API call, <a
|
||||
* href="https://stackoverflow.com/q/7962155">because they're all
|
||||
* wrong</a>. This method is based on that code.
|
||||
*
|
||||
* @return the number of CPU cores, or 1 if the number could not be
|
||||
* determined.
|
||||
*/
|
||||
@SuppressWarnings("try")
|
||||
public static int getCPUCount() {
|
||||
if (cpuCount > 0) {
|
||||
return cpuCount;
|
||||
}
|
||||
|
||||
// Avoid a strict mode warning.
|
||||
try (final StrictModeContext unused = StrictModeContext.allowDiskReads()) {
|
||||
return readCPUCount();
|
||||
}
|
||||
}
|
||||
|
||||
private static int readCPUCount() {
|
||||
class CpuFilter implements FileFilter {
|
||||
@Override
|
||||
public boolean accept(final File pathname) {
|
||||
return Pattern.matches("cpu[0-9]+", pathname.getName());
|
||||
}
|
||||
}
|
||||
try {
|
||||
final File dir = new File("/sys/devices/system/cpu/");
|
||||
return cpuCount = dir.listFiles(new CpuFilter()).length;
|
||||
} catch (final Exception e) {
|
||||
Log.w(LOG_TAG, "Assuming 1 CPU; got exception.", e);
|
||||
return cpuCount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the total memory of the device in MB.
|
||||
*
|
||||
* NB: This cannot be called before GeckoAppShell has been
|
||||
* initialized.
|
||||
*
|
||||
* @return Memory size in MB.
|
||||
*/
|
||||
public static int getMemSize(final Context context) {
|
||||
if (totalRAM >= 0) {
|
||||
return totalRAM;
|
||||
}
|
||||
|
||||
final MemoryInfo memInfo = new MemoryInfo();
|
||||
|
||||
final ActivityManager am = (ActivityManager) context
|
||||
.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
am.getMemoryInfo(memInfo);
|
||||
|
||||
// `getMemoryInfo()` returns a value in B. Convert to MB.
|
||||
totalRAM = (int)(memInfo.totalMem / (1024 * 1024));
|
||||
|
||||
Log.d(LOG_TAG, "System memory: " + totalRAM + "MB.");
|
||||
|
||||
return totalRAM;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the SDK version supported by this device, such as '16'.
|
||||
*/
|
||||
public static int getVersion() {
|
||||
return android.os.Build.VERSION.SDK_INT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the release version string, such as "4.1.2".
|
||||
*/
|
||||
public static String getReleaseVersion() {
|
||||
return android.os.Build.VERSION.RELEASE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the kernel version string, such as "3.4.10-geb45596".
|
||||
*/
|
||||
public static String getKernelVersion() {
|
||||
return System.getProperty("os.version", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the device manufacturer, such as "HTC".
|
||||
*/
|
||||
public static String getManufacturer() {
|
||||
return android.os.Build.MANUFACTURER;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the device name, such as "HTC One".
|
||||
*/
|
||||
public static String getDevice() {
|
||||
// No, not android.os.Build.DEVICE.
|
||||
return android.os.Build.MODEL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Android "hardware" identifier, such as "m7".
|
||||
*/
|
||||
public static String getHardware() {
|
||||
return android.os.Build.HARDWARE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the system OS name. Hardcoded to "Android".
|
||||
*/
|
||||
public static String getName() {
|
||||
// We deliberately differ from PR_SI_SYSNAME, which is "Linux".
|
||||
return "Android";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Android architecture string, including ABI.
|
||||
*/
|
||||
public static String getArchABI() {
|
||||
// Android likes to include the ABI, too ("armeabiv7"), so we
|
||||
// differ to add value.
|
||||
return android.os.Build.CPU_ABI;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,317 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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;
|
||||
|
||||
import org.mozilla.gecko.annotation.RobocopTarget;
|
||||
|
||||
/**
|
||||
* Holds data definitions for our UI Telemetry implementation.
|
||||
*
|
||||
* Note that enum values of "_TEST*" are reserved for testing and
|
||||
* should not be changed without changing the associated tests.
|
||||
*
|
||||
* See mobile/android/base/docs/index.rst for a full dictionary.
|
||||
*/
|
||||
@RobocopTarget
|
||||
public interface TelemetryContract {
|
||||
|
||||
/**
|
||||
* Holds event names. Intended for use with
|
||||
* Telemetry.sendUIEvent() as the "action" parameter.
|
||||
*
|
||||
* Please keep this list sorted.
|
||||
*/
|
||||
public enum Event {
|
||||
// Generic action, usually for tracking menu and toolbar actions.
|
||||
ACTION("action.1"),
|
||||
|
||||
// Cancel a state, action, etc.
|
||||
CANCEL("cancel.1"),
|
||||
|
||||
// Start casting a video.
|
||||
// Note: Only used in JavaScript for now, but here for completeness.
|
||||
CAST("cast.1"),
|
||||
|
||||
// Editing an item.
|
||||
EDIT("edit.1"),
|
||||
|
||||
// Launching (opening) an external application.
|
||||
// Note: Only used in JavaScript for now, but here for completeness.
|
||||
LAUNCH("launch.1"),
|
||||
|
||||
// Loading a URL.
|
||||
LOAD_URL("loadurl.1"),
|
||||
|
||||
LOCALE_BROWSER_RESET("locale.browser.reset.1"),
|
||||
LOCALE_BROWSER_SELECTED("locale.browser.selected.1"),
|
||||
LOCALE_BROWSER_UNSELECTED("locale.browser.unselected.1"),
|
||||
|
||||
// Hide a built-in home panel.
|
||||
PANEL_HIDE("panel.hide.1"),
|
||||
|
||||
// Move a home panel up or down.
|
||||
PANEL_MOVE("panel.move.1"),
|
||||
|
||||
// Remove a custom home panel.
|
||||
PANEL_REMOVE("panel.remove.1"),
|
||||
|
||||
// Set default home panel.
|
||||
PANEL_SET_DEFAULT("panel.setdefault.1"),
|
||||
|
||||
// Show a hidden built-in home panel.
|
||||
PANEL_SHOW("panel.show.1"),
|
||||
|
||||
// Pinning an item.
|
||||
PIN("pin.1"),
|
||||
|
||||
// Outcome of data policy notification: can be true or false.
|
||||
POLICY_NOTIFICATION_SUCCESS("policynotification.success.1"),
|
||||
|
||||
// Sanitizing private data.
|
||||
SANITIZE("sanitize.1"),
|
||||
|
||||
// Saving a resource (reader, bookmark, etc) for viewing later.
|
||||
SAVE("save.1"),
|
||||
|
||||
// Perform a search -- currently used when starting a search in the search activity.
|
||||
SEARCH("search.1"),
|
||||
|
||||
// Remove a search engine.
|
||||
SEARCH_REMOVE("search.remove.1"),
|
||||
|
||||
// Restore default search engines.
|
||||
SEARCH_RESTORE_DEFAULTS("search.restoredefaults.1"),
|
||||
|
||||
// Set default search engine.
|
||||
SEARCH_SET_DEFAULT("search.setdefault.1"),
|
||||
|
||||
// Searches initiated from the widget.
|
||||
SEARCH_WIDGET("search.widget.1"),
|
||||
|
||||
// Sharing content.
|
||||
SHARE("share.1"),
|
||||
|
||||
// Show a UI element.
|
||||
SHOW("show.1"),
|
||||
|
||||
// Undoing a user action.
|
||||
// Note: Only used in JavaScript for now, but here for completeness.
|
||||
UNDO("undo.1"),
|
||||
|
||||
// Unpinning an item.
|
||||
UNPIN("unpin.1"),
|
||||
|
||||
// Stop holding a resource (reader, bookmark, etc) for viewing later.
|
||||
UNSAVE("unsave.1"),
|
||||
|
||||
// When the user performs actions on the in-content network error page.
|
||||
NETERROR("neterror.1"),
|
||||
|
||||
// User actions related to a Progressive Web Application
|
||||
PWA("pwa.1"),
|
||||
|
||||
// VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING.
|
||||
_TEST1("_test_event_1.1"),
|
||||
_TEST2("_test_event_2.1"),
|
||||
_TEST3("_test_event_3.1"),
|
||||
_TEST4("_test_event_4.1"),
|
||||
;
|
||||
|
||||
private final String mString;
|
||||
|
||||
Event(final String string) {
|
||||
mString = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds event methods. Intended for use in
|
||||
* Telemetry.sendUIEvent() as the "method" parameter.
|
||||
*
|
||||
* Please keep this list sorted.
|
||||
*/
|
||||
public enum Method {
|
||||
// Action triggered from the action bar (including the toolbar).
|
||||
ACTIONBAR("actionbar"),
|
||||
|
||||
// Action triggered by hitting the Android back button.
|
||||
BACK("back"),
|
||||
|
||||
// Action triggered from a button.
|
||||
BUTTON("button"),
|
||||
|
||||
// Action taken from a content page -- for example, a search results web page.
|
||||
CONTENT("content"),
|
||||
|
||||
// Action occurred via a context menu.
|
||||
CONTEXT_MENU("contextmenu"),
|
||||
|
||||
// Action triggered from a dialog.
|
||||
DIALOG("dialog"),
|
||||
|
||||
// Action triggered from a doorhanger popup prompt.
|
||||
DOORHANGER("doorhanger"),
|
||||
|
||||
// Action triggered from a view grid item, like a thumbnail.
|
||||
GRID_ITEM("griditem"),
|
||||
|
||||
// Action occurred via an intent.
|
||||
INTENT("intent"),
|
||||
|
||||
// Action occurred via a homescreen launcher.
|
||||
HOMESCREEN("homescreen"),
|
||||
|
||||
// Action triggered from a list.
|
||||
LIST("list"),
|
||||
|
||||
// Action triggered from a view list item, like a row of a list.
|
||||
LIST_ITEM("listitem"),
|
||||
|
||||
// Action occurred via the main menu.
|
||||
MENU("menu"),
|
||||
|
||||
// No method is specified.
|
||||
NONE(null),
|
||||
|
||||
// Action triggered from a notification in the Android notification bar.
|
||||
NOTIFICATION("notification"),
|
||||
|
||||
// Action triggered from a pageaction in the URLBar.
|
||||
// Note: Only used in JavaScript for now, but here for completeness.
|
||||
PAGEACTION("pageaction"),
|
||||
|
||||
// Action triggered from one of a series of views, such as ViewPager.
|
||||
PANEL("panel"),
|
||||
|
||||
// Action triggered by a background service / automatic system making a decision.
|
||||
SERVICE("service"),
|
||||
|
||||
// Action triggered from a settings screen.
|
||||
SETTINGS("settings"),
|
||||
|
||||
// Actions triggered from the share overlay.
|
||||
SHARE_OVERLAY("shareoverlay"),
|
||||
|
||||
// Action triggered from a suggestion provided to the user.
|
||||
SUGGESTION("suggestion"),
|
||||
|
||||
// Action triggered from an OS system action.
|
||||
SYSTEM("system"),
|
||||
|
||||
// Action triggered from a SuperToast.
|
||||
// Note: Only used in JavaScript for now, but here for completeness.
|
||||
TOAST("toast"),
|
||||
|
||||
// Action triggerred by pressing a SearchWidget button
|
||||
WIDGET("widget"),
|
||||
|
||||
// VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING.
|
||||
_TEST1("_test_method_1"),
|
||||
_TEST2("_test_method_2"),
|
||||
;
|
||||
|
||||
private final String mString;
|
||||
|
||||
Method(final String string) {
|
||||
mString = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds session names. Intended for use with
|
||||
* Telemetry.startUISession() as the "sessionName" parameter.
|
||||
*
|
||||
* Please keep this list sorted.
|
||||
*/
|
||||
public enum Session {
|
||||
// Started whenever the activity stream panel is visible. Stopped as soon as the panel is
|
||||
// not visible anymore.
|
||||
ACTIVITY_STREAM("activitystream.1"),
|
||||
|
||||
// Awesomescreen (including frecency search) is active.
|
||||
AWESOMESCREEN("awesomescreen.1"),
|
||||
|
||||
// Used to tag experiments being run.
|
||||
EXPERIMENT("experiment.1"),
|
||||
|
||||
// Started the very first time we believe the application has been launched.
|
||||
FIRSTRUN("firstrun.1"),
|
||||
|
||||
// Awesomescreen frecency search is active.
|
||||
FRECENCY("frecency.1"),
|
||||
|
||||
// Started when a user enters a given home panel.
|
||||
// Session name is dynamic, encoded as "homepanel.1:<panel_id>"
|
||||
HOME_PANEL("homepanel.1"),
|
||||
|
||||
// Started when a Reader viewer becomes active in the foreground.
|
||||
// Note: Only used in JavaScript for now, but here for completeness.
|
||||
READER("reader.1"),
|
||||
|
||||
// Started when the search activity launches.
|
||||
SEARCH_ACTIVITY("searchactivity.1"),
|
||||
|
||||
// Settings activity is active.
|
||||
SETTINGS("settings.1"),
|
||||
|
||||
// VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING.
|
||||
_TEST_STARTED_TWICE("_test_session_started_twice.1"),
|
||||
_TEST_STOPPED_TWICE("_test_session_stopped_twice.1"),
|
||||
;
|
||||
|
||||
private final String mString;
|
||||
|
||||
Session(final String string) {
|
||||
mString = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds reasons for stopping a session. Intended for use in
|
||||
* Telemetry.stopUISession() as the "reason" parameter.
|
||||
*
|
||||
* Please keep this list sorted.
|
||||
*/
|
||||
public enum Reason {
|
||||
// Changes were committed.
|
||||
COMMIT("commit"),
|
||||
|
||||
// No reason is specified.
|
||||
NONE(null),
|
||||
|
||||
// VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING.
|
||||
_TEST1("_test_reason_1"),
|
||||
_TEST2("_test_reason_2"),
|
||||
_TEST_IGNORED("_test_reason_ignored"),
|
||||
;
|
||||
|
||||
private final String mString;
|
||||
|
||||
Reason(final String string) {
|
||||
mString = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mString;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
public interface TouchEventInterceptor extends View.OnTouchListener {
|
||||
/** Override this method for a chance to consume events before the view or its children */
|
||||
public boolean onInterceptTouchEvent(View view, MotionEvent event);
|
||||
}
|
|
@ -12,7 +12,6 @@ import java.net.URI;
|
|||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.UUID;
|
||||
|
@ -30,6 +29,7 @@ import android.media.MediaDrm;
|
|||
import android.media.NotProvisionedException;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
import org.mozilla.gecko.util.ProxySelector;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
|
@ -45,8 +45,6 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
|
|||
// dummy key id to report key status.
|
||||
private static final byte[] DUMMY_KEY_ID = new byte[] {0};
|
||||
|
||||
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
private UUID mSchemeUUID;
|
||||
private Handler mHandler;
|
||||
PostRequestTask mProvisionTask;
|
||||
|
@ -194,7 +192,7 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
|
|||
mSessionMIMETypes.put(sessionId, initDataType);
|
||||
mSessionIds.add(sessionId);
|
||||
if (DEBUG) Log.d(LOGTAG, " StringID : " + new String(
|
||||
sessionId.array(), UTF_8) + " is put into mSessionIds ");
|
||||
sessionId.array(), StringUtils.UTF_8) + " is put into mSessionIds ");
|
||||
} catch (final android.media.NotProvisionedException e) {
|
||||
if (DEBUG) Log.d(LOGTAG, "Device not provisioned:" + e.getMessage());
|
||||
if (sessionId != null) {
|
||||
|
@ -218,7 +216,7 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
|
|||
return;
|
||||
}
|
||||
|
||||
final ByteBuffer session = ByteBuffer.wrap(sessionId.getBytes(UTF_8));
|
||||
final ByteBuffer session = ByteBuffer.wrap(sessionId.getBytes(StringUtils.UTF_8));
|
||||
if (!sessionExists(session)) {
|
||||
onRejectPromise(promiseId, "Invalid session during updateSession.");
|
||||
return;
|
||||
|
@ -253,7 +251,7 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
|
|||
return;
|
||||
}
|
||||
|
||||
final ByteBuffer session = ByteBuffer.wrap(sessionId.getBytes(UTF_8));
|
||||
final ByteBuffer session = ByteBuffer.wrap(sessionId.getBytes(StringUtils.UTF_8));
|
||||
mSessionIds.remove(session);
|
||||
mDrm.closeSession(session.array());
|
||||
onSessionClosed(promiseId, session.array());
|
||||
|
@ -429,10 +427,10 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
|
|||
// No need to handle here if we're not in privacy mode.
|
||||
break;
|
||||
case MediaDrm.EVENT_KEY_EXPIRED:
|
||||
if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_KEY_EXPIRED, sessionId=" + new String(session.array(), UTF_8));
|
||||
if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_KEY_EXPIRED, sessionId=" + new String(session.array(), StringUtils.UTF_8));
|
||||
break;
|
||||
case MediaDrm.EVENT_VENDOR_DEFINED:
|
||||
if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_VENDOR_DEFINED, sessionId=" + new String(session.array(), UTF_8));
|
||||
if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_VENDOR_DEFINED, sessionId=" + new String(session.array(), StringUtils.UTF_8));
|
||||
break;
|
||||
default:
|
||||
if (DEBUG) Log.d(LOGTAG, "Invalid DRM event " + event);
|
||||
|
@ -508,7 +506,7 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
|
|||
|
||||
final int responseCode = urlConnection.getResponseCode();
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), UTF_8));
|
||||
in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StringUtils.UTF_8));
|
||||
String inputLine;
|
||||
final StringBuffer response = new StringBuffer();
|
||||
|
||||
|
@ -516,7 +514,7 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
|
|||
response.append(inputLine);
|
||||
}
|
||||
in.close();
|
||||
mResponseBody = String.valueOf(response).getBytes(UTF_8);
|
||||
mResponseBody = String.valueOf(response).getBytes(StringUtils.UTF_8);
|
||||
if (DEBUG) Log.d(LOGTAG, "Provisioning, response received.");
|
||||
if (mResponseBody != null) Log.d(LOGTAG, "response length=" + mResponseBody.length);
|
||||
} else {
|
||||
|
@ -659,7 +657,7 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
|
|||
mCrypto = new MediaCrypto(mSchemeUUID, cryptoSessionId);
|
||||
mSessionIds.add(mCryptoSessionId);
|
||||
if (DEBUG) Log.d(LOGTAG, "MediaCrypto successfully created! - SId " + INVALID_SESSION_ID +
|
||||
", " + new String(cryptoSessionId, UTF_8));
|
||||
", " + new String(cryptoSessionId, StringUtils.UTF_8));
|
||||
return true;
|
||||
} else {
|
||||
if (DEBUG) Log.d(LOGTAG, "Cannot create MediaCrypto for unsupported scheme.");
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* 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.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
|
||||
public class ActivityUtils {
|
||||
private ActivityUtils() {
|
||||
}
|
||||
|
||||
public static void setFullScreen(final Activity activity, final boolean fullscreen) {
|
||||
// Hide/show the system notification bar
|
||||
final Window window = activity.getWindow();
|
||||
|
||||
int newVis;
|
||||
if (fullscreen) {
|
||||
newVis = View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
newVis |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
} else {
|
||||
newVis |= View.SYSTEM_UI_FLAG_LOW_PROFILE;
|
||||
}
|
||||
} else {
|
||||
// no need to prevent status bar to appear when exiting full screen
|
||||
preventDisplayStatusbar(activity, false);
|
||||
newVis = View.SYSTEM_UI_FLAG_VISIBLE;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
// We also have to set SYSTEM_UI_FLAG_LIGHT_STATUS_BAR with to current system ui status
|
||||
// to support both light and dark status bar.
|
||||
final int oldVis = window.getDecorView().getSystemUiVisibility();
|
||||
newVis |= (oldVis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
}
|
||||
|
||||
window.getDecorView().setSystemUiVisibility(newVis);
|
||||
}
|
||||
|
||||
public static boolean isFullScreen(final Activity activity) {
|
||||
final Window window = activity.getWindow();
|
||||
|
||||
final int vis = window.getDecorView().getSystemUiVisibility();
|
||||
return (vis & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish this activity and launch the default home screen activity.
|
||||
*/
|
||||
public static void goToHomeScreen(final Context context) {
|
||||
final Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||
|
||||
intent.addCategory(Intent.CATEGORY_HOME);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
public static Activity getActivityFromContext(final Context outerContext) {
|
||||
Context context = outerContext;
|
||||
while (context instanceof ContextWrapper) {
|
||||
if (context instanceof Activity) {
|
||||
return (Activity) context;
|
||||
}
|
||||
context = ((ContextWrapper) context).getBaseContext();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void preventDisplayStatusbar(final Activity activity,
|
||||
final boolean registering) {
|
||||
final View decorView = activity.getWindow().getDecorView();
|
||||
if (registering) {
|
||||
decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
|
||||
@Override
|
||||
public void onSystemUiVisibilityChange(final int visibility) {
|
||||
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
|
||||
setFullScreen(activity, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
decorView.setOnSystemUiVisibilityChangeListener(null);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* Copyright (C) 2007-2008 OpenIntents.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mozilla.gecko.util;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Based on https://github.com/iPaulPro/aFileChooser/blob/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java
|
||||
*/
|
||||
public class ContentUriUtils {
|
||||
/**
|
||||
* Get a file path from a Uri. This will get the the path for Storage Access
|
||||
* Framework Documents, as well as the _data field for the MediaStore and
|
||||
* other file-based ContentProviders.<br>
|
||||
* <br>
|
||||
* Callers should check whether the path is local before assuming it
|
||||
* represents a local file.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @author paulburke
|
||||
*/
|
||||
public static @Nullable String getOriginalFilePathFromUri(final Context context, final Uri uri) {
|
||||
// DocumentProvider
|
||||
if (Build.VERSION.SDK_INT >= 19 && DocumentsContract.isDocumentUri(context, uri)) {
|
||||
// ExternalStorageProvider
|
||||
if (isExternalStorageDocument(uri)) {
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
// The AOSP ExternalStorageProvider creates document IDs of the form
|
||||
// "storage device ID" + ':' + "document path".
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
final String docPath = split[1];
|
||||
|
||||
final String rootPath;
|
||||
if ("primary".equalsIgnoreCase(type)) {
|
||||
rootPath = Environment.getExternalStorageDirectory().getAbsolutePath();
|
||||
} else {
|
||||
rootPath = FileUtils.getExternalStoragePath(context, type);
|
||||
}
|
||||
return !TextUtils.isEmpty(rootPath) ?
|
||||
rootPath + "/" + docPath : null;
|
||||
} else if (isDownloadsDocument(uri)) { // DownloadsProvider
|
||||
final String id = DocumentsContract.getDocumentId(uri);
|
||||
// workaround for issue (https://bugzilla.mozilla.org/show_bug.cgi?id=1502721) and
|
||||
// as per https://github.com/Yalantis/uCrop/issues/318#issuecomment-333066640
|
||||
if (!TextUtils.isEmpty(id)) {
|
||||
if (id.startsWith("raw:")) {
|
||||
return id.replaceFirst("raw:", "");
|
||||
}
|
||||
try {
|
||||
final Uri contentUri = ContentUris.withAppendedId(
|
||||
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
|
||||
return getDataColumn(context, contentUri, null, null);
|
||||
} catch (final NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else if (isMediaDocument(uri)) { // MediaProvider
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
Uri contentUri = null;
|
||||
if ("image".equals(type)) {
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("video".equals(type)) {
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("audio".equals(type)) {
|
||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
final String selection = "_id=?";
|
||||
final String[] selectionArgs = new String[] {
|
||||
split[1]
|
||||
};
|
||||
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
}
|
||||
} else if ("content".equalsIgnoreCase(uri.getScheme())) { // MediaStore (and general)
|
||||
// Return the remote address
|
||||
if (isGooglePhotosUri(uri))
|
||||
return uri.getLastPathSegment();
|
||||
|
||||
return getDataColumn(context, uri, null, null);
|
||||
} else if ("file".equalsIgnoreCase(uri.getScheme())) { // File
|
||||
return uri.getPath();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves file contents via getContentResolver().openInputStream() and stores them in a
|
||||
* temporary file.
|
||||
*
|
||||
* @return The path of the temporary file, or <code>null</code> if there was an error
|
||||
* retrieving the file.
|
||||
*/
|
||||
public static @Nullable String getTempFilePathFromContentUri(final Context context,
|
||||
final Uri contentUri) {
|
||||
//copy file and send new file path
|
||||
final String fileName = FileUtils.getFileNameFromContentUri(context, contentUri);
|
||||
final File folder = new File(context.getCacheDir(), FileUtils.CONTENT_TEMP_DIRECTORY);
|
||||
boolean success = true;
|
||||
if (!folder.exists()) {
|
||||
success = folder.mkdirs();
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(fileName) && success) {
|
||||
final File copyFile = new File(folder.getPath(), fileName);
|
||||
FileUtils.copy(context, contentUri, copyFile);
|
||||
return copyFile.getAbsolutePath();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the data column for this Uri. This is useful for
|
||||
* MediaStore Uris, and other file-based ContentProviders.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @param selection (Optional) Filter used in the query.
|
||||
* @param selectionArgs (Optional) Selection arguments used in the query.
|
||||
* @return The value of the _data column, which is typically a file path.
|
||||
* @author paulburke
|
||||
*/
|
||||
private static String getDataColumn(final Context context, final Uri uri,
|
||||
final String selection, final String[] selectionArgs) {
|
||||
final String column = "_data";
|
||||
final String[] projection = {
|
||||
column
|
||||
};
|
||||
|
||||
try (final Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
|
||||
null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
final int column_index = cursor.getColumnIndex(column);
|
||||
return column_index >= 0 ? cursor.getString(column_index) : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is ExternalStorageProvider.
|
||||
* @author paulburke
|
||||
*/
|
||||
public static boolean isExternalStorageDocument(final Uri uri) {
|
||||
return "com.android.externalstorage.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is DownloadsProvider.
|
||||
* @author paulburke
|
||||
*/
|
||||
public static boolean isDownloadsDocument(final Uri uri) {
|
||||
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is MediaProvider.
|
||||
* @author paulburke
|
||||
*/
|
||||
public static boolean isMediaDocument(final Uri uri) {
|
||||
return "com.android.providers.media.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is Google Photos.
|
||||
*/
|
||||
public static boolean isGooglePhotosUri(final Uri uri) {
|
||||
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 androidx.annotation.NonNull;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Utilities to help with manipulating Java's dates and calendars.
|
||||
*/
|
||||
public class DateUtil {
|
||||
private DateUtil() {}
|
||||
|
||||
/**
|
||||
* @param date the date to convert to HTTP format
|
||||
* @return the date as specified in rfc 1123, e.g. "Tue, 01 Feb 2011 14:00:00 GMT"
|
||||
*/
|
||||
public static String getDateInHTTPFormat(@NonNull final Date date) {
|
||||
final DateFormat df = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.US);
|
||||
df.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
return df.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timezone offset for the current date in minutes. See
|
||||
* {@link #getTimezoneOffsetInMinutesForGivenDate(Calendar)} for more details.
|
||||
*/
|
||||
public static int getTimezoneOffsetInMinutes(@NonNull final TimeZone timezone) {
|
||||
return getTimezoneOffsetInMinutesForGivenDate(Calendar.getInstance(timezone));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time zone offset for the given date in minutes. The date makes a difference due to daylight
|
||||
* savings time in some regions. We return minutes because we can accurately represent time zones that are
|
||||
* offset by non-integer hour values, e.g. parts of New Zealand at UTC+12:45.
|
||||
*
|
||||
* @param calendar A calendar with the appropriate time zone & date already set.
|
||||
*/
|
||||
public static int getTimezoneOffsetInMinutesForGivenDate(@NonNull final Calendar calendar) {
|
||||
// via Date.getTimezoneOffset deprecated docs (note: it had incorrect order of operations).
|
||||
// Also, we cast to int because we should never overflow here - the max should be GMT+14 = 840.
|
||||
return (int) TimeUnit.MILLISECONDS.toMinutes(calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET));
|
||||
}
|
||||
}
|
|
@ -17,11 +17,9 @@ import org.yaml.snakeyaml.Yaml;
|
|||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.error.YAMLException;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -67,11 +65,7 @@ public class DebugConfig {
|
|||
} catch (final YAMLException e) {
|
||||
throw new ConfigException(e.getMessage());
|
||||
} finally {
|
||||
try {
|
||||
if (fileInputStream != null) {
|
||||
((Closeable) fileInputStream).close();
|
||||
}
|
||||
} catch (final IOException e) { }
|
||||
IOUtils.safeStreamClose(fileInputStream);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,427 @@
|
|||
/* 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.annotation.TargetApi;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.storage.StorageVolume;
|
||||
import android.provider.MediaStore;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Comparator;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.annotation.RobocopTarget;
|
||||
|
||||
import static org.mozilla.gecko.util.ContentUriUtils.getOriginalFilePathFromUri;
|
||||
import static org.mozilla.gecko.util.ContentUriUtils.getTempFilePathFromContentUri;
|
||||
|
||||
public class FileUtils {
|
||||
private static final String LOGTAG = "GeckoFileUtils";
|
||||
private static final String FILE_SCHEME = "file";
|
||||
private static final String CONTENT_SCHEME = "content";
|
||||
private static final String FILE_ABSOLUTE_URI = FILE_SCHEME + "://%s";
|
||||
public static final String CONTENT_TEMP_DIRECTORY = "contentUri";
|
||||
|
||||
/*
|
||||
* A basic Filter for checking a filename and age.
|
||||
**/
|
||||
static public class NameAndAgeFilter implements FilenameFilter {
|
||||
final private String mName;
|
||||
final private double mMaxAge;
|
||||
|
||||
public NameAndAgeFilter(final String name, final double age) {
|
||||
mName = name;
|
||||
mMaxAge = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(final File dir, final String filename) {
|
||||
if (mName == null || mName.matches(filename)) {
|
||||
final File f = new File(dir, filename);
|
||||
|
||||
if (mMaxAge < 0 || System.currentTimeMillis() - f.lastModified() > mMaxAge) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static void delTree(final File dir, final FilenameFilter filter, final boolean recurse) {
|
||||
String[] files = null;
|
||||
|
||||
if (filter != null) {
|
||||
files = dir.list(filter);
|
||||
} else {
|
||||
files = dir.list();
|
||||
}
|
||||
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final String file : files) {
|
||||
final File f = new File(dir, file);
|
||||
delete(f, recurse);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean delete(final File file) throws IOException {
|
||||
return delete(file, true);
|
||||
}
|
||||
|
||||
public static boolean delete(final File file, final boolean recurse) {
|
||||
if (file.isDirectory() && recurse) {
|
||||
// If the quick delete failed and this is a dir, recursively delete the contents of the dir
|
||||
final String[] files = file.list();
|
||||
for (final String temp : files) {
|
||||
final File fileDelete = new File(file, temp);
|
||||
try {
|
||||
delete(fileDelete);
|
||||
} catch (final IOException ex) {
|
||||
Log.i(LOGTAG, "Error deleting " + fileDelete.getPath(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Even if this is a dir, it should now be empty and delete should work
|
||||
return file.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic solution to read a JSONObject from a file. See
|
||||
* {@link #readStringFromFile(File)} for more details.
|
||||
*
|
||||
* @throws IOException if the file is empty, or another IOException occurs
|
||||
* @throws JSONException if the file could not be converted to a JSONObject.
|
||||
*/
|
||||
public static JSONObject readJSONObjectFromFile(final File file) throws IOException, JSONException {
|
||||
if (file.length() == 0) {
|
||||
// Redirect this exception so it's clearer than when the JSON parser catches it.
|
||||
throw new IOException("Given file is empty - the JSON parser cannot create an object from an empty file");
|
||||
}
|
||||
return new JSONObject(readStringFromFile(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic solution to read from a file. For more details,
|
||||
* see {@link #readStringFromInputStreamAndCloseStream(InputStream, int)}.
|
||||
*
|
||||
* This method loads the entire file into memory so will have the expected performance impact.
|
||||
* If you're trying to read a large file, you should be handling your own reading to avoid
|
||||
* out-of-memory errors.
|
||||
*/
|
||||
public static String readStringFromFile(final File file) throws IOException {
|
||||
// FileInputStream will throw FileNotFoundException if the file does not exist, but
|
||||
// File.length will return 0 if the file does not exist so we catch it sooner.
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException("Given file, " + file + ", does not exist");
|
||||
} else if (file.length() == 0) {
|
||||
return "";
|
||||
}
|
||||
final int len = (int) file.length(); // includes potential EOF character.
|
||||
return readStringFromInputStreamAndCloseStream(new FileInputStream(file), len);
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic solution to read from an input stream in UTF-8. This function will read from the stream until it
|
||||
* is finished and close the stream - this is necessary to close the wrapping resources.
|
||||
*
|
||||
* For a higher-level method, see {@link #readStringFromFile(File)}.
|
||||
*
|
||||
* Since this is generic, it may not be the most performant for your use case.
|
||||
*
|
||||
* @param bufferSize Size of the underlying buffer for read optimizations - must be > 0.
|
||||
*/
|
||||
public static String readStringFromInputStreamAndCloseStream(final InputStream inputStream, final int bufferSize)
|
||||
throws IOException {
|
||||
InputStreamReader reader = null;
|
||||
try {
|
||||
if (bufferSize <= 0) {
|
||||
throw new IllegalArgumentException("Expected buffer size larger than 0. Got: " + bufferSize);
|
||||
}
|
||||
|
||||
final StringBuilder stringBuilder = new StringBuilder(bufferSize);
|
||||
reader = new InputStreamReader(inputStream, StringUtils.UTF_8);
|
||||
|
||||
int charsRead;
|
||||
final char[] buffer = new char[bufferSize];
|
||||
while ((charsRead = reader.read(buffer, 0, bufferSize)) != -1) {
|
||||
stringBuilder.append(buffer, 0, charsRead);
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
} finally {
|
||||
IOUtils.safeStreamClose(reader);
|
||||
IOUtils.safeStreamClose(inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic solution to write a JSONObject to a file.
|
||||
* See {@link #writeStringToFile(File, String)} for more details.
|
||||
*/
|
||||
public static void writeJSONObjectToFile(final File file, final JSONObject obj) throws IOException {
|
||||
writeStringToFile(file, obj.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic solution to write to a File - the given file will be overwritten. If it does not exist yet, it will
|
||||
* be created. See {@link #writeStringToOutputStreamAndCloseStream(OutputStream, String)} for more details.
|
||||
*/
|
||||
public static void writeStringToFile(final File file, final String str) throws IOException {
|
||||
writeStringToOutputStreamAndCloseStream(new FileOutputStream(file, false), str);
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic solution to write to an output stream in UTF-8. The stream will be closed at the
|
||||
* completion of this method - it's necessary in order to close the wrapping resources.
|
||||
*
|
||||
* For a higher-level method, see {@link #writeStringToFile(File, String)}.
|
||||
*
|
||||
* Since this is generic, it may not be the most performant for your use case.
|
||||
*/
|
||||
public static void writeStringToOutputStreamAndCloseStream(final OutputStream outputStream, final String str)
|
||||
throws IOException {
|
||||
try {
|
||||
final OutputStreamWriter writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8"));
|
||||
try {
|
||||
writer.write(str);
|
||||
} finally {
|
||||
writer.close();
|
||||
}
|
||||
} finally {
|
||||
// OutputStreamWriter.close can throw before closing the
|
||||
// underlying stream. For safety, we close here too.
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static class FilenameWhitelistFilter implements FilenameFilter {
|
||||
private final Set<String> mFilenameWhitelist;
|
||||
|
||||
public FilenameWhitelistFilter(final Set<String> filenameWhitelist) {
|
||||
mFilenameWhitelist = filenameWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(final File dir, final String filename) {
|
||||
return mFilenameWhitelist.contains(filename);
|
||||
}
|
||||
}
|
||||
|
||||
public static class FilenameRegexFilter implements FilenameFilter {
|
||||
private final Pattern mPattern;
|
||||
|
||||
// Each time `Pattern.matcher` is called, a new matcher is created. We can avoid the excessive object creation
|
||||
// by caching the returned matcher and calling `Matcher.reset` on it. Since Matcher's are not thread safe,
|
||||
// this assumes `FilenameFilter.accept` is not run in parallel (which, according to the source, it is not).
|
||||
private Matcher mCachedMatcher;
|
||||
|
||||
public FilenameRegexFilter(final Pattern pattern) {
|
||||
mPattern = pattern;
|
||||
}
|
||||
|
||||
public FilenameRegexFilter(final String pattern) {
|
||||
mPattern = Pattern.compile(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(final File dir, final String filename) {
|
||||
if (mCachedMatcher == null) {
|
||||
mCachedMatcher = mPattern.matcher(filename);
|
||||
} else {
|
||||
mCachedMatcher.reset(filename);
|
||||
}
|
||||
return mCachedMatcher.matches();
|
||||
}
|
||||
}
|
||||
|
||||
public static class FileLastModifiedComparator implements Comparator<File> {
|
||||
@Override
|
||||
public int compare(final File lhs, final File rhs) {
|
||||
// Long.compare is API 19+.
|
||||
final long lhsModified = lhs.lastModified();
|
||||
final long rhsModified = rhs.lastModified();
|
||||
if (lhsModified < rhsModified) {
|
||||
return -1;
|
||||
} else if (lhsModified == rhsModified) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static File createTempDir(final File directory, final String prefix) {
|
||||
// Force a prefix null check first
|
||||
if (prefix.length() < 3) {
|
||||
throw new IllegalArgumentException("prefix must be at least 3 characters");
|
||||
}
|
||||
File tempDirectory = directory;
|
||||
if (tempDirectory == null) {
|
||||
final String tmpDir = System.getProperty("java.io.tmpdir", ".");
|
||||
tempDirectory = new File(tmpDir);
|
||||
}
|
||||
File result;
|
||||
final Random random = new Random();
|
||||
do {
|
||||
result = new File(tempDirectory, prefix + random.nextInt());
|
||||
} while (!result.mkdirs());
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String resolveContentUri(final Context context, final Uri uri) {
|
||||
String path = getOriginalFilePathFromUri(context, uri);
|
||||
if (TextUtils.isEmpty(path)) {
|
||||
// We cannot always successfully guess the original path of the file behind the
|
||||
// content:// URI, so we need a fallback. This will break local subresources and
|
||||
// relative links, but unfortunately there's nothing else we can do
|
||||
// (see https://issuetracker.google.com/issues/77406791).
|
||||
path = getTempFilePathFromContentUri(context, uri);
|
||||
}
|
||||
return !TextUtils.isEmpty(path) ? String.format(FILE_ABSOLUTE_URI, path) : path;
|
||||
}
|
||||
|
||||
public static String getFileNameFromContentUri(final Context context, final Uri uri) {
|
||||
final ContentResolver cr = context.getContentResolver();
|
||||
final String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
|
||||
String fileName = null;
|
||||
|
||||
try (final Cursor metaCursor = cr.query(uri, projection, null, null, null);) {
|
||||
if (metaCursor.moveToFirst()) {
|
||||
fileName = metaCursor.getString(0);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return canonicalizeFilename(fileName);
|
||||
}
|
||||
|
||||
public static void copy(final Context context, final Uri srcUri, final File dstFile) {
|
||||
try (final InputStream inputStream = context.getContentResolver().openInputStream(srcUri);
|
||||
final OutputStream outputStream = new FileOutputStream(dstFile)) {
|
||||
IOUtils.copy(inputStream, outputStream);
|
||||
} catch (final Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isContentUri(final Uri uri) {
|
||||
return uri != null && uri.getScheme() != null && CONTENT_SCHEME.equals(uri.getScheme());
|
||||
}
|
||||
|
||||
public static boolean isContentUri(final String sUri) {
|
||||
return sUri != null && sUri.startsWith(CONTENT_SCHEME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find the root path of an external (removable) SD card.
|
||||
*
|
||||
* @param uuid If you know the file system UUID (as returned e.g. by
|
||||
* {@link StorageVolume#getUuid()}) of the storage device you're looking for, this
|
||||
* may be used to filter down the selection of available non-emulated storage
|
||||
* devices. If no storage device matching the given UUID was found, the first
|
||||
* non-emulated storage device will be returned.
|
||||
* @return The root path of the storage device.
|
||||
*/
|
||||
@TargetApi(19)
|
||||
public static @Nullable String getExternalStoragePath(final Context context,
|
||||
final @Nullable String uuid) {
|
||||
// Since around the time of Lollipop or Marshmallow, the common convention is for external
|
||||
// SD cards to be mounted at /storage/<file system UUID>/, however this pattern is still not
|
||||
// guaranteed to be 100 % reliable. Therefore we need another way of getting all potential
|
||||
// mount points for external storage devices.
|
||||
// StorageManager.getStorageVolumes() might possibly do the trick and be just what we need
|
||||
// to enumerate all mount points, but it only works on API24+.
|
||||
// So instead, we use the output of getExternalFilesDirs for this purpose, which works on
|
||||
// API19 and up.
|
||||
final File [] externalStorages = context.getExternalFilesDirs(null);
|
||||
final String uuidDir = !TextUtils.isEmpty(uuid) ? '/' + uuid + '/' : null;
|
||||
|
||||
String firstNonEmulatedStorage = null;
|
||||
String targetStorage = null;
|
||||
for (final File externalStorage : externalStorages) {
|
||||
if (isExternalStorageEmulated(externalStorage)) {
|
||||
// The paths returned by getExternalFilesDirs also include locations that actually
|
||||
// sit on the internal "external" storage, so we need to filter them out again.
|
||||
continue;
|
||||
}
|
||||
String storagePath = externalStorage.getAbsolutePath();
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* NOTE: This is our big assumption in this function: That the folders returned by *
|
||||
* context.getExternalFilesDir() will always be located somewhere inside *
|
||||
* /<storage root path>/Android/<app specific directories>, so that we can retrieve *
|
||||
* the storage root by simply snipping off everything starting from "/Android". *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
storagePath = storagePath.substring(0, storagePath.indexOf("/Android"));
|
||||
if (firstNonEmulatedStorage == null) {
|
||||
firstNonEmulatedStorage = storagePath;
|
||||
}
|
||||
if (!TextUtils.isEmpty(uuidDir) && storagePath.contains(uuidDir)) {
|
||||
targetStorage = storagePath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetStorage == null) {
|
||||
// Either no UUID to narrow down the selection was given, or else this device doesn't
|
||||
// mount its SD cards using the file system UUID, so we just fall back to the first
|
||||
// non-emulated storage path we found.
|
||||
targetStorage = firstNonEmulatedStorage;
|
||||
}
|
||||
return targetStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method because the framework version of this function is only available from API21+.
|
||||
*
|
||||
* @see Environment#isExternalStorageEmulated(File)
|
||||
*/
|
||||
public static boolean isExternalStorageEmulated(final File path) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
return Environment.isExternalStorageEmulated(path);
|
||||
} else {
|
||||
final String absPath = path.getAbsolutePath();
|
||||
// This is rather hacky, but then SD card support on older Android versions
|
||||
// was equally messy.
|
||||
return absPath.contains("/sdcard0") || absPath.contains("/storage/emulated");
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String canonicalizeFilename(@Nullable final String originalFilename) {
|
||||
if (TextUtils.isEmpty(originalFilename)) {
|
||||
return null;
|
||||
} else {
|
||||
return new File(originalFilename).getName();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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;
|
||||
|
||||
public final class FloatUtils {
|
||||
private FloatUtils() {}
|
||||
|
||||
public static boolean fuzzyEquals(final float a, final float b) {
|
||||
return (Math.abs(a - b) < 1e-6);
|
||||
}
|
||||
}
|
|
@ -43,6 +43,8 @@ final class GeckoBackgroundThread extends Thread {
|
|||
|
||||
private static void startThread(final Runnable initialRunnable) {
|
||||
thread = new GeckoBackgroundThread(initialRunnable);
|
||||
ThreadUtils.setBackgroundThread(thread);
|
||||
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ package org.mozilla.gecko.util;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.system.Os;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -50,6 +52,50 @@ public final class HardwareUtils {
|
|||
return sIsLargeTablet || sIsSmallTablet;
|
||||
}
|
||||
|
||||
private static String getPreferredAbi() {
|
||||
String abi = null;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
abi = Build.SUPPORTED_ABIS[0];
|
||||
}
|
||||
if (abi == null) {
|
||||
abi = Build.CPU_ABI;
|
||||
}
|
||||
return abi;
|
||||
}
|
||||
|
||||
public static boolean isARMSystem() {
|
||||
return "armeabi-v7a".equals(getPreferredAbi());
|
||||
}
|
||||
|
||||
public static boolean isARM64System() {
|
||||
// 64-bit support was introduced in 21.
|
||||
return "arm64-v8a".equals(getPreferredAbi());
|
||||
}
|
||||
|
||||
public static boolean isX86System() {
|
||||
if ("x86".equals(getPreferredAbi())) {
|
||||
return true;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
// On some devices we have to look into the kernel release string.
|
||||
try {
|
||||
return Os.uname().release.contains("-x86_");
|
||||
} catch (final Exception e) {
|
||||
Log.w(LOGTAG, "Cannot get uname", e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String getRealAbi() {
|
||||
if (isX86System() && isARMSystem()) {
|
||||
// Some x86 devices try to make us believe we're ARM,
|
||||
// in which case CPU_ABI is not reliable.
|
||||
return "x86";
|
||||
}
|
||||
return getPreferredAbi();
|
||||
}
|
||||
|
||||
private static final int ELF_MACHINE_UNKNOWN = 0;
|
||||
private static final int ELF_MACHINE_X86 = 0x03;
|
||||
private static final int ELF_MACHINE_X86_64 = 0x3e;
|
||||
|
|
|
@ -6,7 +6,6 @@ package org.mozilla.gecko.util;
|
|||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
|
@ -52,11 +51,7 @@ public final class INIParser extends INISection {
|
|||
} catch (final IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (writer != null) {
|
||||
((Closeable) writer).close();
|
||||
}
|
||||
} catch (final IOException e) { }
|
||||
IOUtils.safeStreamClose(writer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,4 +151,27 @@ public final class INIParser extends INISection {
|
|||
getSections();
|
||||
return mSections.get(key);
|
||||
}
|
||||
|
||||
// remove an entire section from the file
|
||||
public void removeSection(final String name) {
|
||||
// ensure that we have parsed the file
|
||||
getSections();
|
||||
mSections.remove(name);
|
||||
}
|
||||
|
||||
// rename a section; nuking any previous section with the new
|
||||
// name in the process
|
||||
public void renameSection(final String oldName, final String newName) {
|
||||
// ensure that we have parsed the file
|
||||
getSections();
|
||||
|
||||
mSections.remove(newName);
|
||||
final INISection section = mSections.get(oldName);
|
||||
if (section == null)
|
||||
return;
|
||||
|
||||
section.setName(newName);
|
||||
mSections.remove(oldName);
|
||||
mSections.put(newName, section);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* 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.util.Log;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Static helper class containing useful methods for manipulating IO objects.
|
||||
*/
|
||||
public class IOUtils {
|
||||
private static final String LOGTAG = "GeckoIOUtils";
|
||||
|
||||
/**
|
||||
* Represents the result of consuming an input stream, holding the returned data as well
|
||||
* as the length of the data returned.
|
||||
* The byte[] is not guaranteed to be trimmed to the size of the data acquired from the stream:
|
||||
* hence the need for the length field. This strategy avoids the need to copy the data into a
|
||||
* trimmed buffer after consumption.
|
||||
*/
|
||||
public static class ConsumedInputStream {
|
||||
public final int consumedLength;
|
||||
// Only reassigned in getTruncatedData.
|
||||
private byte[] mConsumedData;
|
||||
|
||||
public ConsumedInputStream(final int consumedLength, final byte[] consumedData) {
|
||||
this.consumedLength = consumedLength;
|
||||
this.mConsumedData = consumedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data trimmed to the length of the actual payload read, caching the result.
|
||||
*/
|
||||
public byte[] getTruncatedData() {
|
||||
if (mConsumedData.length == consumedLength) {
|
||||
return mConsumedData;
|
||||
}
|
||||
|
||||
mConsumedData = truncateBytes(mConsumedData, consumedLength);
|
||||
return mConsumedData;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return mConsumedData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully read an InputStream into a byte array.
|
||||
* @param iStream the InputStream to consume.
|
||||
* @param bufferSize The initial size of the buffer to allocate. It will be grown as
|
||||
* needed, but if the caller knows something about the InputStream then
|
||||
* passing a good value here can improve performance.
|
||||
*/
|
||||
public static ConsumedInputStream readFully(final InputStream iStream, final int bufferSize) {
|
||||
// Allocate a buffer to hold the raw data downloaded.
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
||||
// The offset of the start of the buffer's free space.
|
||||
int bPointer = 0;
|
||||
|
||||
// The quantity of bytes the last call to read yielded.
|
||||
int lastRead = 0;
|
||||
try {
|
||||
// Fully read the data into the buffer.
|
||||
while (lastRead != -1) {
|
||||
// Read as many bytes as are currently available into the buffer.
|
||||
lastRead = iStream.read(buffer, bPointer, buffer.length - bPointer);
|
||||
bPointer += lastRead;
|
||||
|
||||
// If buffer has overflowed, double its size and carry on.
|
||||
if (bPointer > buffer.length) {
|
||||
final int newBufferSize = bufferSize * 2;
|
||||
final byte[] newBuffer = new byte[newBufferSize];
|
||||
|
||||
// Copy the contents of the old buffer into the new buffer.
|
||||
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
|
||||
buffer = newBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
return new ConsumedInputStream(bPointer + 1, buffer);
|
||||
} catch (final IOException e) {
|
||||
Log.e(LOGTAG, "Error consuming input stream.", e);
|
||||
} finally {
|
||||
IOUtils.safeStreamClose(iStream);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate a given byte[] to a given length. Returns a new byte[] with the first length many
|
||||
* bytes of the input.
|
||||
*/
|
||||
public static byte[] truncateBytes(final byte[] bytes, final int length) {
|
||||
final byte[] newBytes = new byte[length];
|
||||
System.arraycopy(bytes, 0, newBytes, 0, length);
|
||||
|
||||
return newBytes;
|
||||
}
|
||||
|
||||
public static void safeStreamClose(final Closeable stream) {
|
||||
try {
|
||||
if (stream != null)
|
||||
stream.close();
|
||||
} catch (final IOException e) { }
|
||||
}
|
||||
|
||||
public static void copy(final InputStream in, final OutputStream out) throws IOException {
|
||||
final byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,16 +9,105 @@ package org.mozilla.gecko.util;
|
|||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.mozilla.gecko.mozglue.SafeIntent;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utilities for Intents.
|
||||
*/
|
||||
public class IntentUtils {
|
||||
public static final String ENV_VAR_IN_AUTOMATION = "MOZ_IN_AUTOMATION";
|
||||
|
||||
private static final String ENV_VAR_REGEX = "(.+)=(.*)";
|
||||
|
||||
private IntentUtils() {}
|
||||
|
||||
/**
|
||||
* Returns a list of environment variables and their values. These are parsed from an Intent extra
|
||||
* with the key -> value format: env# -> ENV_VAR=VALUE, where # is an integer starting at 0.
|
||||
*
|
||||
* @return A Map of environment variable name to value, e.g. ENV_VAR -> VALUE
|
||||
*/
|
||||
public static HashMap<String, String> getEnvVarMap(@NonNull final SafeIntent intent) {
|
||||
// Optimization: get matcher for re-use. Pattern.matcher creates a new object every time so it'd be great
|
||||
// to avoid the unnecessary allocation, particularly because we expect to be called on the startup path.
|
||||
final Pattern envVarPattern = Pattern.compile(ENV_VAR_REGEX);
|
||||
final Matcher matcher = envVarPattern.matcher(""); // argument does not matter here.
|
||||
|
||||
// This is expected to be an external intent so we should use SafeIntent to prevent crashing.
|
||||
final HashMap<String, String> out = new HashMap<>();
|
||||
int i = 0;
|
||||
while (true) {
|
||||
final String envKey = "env" + i;
|
||||
i += 1;
|
||||
if (!intent.hasExtra(envKey)) {
|
||||
break;
|
||||
}
|
||||
|
||||
maybeAddEnvVarToEnvVarMap(out, intent, envKey, matcher);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param envVarMap the map to add the env var to
|
||||
* @param intent the intent from which to extract the env var
|
||||
* @param envKey the key at which the env var resides
|
||||
* @param envVarMatcher a matcher initialized with the env var pattern to extract
|
||||
*/
|
||||
private static void maybeAddEnvVarToEnvVarMap(@NonNull final HashMap<String, String> envVarMap,
|
||||
@NonNull final SafeIntent intent, @NonNull final String envKey, @NonNull final Matcher envVarMatcher) {
|
||||
final String envValue = intent.getStringExtra(envKey);
|
||||
if (envValue == null) {
|
||||
return; // nothing to do here!
|
||||
}
|
||||
|
||||
envVarMatcher.reset(envValue);
|
||||
if (envVarMatcher.matches()) {
|
||||
final String envVarName = envVarMatcher.group(1);
|
||||
final String envVarValue = envVarMatcher.group(2);
|
||||
envVarMap.put(envVarName, envVarValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static Bundle getBundleExtraSafe(final Intent intent, final String name) {
|
||||
return new SafeIntent(intent).getBundleExtra(name);
|
||||
}
|
||||
|
||||
public static String getStringExtraSafe(final Intent intent, final String name) {
|
||||
return new SafeIntent(intent).getStringExtra(name);
|
||||
}
|
||||
|
||||
public static boolean getBooleanExtraSafe(final Intent intent, final String name, final boolean defaultValue) {
|
||||
return new SafeIntent(intent).getBooleanExtra(name, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether or not we're in automation from the passed in environment variables.
|
||||
*
|
||||
* We need to read environment variables from the intent string
|
||||
* extra because environment variables from our test harness aren't set
|
||||
* until Gecko is loaded, and we need to know this before then.
|
||||
*
|
||||
* The return value of this method should be used early since other
|
||||
* initialization may depend on its results.
|
||||
*/
|
||||
@CheckResult
|
||||
public static boolean getIsInAutomationFromEnvironment(final SafeIntent intent) {
|
||||
final HashMap<String, String> envVars = IntentUtils.getEnvVarMap(intent);
|
||||
return !TextUtils.isEmpty(envVars.get(IntentUtils.ENV_VAR_IN_AUTOMATION));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Uri instance which is equivalent to uri,
|
||||
* but with a guaranteed-lowercase scheme as if the API level 16 method
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
|
||||
package org.mozilla.gecko.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
public class NetworkUtils {
|
||||
|
@ -63,6 +65,13 @@ public class NetworkUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether network connectivity exists and it is possible to establish connections and pass data.
|
||||
*/
|
||||
public static boolean isConnected(final @NonNull Context context) {
|
||||
return isConnected((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));
|
||||
}
|
||||
|
||||
public static boolean isConnected(final ConnectivityManager connectivityManager) {
|
||||
if (connectivityManager == null) {
|
||||
return false;
|
||||
|
@ -101,6 +110,11 @@ public class NetworkUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean isWifi(@NonNull final Context context) {
|
||||
final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
return getConnectionType(connectivityManager) == ConnectionType.WIFI;
|
||||
}
|
||||
|
||||
public static ConnectionType getConnectionType(final ConnectivityManager connectivityManager) {
|
||||
if (connectivityManager == null) {
|
||||
return ConnectionType.NONE;
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/* 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.content.res.Resources;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* {@code RawResource} provides API to load raw resources in different
|
||||
* forms. For now, we only load them as strings. We're using raw resources
|
||||
* as localizable 'assets' as opposed to a string that can be directly
|
||||
* translatable e.g. JSON file vs string.
|
||||
*
|
||||
* This is just a utility class to avoid code duplication for the different
|
||||
* cases where need to read such assets.
|
||||
*/
|
||||
public final class RawResource {
|
||||
public static String getAsString(final Context context, final int id) throws IOException {
|
||||
InputStreamReader reader = null;
|
||||
|
||||
try {
|
||||
final Resources res = context.getResources();
|
||||
final InputStream is = res.openRawResource(id);
|
||||
if (is == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
reader = new InputStreamReader(is);
|
||||
|
||||
final char[] buffer = new char[1024];
|
||||
final StringWriter s = new StringWriter();
|
||||
|
||||
int n;
|
||||
while ((n = reader.read(buffer, 0, buffer.length)) != -1) {
|
||||
s.write(buffer, 0, n);
|
||||
}
|
||||
|
||||
return s.toString();
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2017 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.
|
||||
//
|
||||
// Copied from Chromium's /src/base/android/java/src/org/chromium/base/StrictModeContext.java.
|
||||
|
||||
package org.mozilla.gecko.util;
|
||||
|
||||
import android.os.StrictMode;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* Enables try-with-resources compatible StrictMode violation whitelisting.
|
||||
*
|
||||
* Example:
|
||||
* <pre>
|
||||
* try (StrictModeContext unused = StrictModeContext.allowDiskWrites()) {
|
||||
* return Example.doThingThatRequiresDiskWrites();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Because the StrictModeContext variable is technically unused, the containing method might have to
|
||||
* be annotated with <code>@SuppressWarnings("try")</code>.
|
||||
*
|
||||
*/
|
||||
public final class StrictModeContext implements Closeable {
|
||||
private final StrictMode.ThreadPolicy mThreadPolicy;
|
||||
private final StrictMode.VmPolicy mVmPolicy;
|
||||
|
||||
private StrictModeContext(final StrictMode.ThreadPolicy threadPolicy,
|
||||
final StrictMode.VmPolicy vmPolicy) {
|
||||
mThreadPolicy = threadPolicy;
|
||||
mVmPolicy = vmPolicy;
|
||||
}
|
||||
|
||||
private StrictModeContext(final StrictMode.ThreadPolicy threadPolicy) {
|
||||
this(threadPolicy, null);
|
||||
}
|
||||
|
||||
private StrictModeContext(final StrictMode.VmPolicy vmPolicy) {
|
||||
this(null, vmPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for disabling all VM-level StrictMode checks with try-with-resources.
|
||||
* Includes everything listed here:
|
||||
* https://developer.android.com/reference/android/os/StrictMode.VmPolicy.Builder.html
|
||||
*/
|
||||
public static StrictModeContext allowAllVmPolicies() {
|
||||
final StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
|
||||
StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX);
|
||||
return new StrictModeContext(oldPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for disabling StrictMode for disk-writes and -reads with
|
||||
* try-with-resources.
|
||||
*/
|
||||
public static StrictModeContext allowDiskWrites() {
|
||||
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
|
||||
return new StrictModeContext(oldPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for disabling StrictMode for disk-reads with try-with-resources.
|
||||
*/
|
||||
public static StrictModeContext allowDiskReads() {
|
||||
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
|
||||
return new StrictModeContext(oldPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for disabling StrictMode for slow calls with try-with-resources.
|
||||
*/
|
||||
public static StrictModeContext allowSlowCalls() {
|
||||
final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
|
||||
StrictMode.setThreadPolicy(
|
||||
new StrictMode.ThreadPolicy.Builder(oldPolicy).permitCustomSlowCalls().build());
|
||||
return new StrictModeContext(oldPolicy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (mThreadPolicy != null) {
|
||||
StrictMode.setThreadPolicy(mThreadPolicy);
|
||||
}
|
||||
if (mVmPolicy != null) {
|
||||
StrictMode.setVmPolicy(mVmPolicy);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,331 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Set;
|
||||
|
||||
public class StringUtils {
|
||||
private static final String LOGTAG = "GeckoStringUtils";
|
||||
|
||||
private static final String FILTER_URL_PREFIX = "filter://";
|
||||
private static final String USER_ENTERED_URL_PREFIX = "user-entered:";
|
||||
|
||||
|
||||
/**
|
||||
* The UTF-8 charset.
|
||||
*/
|
||||
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
/*
|
||||
* This method tries to guess if the given string could be a search query or URL,
|
||||
* and returns a previous result if there is ambiguity
|
||||
*
|
||||
* Search examples:
|
||||
* foo
|
||||
* foo bar.com
|
||||
* foo http://bar.com
|
||||
*
|
||||
* URL examples
|
||||
* foo.com
|
||||
* foo.c
|
||||
* :foo
|
||||
* http://foo.com bar
|
||||
*
|
||||
* wasSearchQuery specifies whether text was a search query before the latest change
|
||||
* in text. In ambiguous cases where the new text can be either a search or a URL,
|
||||
* wasSearchQuery is returned
|
||||
*/
|
||||
public static boolean isSearchQuery(final String text, final boolean wasSearchQuery) {
|
||||
// We remove leading and trailing white spaces when decoding URLs
|
||||
final String trimmedText = text.trim();
|
||||
if (trimmedText.length() == 0) {
|
||||
return wasSearchQuery;
|
||||
}
|
||||
final int colon = trimmedText.indexOf(':');
|
||||
final int dot = trimmedText.indexOf('.');
|
||||
final int space = trimmedText.indexOf(' ');
|
||||
|
||||
// If a space is found in a trimmed string, we assume this is a search query(Bug 1278245)
|
||||
if (space > -1) {
|
||||
return true;
|
||||
}
|
||||
// Otherwise, if a dot or a colon is found, we assume this is a URL
|
||||
if (dot > -1 || colon > -1) {
|
||||
return false;
|
||||
}
|
||||
// Otherwise, text is ambiguous, and we keep its status unchanged
|
||||
return wasSearchQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for the existence of %s and %S in a given URL
|
||||
*
|
||||
* @return True if %s or %S exists, False otherwise.
|
||||
*/
|
||||
public static boolean queryExists(final String inputURL) {
|
||||
if (inputURL == null) {
|
||||
return false;
|
||||
}
|
||||
return inputURL.contains("%s") || inputURL.contains("%S");
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the ref from a URL, if present
|
||||
*
|
||||
* @return The base URL, without the ref. The original String is returned if it has no ref,
|
||||
* of if the input is malformed.
|
||||
*/
|
||||
public static String stripRef(final String inputURL) {
|
||||
if (inputURL == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int refIndex = inputURL.indexOf('#');
|
||||
|
||||
if (refIndex >= 0) {
|
||||
return inputURL.substring(0, refIndex);
|
||||
}
|
||||
|
||||
return inputURL;
|
||||
}
|
||||
|
||||
public static class UrlFlags {
|
||||
public static final int NONE = 0;
|
||||
public static final int STRIP_HTTPS = 1;
|
||||
}
|
||||
|
||||
public static String stripScheme(final String url) {
|
||||
return stripScheme(url, UrlFlags.NONE);
|
||||
}
|
||||
|
||||
public static String stripScheme(final String url, final int flags) {
|
||||
if (url == null) {
|
||||
return url;
|
||||
}
|
||||
|
||||
String newURL = url;
|
||||
|
||||
if (newURL.startsWith("http://")) {
|
||||
newURL = newURL.replace("http://", "");
|
||||
} else if (newURL.startsWith("https://") && flags == UrlFlags.STRIP_HTTPS) {
|
||||
newURL = newURL.replace("https://", "");
|
||||
}
|
||||
|
||||
if (newURL.endsWith("/")) {
|
||||
newURL = newURL.substring(0, newURL.length() - 1);
|
||||
}
|
||||
|
||||
return newURL;
|
||||
}
|
||||
|
||||
public static boolean isHttpOrHttps(final String url) {
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return url.startsWith("http://") || url.startsWith("https://");
|
||||
}
|
||||
|
||||
public static String stripCommonSubdomains(final String host) {
|
||||
if (host == null) {
|
||||
return host;
|
||||
}
|
||||
|
||||
// In contrast to desktop, we also strip mobile subdomains,
|
||||
// since its unlikely users are intentionally typing them
|
||||
int start = 0;
|
||||
|
||||
if (host.startsWith("www.")) {
|
||||
start = 4;
|
||||
} else if (host.startsWith("mobile.")) {
|
||||
start = 7;
|
||||
} else if (host.startsWith("m.")) {
|
||||
start = 2;
|
||||
}
|
||||
|
||||
return host.substring(start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the url query string for the first value with the given key.
|
||||
*/
|
||||
public static String getQueryParameter(final String url, final String desiredKey) {
|
||||
if (TextUtils.isEmpty(url) || TextUtils.isEmpty(desiredKey)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String[] urlParts = url.split("\\?");
|
||||
if (urlParts.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String query = urlParts[1];
|
||||
for (final String param : query.split("&")) {
|
||||
final String pair[] = param.split("=");
|
||||
final String key = Uri.decode(pair[0]);
|
||||
|
||||
// Key is empty or does not match the key we're looking for, discard
|
||||
if (TextUtils.isEmpty(key) || !key.equals(desiredKey)) {
|
||||
continue;
|
||||
}
|
||||
// No value associated with key, discard
|
||||
if (pair.length < 2) {
|
||||
continue;
|
||||
}
|
||||
final String value = Uri.decode(pair[1]);
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isFilterUrl(final String url) {
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return url.startsWith(FILTER_URL_PREFIX);
|
||||
}
|
||||
|
||||
public static String getFilterFromUrl(final String url) {
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return url.substring(FILTER_URL_PREFIX.length());
|
||||
}
|
||||
|
||||
public static boolean isShareableUrl(final String url) {
|
||||
final String scheme = Uri.parse(url).getScheme();
|
||||
return !("about".equals(scheme) || "chrome".equals(scheme) ||
|
||||
"file".equals(scheme) || "resource".equals(scheme));
|
||||
}
|
||||
|
||||
public static boolean isUserEnteredUrl(final String url) {
|
||||
return (url != null && url.startsWith(USER_ENTERED_URL_PREFIX));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a url with a user-entered scheme, extract the
|
||||
* scheme-specific component. For e.g, given "user-entered://www.google.com",
|
||||
* this method returns "//www.google.com". If the passed url
|
||||
* does not have a user-entered scheme, the same url will be returned.
|
||||
*
|
||||
* @param url to be decoded
|
||||
* @return url component entered by user
|
||||
*/
|
||||
public static String decodeUserEnteredUrl(final String url) {
|
||||
final Uri uri = Uri.parse(url);
|
||||
if ("user-entered".equals(uri.getScheme())) {
|
||||
return uri.getSchemeSpecificPart();
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
public static String encodeUserEnteredUrl(final String url) {
|
||||
return Uri.fromParts("user-entered", url, null).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compatibility layer for API < 11.
|
||||
*
|
||||
* Returns a set of the unique names of all query parameters. Iterating
|
||||
* over the set will return the names in order of their first occurrence.
|
||||
*
|
||||
* @param uri
|
||||
* @throws UnsupportedOperationException if this isn't a hierarchical URI
|
||||
*
|
||||
* @return a set of decoded names
|
||||
*/
|
||||
public static Set<String> getQueryParameterNames(final Uri uri) {
|
||||
return uri.getQueryParameterNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The index of the path segment of an URL, or -1 if no path segment was detected.
|
||||
*/
|
||||
public static int pathStartIndex(final String text) {
|
||||
if (text.contains("://")) {
|
||||
return text.indexOf('/', text.indexOf("://") + 3);
|
||||
} else {
|
||||
return text.indexOf('/');
|
||||
}
|
||||
}
|
||||
|
||||
public static String safeSubstring(@NonNull final String str, final int start, final int end) {
|
||||
return str.substring(
|
||||
Math.max(0, start),
|
||||
Math.min(end, str.length()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this might be a RTL (right-to-left) text by looking at the first character.
|
||||
*/
|
||||
public static boolean isRTL(final String text) {
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final char character = text.charAt(0);
|
||||
final byte directionality = Character.getDirectionality(character);
|
||||
|
||||
return directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT
|
||||
|| directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC
|
||||
|| directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING
|
||||
|| directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force LTR (left-to-right) by prepending the text with the "left-to-right mark" (U+200E) if needed.
|
||||
*/
|
||||
public static String forceLTR(final String text) {
|
||||
if (!isRTL(text)) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return "\u200E" + text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Case-insensitive version of {@link String#startsWith(String)}.
|
||||
*/
|
||||
public static boolean caseInsensitiveStartsWith(final String text, final String prefix) {
|
||||
return caseInsensitiveStartsWith(text, prefix, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Case-insensitive version of {@link String#startsWith(String, int)}.
|
||||
*/
|
||||
public static boolean caseInsensitiveStartsWith(final String text, final String prefix,
|
||||
final int start) {
|
||||
return text.regionMatches(true, start, prefix, 0, prefix.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Measures the width of the given substring when rendered using the specified Paint.
|
||||
*
|
||||
* @param text String to measure and return its width
|
||||
* @param start Index of the first char in the string to measure
|
||||
* @param end 1 past the last char in the string measure
|
||||
* @param textPaint the paint used to render the text
|
||||
* @return the width of the specified substring in screen pixels
|
||||
*/
|
||||
public static int getTextWidth(final String text, final int start, final int end, final Paint textPaint) {
|
||||
final Rect bounds = new Rect();
|
||||
textPaint.getTextBounds(text, start, end, bounds);
|
||||
return bounds.width();
|
||||
}
|
||||
}
|
|
@ -26,6 +26,8 @@ public final class ThreadUtils {
|
|||
private static final Thread sUiThread = Looper.getMainLooper().getThread();
|
||||
private static final Handler sUiHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private static volatile Thread sBackgroundThread;
|
||||
|
||||
// Referenced directly from GeckoAppShell in highly performance-sensitive code (The extra
|
||||
// function call of the getter was harming performance. (Bug 897123))
|
||||
// Once Bug 709230 is resolved we should reconsider this as ProGuard should be able to optimise
|
||||
|
@ -33,6 +35,10 @@ public final class ThreadUtils {
|
|||
public static Handler sGeckoHandler;
|
||||
public static volatile Thread sGeckoThread;
|
||||
|
||||
public static void setBackgroundThread(final Thread thread) {
|
||||
sBackgroundThread = thread;
|
||||
}
|
||||
|
||||
public static Thread getUiThread() {
|
||||
return sUiThread;
|
||||
}
|
||||
|
@ -61,6 +67,10 @@ public final class ThreadUtils {
|
|||
sUiHandler.post(runnable);
|
||||
}
|
||||
|
||||
public static Thread getBackgroundThread() {
|
||||
return sBackgroundThread;
|
||||
}
|
||||
|
||||
public static Handler getBackgroundHandler() {
|
||||
return GeckoBackgroundThread.getHandler();
|
||||
}
|
||||
|
@ -77,6 +87,10 @@ public final class ThreadUtils {
|
|||
assertOnThread(getUiThread(), AssertBehavior.THROW);
|
||||
}
|
||||
|
||||
public static void assertNotOnUiThread() {
|
||||
assertNotOnThread(getUiThread(), AssertBehavior.THROW);
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static void assertOnGeckoThread() {
|
||||
assertOnThread(sGeckoThread, AssertBehavior.THROW);
|
||||
|
@ -86,6 +100,11 @@ public final class ThreadUtils {
|
|||
assertOnThreadComparison(expectedThread, behavior, true);
|
||||
}
|
||||
|
||||
public static void assertNotOnThread(final Thread expectedThread,
|
||||
final AssertBehavior behavior) {
|
||||
assertOnThreadComparison(expectedThread, behavior, false);
|
||||
}
|
||||
|
||||
private static void assertOnThreadComparison(final Thread expectedThread,
|
||||
final AssertBehavior behavior,
|
||||
final boolean expected) {
|
||||
|
@ -117,10 +136,26 @@ public final class ThreadUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean isOnGeckoThread() {
|
||||
if (sGeckoThread != null) {
|
||||
return isOnThread(sGeckoThread);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isOnUiThread() {
|
||||
return isOnThread(getUiThread());
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static boolean isOnBackgroundThread() {
|
||||
if (sBackgroundThread == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isOnThread(sBackgroundThread);
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static boolean isOnThread(final Thread thread) {
|
||||
return (Thread.currentThread().getId() == thread.getId());
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utilities for UUIDs.
|
||||
*/
|
||||
public class UUIDUtil {
|
||||
private UUIDUtil() {}
|
||||
|
||||
public static final String UUID_REGEX = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}";
|
||||
public static final Pattern UUID_PATTERN = Pattern.compile(UUID_REGEX);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/* 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.os.Handler;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* A Handler to help prevent memory leaks when using Handlers as inner classes.
|
||||
*
|
||||
* To use, extend the Handler, if it's an inner class, make it static,
|
||||
* and reference `this` via the associated WeakReference.
|
||||
*
|
||||
* For additional context, see the "HandlerLeak" android lint item and this post by Romain Guy:
|
||||
* https://groups.google.com/forum/#!msg/android-developers/1aPZXZG6kWk/lIYDavGYn5UJ
|
||||
*/
|
||||
public class WeakReferenceHandler<T> extends Handler {
|
||||
public final WeakReference<T> mTarget;
|
||||
|
||||
public WeakReferenceHandler(final T that) {
|
||||
super();
|
||||
mTarget = new WeakReference<>(that);
|
||||
}
|
||||
}
|
|
@ -10,13 +10,13 @@ import org.mozilla.gecko.AndroidGamepadManager;
|
|||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.InputMethods;
|
||||
import org.mozilla.gecko.SurfaceViewWrapper;
|
||||
import org.mozilla.gecko.util.ActivityUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
|
@ -213,17 +213,6 @@ public class GeckoView extends FrameLayout {
|
|||
init();
|
||||
}
|
||||
|
||||
private static Activity getActivityFromContext(final Context outerContext) {
|
||||
Context context = outerContext;
|
||||
while (context instanceof ContextWrapper) {
|
||||
if (context instanceof Activity) {
|
||||
return (Activity) context;
|
||||
}
|
||||
context = ((ContextWrapper) context).getBaseContext();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
|
@ -246,7 +235,7 @@ public class GeckoView extends FrameLayout {
|
|||
|
||||
mSurfaceWrapper.setListener(mDisplay);
|
||||
|
||||
final Activity activity = getActivityFromContext(getContext());
|
||||
final Activity activity = ActivityUtils.getActivityFromContext(getContext());
|
||||
if (activity != null) {
|
||||
mSelectionActionDelegate = new BasicSelectionActionDelegate(activity);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 org.junit.Test;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for date utilities.
|
||||
*/
|
||||
public class TestDateUtil {
|
||||
@Test
|
||||
public void testGetDateInHTTPFormatGMT() {
|
||||
final TimeZone gmt = TimeZone.getTimeZone("GMT");
|
||||
final GregorianCalendar calendar = new GregorianCalendar(gmt, Locale.US);
|
||||
calendar.set(2011, Calendar.FEBRUARY, 1, 14, 0, 0);
|
||||
final String expectedDate = "Tue, 01 Feb 2011 14:00:00 GMT";
|
||||
|
||||
final String actualDate = DateUtil.getDateInHTTPFormat(calendar.getTime());
|
||||
assertEquals("Returned date is expected", expectedDate, actualDate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDateInHTTPFormatNonGMT() {
|
||||
final TimeZone kst = TimeZone.getTimeZone("Asia/Seoul"); // no daylight savings time.
|
||||
final GregorianCalendar calendar = new GregorianCalendar(kst, Locale.US);
|
||||
calendar.set(2011, Calendar.FEBRUARY, 1, 14, 0, 0);
|
||||
final String expectedDate = "Tue, 01 Feb 2011 05:00:00 GMT";
|
||||
|
||||
final String actualDate = DateUtil.getDateInHTTPFormat(calendar.getTime());
|
||||
assertEquals("Returned date is expected", expectedDate, actualDate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTimezoneOffsetInMinutes() {
|
||||
assertEquals("GMT has no offset", 0, DateUtil.getTimezoneOffsetInMinutes(TimeZone.getTimeZone("GMT")));
|
||||
|
||||
// We use custom timezones because they don't have daylight savings time.
|
||||
assertEquals("Offset for GMT-8 is correct",
|
||||
-480, DateUtil.getTimezoneOffsetInMinutes(TimeZone.getTimeZone("GMT-8")));
|
||||
assertEquals("Offset for GMT+12:45 is correct",
|
||||
765, DateUtil.getTimezoneOffsetInMinutes(TimeZone.getTimeZone("GMT+12:45")));
|
||||
|
||||
// We use a non-custom timezone without DST.
|
||||
assertEquals("Offset for KST is correct",
|
||||
540, DateUtil.getTimezoneOffsetInMinutes(TimeZone.getTimeZone("Asia/Seoul")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTimezoneOffsetInMinutesForGivenDateNoDaylightSavingsTime() {
|
||||
final TimeZone kst = TimeZone.getTimeZone("Asia/Seoul");
|
||||
final Calendar[] calendars =
|
||||
new Calendar[] { getCalendarForMonth(Calendar.DECEMBER), getCalendarForMonth(Calendar.AUGUST) };
|
||||
for (final Calendar cal : calendars) {
|
||||
cal.setTimeZone(kst);
|
||||
assertEquals("Offset for KST does not change with daylight savings time",
|
||||
540, DateUtil.getTimezoneOffsetInMinutesForGivenDate(cal));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTimezoneOffsetInMinutesForGivenDateDaylightSavingsTime() {
|
||||
final TimeZone pacificTimeZone = TimeZone.getTimeZone("America/Los_Angeles");
|
||||
final Calendar pstCalendar = getCalendarForMonth(Calendar.DECEMBER);
|
||||
final Calendar pdtCalendar = getCalendarForMonth(Calendar.AUGUST);
|
||||
pstCalendar.setTimeZone(pacificTimeZone);
|
||||
pdtCalendar.setTimeZone(pacificTimeZone);
|
||||
assertEquals("Offset for PST is correct", -480, DateUtil.getTimezoneOffsetInMinutesForGivenDate(pstCalendar));
|
||||
assertEquals("Offset for PDT is correct", -420, DateUtil.getTimezoneOffsetInMinutesForGivenDate(pdtCalendar));
|
||||
|
||||
}
|
||||
|
||||
private Calendar getCalendarForMonth(final int month) {
|
||||
return new GregorianCalendar(2000, month, 1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,360 @@
|
|||
/*
|
||||
* 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 org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Matchers;
|
||||
import org.mozilla.gecko.util.FileUtils.FileLastModifiedComparator;
|
||||
import org.mozilla.gecko.util.FileUtils.FilenameRegexFilter;
|
||||
import org.mozilla.gecko.util.FileUtils.FilenameWhitelistFilter;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static junit.framework.Assert.*;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.notNull;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Tests the utilities in {@link FileUtils}.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class TestFileUtils {
|
||||
|
||||
private static final Charset CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder tempDir = new TemporaryFolder();
|
||||
public File testFile;
|
||||
public File nonExistentFile;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
testFile = tempDir.newFile();
|
||||
nonExistentFile = new File(tempDir.getRoot(), "non-existent-file");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadJSONObjectFromFile() throws Exception {
|
||||
final JSONObject expected = new JSONObject("{\"str\": \"some str\"}");
|
||||
writeStringToFile(testFile, expected.toString());
|
||||
|
||||
final JSONObject actual = FileUtils.readJSONObjectFromFile(testFile);
|
||||
assertEquals("JSON contains expected str", expected.getString("str"), actual.getString("str"));
|
||||
}
|
||||
|
||||
@Test(expected=IOException.class)
|
||||
public void testReadJSONObjectFromFileEmptyFile() throws Exception {
|
||||
assertEquals("Test file is empty", 0, testFile.length());
|
||||
FileUtils.readJSONObjectFromFile(testFile); // expected to throw
|
||||
}
|
||||
|
||||
@Test(expected=JSONException.class)
|
||||
public void testReadJSONObjectFromFileInvalidJSON() throws Exception {
|
||||
writeStringToFile(testFile, "not a json str");
|
||||
FileUtils.readJSONObjectFromFile(testFile); // expected to throw
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadStringFromFileReadsData() throws Exception {
|
||||
final String expected = "String to write contains hard characters: !\n \\s..\"'\u00f1";
|
||||
writeStringToFile(testFile, expected);
|
||||
|
||||
final String actual = FileUtils.readStringFromFile(testFile);
|
||||
assertEquals("Read content matches written content", expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadStringFromFileEmptyFile() throws Exception {
|
||||
assertEquals("Test file is empty", 0, testFile.length());
|
||||
|
||||
final String actual = FileUtils.readStringFromFile(testFile);
|
||||
assertEquals("Read content is empty", "", actual);
|
||||
}
|
||||
|
||||
@Test(expected=FileNotFoundException.class)
|
||||
public void testReadStringFromNonExistentFile() throws Exception {
|
||||
assertFalse("File does not exist", nonExistentFile.exists());
|
||||
FileUtils.readStringFromFile(nonExistentFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadStringFromInputStreamAndCloseStreamBufferLenIsFileLen() throws Exception {
|
||||
final String expected = "String to write contains hard characters: !\n \\s..\"'\u00f1";
|
||||
writeStringToFile(testFile, expected);
|
||||
|
||||
final FileInputStream stream = new FileInputStream(testFile);
|
||||
final String actual = FileUtils.readStringFromInputStreamAndCloseStream(stream, expected.length());
|
||||
assertEquals("Read content matches written content", expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadStringFromInputStreamAndCloseStreamBufferLenIsBiggerThanFile() throws Exception {
|
||||
final String expected = "aoeuhtns";
|
||||
writeStringToFile(testFile, expected);
|
||||
|
||||
final FileInputStream stream = new FileInputStream(testFile);
|
||||
final String actual = FileUtils.readStringFromInputStreamAndCloseStream(stream, expected.length() + 1024);
|
||||
assertEquals("Read content matches written content", expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadStringFromInputStreamAndCloseStreamBufferLenIsSmallerThanFile() throws Exception {
|
||||
final String expected = "aoeuhtns aoeusth aoeusth aoeusnth aoeusth aoeusnth aoesuth";
|
||||
writeStringToFile(testFile, expected);
|
||||
|
||||
final FileInputStream stream = new FileInputStream(testFile);
|
||||
final String actual = FileUtils.readStringFromInputStreamAndCloseStream(stream, 8);
|
||||
assertEquals("Read content matches written content", expected, actual);
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void testReadStringFromInputStreamAndCloseStreamBufferLenIsZero() throws Exception {
|
||||
final String expected = "aoeuhtns aoeusth aoeusth aoeusnth aoeusth aoeusnth aoesuth";
|
||||
writeStringToFile(testFile, expected);
|
||||
|
||||
final FileInputStream stream = new FileInputStream(testFile);
|
||||
FileUtils.readStringFromInputStreamAndCloseStream(stream, 0); // expected to throw.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadStringFromInputStreamAndCloseStreamIsEmptyStream() throws Exception {
|
||||
assertTrue("Test file exists", testFile.exists());
|
||||
assertEquals("Test file is empty", 0, testFile.length());
|
||||
|
||||
final FileInputStream stream = new FileInputStream(testFile);
|
||||
final String actual = FileUtils.readStringFromInputStreamAndCloseStream(stream, 8);
|
||||
assertEquals("Read content from stream is empty", "", actual);
|
||||
}
|
||||
|
||||
@Test(expected=IOException.class)
|
||||
public void testReadStringFromInputStreamAndCloseStreamClosesStream() throws Exception {
|
||||
final String expected = "String to write contains hard characters: !\n \\s..\"'\u00f1";
|
||||
writeStringToFile(testFile, expected);
|
||||
|
||||
final FileInputStream stream = new FileInputStream(testFile);
|
||||
try {
|
||||
stream.read(); // should not throw because stream is open.
|
||||
FileUtils.readStringFromInputStreamAndCloseStream(stream, expected.length());
|
||||
} catch (final IOException e) {
|
||||
fail("Did not expect method to throw when writing file: " + e);
|
||||
}
|
||||
|
||||
stream.read(); // expected to throw because stream is closed.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteStringToOutputStreamAndCloseStreamWritesData() throws Exception {
|
||||
final String expected = "A string with some data in it! \u00f1 \n";
|
||||
final FileOutputStream fos = new FileOutputStream(testFile, false);
|
||||
FileUtils.writeStringToOutputStreamAndCloseStream(fos, expected);
|
||||
|
||||
assertTrue("Written file exists", testFile.exists());
|
||||
assertEquals("Read data equals written data", expected, readStringFromFile(testFile, expected.length()));
|
||||
}
|
||||
|
||||
@Test(expected=IOException.class)
|
||||
public void testWriteStringToOutputStreamAndCloseStreamClosesStream() throws Exception {
|
||||
final FileOutputStream fos = new FileOutputStream(testFile, false);
|
||||
try {
|
||||
fos.write('c'); // should not throw because stream is open.
|
||||
FileUtils.writeStringToOutputStreamAndCloseStream(fos, "some string with data");
|
||||
} catch (final IOException e) {
|
||||
fail("Did not expect method to throw when writing file: " + e);
|
||||
}
|
||||
|
||||
fos.write('c'); // expected to throw because stream is closed.
|
||||
}
|
||||
|
||||
/**
|
||||
* The Writer we wrap our stream in can throw in .close(), preventing the underlying stream from closing.
|
||||
* I added code to prevent ensure we close if the writer .close() throws.
|
||||
*
|
||||
* I wrote this test to test that code, however, we'd have to mock the writer [1] and that isn't straight-forward.
|
||||
* I left this test around because it's a good test of other code.
|
||||
*
|
||||
* [1]: We thought we could mock FileOutputStream.flush but it's only flushed if the Writer thinks it should be
|
||||
* flushed. We can write directly to the Stream, but that doesn't change the Writer state and doesn't affect whether
|
||||
* it thinks it should be flushed.
|
||||
*/
|
||||
@Test(expected=IOException.class)
|
||||
public void testWriteStringToOutputStreamAndCloseStreamClosesStreamIfWriterThrows() throws Exception {
|
||||
final FileOutputStream fos = mock(FileOutputStream.class);
|
||||
doThrow(IOException.class).when(fos).write(any(byte[].class), anyInt(), anyInt());
|
||||
doThrow(IOException.class).when(fos).write(anyInt());
|
||||
doThrow(IOException.class).when(fos).write(any(byte[].class));
|
||||
|
||||
boolean exceptionCaught = false;
|
||||
try {
|
||||
FileUtils.writeStringToOutputStreamAndCloseStream(fos, "some string with data");
|
||||
} catch (final IOException e) {
|
||||
exceptionCaught = true;
|
||||
}
|
||||
assertTrue("Exception caught during tested method", exceptionCaught); // not strictly necessary but documents assumptions
|
||||
|
||||
fos.write('c'); // expected to throw because stream is closed.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteStringToFile() throws Exception {
|
||||
final String expected = "String to write contains hard characters: !\n \\s..\"'\u00f1";
|
||||
FileUtils.writeStringToFile(testFile, expected);
|
||||
|
||||
assertTrue("Written file exists", testFile.exists());
|
||||
assertEquals("Read data equals written data", expected, readStringFromFile(testFile, expected.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteStringToFileEmptyString() throws Exception {
|
||||
final String expected = "";
|
||||
FileUtils.writeStringToFile(testFile, expected);
|
||||
|
||||
assertTrue("Written file exists", testFile.exists());
|
||||
assertEquals("Written file is empty", 0, testFile.length());
|
||||
assertEquals("Read data equals written (empty) data", expected, readStringFromFile(testFile, expected.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteStringToFileCreatesNewFile() throws Exception {
|
||||
final String expected = "some str to write";
|
||||
assertFalse("Non existent file does not exist", nonExistentFile.exists());
|
||||
FileUtils.writeStringToFile(nonExistentFile, expected); // expected to create file
|
||||
|
||||
assertTrue("Written file was created", nonExistentFile.exists());
|
||||
assertEquals("Read data equals written data", expected, readStringFromFile(nonExistentFile, (int) nonExistentFile.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteStringToFileOverwritesFile() throws Exception {
|
||||
writeStringToFile(testFile, "data");
|
||||
|
||||
final String expected = "some str to write";
|
||||
FileUtils.writeStringToFile(testFile, expected);
|
||||
|
||||
assertTrue("Written file was created", testFile.exists());
|
||||
assertEquals("Read data equals written data", expected, readStringFromFile(testFile, (int) testFile.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteJSONObjectToFile() throws Exception {
|
||||
final JSONObject expected = new JSONObject()
|
||||
.put("int", 1)
|
||||
.put("str", "1")
|
||||
.put("bool", true)
|
||||
.put("null", JSONObject.NULL)
|
||||
.put("raw null", null);
|
||||
FileUtils.writeJSONObjectToFile(testFile, expected);
|
||||
|
||||
assertTrue("Written file exists", testFile.exists());
|
||||
|
||||
// JSONObject.equals compares references so we have to assert each key individually. >:(
|
||||
final JSONObject actual = new JSONObject(readStringFromFile(testFile, (int) testFile.length()));
|
||||
assertEquals(1, actual.getInt("int"));
|
||||
assertEquals("1", actual.getString("str"));
|
||||
assertEquals(true, actual.getBoolean("bool"));
|
||||
assertEquals(JSONObject.NULL, actual.get("null"));
|
||||
assertFalse(actual.has("raw null"));
|
||||
}
|
||||
|
||||
// Since the read methods may not be tested yet.
|
||||
private static String readStringFromFile(final File file, final int bufferLen) throws IOException {
|
||||
final char[] buffer = new char[bufferLen];
|
||||
try (final InputStreamReader reader = new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"))) {
|
||||
reader.read(buffer, 0, buffer.length);
|
||||
}
|
||||
return new String(buffer);
|
||||
}
|
||||
|
||||
// Since the write methods may not be tested yet.
|
||||
private static void writeStringToFile(final File file, final String str) throws IOException {
|
||||
try (final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file, false), CHARSET)) {
|
||||
writer.write(str);
|
||||
}
|
||||
assertTrue("Written file from helper method exists", file.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilenameWhitelistFilter() {
|
||||
final String[] expectedToAccept = new String[] { "one", "two", "three" };
|
||||
final Set<String> whitelist = new HashSet<>(Arrays.asList(expectedToAccept));
|
||||
final FilenameWhitelistFilter testFilter = new FilenameWhitelistFilter(whitelist);
|
||||
for (final String str : expectedToAccept) {
|
||||
assertTrue("Filename, " + str + ", in whitelist is accepted", testFilter.accept(testFile, str));
|
||||
}
|
||||
|
||||
final String[] notExpectedToAccept = new String[] { "not-in-whitelist", "meh", "whatever" };
|
||||
for (final String str : notExpectedToAccept) {
|
||||
assertFalse("Filename, " + str + ", not in whitelist is not accepted", testFilter.accept(testFile, str));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilenameRegexFilter() {
|
||||
final Pattern pattern = Pattern.compile("[a-z]{1,6}");
|
||||
final FilenameRegexFilter testFilter = new FilenameRegexFilter(pattern);
|
||||
final String[] expectedToAccept = new String[] { "duckie", "goes", "quack" };
|
||||
for (final String str : expectedToAccept) {
|
||||
assertTrue("Filename, " + str + ", matching regex expected to accept", testFilter.accept(testFile, str));
|
||||
}
|
||||
|
||||
final String[] notExpectedToAccept = new String[] { "DUCKIE", "1337", "2fast" };
|
||||
for (final String str : notExpectedToAccept) {
|
||||
assertFalse("Filename, " + str + ", not matching regex not expected to accept", testFilter.accept(testFile, str));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileLastModifiedComparator() {
|
||||
final FileLastModifiedComparator testComparator = new FileLastModifiedComparator();
|
||||
final File oldFile = mock(File.class);
|
||||
final File newFile = mock(File.class);
|
||||
final File equallyNewFile = mock(File.class);
|
||||
when(oldFile.lastModified()).thenReturn(10L);
|
||||
when(newFile.lastModified()).thenReturn(100L);
|
||||
when(equallyNewFile.lastModified()).thenReturn(100L);
|
||||
|
||||
assertTrue("Old file is less than new file", testComparator.compare(oldFile, newFile) < 0);
|
||||
assertTrue("New file is greater than old file", testComparator.compare(newFile, oldFile) > 0);
|
||||
assertTrue("New files are equal", testComparator.compare(newFile, equallyNewFile) == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTempDir() throws Exception {
|
||||
final String prefix = "tmp";
|
||||
final File directory = tempDir.newFolder();
|
||||
final File tempDir1 = FileUtils.createTempDir(directory, prefix);
|
||||
final File tempDir2 = FileUtils.createTempDir(directory, prefix);
|
||||
|
||||
assertThat(tempDir1, not(nullValue()));
|
||||
assertThat(tempDir1.isDirectory(), is(true));
|
||||
assertThat(tempDir2, not(nullValue()));
|
||||
assertThat(tempDir2.isDirectory(), is(true));
|
||||
assertThat(tempDir1.getAbsolutePath(), is(not(tempDir2.getAbsolutePath())));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for float utilities.
|
||||
*/
|
||||
public class TestFloatUtils {
|
||||
|
||||
@Test
|
||||
public void testEqualIfComparingZeros() {
|
||||
assertTrue(FloatUtils.fuzzyEquals(0, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualFailIf5thDigitIsDifferent() {
|
||||
assertFalse(FloatUtils.fuzzyEquals(0.00001f, 0.00002f));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualSuccessIf6thDigitIsDifferent() {
|
||||
assertTrue(FloatUtils.fuzzyEquals(0.000001f, 0.000002f));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualFail() {
|
||||
assertFalse(FloatUtils.fuzzyEquals(10, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualSuccessIfPromoted() {
|
||||
assertTrue(FloatUtils.fuzzyEquals(5, 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualSuccessIfUnPromoted() {
|
||||
assertTrue(FloatUtils.fuzzyEquals(5.6f, 5.6f));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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.Intent;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.mozglue.SafeIntent;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests for the Intent utilities.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class TestIntentUtils {
|
||||
|
||||
private static final Map<String, String> TEST_ENV_VAR_MAP;
|
||||
static {
|
||||
final HashMap<String, String> tempMap = new HashMap<>();
|
||||
tempMap.put("ZERO", "0");
|
||||
tempMap.put("ONE", "1");
|
||||
tempMap.put("STRING", "TEXT");
|
||||
tempMap.put("L_WHITESPACE", " LEFT");
|
||||
tempMap.put("R_WHITESPACE", "RIGHT ");
|
||||
tempMap.put("ALL_WHITESPACE", " ALL ");
|
||||
tempMap.put("WHITESPACE_IN_VALUE", "IN THE MIDDLE");
|
||||
tempMap.put("WHITESPACE IN KEY", "IS_PROBABLY_NOT_VALID_ANYWAY");
|
||||
tempMap.put("BLANK_VAL", "");
|
||||
TEST_ENV_VAR_MAP = Collections.unmodifiableMap(tempMap);
|
||||
}
|
||||
|
||||
private Intent testIntent;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
testIntent = getIntentWithTestData();
|
||||
}
|
||||
|
||||
private static Intent getIntentWithTestData() {
|
||||
final Intent out = new Intent(Intent.ACTION_VIEW);
|
||||
int i = 0;
|
||||
for (final String key : TEST_ENV_VAR_MAP.keySet()) {
|
||||
final String value = key + "=" + TEST_ENV_VAR_MAP.get(key);
|
||||
out.putExtra("env" + i, value);
|
||||
i += 1;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetEnvVarMap() throws Exception {
|
||||
final HashMap<String, String> actual = IntentUtils.getEnvVarMap(new SafeIntent(testIntent));
|
||||
for (final String actualEnvVarName : actual.keySet()) {
|
||||
assertTrue("Actual key exists in test data: " + actualEnvVarName,
|
||||
TEST_ENV_VAR_MAP.containsKey(actualEnvVarName));
|
||||
|
||||
final String expectedValue = TEST_ENV_VAR_MAP.get(actualEnvVarName);
|
||||
final String actualValue = actual.get(actualEnvVarName);
|
||||
assertEquals("Actual env var value matches test data", expectedValue, actualValue);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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 org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class TestStringUtils {
|
||||
@Test
|
||||
public void testIsHttpOrHttps() {
|
||||
// No value
|
||||
assertFalse(StringUtils.isHttpOrHttps(null));
|
||||
assertFalse(StringUtils.isHttpOrHttps(""));
|
||||
|
||||
// Garbage
|
||||
assertFalse(StringUtils.isHttpOrHttps("lksdjflasuf"));
|
||||
|
||||
// URLs with http/https
|
||||
assertTrue(StringUtils.isHttpOrHttps("https://www.google.com"));
|
||||
assertTrue(StringUtils.isHttpOrHttps("http://www.facebook.com"));
|
||||
assertTrue(StringUtils.isHttpOrHttps("https://mozilla.org/en-US/firefox/products/"));
|
||||
|
||||
// IP addresses
|
||||
assertTrue(StringUtils.isHttpOrHttps("https://192.168.0.1"));
|
||||
assertTrue(StringUtils.isHttpOrHttps("http://63.245.215.20/en-US/firefox/products"));
|
||||
|
||||
// Other protocols
|
||||
assertFalse(StringUtils.isHttpOrHttps("ftp://people.mozilla.org"));
|
||||
assertFalse(StringUtils.isHttpOrHttps("javascript:window.google.com"));
|
||||
assertFalse(StringUtils.isHttpOrHttps("tel://1234567890"));
|
||||
|
||||
// No scheme
|
||||
assertFalse(StringUtils.isHttpOrHttps("google.com"));
|
||||
assertFalse(StringUtils.isHttpOrHttps("git@github.com:mozilla/gecko-dev.git"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStripRef() {
|
||||
assertEquals(StringUtils.stripRef(null), null);
|
||||
assertEquals(StringUtils.stripRef(""), "");
|
||||
|
||||
assertEquals(StringUtils.stripRef("??AAABBBCCC"), "??AAABBBCCC");
|
||||
assertEquals(StringUtils.stripRef("https://mozilla.org"), "https://mozilla.org");
|
||||
assertEquals(StringUtils.stripRef("https://mozilla.org#BBBB"), "https://mozilla.org");
|
||||
assertEquals(StringUtils.stripRef("https://mozilla.org/#BBBB"), "https://mozilla.org/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStripScheme() {
|
||||
assertEquals("mozilla.org", StringUtils.stripScheme("http://mozilla.org"));
|
||||
assertEquals("mozilla.org", StringUtils.stripScheme("http://mozilla.org/"));
|
||||
assertEquals("https://mozilla.org", StringUtils.stripScheme("https://mozilla.org"));
|
||||
assertEquals("https://mozilla.org", StringUtils.stripScheme("https://mozilla.org/"));
|
||||
assertEquals("mozilla.org", StringUtils.stripScheme("https://mozilla.org/", StringUtils.UrlFlags.STRIP_HTTPS));
|
||||
assertEquals("mozilla.org", StringUtils.stripScheme("https://mozilla.org", StringUtils.UrlFlags.STRIP_HTTPS));
|
||||
assertEquals("", StringUtils.stripScheme("http://"));
|
||||
assertEquals("", StringUtils.stripScheme("https://", StringUtils.UrlFlags.STRIP_HTTPS));
|
||||
// This edge case is not handled properly yet
|
||||
// assertEquals(StringUtils.stripScheme("https://"), "");
|
||||
assertEquals(null, StringUtils.stripScheme(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsRTL() {
|
||||
assertFalse(StringUtils.isRTL("mozilla.org"));
|
||||
assertFalse(StringUtils.isRTL("something.عربي"));
|
||||
|
||||
assertTrue(StringUtils.isRTL("عربي"));
|
||||
assertTrue(StringUtils.isRTL("عربي.org"));
|
||||
|
||||
// Text with LTR mark
|
||||
assertFalse(StringUtils.isRTL("\u200EHello"));
|
||||
assertFalse(StringUtils.isRTL("\u200Eعربي"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceLTR() {
|
||||
assertFalse(StringUtils.isRTL(StringUtils.forceLTR("عربي")));
|
||||
assertFalse(StringUtils.isRTL(StringUtils.forceLTR("عربي.org")));
|
||||
|
||||
// Strings that are already LTR are not modified
|
||||
final String someLtrString = "HelloWorld";
|
||||
assertEquals(someLtrString, StringUtils.forceLTR(someLtrString));
|
||||
|
||||
// We add the LTR mark only once
|
||||
final String someRtlString = "عربي";
|
||||
assertEquals(4, someRtlString.length());
|
||||
final String forcedLtrString = StringUtils.forceLTR(someRtlString);
|
||||
assertEquals(5, forcedLtrString.length());
|
||||
final String forcedAgainLtrString = StringUtils.forceLTR(forcedLtrString);
|
||||
assertEquals(5, forcedAgainLtrString.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsSearchQuery(){
|
||||
final boolean any = true;
|
||||
// test trim
|
||||
assertFalse(StringUtils.isSearchQuery("",false));
|
||||
assertTrue(StringUtils.isSearchQuery("",true));
|
||||
|
||||
// test space
|
||||
assertTrue(StringUtils.isSearchQuery(" apple pen ",any));
|
||||
assertTrue(StringUtils.isSearchQuery("pineapple pen",any));
|
||||
assertTrue(StringUtils.isSearchQuery(": :",any));
|
||||
assertTrue(StringUtils.isSearchQuery(". .",any));
|
||||
assertTrue(StringUtils.isSearchQuery("gcm site:stackoverflow.com",any));
|
||||
assertTrue(StringUtils.isSearchQuery("/mnt/etc/resolv.conf does not exist",true));
|
||||
|
||||
// test colon
|
||||
assertFalse(StringUtils.isSearchQuery(":",any));
|
||||
assertFalse(StringUtils.isSearchQuery("site:stackoverflow.com",any));
|
||||
assertFalse(StringUtils.isSearchQuery("http:mozilla.com",any));
|
||||
assertFalse(StringUtils.isSearchQuery("http://mozilla.com",any));
|
||||
assertFalse(StringUtils.isSearchQuery("http:/mozilla.com",any));
|
||||
|
||||
// test dot
|
||||
assertFalse(StringUtils.isSearchQuery(".",any));
|
||||
assertFalse(StringUtils.isSearchQuery("cd..",any));
|
||||
assertFalse(StringUtils.isSearchQuery("cd...",any));
|
||||
assertFalse(StringUtils.isSearchQuery("mozilla.com",any));
|
||||
|
||||
|
||||
// test ambiguous
|
||||
String ambiguous = "~!@#$%^&*()_+`34567890-=qwertyuiop[]\\QWERTYUIOP{}|asdfghjkl;'ASDFGHJKL:\"ZXCVBNM<>?zxcvbnm,./";
|
||||
ambiguous = ambiguous.replace(" ","").replace(".","").replace(":","");
|
||||
assertTrue(StringUtils.isSearchQuery(ambiguous,true));
|
||||
assertFalse(StringUtils.isSearchQuery(ambiguous,false));
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryExists(){
|
||||
// test empty
|
||||
assertFalse(StringUtils.queryExists(""));
|
||||
|
||||
// test single
|
||||
assertFalse(StringUtils.queryExists("mozilla.org"));
|
||||
assertFalse(StringUtils.queryExists("https://www.google.com/"));
|
||||
assertTrue(StringUtils.queryExists("https://www.google.com/search?q=%s"));
|
||||
assertTrue(StringUtils.queryExists("https://www.google.com/search?q=%S"));
|
||||
assertTrue(StringUtils.queryExists("%s"));
|
||||
assertTrue(StringUtils.queryExists("%S"));
|
||||
|
||||
//test double
|
||||
assertTrue(StringUtils.queryExists("%s%S"));
|
||||
assertTrue(StringUtils.queryExists("https://www.google.com/search?q=%s%S"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathStartIndex(){
|
||||
// Tests without protocol
|
||||
assertTrue(StringUtils.pathStartIndex("mozilla.org") == -1);
|
||||
assertTrue(StringUtils.pathStartIndex("mozilla.org/en-US") == 11);
|
||||
|
||||
// Tests with protocol
|
||||
assertTrue(StringUtils.pathStartIndex("https://mozilla.org") == -1);
|
||||
assertTrue(StringUtils.pathStartIndex("https://mozilla.org/") == 19);
|
||||
assertTrue(StringUtils.pathStartIndex("https://mozilla.org/en-US") == 19);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests for uuid utils.
|
||||
*/
|
||||
public class TestUUIDUtil {
|
||||
private static final String[] validUUIDs = {
|
||||
"904cd9f8-af63-4525-8ce0-b9127e5364fa",
|
||||
"8d584bd2-00ea-4043-a617-ed4ce7018ed0",
|
||||
"3abad327-2669-4f68-b9ef-7ace8c5314d6",
|
||||
};
|
||||
|
||||
private static final String[] invalidUUIDs = {
|
||||
"its-not-a-uuid-mate",
|
||||
"904cd9f8-af63-4525-8ce0-b9127e5364falol",
|
||||
"904cd9f8-af63-4525-8ce0-b9127e5364f",
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testUUIDRegex() {
|
||||
for (final String uuid : validUUIDs) {
|
||||
assertTrue("Valid UUID matches UUID-regex", uuid.matches(UUIDUtil.UUID_REGEX));
|
||||
}
|
||||
for (final String uuid : invalidUUIDs) {
|
||||
assertFalse("Invalid UUID does not match UUID-regex", uuid.matches(UUIDUtil.UUID_REGEX));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUUIDPattern() {
|
||||
for (final String uuid : validUUIDs) {
|
||||
assertTrue("Valid UUID matches UUID-regex", UUIDUtil.UUID_PATTERN.matcher(uuid).matches());
|
||||
}
|
||||
for (final String uuid : invalidUUIDs) {
|
||||
assertFalse("Invalid UUID does not match UUID-regex", UUIDUtil.UUID_PATTERN.matcher(uuid).matches());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,10 +58,12 @@ include $(topsrcdir)/toolkit/mozapps/installer/packager.mk
|
|||
ifneq (,$(JS_BINARY))
|
||||
ifndef MOZ_DEBUG
|
||||
ifndef NIGHTLY_BUILD
|
||||
ifndef FENNEC_NIGHTLY
|
||||
MOZ_PACKAGER_MINIFY_JS=1
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq (bundle, $(MOZ_FS_LAYOUT))
|
||||
BINPATH = $(_BINPATH)
|
||||
|
|
|
@ -45,6 +45,22 @@ project_flag(
|
|||
default=False,
|
||||
)
|
||||
|
||||
option(
|
||||
env="FENNEC_NIGHTLY",
|
||||
help="Enable experimental code for Fennec Nightly users. NOTE: This is *not* equivalent "
|
||||
"to the NIGHTLY_BUILD flag set during configure.",
|
||||
default=False,
|
||||
)
|
||||
|
||||
set_config("FENNEC_NIGHTLY", depends_if("FENNEC_NIGHTLY")(lambda _: True))
|
||||
set_define("FENNEC_NIGHTLY", depends_if("FENNEC_NIGHTLY")(lambda _: True))
|
||||
|
||||
|
||||
@depends("FENNEC_NIGHTLY")
|
||||
def fennec_nightly(nightly):
|
||||
return bool(nightly)
|
||||
|
||||
|
||||
imply_option("MOZ_NORMANDY", False)
|
||||
imply_option("MOZ_SERVICES_HEALTHREPORT", True)
|
||||
imply_option("MOZ_ANDROID_HISTORY", True)
|
||||
|
|
|
@ -0,0 +1,997 @@
|
|||
[
|
||||
{
|
||||
"info": {
|
||||
"browsertime": {
|
||||
"version": "8.3.0"
|
||||
},
|
||||
"url": "https://www.bbc.com/news/world-middle-east-53598965",
|
||||
"timestamp": "2020-08-03T11:18:37+02:00",
|
||||
"connectivity": {
|
||||
"engine": "external",
|
||||
"profile": "native"
|
||||
},
|
||||
"extra": {},
|
||||
"alias": "pageload"
|
||||
},
|
||||
"files": {
|
||||
"video": [
|
||||
"pages/www.bbc.com/news/world-middle-east-53598965/data/video/1.mp4"
|
||||
],
|
||||
"screenshot": [],
|
||||
"timeline": [],
|
||||
"consoleLog": [],
|
||||
"netLog": [],
|
||||
"perfLog": []
|
||||
},
|
||||
"cdp": {
|
||||
"performance": []
|
||||
},
|
||||
"timestamps": [
|
||||
"2020-08-03T11:18:12+02:00"
|
||||
],
|
||||
"browserScripts": [
|
||||
{
|
||||
"browser": {
|
||||
"appConstants": {
|
||||
"ACCESSIBILITY": true,
|
||||
"ANDROID_PACKAGE_NAME": "org.mozilla.firefox",
|
||||
"ASAN": false,
|
||||
"ASAN_REPORTER": false,
|
||||
"BROWSER_CHROME_URL": "chrome://browser/content/browser.xhtml",
|
||||
"DEBUG": false,
|
||||
"DEBUG_JS_MODULES": "",
|
||||
"DLL_PREFIX": "lib",
|
||||
"DLL_SUFFIX": ".dylib",
|
||||
"EARLY_BETA_OR_EARLIER": true,
|
||||
"ENABLE_REMOTE_AGENT": true,
|
||||
"FENNEC_NIGHTLY": false,
|
||||
"HAVE_SHELL_SERVICE": true,
|
||||
"HAVE_USR_LIB64_DIR": false,
|
||||
"MENUBAR_CAN_AUTOHIDE": false,
|
||||
"MOZILLA_OFFICIAL": false,
|
||||
"MOZ_ALLOW_ADDON_SIDELOAD": false,
|
||||
"MOZ_ALLOW_LEGACY_EXTENSIONS": false,
|
||||
"MOZ_ANDROID_HISTORY": false,
|
||||
"MOZ_APP_BASENAME": "Firefox",
|
||||
"MOZ_APP_NAME": "firefox",
|
||||
"MOZ_APP_VERSION": "81.0a1",
|
||||
"MOZ_APP_VERSION_DISPLAY": "81.0a1",
|
||||
"MOZ_BING_API_CLIENTID": "no-bing-api-clientid",
|
||||
"MOZ_BING_API_KEY": "no-bing-api-key",
|
||||
"MOZ_BITS_DOWNLOAD": false,
|
||||
"MOZ_BUILDID": "2020073017",
|
||||
"MOZ_BUILD_APP": "browser",
|
||||
"MOZ_CODE_COVERAGE": false,
|
||||
"MOZ_CRASHREPORTER": true,
|
||||
"MOZ_DATA_REPORTING": true,
|
||||
"MOZ_DEV_EDITION": false,
|
||||
"MOZ_GECKO_PROFILER": true,
|
||||
"MOZ_GOOGLE_LOCATION_SERVICE_API_KEY": "no-google-location-service-api-key",
|
||||
"MOZ_GOOGLE_SAFEBROWSING_API_KEY": "no-google-safebrowsing-api-key",
|
||||
"MOZ_MACBUNDLE_NAME": "Nightly.app",
|
||||
"MOZ_MAINTENANCE_SERVICE": false,
|
||||
"MOZ_MOZILLA_API_KEY": "no-mozilla-api-key",
|
||||
"MOZ_NEW_NOTIFICATION_STORE": true,
|
||||
"MOZ_NEW_XULSTORE": true,
|
||||
"MOZ_NORMANDY": true,
|
||||
"MOZ_OFFICIAL_BRANDING": false,
|
||||
"MOZ_PLACES": true,
|
||||
"MOZ_REQUIRE_SIGNING": false,
|
||||
"MOZ_RUST_FXA_CLIENT": true,
|
||||
"MOZ_SANDBOX": false,
|
||||
"MOZ_SERVICES_HEALTHREPORT": true,
|
||||
"MOZ_SERVICES_SYNC": false,
|
||||
"MOZ_SWITCHBOARD": false,
|
||||
"MOZ_SYSTEM_NSS": false,
|
||||
"MOZ_TELEMETRY_ON_BY_DEFAULT": false,
|
||||
"MOZ_TELEMETRY_REPORTING": false,
|
||||
"MOZ_UNSIGNED_SCOPES": 0,
|
||||
"MOZ_UPDATER": true,
|
||||
"MOZ_UPDATE_AGENT": false,
|
||||
"MOZ_UPDATE_CHANNEL": "default",
|
||||
"MOZ_WEBRTC": true,
|
||||
"MOZ_WIDGET_GTK": false,
|
||||
"MOZ_WIDGET_TOOLKIT": "cocoa",
|
||||
"NIGHTLY_BUILD": true,
|
||||
"OMNIJAR_NAME": "omni.ja",
|
||||
"RELEASE_OR_BETA": false,
|
||||
"SOURCE_REVISION_URL": "",
|
||||
"TELEMETRY_PING_FORMAT_VERSION": 4,
|
||||
"TSAN": false,
|
||||
"XP_UNIX": true,
|
||||
"isPlatformAndVersionAtLeast": {},
|
||||
"isPlatformAndVersionAtMost": {},
|
||||
"platform": "macosx",
|
||||
"unixstyle": "other"
|
||||
},
|
||||
"asyncAppConstants": {
|
||||
"ACCESSIBILITY": true,
|
||||
"ANDROID_PACKAGE_NAME": "org.mozilla.firefox",
|
||||
"ASAN": false,
|
||||
"ASAN_REPORTER": false,
|
||||
"BROWSER_CHROME_URL": "chrome://browser/content/browser.xhtml",
|
||||
"DEBUG": false,
|
||||
"DEBUG_JS_MODULES": "",
|
||||
"DLL_PREFIX": "lib",
|
||||
"DLL_SUFFIX": ".dylib",
|
||||
"EARLY_BETA_OR_EARLIER": true,
|
||||
"ENABLE_REMOTE_AGENT": true,
|
||||
"FENNEC_NIGHTLY": false,
|
||||
"HAVE_SHELL_SERVICE": true,
|
||||
"HAVE_USR_LIB64_DIR": false,
|
||||
"MENUBAR_CAN_AUTOHIDE": false,
|
||||
"MOZILLA_OFFICIAL": false,
|
||||
"MOZ_ALLOW_ADDON_SIDELOAD": false,
|
||||
"MOZ_ALLOW_LEGACY_EXTENSIONS": false,
|
||||
"MOZ_ANDROID_HISTORY": false,
|
||||
"MOZ_APP_BASENAME": "Firefox",
|
||||
"MOZ_APP_NAME": "firefox",
|
||||
"MOZ_APP_VERSION": "81.0a1",
|
||||
"MOZ_APP_VERSION_DISPLAY": "81.0a1",
|
||||
"MOZ_BING_API_CLIENTID": "no-bing-api-clientid",
|
||||
"MOZ_BING_API_KEY": "no-bing-api-key",
|
||||
"MOZ_BITS_DOWNLOAD": false,
|
||||
"MOZ_BUILDID": "200730174322",
|
||||
"MOZ_BUILD_APP": "browser",
|
||||
"MOZ_CODE_COVERAGE": false,
|
||||
"MOZ_CRASHREPORTER": true,
|
||||
"MOZ_DATA_REPORTING": true,
|
||||
"MOZ_DEV_EDITION": false,
|
||||
"MOZ_GECKO_PROFILER": true,
|
||||
"MOZ_GOOGLE_LOCATION_SERVICE_API_KEY": "no-google-location-service-api-key",
|
||||
"MOZ_GOOGLE_SAFEBROWSING_API_KEY": "no-google-safebrowsing-api-key",
|
||||
"MOZ_MACBUNDLE_NAME": "Nightly.app",
|
||||
"MOZ_MAINTENANCE_SERVICE": false,
|
||||
"MOZ_MOZILLA_API_KEY": "no-mozilla-api-key",
|
||||
"MOZ_NEW_NOTIFICATION_STORE": true,
|
||||
"MOZ_NEW_XULSTORE": true,
|
||||
"MOZ_NORMANDY": true,
|
||||
"MOZ_OFFICIAL_BRANDING": false,
|
||||
"MOZ_PLACES": true,
|
||||
"MOZ_REQUIRE_SIGNING": false,
|
||||
"MOZ_RUST_FXA_CLIENT": true,
|
||||
"MOZ_SANDBOX": false,
|
||||
"MOZ_SERVICES_HEALTHREPORT": true,
|
||||
"MOZ_SERVICES_SYNC": false,
|
||||
"MOZ_SWITCHBOARD": false,
|
||||
"MOZ_SYSTEM_NSS": false,
|
||||
"MOZ_TELEMETRY_ON_BY_DEFAULT": false,
|
||||
"MOZ_TELEMETRY_REPORTING": false,
|
||||
"MOZ_UNSIGNED_SCOPES": 0,
|
||||
"MOZ_UPDATER": true,
|
||||
"MOZ_UPDATE_AGENT": false,
|
||||
"MOZ_UPDATE_CHANNEL": "default",
|
||||
"MOZ_WEBRTC": true,
|
||||
"MOZ_WIDGET_GTK": false,
|
||||
"MOZ_WIDGET_TOOLKIT": "cocoa",
|
||||
"NIGHTLY_BUILD": true,
|
||||
"OMNIJAR_NAME": "omni.ja",
|
||||
"RELEASE_OR_BETA": false,
|
||||
"SOURCE_REVISION_URL": "",
|
||||
"TELEMETRY_PING_FORMAT_VERSION": 4,
|
||||
"TSAN": false,
|
||||
"XP_UNIX": true,
|
||||
"isPlatformAndVersionAtLeast": {},
|
||||
"isPlatformAndVersionAtMost": {},
|
||||
"platform": "macosx",
|
||||
"unixstyle": "other"
|
||||
},
|
||||
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0",
|
||||
"windowSize": "1366x768"
|
||||
},
|
||||
"pageinfo": {
|
||||
"documentHeight": 8937,
|
||||
"documentSize": {
|
||||
"decodedBodySize": 270461,
|
||||
"encodedBodySize": 67482,
|
||||
"transferSize": 68337
|
||||
},
|
||||
"documentTitle": "Coronavirus: Iran cover-up of deaths revealed by data leak - BBC News",
|
||||
"documentWidth": 1366,
|
||||
"domElements": 1348,
|
||||
"navigationStartTime": 1596629,
|
||||
"nextHopProtocol": "h2",
|
||||
"resources": {
|
||||
"count": 91,
|
||||
"duration": 26360.459999999992
|
||||
},
|
||||
"responsive": true,
|
||||
"url": "https://www.bbc.com/news/world-middle-east-53598965",
|
||||
"visualElements": {
|
||||
"heroes": [
|
||||
{
|
||||
"filename": "_113766981_iranhospital.jpg",
|
||||
"height": 363,
|
||||
"name": "LargestImage",
|
||||
"width": 646,
|
||||
"x": 195,
|
||||
"y": 403
|
||||
},
|
||||
{
|
||||
"filename": null,
|
||||
"height": 72,
|
||||
"name": "Heading",
|
||||
"width": 645,
|
||||
"x": 195,
|
||||
"y": 196
|
||||
}
|
||||
],
|
||||
"viewport": {
|
||||
"height": 694,
|
||||
"width": 1366
|
||||
}
|
||||
}
|
||||
},
|
||||
"timings": {
|
||||
"firstPaint": 1084,
|
||||
"loadEventEnd": 8274,
|
||||
"navigationTiming": {
|
||||
"connectStart": 20,
|
||||
"domComplete": 8238,
|
||||
"domContentLoadedEventEnd": 4165,
|
||||
"domContentLoadedEventStart": 4159,
|
||||
"domInteractive": 1415,
|
||||
"domainLookupEnd": 20,
|
||||
"domainLookupStart": 20,
|
||||
"duration": 8274,
|
||||
"fetchStart": 20,
|
||||
"loadEventEnd": 8274,
|
||||
"loadEventStart": 8264,
|
||||
"redirectEnd": 0,
|
||||
"redirectStart": 0,
|
||||
"requestStart": 29,
|
||||
"responseEnd": 117,
|
||||
"responseStart": 117,
|
||||
"secureConnectionStart": 0,
|
||||
"startTime": 0,
|
||||
"unloadEventEnd": 124,
|
||||
"unloadEventStart": 120,
|
||||
"workerStart": 0
|
||||
},
|
||||
"pageTimings": {
|
||||
"backEndTime": 117,
|
||||
"domContentLoadedTime": 4159,
|
||||
"domInteractiveTime": 1415,
|
||||
"domainLookupTime": 0,
|
||||
"frontEndTime": 8147,
|
||||
"pageDownloadTime": 0,
|
||||
"pageLoadTime": 8264,
|
||||
"redirectionTime": 0,
|
||||
"serverConnectionTime": 0,
|
||||
"serverResponseTime": 88
|
||||
},
|
||||
"rumSpeedIndex": 5542,
|
||||
"serverTimings": [],
|
||||
"timeToContentfulPaint": 1124,
|
||||
"timeToDomContentFlushed": 4158,
|
||||
"timeToFirstInteractive": 11858,
|
||||
"userTimings": {
|
||||
"marks": [],
|
||||
"measures": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"visualMetrics": [],
|
||||
"cpu": [],
|
||||
"extras": [
|
||||
{}
|
||||
],
|
||||
"fullyLoaded": [],
|
||||
"errors": [
|
||||
[]
|
||||
],
|
||||
"statistics": {
|
||||
"browser": {
|
||||
"appConstants": {
|
||||
"MOZ_BUILDID": {
|
||||
"median": 2020073017,
|
||||
"mean": 2020073017,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 2020073017,
|
||||
"p10": 2020073017,
|
||||
"p90": 2020073017,
|
||||
"p99": 2020073017,
|
||||
"max": 2020073017
|
||||
},
|
||||
"MOZ_UNSIGNED_SCOPES": {
|
||||
"median": 0,
|
||||
"mean": 0,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 0,
|
||||
"p10": 0,
|
||||
"p90": 0,
|
||||
"p99": 0,
|
||||
"max": 0
|
||||
},
|
||||
"TELEMETRY_PING_FORMAT_VERSION": {
|
||||
"median": 4,
|
||||
"mean": 4,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 4,
|
||||
"p10": 4,
|
||||
"p90": 4,
|
||||
"p99": 4,
|
||||
"max": 4
|
||||
}
|
||||
},
|
||||
"asyncAppConstants": {
|
||||
"MOZ_BUILDID": {
|
||||
"median": 2020073017,
|
||||
"mean": 2020073017,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 2020073017,
|
||||
"p10": 2020073017,
|
||||
"p90": 2020073017,
|
||||
"p99": 2020073017,
|
||||
"max": 2020073017
|
||||
},
|
||||
"MOZ_UNSIGNED_SCOPES": {
|
||||
"median": 0,
|
||||
"mean": 0,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 0,
|
||||
"p10": 0,
|
||||
"p90": 0,
|
||||
"p99": 0,
|
||||
"max": 0
|
||||
},
|
||||
"TELEMETRY_PING_FORMAT_VERSION": {
|
||||
"median": 4,
|
||||
"mean": 4,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 4,
|
||||
"p10": 4,
|
||||
"p90": 4,
|
||||
"p99": 4,
|
||||
"max": 4
|
||||
}
|
||||
}
|
||||
},
|
||||
"pageinfo": {
|
||||
"documentHeight": {
|
||||
"median": 8937,
|
||||
"mean": 8937,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 8937,
|
||||
"p10": 8937,
|
||||
"p90": 8937,
|
||||
"p99": 8937,
|
||||
"max": 8937
|
||||
},
|
||||
"documentSize": {
|
||||
"decodedBodySize": {
|
||||
"median": 270461,
|
||||
"mean": 270461,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 270461,
|
||||
"p10": 270461,
|
||||
"p90": 270461,
|
||||
"p99": 270461,
|
||||
"max": 270461
|
||||
},
|
||||
"encodedBodySize": {
|
||||
"median": 67482,
|
||||
"mean": 67482,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 67482,
|
||||
"p10": 67482,
|
||||
"p90": 67482,
|
||||
"p99": 67482,
|
||||
"max": 67482
|
||||
},
|
||||
"transferSize": {
|
||||
"median": 68337,
|
||||
"mean": 68337,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 68337,
|
||||
"p10": 68337,
|
||||
"p90": 68337,
|
||||
"p99": 68337,
|
||||
"max": 68337
|
||||
}
|
||||
},
|
||||
"documentWidth": {
|
||||
"median": 1366,
|
||||
"mean": 1366,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 1366,
|
||||
"p10": 1366,
|
||||
"p90": 1366,
|
||||
"p99": 1366,
|
||||
"max": 1366
|
||||
},
|
||||
"domElements": {
|
||||
"median": 1348,
|
||||
"mean": 1348,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 1348,
|
||||
"p10": 1348,
|
||||
"p90": 1348,
|
||||
"p99": 1348,
|
||||
"max": 1348
|
||||
},
|
||||
"navigationStartTime": {
|
||||
"median": 1596629,
|
||||
"mean": 1596629,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 1596629,
|
||||
"p10": 1596629,
|
||||
"p90": 1596629,
|
||||
"p99": 1596629,
|
||||
"max": 1596629
|
||||
},
|
||||
"resources": {
|
||||
"count": {
|
||||
"median": 91,
|
||||
"mean": 91,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 91,
|
||||
"p10": 91,
|
||||
"p90": 91,
|
||||
"p99": 91,
|
||||
"max": 91
|
||||
},
|
||||
"duration": {
|
||||
"median": 26360,
|
||||
"mean": 26360,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 26360,
|
||||
"p10": 26360,
|
||||
"p90": 26360,
|
||||
"p99": 26360,
|
||||
"max": 26360
|
||||
}
|
||||
},
|
||||
"visualElements": {
|
||||
"heroes": [
|
||||
{
|
||||
"height": {
|
||||
"median": 363,
|
||||
"mean": 363,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 363,
|
||||
"p10": 363,
|
||||
"p90": 363,
|
||||
"p99": 363,
|
||||
"max": 363
|
||||
},
|
||||
"width": {
|
||||
"median": 646,
|
||||
"mean": 646,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 646,
|
||||
"p10": 646,
|
||||
"p90": 646,
|
||||
"p99": 646,
|
||||
"max": 646
|
||||
},
|
||||
"x": {
|
||||
"median": 195,
|
||||
"mean": 195,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 195,
|
||||
"p10": 195,
|
||||
"p90": 195,
|
||||
"p99": 195,
|
||||
"max": 195
|
||||
},
|
||||
"y": {
|
||||
"median": 403,
|
||||
"mean": 403,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 403,
|
||||
"p10": 403,
|
||||
"p90": 403,
|
||||
"p99": 403,
|
||||
"max": 403
|
||||
}
|
||||
},
|
||||
{
|
||||
"height": {
|
||||
"median": 72,
|
||||
"mean": 72,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 72,
|
||||
"p10": 72,
|
||||
"p90": 72,
|
||||
"p99": 72,
|
||||
"max": 72
|
||||
},
|
||||
"width": {
|
||||
"median": 645,
|
||||
"mean": 645,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 645,
|
||||
"p10": 645,
|
||||
"p90": 645,
|
||||
"p99": 645,
|
||||
"max": 645
|
||||
},
|
||||
"x": {
|
||||
"median": 195,
|
||||
"mean": 195,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 195,
|
||||
"p10": 195,
|
||||
"p90": 195,
|
||||
"p99": 195,
|
||||
"max": 195
|
||||
},
|
||||
"y": {
|
||||
"median": 196,
|
||||
"mean": 196,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 196,
|
||||
"p10": 196,
|
||||
"p90": 196,
|
||||
"p99": 196,
|
||||
"max": 196
|
||||
}
|
||||
}
|
||||
],
|
||||
"viewport": {
|
||||
"height": {
|
||||
"median": 694,
|
||||
"mean": 694,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 694,
|
||||
"p10": 694,
|
||||
"p90": 694,
|
||||
"p99": 694,
|
||||
"max": 694
|
||||
},
|
||||
"width": {
|
||||
"median": 1366,
|
||||
"mean": 1366,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 1366,
|
||||
"p10": 1366,
|
||||
"p90": 1366,
|
||||
"p99": 1366,
|
||||
"max": 1366
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"timings": {
|
||||
"firstPaint": {
|
||||
"median": 1084,
|
||||
"mean": 1084,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 1084,
|
||||
"p10": 1084,
|
||||
"p90": 1084,
|
||||
"p99": 1084,
|
||||
"max": 1084
|
||||
},
|
||||
"loadEventEnd": {
|
||||
"median": 8274,
|
||||
"mean": 8274,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 8274,
|
||||
"p10": 8274,
|
||||
"p90": 8274,
|
||||
"p99": 8274,
|
||||
"max": 8274
|
||||
},
|
||||
"navigationTiming": {
|
||||
"connectStart": {
|
||||
"median": 20,
|
||||
"mean": 20,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 20,
|
||||
"p10": 20,
|
||||
"p90": 20,
|
||||
"p99": 20,
|
||||
"max": 20
|
||||
},
|
||||
"domComplete": {
|
||||
"median": 8238,
|
||||
"mean": 8238,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 8238,
|
||||
"p10": 8238,
|
||||
"p90": 8238,
|
||||
"p99": 8238,
|
||||
"max": 8238
|
||||
},
|
||||
"domContentLoadedEventEnd": {
|
||||
"median": 4165,
|
||||
"mean": 4165,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 4165,
|
||||
"p10": 4165,
|
||||
"p90": 4165,
|
||||
"p99": 4165,
|
||||
"max": 4165
|
||||
},
|
||||
"domContentLoadedEventStart": {
|
||||
"median": 4159,
|
||||
"mean": 4159,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 4159,
|
||||
"p10": 4159,
|
||||
"p90": 4159,
|
||||
"p99": 4159,
|
||||
"max": 4159
|
||||
},
|
||||
"domInteractive": {
|
||||
"median": 1415,
|
||||
"mean": 1415,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 1415,
|
||||
"p10": 1415,
|
||||
"p90": 1415,
|
||||
"p99": 1415,
|
||||
"max": 1415
|
||||
},
|
||||
"domainLookupEnd": {
|
||||
"median": 20,
|
||||
"mean": 20,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 20,
|
||||
"p10": 20,
|
||||
"p90": 20,
|
||||
"p99": 20,
|
||||
"max": 20
|
||||
},
|
||||
"domainLookupStart": {
|
||||
"median": 20,
|
||||
"mean": 20,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 20,
|
||||
"p10": 20,
|
||||
"p90": 20,
|
||||
"p99": 20,
|
||||
"max": 20
|
||||
},
|
||||
"duration": {
|
||||
"median": 8274,
|
||||
"mean": 8274,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 8274,
|
||||
"p10": 8274,
|
||||
"p90": 8274,
|
||||
"p99": 8274,
|
||||
"max": 8274
|
||||
},
|
||||
"fetchStart": {
|
||||
"median": 20,
|
||||
"mean": 20,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 20,
|
||||
"p10": 20,
|
||||
"p90": 20,
|
||||
"p99": 20,
|
||||
"max": 20
|
||||
},
|
||||
"loadEventEnd": {
|
||||
"median": 8274,
|
||||
"mean": 8274,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 8274,
|
||||
"p10": 8274,
|
||||
"p90": 8274,
|
||||
"p99": 8274,
|
||||
"max": 8274
|
||||
},
|
||||
"loadEventStart": {
|
||||
"median": 8264,
|
||||
"mean": 8264,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 8264,
|
||||
"p10": 8264,
|
||||
"p90": 8264,
|
||||
"p99": 8264,
|
||||
"max": 8264
|
||||
},
|
||||
"redirectEnd": {
|
||||
"median": 0,
|
||||
"mean": 0,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 0,
|
||||
"p10": 0,
|
||||
"p90": 0,
|
||||
"p99": 0,
|
||||
"max": 0
|
||||
},
|
||||
"redirectStart": {
|
||||
"median": 0,
|
||||
"mean": 0,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 0,
|
||||
"p10": 0,
|
||||
"p90": 0,
|
||||
"p99": 0,
|
||||
"max": 0
|
||||
},
|
||||
"requestStart": {
|
||||
"median": 29,
|
||||
"mean": 29,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 29,
|
||||
"p10": 29,
|
||||
"p90": 29,
|
||||
"p99": 29,
|
||||
"max": 29
|
||||
},
|
||||
"responseEnd": {
|
||||
"median": 117,
|
||||
"mean": 117,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 117,
|
||||
"p10": 117,
|
||||
"p90": 117,
|
||||
"p99": 117,
|
||||
"max": 117
|
||||
},
|
||||
"responseStart": {
|
||||
"median": 117,
|
||||
"mean": 117,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 117,
|
||||
"p10": 117,
|
||||
"p90": 117,
|
||||
"p99": 117,
|
||||
"max": 117
|
||||
},
|
||||
"secureConnectionStart": {
|
||||
"median": 0,
|
||||
"mean": 0,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 0,
|
||||
"p10": 0,
|
||||
"p90": 0,
|
||||
"p99": 0,
|
||||
"max": 0
|
||||
},
|
||||
"startTime": {
|
||||
"median": 0,
|
||||
"mean": 0,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 0,
|
||||
"p10": 0,
|
||||
"p90": 0,
|
||||
"p99": 0,
|
||||
"max": 0
|
||||
},
|
||||
"unloadEventEnd": {
|
||||
"median": 124,
|
||||
"mean": 124,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 124,
|
||||
"p10": 124,
|
||||
"p90": 124,
|
||||
"p99": 124,
|
||||
"max": 124
|
||||
},
|
||||
"unloadEventStart": {
|
||||
"median": 120,
|
||||
"mean": 120,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 120,
|
||||
"p10": 120,
|
||||
"p90": 120,
|
||||
"p99": 120,
|
||||
"max": 120
|
||||
},
|
||||
"workerStart": {
|
||||
"median": 0,
|
||||
"mean": 0,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 0,
|
||||
"p10": 0,
|
||||
"p90": 0,
|
||||
"p99": 0,
|
||||
"max": 0
|
||||
}
|
||||
},
|
||||
"pageTimings": {
|
||||
"backEndTime": {
|
||||
"median": 117,
|
||||
"mean": 117,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 117,
|
||||
"p10": 117,
|
||||
"p90": 117,
|
||||
"p99": 117,
|
||||
"max": 117
|
||||
},
|
||||
"domContentLoadedTime": {
|
||||
"median": 4159,
|
||||
"mean": 4159,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 4159,
|
||||
"p10": 4159,
|
||||
"p90": 4159,
|
||||
"p99": 4159,
|
||||
"max": 4159
|
||||
},
|
||||
"domInteractiveTime": {
|
||||
"median": 1415,
|
||||
"mean": 1415,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 1415,
|
||||
"p10": 1415,
|
||||
"p90": 1415,
|
||||
"p99": 1415,
|
||||
"max": 1415
|
||||
},
|
||||
"domainLookupTime": {
|
||||
"median": 0,
|
||||
"mean": 0,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 0,
|
||||
"p10": 0,
|
||||
"p90": 0,
|
||||
"p99": 0,
|
||||
"max": 0
|
||||
},
|
||||
"frontEndTime": {
|
||||
"median": 8147,
|
||||
"mean": 8147,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 8147,
|
||||
"p10": 8147,
|
||||
"p90": 8147,
|
||||
"p99": 8147,
|
||||
"max": 8147
|
||||
},
|
||||
"pageDownloadTime": {
|
||||
"median": 0,
|
||||
"mean": 0,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 0,
|
||||
"p10": 0,
|
||||
"p90": 0,
|
||||
"p99": 0,
|
||||
"max": 0
|
||||
},
|
||||
"pageLoadTime": {
|
||||
"median": 8264,
|
||||
"mean": 8264,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 8264,
|
||||
"p10": 8264,
|
||||
"p90": 8264,
|
||||
"p99": 8264,
|
||||
"max": 8264
|
||||
},
|
||||
"redirectionTime": {
|
||||
"median": 0,
|
||||
"mean": 0,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 0,
|
||||
"p10": 0,
|
||||
"p90": 0,
|
||||
"p99": 0,
|
||||
"max": 0
|
||||
},
|
||||
"serverConnectionTime": {
|
||||
"median": 0,
|
||||
"mean": 0,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 0,
|
||||
"p10": 0,
|
||||
"p90": 0,
|
||||
"p99": 0,
|
||||
"max": 0
|
||||
},
|
||||
"serverResponseTime": {
|
||||
"median": 88,
|
||||
"mean": 88,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 88,
|
||||
"p10": 88,
|
||||
"p90": 88,
|
||||
"p99": 88,
|
||||
"max": 88
|
||||
}
|
||||
},
|
||||
"rumSpeedIndex": {
|
||||
"median": 5542,
|
||||
"mean": 5542,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 5542,
|
||||
"p10": 5542,
|
||||
"p90": 5542,
|
||||
"p99": 5542,
|
||||
"max": 5542
|
||||
},
|
||||
"timeToContentfulPaint": {
|
||||
"median": 1124,
|
||||
"mean": 1124,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 1124,
|
||||
"p10": 1124,
|
||||
"p90": 1124,
|
||||
"p99": 1124,
|
||||
"max": 1124
|
||||
},
|
||||
"timeToDomContentFlushed": {
|
||||
"median": 4158,
|
||||
"mean": 4158,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 4158,
|
||||
"p10": 4158,
|
||||
"p90": 4158,
|
||||
"p99": 4158,
|
||||
"max": 4158
|
||||
},
|
||||
"timeToFirstInteractive": {
|
||||
"median": 11858,
|
||||
"mean": 11858,
|
||||
"mdev": 0,
|
||||
"stddev": 0,
|
||||
"min": 11858,
|
||||
"p10": 11858,
|
||||
"p90": 11858,
|
||||
"p99": 11858,
|
||||
"max": 11858
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
|
@ -23,6 +23,13 @@ this.AppConstants = Object.freeze({
|
|||
false,
|
||||
#endif
|
||||
|
||||
FENNEC_NIGHTLY:
|
||||
#ifdef FENNEC_NIGHTLY
|
||||
true,
|
||||
#else
|
||||
false,
|
||||
#endif
|
||||
|
||||
RELEASE_OR_BETA:
|
||||
#ifdef RELEASE_OR_BETA
|
||||
true,
|
||||
|
|
|
@ -773,9 +773,9 @@ add_old_configure_assignment(
|
|||
)
|
||||
|
||||
|
||||
@depends("MOZ_TELEMETRY_REPORTING", milestone.is_nightly)
|
||||
def telemetry_on_by_default(reporting, is_nightly):
|
||||
return reporting and is_nightly
|
||||
@depends("MOZ_TELEMETRY_REPORTING", milestone.is_nightly, fennec_nightly)
|
||||
def telemetry_on_by_default(reporting, is_nightly, fennec_nightly):
|
||||
return reporting and (is_nightly or fennec_nightly)
|
||||
|
||||
|
||||
set_define("MOZ_TELEMETRY_ON_BY_DEFAULT", True, when=telemetry_on_by_default)
|
||||
|
|
|
@ -26,6 +26,37 @@ class ScreenHelperAndroid::ScreenHelperSupport final
|
|||
typedef java::ScreenManagerHelper::Natives<ScreenHelperSupport> Base;
|
||||
|
||||
static void RefreshScreenInfo() { gHelper->Refresh(); }
|
||||
|
||||
static int32_t AddDisplay(int32_t aDisplayType, int32_t aWidth,
|
||||
int32_t aHeight, float aDensity) {
|
||||
static Atomic<uint32_t> nextId;
|
||||
|
||||
uint32_t screenId = ++nextId;
|
||||
NS_DispatchToMainThread(
|
||||
NS_NewRunnableFunction(
|
||||
"ScreenHelperAndroid::ScreenHelperSupport::AddDisplay",
|
||||
[aDisplayType, aWidth, aHeight, aDensity, screenId] {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
gHelper->AddScreen(
|
||||
screenId, static_cast<DisplayType>(aDisplayType),
|
||||
LayoutDeviceIntRect(0, 0, aWidth, aHeight), aDensity);
|
||||
})
|
||||
.take());
|
||||
return screenId;
|
||||
}
|
||||
|
||||
static void RemoveDisplay(int32_t aScreenId) {
|
||||
NS_DispatchToMainThread(
|
||||
NS_NewRunnableFunction(
|
||||
"ScreenHelperAndroid::ScreenHelperSupport::RemoveDisplay",
|
||||
[aScreenId] {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
gHelper->RemoveScreen(aScreenId);
|
||||
})
|
||||
.take());
|
||||
}
|
||||
};
|
||||
|
||||
static already_AddRefed<Screen> MakePrimaryScreen() {
|
||||
|
@ -67,6 +98,24 @@ void ScreenHelperAndroid::Refresh() {
|
|||
manager.Refresh(ToTArray<AutoTArray<RefPtr<Screen>, 1>>(mScreens.Values()));
|
||||
}
|
||||
|
||||
void ScreenHelperAndroid::AddScreen(uint32_t aScreenId,
|
||||
DisplayType aDisplayType,
|
||||
LayoutDeviceIntRect aRect, float aDensity) {
|
||||
MOZ_ASSERT(aScreenId > 0);
|
||||
MOZ_ASSERT(!mScreens.Get(aScreenId, nullptr));
|
||||
|
||||
mScreens.InsertOrUpdate(
|
||||
aScreenId, MakeRefPtr<Screen>(aRect, aRect, 24, 24,
|
||||
DesktopToLayoutDeviceScale(aDensity),
|
||||
CSSToLayoutDeviceScale(1.0f), 160.0f));
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void ScreenHelperAndroid::RemoveScreen(uint32_t aScreenId) {
|
||||
mScreens.Remove(aScreenId);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
already_AddRefed<Screen> ScreenHelperAndroid::ScreenForId(uint32_t aScreenId) {
|
||||
RefPtr<Screen> screen = mScreens.Get(aScreenId);
|
||||
return screen.forget();
|
||||
|
|
|
@ -23,6 +23,11 @@ class ScreenHelperAndroid final : public ScreenManager::Helper {
|
|||
static ScreenHelperAndroid* GetSingleton();
|
||||
|
||||
void Refresh();
|
||||
|
||||
void AddScreen(uint32_t aScreenId, DisplayType aDisplayType,
|
||||
LayoutDeviceIntRect aRect = LayoutDeviceIntRect(),
|
||||
float aDensity = 1.0f);
|
||||
void RemoveScreen(uint32_t aId);
|
||||
already_AddRefed<Screen> ScreenForId(uint32_t aScreenId);
|
||||
|
||||
private:
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Components.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/ProfilerLabels.h"
|
||||
|
@ -91,6 +92,9 @@ nsIGeolocationUpdate* gLocationCallback = nullptr;
|
|||
nsAppShell* nsAppShell::sAppShell;
|
||||
StaticAutoPtr<Mutex> nsAppShell::sAppShellLock;
|
||||
|
||||
uint32_t nsAppShell::Queue::sLatencyCount[];
|
||||
uint64_t nsAppShell::Queue::sLatencyTime[];
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED(nsAppShell, nsBaseAppShell, nsIObserver)
|
||||
|
||||
class WakeLockListener final : public nsIDOMMozWakeLockListener {
|
||||
|
@ -473,6 +477,40 @@ nsAppShell::~nsAppShell() {
|
|||
|
||||
void nsAppShell::NotifyNativeEvent() { mEventQueue.Signal(); }
|
||||
|
||||
void nsAppShell::RecordLatencies() {
|
||||
if (!mozilla::Telemetry::CanRecordExtended()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mozilla::Telemetry::HistogramID timeIDs[] = {
|
||||
mozilla::Telemetry::HistogramID::FENNEC_LOOP_UI_LATENCY,
|
||||
mozilla::Telemetry::HistogramID::FENNEC_LOOP_OTHER_LATENCY};
|
||||
|
||||
static_assert(ArrayLength(Queue::sLatencyCount) == Queue::LATENCY_COUNT,
|
||||
"Count array length mismatch");
|
||||
static_assert(ArrayLength(Queue::sLatencyTime) == Queue::LATENCY_COUNT,
|
||||
"Time array length mismatch");
|
||||
static_assert(ArrayLength(timeIDs) == Queue::LATENCY_COUNT,
|
||||
"Time ID array length mismatch");
|
||||
|
||||
for (size_t i = 0; i < Queue::LATENCY_COUNT; i++) {
|
||||
if (!Queue::sLatencyCount[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint64_t time =
|
||||
Queue::sLatencyTime[i] / 1000ull / Queue::sLatencyCount[i];
|
||||
if (time) {
|
||||
mozilla::Telemetry::Accumulate(
|
||||
timeIDs[i], uint32_t(std::min<uint64_t>(UINT32_MAX, time)));
|
||||
}
|
||||
|
||||
// Reset latency counts.
|
||||
Queue::sLatencyCount[i] = 0;
|
||||
Queue::sLatencyTime[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult nsAppShell::Init() {
|
||||
nsresult rv = nsBaseAppShell::Init();
|
||||
nsCOMPtr<nsIObserverService> obsServ =
|
||||
|
|
|
@ -143,6 +143,8 @@ class nsAppShell : public nsBaseAppShell {
|
|||
static nsAppShell* sAppShell;
|
||||
static mozilla::StaticAutoPtr<mozilla::Mutex> sAppShellLock;
|
||||
|
||||
static void RecordLatencies();
|
||||
|
||||
virtual ~nsAppShell();
|
||||
|
||||
nsresult AddObserver(const nsAString& aObserverKey, nsIObserver* aObserver);
|
||||
|
@ -168,6 +170,9 @@ class nsAppShell : public nsBaseAppShell {
|
|||
|
||||
public:
|
||||
enum { LATENCY_UI, LATENCY_OTHER, LATENCY_COUNT };
|
||||
static uint32_t sLatencyCount[LATENCY_COUNT];
|
||||
static uint64_t sLatencyTime[LATENCY_COUNT];
|
||||
|
||||
Queue() : mMonitor("nsAppShell.Queue") {}
|
||||
|
||||
void Signal() {
|
||||
|
@ -189,6 +194,20 @@ class nsAppShell : public nsBaseAppShell {
|
|||
}
|
||||
|
||||
mozilla::UniquePtr<Event> Pop(bool mayWait) {
|
||||
#ifdef EARLY_BETA_OR_EARLIER
|
||||
bool isQueueEmpty = false;
|
||||
if (mayWait) {
|
||||
mozilla::MonitorAutoLock lock(mMonitor);
|
||||
isQueueEmpty = mQueue.isEmpty();
|
||||
}
|
||||
if (isQueueEmpty) {
|
||||
// Record latencies when we're about to be idle.
|
||||
// Note: We can't call this while holding the lock because
|
||||
// nsAppShell::RecordLatencies may try to dispatch an event to the main
|
||||
// thread which tries to acquire the lock again.
|
||||
nsAppShell::RecordLatencies();
|
||||
}
|
||||
#endif
|
||||
mozilla::MonitorAutoLock lock(mMonitor);
|
||||
|
||||
if (mayWait && mQueue.isEmpty()) {
|
||||
|
@ -201,6 +220,14 @@ class nsAppShell : public nsBaseAppShell {
|
|||
return event;
|
||||
}
|
||||
|
||||
#ifdef EARLY_BETA_OR_EARLIER
|
||||
const size_t latencyType =
|
||||
event->IsUIEvent() ? LATENCY_UI : LATENCY_OTHER;
|
||||
const uint64_t latency = Event::GetTime() - event->mPostTime;
|
||||
|
||||
sLatencyCount[latencyType]++;
|
||||
sLatencyTime[latencyType] += latency;
|
||||
#endif
|
||||
return event;
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче