Revert "Issue #1053 - Drop support Android and remove Fennec - Part 1a: Remove mobile/android"

This reverts commit c9b411d6cd.
This commit is contained in:
ownedbywuigi 2026-04-11 09:47:50 +01:00
commit b7eea62509
4250 changed files with 8292 additions and 446758 deletions

View file

@ -123,6 +123,7 @@ MACH_MODULES = [
'tools/lint/mach_commands.py',
'tools/mach_commands.py',
'tools/power/mach_commands.py',
'mobile/android/mach_commands.py',
]
if os.path.exists('addon-sdk/mach_commands.py'):

View file

@ -24,7 +24,10 @@ globals:
strings: false
Strings: false
Task: false
<<<<<<< HEAD
TelemetryStopwatch: false
=======
>>>>>>> parent of c9b411d6cd (Issue #1053 - Drop support Android and remove Fennec - Part 1a: Remove mobile/android)
UITelemetry: false
UserAgentOverrides: 0
XPCOMUtils: false

View file

@ -1,373 +0,0 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
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/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View file

@ -1,11 +0,0 @@
# 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/.
include $(topsrcdir)/config/rules.mk
include $(topsrcdir)/testing/testsuite-targets.mk
package-mobile-tests:
$(MAKE) stage-mochitest DIST_BIN=$(DEPTH)/$(DIST)/bin/xulrunner
$(NSINSTALL) -D $(DIST)/$(PKG_PATH)
@(cd $(PKG_STAGE) && tar $(TAR_CREATE_FLAGS) - *) | bzip2 -f > $(DIST)/$(PKG_PATH)$(TEST_PACKAGE)

View file

@ -1,17 +0,0 @@
# vim: set filetype=python:
# 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/.
include('/toolkit/toolkit.mozbuild')
if CONFIG['ENABLE_TESTS']:
DIRS += ['/testing/instrumentation']
if CONFIG['MOZ_EXTENSIONS']:
DIRS += ['/extensions']
DIRS += [
'/%s' % CONFIG['MOZ_BRANDING_DIRECTORY'],
'/mobile/android',
]

View file

@ -1 +0,0 @@
This is an example asset.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

File diff suppressed because it is too large Load diff

View file

@ -1,404 +0,0 @@
buildDir "${topobjdir}/gradle/build/mobile/android/app"
apply plugin: 'android-sdk-manager' // Must come before 'com.android.*'.
apply plugin: 'com.android.application'
apply plugin: 'checkstyle'
android {
compileSdkVersion 23
buildToolsVersion mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION
defaultConfig {
targetSdkVersion 23
minSdkVersion 15
applicationId mozconfig.substs.ANDROID_PACKAGE_NAME
testApplicationId 'org.mozilla.roboexample.test'
testInstrumentationRunner 'org.mozilla.gecko.FennecInstrumentationTestRunner'
manifestPlaceholders = [
ANDROID_PACKAGE_NAME: mozconfig.substs.ANDROID_PACKAGE_NAME,
MOZ_ANDROID_MIN_SDK_VERSION: mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION,
MOZ_ANDROID_SHARED_ID: "${mozconfig.substs.ANDROID_PACKAGE_NAME}.sharedID",
]
// Used by Robolectric based tests; see TestRunner.
buildConfigField 'String', 'BUILD_DIR', "\"${project.buildDir}\""
vectorDrawables.useSupportLibrary = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
dexOptions {
javaMaxHeapSize "2g"
}
lintOptions {
abortOnError true
}
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFile "${topsrcdir}/mobile/android/config/proguard/proguard.cfg"
}
}
productFlavors {
// For API 21+ - with multi dex, this will be faster for local development.
local {
// For multi dex, setting `minSdkVersion 21` allows the Android gradle plugin to
// pre-DEX each module and produce an APK that can be tested on
// Android Lollipop without time consuming DEX merging processes.
minSdkVersion 21
dexOptions {
preDexLibraries true
// We only call `MultiDex.install()` for the automation build flavor
// so this may not work. However, I don't think the multidex support
// library is necessary for 21+, so I expect that it will work.
multiDexEnabled true
}
}
// For API < 21 - does not support multi dex because local development
// is slow in that case. Most builds will not require multi dex so this
// should not be an issue.
localOld {
}
// Automation builds.
automation {
dexOptions {
// As of FF48 on beta, the "test", "lint", etc. treeherder jobs fail because they
// exceed the method limit. Beta includes Adjust and its GPS dependencies, which
// increase the method count & explain the failures. Furthermore, this error only
// occurs on debug builds because we don't proguard.
//
// We enable multidex as an easy, quick-fix with minimal side effects but before we
// move to gradle for our production builds, we should re-evaluate this decision
// (bug 1286677).
multiDexEnabled true
}
}
}
sourceSets {
main {
manifest.srcFile "${project.buildDir}/generated/source/preprocessed_manifest/AndroidManifest.xml"
aidl {
srcDir "${topsrcdir}/mobile/android/base/aidl"
}
java {
srcDir "${topsrcdir}/mobile/android/base/java"
srcDir "${topsrcdir}/mobile/android/search/java"
srcDir "${topsrcdir}/mobile/android/javaaddons/java"
srcDir "${topsrcdir}/mobile/android/services/src/main/java"
if (mozconfig.substs.MOZ_ANDROID_MLS_STUMBLER) {
srcDir "${topsrcdir}/mobile/android/stumbler/java"
}
exclude 'org/mozilla/gecko/CrashReporter.java'
if (!mozconfig.substs.MOZ_NATIVE_DEVICES) {
exclude 'org/mozilla/gecko/ChromeCastDisplay.java'
exclude 'org/mozilla/gecko/ChromeCastPlayer.java'
exclude 'org/mozilla/gecko/GeckoMediaPlayer.java'
exclude 'org/mozilla/gecko/GeckoPresentationDisplay.java'
exclude 'org/mozilla/gecko/MediaPlayerManager.java'
}
if (mozconfig.substs.MOZ_WEBRTC) {
srcDir "${topsrcdir}/media/webrtc/trunk/webrtc/modules/audio_device/android/java/src"
srcDir "${topsrcdir}/media/webrtc/trunk/webrtc/modules/video_capture/android/java/src"
srcDir "${topsrcdir}/media/webrtc/trunk/webrtc/modules/video_render/android/java/src"
}
if (mozconfig.substs.MOZ_INSTALL_TRACKING) {
exclude 'org/mozilla/gecko/adjust/StubAdjustHelper.java'
} else {
exclude 'org/mozilla/gecko/adjust/AdjustHelper.java'
}
if (!mozconfig.substs.MOZ_ANDROID_GCM) {
exclude 'org/mozilla/gecko/gcm/**/*.java'
exclude 'org/mozilla/gecko/push/**/*.java'
}
srcDir "${project.buildDir}/generated/source/preprocessed_code" // See syncPreprocessedCode.
}
res {
srcDir "${topsrcdir}/${mozconfig.substs.MOZ_BRANDING_DIRECTORY}/res"
srcDir "${project.buildDir}/generated/source/preprocessed_resources" // See syncPreprocessedResources.
srcDir "${topsrcdir}/mobile/android/base/resources"
srcDir "${topsrcdir}/mobile/android/services/src/main/res"
if (mozconfig.substs.MOZ_CRASHREPORTER) {
srcDir "${topsrcdir}/mobile/android/base/crashreporter/res"
}
}
assets {
if (mozconfig.substs.MOZ_ANDROID_DISTRIBUTION_DIRECTORY && !mozconfig.substs.MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER) {
// If we are packaging the bouncer, it will have the distribution, so don't put
// it in the main APK as well.
srcDir "${mozconfig.substs.MOZ_ANDROID_DISTRIBUTION_DIRECTORY}/assets"
}
srcDir "${topsrcdir}/mobile/android/app/assets"
}
}
test {
java {
srcDir "${topsrcdir}/mobile/android/tests/background/junit4/src"
if (!mozconfig.substs.MOZ_ANDROID_GCM) {
exclude 'org/mozilla/gecko/gcm/**/*.java'
exclude 'org/mozilla/gecko/push/**/*.java'
}
}
resources {
srcDir "${topsrcdir}/mobile/android/tests/background/junit4/resources"
}
}
androidTest {
java {
srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/src"
srcDir "${topsrcdir}/mobile/android/tests/background/junit3/src"
srcDir "${topsrcdir}/mobile/android/tests/browser/junit3/src"
srcDir "${topsrcdir}/mobile/android/tests/javaddons/src"
}
res {
srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/res"
}
assets {
srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/assets"
}
}
}
testOptions {
unitTests.all {
// We'd like to use (Runtime.runtime.availableProcessors()/2), but
// we have tests that start test servers and the bound ports
// collide. We'll fix this soon to have much faster test cycles.
maxParallelForks 1
}
}
}
dependencies {
compile 'com.android.support:multidex:1.0.0'
compile "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
compile "com.android.support:appcompat-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
compile "com.android.support:cardview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
compile "com.android.support:recyclerview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
compile "com.android.support:design:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
compile "com.android.support:customtabs:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
compile "com.android.support:palette-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
if (mozconfig.substs.MOZ_NATIVE_DEVICES) {
compile "com.android.support:mediarouter-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
compile "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
compile "com.google.android.gms:play-services-cast:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
}
if (mozconfig.substs.MOZ_INSTALL_TRACKING) {
compile "com.google.android.gms:play-services-ads:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
}
if (mozconfig.substs.MOZ_ANDROID_GCM) {
compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
compile "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
compile "com.google.android.gms:play-services-gcm:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
compile "com.google.android.gms:play-services-measurement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
}
// Include LeakCanary in most gradle based builds. LeakCanary adds about 5k methods, so we disable
// it for the (non-proguarded, non-multidex) localOld builds to allow space for other libraries.
// Gradle based tests include the no-op version. Mach based builds only include the no-op version
// of this library.
// It doesn't seem like there is a non-trivial way to be conditional on 'localOld', so instead we explicitly
// define a version of leakcanary for every flavor:
localCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta1'
localOldCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
automationCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
compile project(':geckoview')
compile project(':thirdparty')
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.1.2'
testCompile 'org.simpleframework:simple-http:6.0.1'
testCompile 'org.mockito:mockito-core:1.10.19'
// Including the Robotium JAR directly can cause issues with dexing.
androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.5.4'
}
// TODO: (bug 1261486): This impl is not robust -
// we just wanted to land something.
task checkstyle(type: Checkstyle) {
configFile file("checkstyle.xml")
// TODO: should use sourceSets from project instead of hard-coded str.
source '../base/java/'
// TODO: This ignores our pre-processed resources.
include '**/*.java'
// TODO: classpath should probably be something.
classpath = files()
}
task syncPreprocessedCode(type: Sync, dependsOn: rootProject.generateCodeAndResources) {
into("${project.buildDir}/generated/source/preprocessed_code")
from("${topobjdir}/mobile/android/base/generated/preprocessed") {
// All other preprocessed code is included in the geckoview project.
include '**/AdjustConstants.java'
}
}
// The localization system uses the moz.build preprocessor to interpolate a .dtd
// file of XML entity definitions into an XML file of elements referencing those
// entities. (Each locale produces its own .dtd file, backstopped by the en-US
// .dtd file in tree.) Android Studio (and IntelliJ) don't handle these inline
// entities smoothly. This filter merely expands the entities in place, making
// them appear properly throughout the IDE. Be aware that this assumes that the
// JVM's file.encoding is utf-8. See comments in
// mobile/android/mach_commands.py.
class ExpandXMLEntitiesFilter extends FilterReader {
ExpandXMLEntitiesFilter(Reader input) {
// Extremely inefficient, but whatever.
super(new StringReader(groovy.xml.XmlUtil.serialize(new XmlParser(false, false, true).parse(input))))
}
}
task syncPreprocessedResources(type: Sync, dependsOn: rootProject.generateCodeAndResources) {
into("${project.buildDir}/generated/source/preprocessed_resources")
from("${topobjdir}/mobile/android/base/res")
filesMatching('**/strings.xml') {
filter(ExpandXMLEntitiesFilter)
}
}
// It's not easy -- see the backout in Bug 1242213 -- to change the <manifest>
// package for Fennec. Gradle has grown a mechanism to achieve what we want for
// Fennec, however, with applicationId. To use the same manifest as moz.build,
// we replace the package with org.mozilla.gecko (the eventual package) here.
task rewriteManifestPackage(type: Copy, dependsOn: rootProject.generateCodeAndResources) {
into("${project.buildDir}/generated/source/preprocessed_manifest")
from("${topobjdir}/mobile/android/base/AndroidManifest.xml")
filter { it.replaceFirst(/package=".*?"/, 'package="org.mozilla.gecko"') }
}
apply from: "${topsrcdir}/mobile/android/gradle/with_gecko_binaries.gradle"
android.applicationVariants.all { variant ->
variant.preBuild.dependsOn rewriteManifestPackage
variant.preBuild.dependsOn syncPreprocessedCode
variant.preBuild.dependsOn syncPreprocessedResources
// Automation builds don't include Gecko binaries, since those binaries are
// not produced until after build time (at package time). Therefore,
// automation builds include the Gecko binaries into the APK at package
// time. The "withGeckoBinaries" variant of the :geckoview project also
// does this. (It does what it says on the tin!) For notes on this
// approach, see mobile/android/gradle/with_gecko_binaries.gradle.
// Like 'local' or 'localOld'.
def productFlavor = variant.productFlavors[0].name
// :app uses :geckoview:release and handles it's own Gecko binary inclusion,
// even though this would be most naturally done in the :geckoview project.
if (!productFlavor.equals('automation')) {
configureVariantWithGeckoBinaries(variant)
}
}
apply plugin: 'spoon'
spoon {
// For now, let's be verbose.
debug = true
// It's not helpful to pass when we don't have a device connected.
failIfNoDeviceConnected = true
def spoonPackageName
if (gradle.startParameter.taskNames.contains('runBrowserTests')) {
spoonPackageName = 'org.mozilla.tests.browser.junit3'
}
if (gradle.startParameter.taskNames.contains('runBackgroundTests')) {
spoonPackageName = 'org.mozilla.gecko.background'
}
if (project.hasProperty('spoonPackageName')) {
// Command line overrides everything.
spoonPackageName = project.spoonPackageName
}
if (spoonPackageName) {
instrumentationArgs = ['-e', "package=${spoonPackageName}".toString()]
}
}
// See discussion at https://github.com/stanfy/spoon-gradle-plugin/issues/9.
afterEvaluate {
tasks["spoonLocal${android.testBuildType.capitalize()}AndroidTest"].outputs.upToDateWhen { false }
// This is an awkward way to define different sets of instrumentation tests.
// The task name itself is fished at runtime and the package name configured
// in the spoon configuration.
task runBrowserTests {
dependsOn tasks["spoonLocalOldDebugAndroidTest"]
}
task runBackgroundTests {
dependsOn tasks["spoonLocalOldDebugAndroidTest"]
}
}
// Bug 1299015: Complain to treeherder if checkstyle, lint, or unittest fails. It's not obvious
// how to listen to individual errors in most cases, so we just link to the reports for now.
def makeTaskExecutionListener(artifactRootUrl) {
return new TaskExecutionListener() {
void beforeExecute(Task task) {
// Do nothing.
}
void afterExecute(Task task, TaskState state) {
if (!state.failure) {
return
}
// Link to the failing report. The task path and the report path
// depend on the android-lint task in
// taskcluster/ci/android-stuff/kind.yml. It's not possible to link
// directly, so for now consumers will need to copy-paste the URL.
switch (task.path) {
case ':app:checkstyle':
def url = "${artifactRootUrl}/public/android/checkstyle/checkstyle.xml"
println "TEST-UNEXPECTED-FAIL | android-checkstyle | Checkstyle rule violations were found. See the report at: $url"
break
case ':app:lintAutomationDebug':
def url = "${artifactRootUrl}/public/android/lint/lint-results-automationDebug.html"
println "TEST-UNEXPECTED-FAIL | android-lint | Lint found errors in the project; aborting build. See the report at: $url"
break
case ':app:testAutomationDebugUnitTest':
def url = "${artifactRootUrl}/public/android/unittest/automationDebug/index.html"
println "TEST-UNEXPECTED-FAIL | android-test | There were failing tests. See the report at: $url"
break
}
}
}
}
// TASK_ID and RUN_ID are provided by docker-worker; see
// https://docs.taskcluster.net/manual/execution/workers/docker-worker.
if (System.env.TASK_ID && System.env.RUN_ID) {
def artifactRootUrl = "https://queue.taskcluster.net/v1/task/${System.env.TASK_ID}/runs/${System.env.RUN_ID}/artifacts"
gradle.addListener(makeTaskExecutionListener(artifactRootUrl))
}

View file

@ -1,63 +0,0 @@
<?xml version="1.0"?>
<!-- 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/. -->
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.2//EN"
"http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
<!-- TODO: Clean up code & add checks:
- WhitespaceAround
- EmptyLineSeparator
- NeedBraces
- LeftCurly // placement of "{" in new scope or literal
- RightCurly // placement of "}" in close scope or literal
- Indentation
- OneStatementPerLine
- OperatorWrap
- SeparatorWrap
- MultipleVariableDeclarations
- FallThrough
(I spent too much time already)
Maybe add:
- OneTopLevelClass
- OverloadMethodsDeclarationOrder
- Empty*Block // better to catch errors!
(I spent too much time already)
See http://checkstyle.sourceforge.net/google_style.html
for a good set of defaults.
-->
<module name="Checker">
<property name="charset" value="UTF-8"/>
<!-- TODO: <property name="fileExtensions" value="java, properties, xml"/> -->
<module name="FileTabCharacter"> <!-- No tabs! -->
<property name="eachLine" value="true"/>
</module>
<module name="RegexpSingleline"> <!-- excess whitespace -->
<property name="format" value="\s+$"/>
<property name="message" value="Trailing whitespace"/>
</module>
<module name="TreeWalker">
<module name="GenericWhitespace"/> <!-- whitespace for generics -->
<module name="NoLineWrap">
<property name="tokens" value="IMPORT,PACKAGE_DEF"/>
</module>
<module name="OuterTypeFilename"/> <!-- `class Lol` only in Lol.java -->
<module name="WhitespaceAfter">
<!-- TODO: (bug 1263059) Remove specific tokens to enable CAST check. -->
<property name="tokens" value="COMMA, SEMI"/>
</module>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
</module>
</module>
</module>

View file

@ -1,223 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- Enable relevant checks disabled by default -->
<issue id="NegativeMargin" severity="warning" />
<!-- We have a custom menu and don't conform to the recommended styles. -->
<issue id="IconColors" severity="ignore" />
<!-- We use our own preprocessing to either add or remove
`android:debuggable` when building with mach so it's
not actually hard-coded. We can probably remove this
warning when we switch to gradle. -->
<issue id="HardcodedDebugMode" severity="ignore" />
<!-- We have our own l10n system & don't use the platform's plurals. -->
<issue id="PluralsCandidate" severity="ignore" />
<!-- We don't want to have to follow the SDK release schedule: we can keep
the warning in order to not forget that there's a new SDK, but there's
no need to break on update. -->
<issue id="OldTargetApi" severity="warning" />
<!-- We want all lint warnings to be fatal errors.
Right now, we set these to lint warnings so:
DO NOT ADD TO THIS LIST.
We did this so we can land lint in automation
and not fail everything. -->
<issue id="AppCompatResource" severity="warning" />
<issue id="GoogleAppIndexingDeepLinkError" severity="warning" />
<issue id="GoogleAppIndexingUrlError" severity="warning" />
<issue id="Instantiatable" severity="warning" />
<issue id="LongLogTag" severity="warning" />
<issue id="MissingPermission" severity="warning" />
<issue id="NewApi" severity="warning" />
<issue id="OnClick" severity="warning" />
<issue id="ReferenceType" severity="warning" />
<issue id="ResourceAsColor" severity="warning" />
<issue id="ResourceType" severity="warning" />
<issue id="ValidFragment" severity="warning" />
<issue id="WrongConstant" severity="warning" />
<!-- We fixed all "Registered" lint errors. However the current gradle plugin has a bug where
it ignores @SuppressLint annotations for this check. See CrashReporter class and
https://code.google.com/p/android/issues/detail?id=204846 -->
<issue id="Registered" severity="warning" />
<!-- WHEN YOU FIX A LINT WARNING, ADD IT TO THIS LIST.
We want all lint warnings to be fatal errors.
This is the list of checks that we've explicitly
set as errors. Ideally, once we have no more warnings,
we switch to the `warningsAsErrors` lint option
(bug 1253737) rather than listing everything explicitly. -->
<issue id="AaptCrash" severity="error" />
<issue id="Accessibility" severity="error" />
<issue id="AccidentalOctal" severity="error" />
<issue id="AdapterViewChildren" severity="error" />
<issue id="AddJavascriptInterface" severity="error" />
<issue id="AllowBackup" severity="error" />
<issue id="AlwaysShowAction" severity="error" />
<issue id="AndroidGradlePluginVersion" severity="error" />
<issue id="AppCompatMethod" severity="error" />
<issue id="AppIndexingError" severity="error" />
<issue id="AppIndexingWarning" severity="error" />
<issue id="Assert" severity="error" />
<issue id="ButtonCase" severity="error" />
<issue id="ButtonOrder" severity="error" />
<issue id="ByteOrderMark" severity="error" />
<issue id="CheckResult" severity="error" />
<issue id="Correctness" severity="error" />
<issue id="CutPasteId" severity="error" />
<issue id="DalvikOverride" severity="error" />
<issue id="DeviceAdmin" severity="error" />
<issue id="DisableBaselineAlignment" severity="error" />
<issue id="DrawAllocation" severity="error" />
<issue id="DuplicateActivity" severity="error" />
<issue id="DuplicateDefinition" severity="error" />
<issue id="DuplicateIds" severity="error" />
<issue id="DuplicateIncludedIds" severity="error" />
<issue id="DuplicateUsesFeature" severity="error" />
<issue id="ExportedContentProvider" severity="error" />
<issue id="ExportedPreferenceActivity" severity="error" />
<issue id="ExtraText" severity="error" />
<issue id="ExtraTranslation" severity="error" />
<issue id="FloatMath" severity="error" />
<issue id="FullBackupContent" severity="error" />
<issue id="GetInstance" severity="error" />
<issue id="GifUsage" severity="error" />
<issue id="GradleCompatible" severity="error" />
<issue id="GradleDependency" severity="error" />
<issue id="GradleDeprecated" severity="error" />
<issue id="GradleDynamicVersion" severity="error" />
<issue id="GradleGetter" severity="error" />
<issue id="GradleIdeError" severity="error" />
<issue id="GradlePath" severity="error" />
<issue id="GrantAllUris" severity="error" />
<issue id="GridLayout" severity="error" />
<issue id="HandlerLeak" severity="error" />
<issue id="HardcodedText" severity="error" />
<issue id="IconExtension" severity="error" />
<issue id="IconLauncherShape" severity="error" />
<issue id="IconMixedNinePatch" severity="error" />
<issue id="IconNoDpi" severity="error" />
<issue id="IllegalResourceRef" severity="error" />
<issue id="ImpliedQuantity" severity="error" />
<issue id="InOrMmUsage" severity="error" />
<issue id="IncludeLayoutParam" severity="error" />
<issue id="InconsistentArrays" severity="error" />
<issue id="InefficientWeight" severity="error" />
<issue id="InnerclassSeparator" severity="error" />
<issue id="Internationalization" severity="error" />
<issue id="InvalidId" severity="error" />
<issue id="InvalidPackage" severity="error" />
<issue id="InvalidResourceFolder" severity="error" />
<issue id="JavascriptInterface" severity="error" />
<issue id="LabelFor" severity="error" />
<issue id="LibraryCustomView" severity="error" />
<issue id="LocalSuppress" severity="error" />
<issue id="LocaleFolder" severity="error" />
<issue id="LogTagMismatch" severity="error" />
<issue id="MangledCRLF" severity="error" />
<issue id="ManifestOrder" severity="error" />
<issue id="ManifestTypo" severity="error" />
<issue id="MenuTitle" severity="error" />
<issue id="MergeRootFrame" severity="error" />
<issue id="MipmapIcons" severity="error" />
<issue id="MissingApplicationIcon" severity="error" />
<issue id="MissingId" severity="error" />
<issue id="MissingPrefix" severity="error" />
<issue id="MissingQuantity" severity="error" />
<issue id="MissingRegistered" severity="error" />
<issue id="MissingSuperCall" severity="error" />
<issue id="MissingTranslation" severity="error" />
<issue id="MissingVersion" severity="error" />
<issue id="MockLocation" severity="error" />
<issue id="MultipleUsesSdk" severity="error" />
<issue id="NamespaceTypo" severity="error" />
<issue id="NestedScrolling" severity="error" />
<issue id="NfcTechWhitespace" severity="error" />
<issue id="NotSibling" severity="error" />
<issue id="ObsoleteLayoutParam" severity="error" />
<issue id="OnClick" severity="error" />
<issue id="Orientation" severity="error" />
<issue id="Override" severity="error" />
<issue id="OverrideAbstract" severity="error" />
<issue id="PackagedPrivateKey" severity="error" />
<issue id="ParcelCreator" severity="error" />
<issue id="Performance" severity="error" />
<issue id="Proguard" severity="error" />
<issue id="ProguardSplit" severity="error" />
<issue id="PropertyEscape" severity="error" />
<issue id="ProtectedPermissions" severity="error" />
<issue id="PxUsage" severity="error" />
<issue id="Range" severity="error" />
<issue id="RelativeOverlap" severity="error" />
<issue id="RequiredSize" severity="error" />
<issue id="ResAuto" severity="error" />
<issue id="ResourceCycle" severity="error" />
<issue id="ResourceName" severity="error" />
<issue id="ResourceType" severity="error" />
<issue id="RtlCompat" severity="error" />
<issue id="RtlEnabled" severity="error" />
<issue id="ScrollViewCount" severity="error" />
<issue id="ScrollViewSize" severity="error" />
<issue id="SecureRandom" severity="error" />
<issue id="Security" severity="error" />
<issue id="ServiceCast" severity="error" />
<issue id="SetJavaScriptEnabled" severity="error" />
<issue id="ShiftFlags" severity="error" />
<issue id="ShortAlarm" severity="error" />
<issue id="ShowToast" severity="error" />
<issue id="SignatureOrSystemPermissions" severity="error" />
<issue id="StringFormatCount" severity="error" />
<issue id="StringFormatInvalid" severity="error" />
<issue id="StringFormatMatches" severity="error" />
<issue id="StringShouldBeInt" severity="error" />
<issue id="SuspiciousImport" severity="error" />
<issue id="TextFields" severity="error" />
<issue id="TextViewEdits" severity="error" />
<issue id="TooDeepLayout" severity="error" />
<issue id="TooManyViews" severity="error" />
<issue id="TrulyRandom" severity="error" />
<issue id="TypographyDashes" severity="error" />
<issue id="TypographyFractions" severity="error" />
<issue id="TypographyOther" severity="error" />
<issue id="Typos" severity="error" />
<issue id="UniqueConstants" severity="error" />
<issue id="UniquePermission" severity="error" />
<issue id="UnknownId" severity="error" />
<issue id="UnknownIdInLayout" severity="error" />
<issue id="UnlocalizedSms" severity="error" />
<issue id="UnusedNamespace" severity="error" />
<issue id="UnusedQuantity" severity="error" />
<issue id="UnusedResources" severity="error">
<!-- The moz.build based build system leaves a .mkdir.done file lying around in the
preprocessed_resources res/raw folder. Lint reports it as unused. We should get
rid of the file eventually. See bug 1268948. -->
<ignore path="**/raw/.mkdir.done" />
</issue>
<issue id="Usability" severity="error" />
<issue id="UseCheckPermission" severity="error" />
<issue id="UseCompoundDrawables" severity="error" />
<issue id="UselessLeaf" severity="error" />
<issue id="UsesMinSdkAttributes" severity="error" />
<issue id="UsingHttp" severity="error" />
<issue id="ViewHolder" severity="error" />
<issue id="ViewTag" severity="error" />
<issue id="Wakelock" severity="error" />
<issue id="WebViewLayout" severity="error" />
<issue id="WorldReadableFiles" severity="error" />
<issue id="WorldWriteableFiles" severity="error" />
<issue id="WrongCall" severity="error" />
<issue id="WrongCase" severity="error" />
<issue id="WrongConstant" severity="error" />
<issue id="WrongFolder" severity="error" />
<issue id="WrongManifestParent" severity="error" />
<issue id="WrongRegion" severity="error" />
<issue id="WrongThread" severity="error" />
<issue id="WrongViewCast" severity="error" />
</lint>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -901,6 +901,7 @@ pref("dom.push.maxRecentMessageIDsPerSubscription", 0);
pref("dom.push.enabled", false);
#endif
<<<<<<< HEAD
// The remote content URL where FxAccountsWebChannel messages originate. Must use HTTPS.
pref("identity.fxaccounts.remote.webchannel.uri", "https://accounts.firefox.com");
@ -913,6 +914,8 @@ pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com
// Token server used by Firefox Account-authenticated Sync.
pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sync/1.5");
=======
>>>>>>> parent of c9b411d6cd (Issue #1053 - Drop support Android and remove Fennec - Part 1a: Remove mobile/android)
// Enable Presentation API
pref("dom.presentation.enabled", false);
pref("dom.presentation.discovery.enabled", true);

View file

@ -1,30 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
for var in ('APP_NAME', 'APP_VERSION'):
DEFINES[var] = CONFIG['MOZ_%s' % var]
for var in ('MOZ_UPDATER', 'MOZ_APP_UA_NAME', 'ANDROID_PACKAGE_NAME'):
DEFINES[var] = CONFIG[var]
for var in ('MOZ_ANDROID_GCM', ):
if CONFIG[var]:
DEFINES[var] = 1
for var in ('MOZ_ANDROID_GCM_SENDERID', ):
if CONFIG[var]:
DEFINES[var] = CONFIG[var]
if CONFIG['MOZ_PKG_SPECIAL']:
DEFINES['MOZ_PKG_SPECIAL'] = CONFIG['MOZ_PKG_SPECIAL']
JS_PREFERENCE_PP_FILES += [
'mobile.js',
]
FINAL_TARGET_PP_FILES += [
'ua-update.json.in',
]

View file

@ -1,33 +0,0 @@
buildDir "${topobjdir}/gradle/build/mobile/android/omnijar"
apply plugin: 'java'
// This project is a dummy project; the JAR produced is not used. The :app
// project uses the set of inputs here to check if the omnijar needs to be
// rebuilt. By listing them here as resource directories, IntelliJ labels each
// checked directly nicely. Why list the directories here? There's a mismatch
// between SourceDirectorySet and TaskInputs: the former is directory oriented,
// while the latter is more general. That means its easy to convert this list
// into inputs for :app, but not vice-versa. Sadly this implies that :app
// evaluation depends on :omnijar, but the evaluation overhead is low enough
// that we accept it.
sourceSets {
main {
// Depend on the Gecko resources in mobile/android.
resources {
srcDir "${topsrcdir}/mobile/android/chrome"
srcDir "${topsrcdir}/mobile/android/components"
srcDir "${topsrcdir}/mobile/android/locales"
srcDir "${topsrcdir}/mobile/android/modules"
srcDir "${topsrcdir}/mobile/android/themes"
srcDir "${topsrcdir}/toolkit"
}
}
}
apply plugin: 'idea'
idea {
module {
}
}

View file

@ -1,61 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mozilla.roboexample.test"
android:sharedUserId="${MOZ_ANDROID_SHARED_ID}"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="${MOZ_ANDROID_MIN_SDK_VERSION}"
android:targetSdkVersion="23"/>
<!-- TODO: re-instate maxSdkVersion. -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<instrumentation
android:name="org.mozilla.gecko.FennecInstrumentationTestRunner"
android:targetPackage="${ANDROID_PACKAGE_NAME}" />
<application
android:label="@string/app_name">
<uses-library android:name="android.test.runner" />
<!-- Fake handlers to ensure that we have some share intents to show in our share handler list -->
<activity android:name="org.mozilla.gecko.RobocopShare1"
android:label="Robocop fake activity">
<intent-filter android:label="Fake robocop share handler 1">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity android:name="org.mozilla.gecko.RobocopShare2"
android:label="Robocop fake activity 2">
<intent-filter android:label="Fake robocop share handler 2">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity android:name="org.mozilla.gecko.LaunchFennecWithConfigurationActivity"
android:label="Robocop Fennec">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -1,27 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko;
import android.app.Application;
import org.robolectric.TestLifecycleApplication;
import java.lang.reflect.Method;
/**
* GeckoApplication isn't test-lifecycle friendly: onCreate is called multiple times, which
* re-registers Gecko event listeners, which fails. This class is magically named so that
* Robolectric uses it instead of the application defined in the Android manifest. See
* http://robolectric.blogspot.ca/2013/04/the-test-lifecycle-in-20.html.
*/
public class TestGeckoApplication extends Application implements TestLifecycleApplication {
@Override public void beforeTest(Method method) {
}
@Override public void prepareTest(Object test) {
}
@Override public void afterTest(Method method) {
}
}

View file

@ -1,15 +0,0 @@
#filter slashslash
// Everything after the first // on a line will be removed by the preproccesor.
// Send these sites a custom user-agent. Bugs should be included with an entry.
// NOTE: trailing commas are not valid JSON and will prevent the CDN from syncing.
{
"weather.yahoo.co.jp": "Mozilla/5.0 (Linux; Android 5.0.2; Galaxy Nexus Build/IMM76B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
// bug 1177298, lohaco.jp
"lohaco.jp": "Mozilla/5.0 (Linux; Android 5.0.2; Galaxy Nexus Build/IMM76B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
// bug 1177298, www.nhk.or.jp
"nhk.or.jp": "\\)\\s# AppleWebKit ",
// bug 1177298, uniqlo.com
"uniqlo.com": "\\)\\s#) Mobile Safari ",
// bug 1338260, directv.com
"directv.com": "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36"
}

View file

@ -1,32 +0,0 @@
//#filter substitution
//#include @OBJDIR@/adjust_sdk_app_token
/* -*- 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.adjust.AdjustHelperInterface;
//#ifdef MOZ_INSTALL_TRACKING
import org.mozilla.gecko.adjust.AdjustHelper;
//#else
import org.mozilla.gecko.adjust.StubAdjustHelper;
//#endif
public class AdjustConstants {
public static final String MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN =
//#ifdef MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN
"@MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN@";
//#else
null;
//#endif
public static AdjustHelperInterface getAdjustHelper() {
//#ifdef MOZ_INSTALL_TRACKING
return new AdjustHelper();
//#else
return new StubAdjustHelper();
//#endif
}
}

View file

@ -1,433 +0,0 @@
#filter substitution
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="@ANDROID_PACKAGE_NAME@"
android:installLocation="auto"
android:versionCode="@ANDROID_VERSION_CODE@"
android:versionName="@MOZ_APP_VERSION@"
#ifdef MOZ_ANDROID_SHARED_ID
android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
#endif
>
<uses-sdk android:minSdkVersion="@MOZ_ANDROID_MIN_SDK_VERSION@"
#ifdef MOZ_ANDROID_MAX_SDK_VERSION
android:maxSdkVersion="@MOZ_ANDROID_MAX_SDK_VERSION@"
#endif
android:targetSdkVersion="23"/>
<!-- The bouncer APK and the main APK should define the same set of
<permission>, <uses-permission>, and <uses-feature> elements. This reduces
the likelihood of permission-related surprises when installing the main APK
on top of a pre-installed bouncer APK. Add such shared elements in the
fileincluded here, so that they can be referenced by both APKs. -->
#include FennecManifest_permissions.xml.in
<application android:label="@string/moz_app_displayname"
android:icon="@drawable/icon"
android:logo="@drawable/logo"
android:name="@MOZ_ANDROID_APPLICATION_CLASS@"
android:hardwareAccelerated="true"
android:allowBackup="false"
# The preprocessor does not yet support arbitrary parentheses, so this cannot
# be parenthesized thus to clarify that the logical AND operator has precedence:
# !defined(MOZILLA_OFFICIAL) || (defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG))
#if !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG)
android:debuggable="true">
#else
android:debuggable="false">
#endif
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true"/>
#ifdef MOZ_NATIVE_DEVICES
<!-- This resources comes from Google Play Services. Required for casting support. -->
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
<service android:name="org.mozilla.gecko.RemotePresentationService" android:exported="false"/>
#endif
<!-- This activity handles all incoming Intents and dispatches them to other activities. -->
<activity android:name="org.mozilla.gecko.LauncherActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:relinquishTaskIdentity="true"
android:taskAffinity=""
android:excludeFromRecents="true" />
<!-- Fennec is shipped as the Android package named
org.mozilla.{fennec,firefox,firefox_beta}. The internal Java
package hierarchy inside the Android package used to have an
org.mozilla.{fennec,firefox,firefox_beta} subtree *and* an
org.mozilla.gecko subtree; it now only has org.mozilla.gecko. -->
<activity android:name="@MOZ_ANDROID_BROWSER_INTENT_CLASS@"
android:label="@string/moz_app_displayname"
android:taskAffinity="@ANDROID_PACKAGE_NAME@.BROWSER"
android:alwaysRetainTaskState="true"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection|smallestScreenSize|screenLayout"
android:windowSoftInputMode="stateUnspecified|adjustResize"
android:launchMode="singleTask"
android:exported="true"
android:theme="@style/Gecko.App" />
<!-- Bug 1256615 / Bug 1268455: We published an .App alias and we need to maintain it
forever. If we don't, home screen shortcuts will disappear because the intent
filter details change. -->
<activity-alias android:name=".App"
android:label="@MOZ_APP_DISPLAYNAME@"
android:targetActivity="org.mozilla.gecko.LauncherActivity">
<!-- android:priority ranges between -1000 and 1000. We never want
another activity to usurp the MAIN action, so we ratchet our
priority up. -->
<intent-filter android:priority="999">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER"/>
<category android:name="android.intent.category.APP_BROWSER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:scheme="about" />
<data android:scheme="javascript" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:mimeType="text/html"/>
<data android:mimeType="text/plain"/>
<data android:mimeType="application/xhtml+xml"/>
</intent-filter>
<meta-data android:name="com.sec.minimode.icon.portrait.normal"
android:resource="@drawable/icon"/>
<meta-data android:name="com.sec.minimode.icon.landscape.normal"
android:resource="@drawable/icon" />
<intent-filter>
<action android:name="org.mozilla.gecko.ACTION_ALERT_CALLBACK" />
</intent-filter>
<intent-filter>
<action android:name="org.mozilla.gecko.GUEST_SESSION_INPROGRESS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="org.mozilla.gecko.UPDATE"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.WEB_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="" />
<data android:scheme="http" />
<data android:scheme="https" />
</intent-filter>
<!-- For XPI installs from websites and the download manager. -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:mimeType="application/x-xpinstall" />
</intent-filter>
<!-- For XPI installs from file: URLs. -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:host="" />
<data android:scheme="file" />
<data android:pathPattern=".*\\.xpi" />
</intent-filter>
#ifdef MOZ_ANDROID_BEAM
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
<data android:scheme="https" />
</intent-filter>
#endif
<!-- For debugging -->
<intent-filter>
<action android:name="org.mozilla.gecko.DEBUG" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity-alias>
<service android:name="org.mozilla.gecko.GeckoService" />
<activity android:name="org.mozilla.gecko.trackingprotection.TrackingProtectionPrompt"
android:launchMode="singleTop"
android:theme="@style/OverlayActivity" />
<activity android:name="org.mozilla.gecko.promotion.SimpleHelperUI"
android:launchMode="singleTop"
android:theme="@style/OverlayActivity" />
<activity android:name="org.mozilla.gecko.promotion.HomeScreenPrompt"
android:launchMode="singleTop"
android:theme="@style/OverlayActivity" />
<!-- The main reason for the Tab Queue build flag is to not mess with the VIEW intent filter
before the rest of the plumbing is in place -->
<service android:name="org.mozilla.gecko.tabqueue.TabQueueService" />
<activity android:name="org.mozilla.gecko.tabqueue.TabQueuePrompt"
android:launchMode="singleTop"
android:theme="@style/OverlayActivity" />
<receiver android:name="org.mozilla.gecko.restrictions.RestrictionProvider">
<intent-filter>
<action android:name="android.intent.action.GET_RESTRICTION_ENTRIES" />
</intent-filter>
</receiver>
<!-- Masquerade as the Resolver so that we can be opened from the Marketplace. -->
<activity-alias
android:name="com.android.internal.app.ResolverActivity"
android:targetActivity="@MOZ_ANDROID_BROWSER_INTENT_CLASS@"
android:exported="true" />
<receiver android:name="org.mozilla.gecko.GeckoUpdateReceiver">
<intent-filter>
<action android:name="@ANDROID_PACKAGE_NAME@.CHECK_UPDATE_RESULT" />
</intent-filter>
</receiver>
<receiver android:name="org.mozilla.gecko.GeckoMessageReceiver"
android:exported="false">
</receiver>
<!-- Catch install referrer so we can do post-install work. -->
<receiver android:name="org.mozilla.gecko.distribution.ReferrerReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER" />
</intent-filter>
</receiver>
<service android:name="org.mozilla.gecko.Restarter"
android:exported="false"
android:process="@MANGLED_ANDROID_PACKAGE_NAME@.Restarter">
</service>
<service android:name="org.mozilla.gecko.media.MediaControlService"
android:exported="false">
</service>
<receiver android:name="org.mozilla.gecko.AlarmReceiver" >
</receiver>
<receiver
android:name="org.mozilla.gecko.notifications.WhatsNewReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<data android:scheme="package" android:path="org.mozilla.gecko" />
</intent-filter>
</receiver>
<receiver
android:name="org.mozilla.gecko.notifications.NotificationReceiver"
android:exported="false">
<!-- Notification API V2 -->
<intent-filter>
<action android:name="@ANDROID_PACKAGE_NAME@.helperBroadcastAction" />
<action android:name="@ANDROID_PACKAGE_NAME@.NOTIFICATION_CLICK" />
<action android:name="@ANDROID_PACKAGE_NAME@.NOTIFICATION_CLOSE" />
<data android:scheme="moz-notification" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
#include ../services/manifests/FxAccountAndroidManifest_activities.xml.in
#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
#include ../search/manifests/SearchAndroidManifest_activities.xml.in
#endif
<activity android:name="org.mozilla.gecko.preferences.GeckoPreferences"
android:theme="@style/Gecko.Preferences"
android:configChanges="orientation|screenSize|locale|layoutDirection"
android:excludeFromRecents="true"/>
<provider android:name="org.mozilla.gecko.db.BrowserProvider"
android:authorities="@ANDROID_PACKAGE_NAME@.db.browser"
android:exported="false"/>
<provider android:name="org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy"
android:authorities="@ANDROID_PACKAGE_NAME@.partnerbookmarks"
android:exported="false"/>
<!-- Share overlay activity
Setting launchMode="singleTop" ensures onNewIntent is called when the Activity is
reused. Ideally we create a new instance but Android L breaks this (bug 1137928). -->
<activity android:name="org.mozilla.gecko.overlays.ui.ShareDialog"
android:label="@string/overlay_share_label"
android:theme="@style/OverlayActivity"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|locale|layoutDirection"
android:launchMode="singleTop"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
#ifdef MOZ_ANDROID_CUSTOM_TABS
<activity android:name="org.mozilla.gecko.customtabs.CustomTabsActivity"
android:theme="@style/Theme.AppCompat.NoActionBar" />
#endif
<!-- Service to handle requests from overlays. -->
<service android:name="org.mozilla.gecko.overlays.service.OverlayActionService" />
<!--
Ensure that passwords provider runs in its own process. (Bug 718760.)
Process name is per-application to avoid loading CPs from multiple
Fennec versions into the same process. (Bug 749727.)
Process name is a mangled version to avoid a Talos bug. (Bug 750548.)
-->
<provider android:name="org.mozilla.gecko.db.PasswordsProvider"
android:label="@string/sync_configure_engines_title_passwords"
android:authorities="@ANDROID_PACKAGE_NAME@.db.passwords"
android:exported="false"
android:process="@MANGLED_ANDROID_PACKAGE_NAME@.PasswordsProvider"/>
<provider android:name="org.mozilla.gecko.db.LoginsProvider"
android:label="@string/sync_configure_engines_title_passwords"
android:authorities="@ANDROID_PACKAGE_NAME@.db.logins"
android:exported="false"/>
<provider android:name="org.mozilla.gecko.db.FormHistoryProvider"
android:label="@string/sync_configure_engines_title_history"
android:authorities="@ANDROID_PACKAGE_NAME@.db.formhistory"
android:exported="false"/>
<provider android:name="org.mozilla.gecko.GeckoProfilesProvider"
android:authorities="@ANDROID_PACKAGE_NAME@.profiles"
android:exported="false"/>
<provider android:name="org.mozilla.gecko.db.TabsProvider"
android:label="@string/sync_configure_engines_title_tabs"
android:authorities="@ANDROID_PACKAGE_NAME@.db.tabs"
android:exported="false"/>
<provider android:name="org.mozilla.gecko.db.HomeProvider"
android:authorities="@ANDROID_PACKAGE_NAME@.db.home"
android:exported="false"/>
<provider android:name="org.mozilla.gecko.db.SearchHistoryProvider"
android:authorities="@ANDROID_PACKAGE_NAME@.db.searchhistory"
android:exported="false"/>
<service
android:exported="false"
android:name="org.mozilla.gecko.updater.UpdateService"
android:process="@MANGLED_ANDROID_PACKAGE_NAME@.UpdateService">
</service>
<service
android:exported="false"
android:name="org.mozilla.gecko.notifications.NotificationService">
</service>
<service
android:exported="false"
android:name="org.mozilla.gecko.dlc.DownloadContentService">
</service>
<service
android:exported="false"
android:name="org.mozilla.gecko.feeds.FeedService">
</service>
<!-- DON'T EXPORT THIS, please! An attacker could delete arbitrary files. -->
<service
android:exported="false"
android:name="org.mozilla.gecko.cleanup.FileCleanupService">
</service>
<receiver
android:name="org.mozilla.gecko.feeds.FeedAlarmReceiver"
android:exported="false" />
<receiver
android:name="org.mozilla.gecko.BootReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
</intent-filter>
</receiver>
<receiver
android:name="org.mozilla.gecko.PackageReplacedReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"></action>
</intent-filter>
</receiver>
<service
android:name="org.mozilla.gecko.telemetry.TelemetryUploadService"
android:exported="false"/>
#ifdef MOZ_ANDROID_CUSTOM_TABS
<service
android:name="org.mozilla.gecko.customtabs.GeckoCustomTabsService"
android:exported="true">
<intent-filter>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent-filter>
</service>
#endif
#include ../services/manifests/FxAccountAndroidManifest_services.xml.in
<service
android:name="org.mozilla.gecko.tabqueue.TabReceivedService"
android:exported="false" />
#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
#include ../search/manifests/SearchAndroidManifest_services.xml.in
#endif
#ifdef MOZ_ANDROID_MLS_STUMBLER
#include ../stumbler/manifests/StumblerManifest_services.xml.in
#endif
#ifdef MOZ_ANDROID_GCM
#include GcmAndroidManifest_services.xml.in
#endif
<service
android:name="org.mozilla.gecko.media.MediaManager"
android:enabled="true"
android:exported="false"
android:process=":media"
android:isolatedProcess="false">
</service>
</application>
</manifest>

View file

@ -1,342 +0,0 @@
//#filter substitution
/* -*- 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.content.Context;
import android.os.Build;
//#ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
import android.support.multidex.MultiDex;
//#endif
/**
* A collection of constants that pertain to the build and runtime state of the
* application. Typically these are sourced from build-time definitions (see
* Makefile.in). This is a Java-side substitute for nsIXULAppInfo, amongst
* other things.
*
* See also SysInfo.java, which includes some of the values available from
* nsSystemInfo inside Gecko.
*/
// Normally, we'd annotate with @RobocopTarget. Since AppConstants is compiled
// before RobocopTarget, we instead add o.m.g.AppConstants directly to the
// Proguard configuration.
public class AppConstants {
public static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@";
public static final String MANGLED_ANDROID_PACKAGE_NAME = "@MANGLED_ANDROID_PACKAGE_NAME@";
public static final String MOZ_ANDROID_SHARED_FXACCOUNT_TYPE = "@ANDROID_PACKAGE_NAME@_fxaccount";
/**
* Encapsulates access to compile-time version definitions, allowing
* for dead code removal for particular APKs.
*/
public static final class Versions {
public static final int MIN_SDK_VERSION = @MOZ_ANDROID_MIN_SDK_VERSION@;
public static final int MAX_SDK_VERSION =
//#ifdef MOZ_ANDROID_MAX_SDK_VERSION
@MOZ_ANDROID_MAX_SDK_VERSION@;
//#else
999;
//#endif
/*
* The SDK_INT >= N check can only pass if our MAX_SDK_VERSION is
* _greater than or equal_ to that number, because otherwise we
* won't be installed on the device.
*
* If MIN_SDK_VERSION is greater than or equal to the number, there
* is no need to do the runtime check.
*/
public static final boolean feature16Plus = MIN_SDK_VERSION >= 16 || (MAX_SDK_VERSION >= 16 && Build.VERSION.SDK_INT >= 16);
public static final boolean feature17Plus = MIN_SDK_VERSION >= 17 || (MAX_SDK_VERSION >= 17 && Build.VERSION.SDK_INT >= 17);
public static final boolean feature19Plus = MIN_SDK_VERSION >= 19 || (MAX_SDK_VERSION >= 19 && Build.VERSION.SDK_INT >= 19);
public static final boolean feature20Plus = MIN_SDK_VERSION >= 20 || (MAX_SDK_VERSION >= 20 && Build.VERSION.SDK_INT >= 20);
public static final boolean feature21Plus = MIN_SDK_VERSION >= 21 || (MAX_SDK_VERSION >= 21 && Build.VERSION.SDK_INT >= 21);
/*
* If our MIN_SDK_VERSION is 14 or higher, we must be an ICS device.
* If our MAX_SDK_VERSION is lower than ICS, we must not be an ICS device.
* Otherwise, we need a range check.
*/
public static final boolean preMarshmallow = MAX_SDK_VERSION < 23 || (MIN_SDK_VERSION < 23 && Build.VERSION.SDK_INT < 23);
public static final boolean preLollipop = MAX_SDK_VERSION < 21 || (MIN_SDK_VERSION < 21 && Build.VERSION.SDK_INT < 21);
public static final boolean preJBMR2 = MAX_SDK_VERSION < 18 || (MIN_SDK_VERSION < 18 && Build.VERSION.SDK_INT < 18);
public static final boolean preJBMR1 = MAX_SDK_VERSION < 17 || (MIN_SDK_VERSION < 17 && Build.VERSION.SDK_INT < 17);
public static final boolean preJB = MAX_SDK_VERSION < 16 || (MIN_SDK_VERSION < 16 && Build.VERSION.SDK_INT < 16);
public static final boolean preN = MAX_SDK_VERSION < 24 || (MIN_SDK_VERSION < 24 && Build.VERSION.SDK_INT < 24);
}
/**
* The name of the Java class that represents the android application.
*/
public static final String MOZ_ANDROID_APPLICATION_CLASS = "@MOZ_ANDROID_APPLICATION_CLASS@";
/**
* The name of the Java class that launches the browser activity.
*/
public static final String MOZ_ANDROID_BROWSER_INTENT_CLASS = "@MOZ_ANDROID_BROWSER_INTENT_CLASS@";
/**
* The name of the Java class that launches the search activity.
*/
public static final String MOZ_ANDROID_SEARCH_INTENT_CLASS = "@MOZ_ANDROID_SEARCH_INTENT_CLASS@";
public static final String GRE_MILESTONE = "@GRE_MILESTONE@";
public static final String MOZ_APP_ABI = "@MOZ_APP_ABI@";
public static final String MOZ_APP_BASENAME = "@MOZ_APP_BASENAME@";
// For the benefit of future archaeologists:
// GRE_BUILDID is exactly the same as MOZ_APP_BUILDID unless you're running
// on XULRunner, which is never the case on Android.
public static final String MOZ_APP_BUILDID = "@MOZ_BUILDID@";
public static final String MOZ_APP_ID = "@MOZ_APP_ID@";
public static final String MOZ_APP_NAME = "@MOZ_APP_NAME@";
public static final String MOZ_APP_VENDOR = "@MOZ_APP_VENDOR@";
public static final String MOZ_APP_VERSION = "@MOZ_APP_VERSION@";
public static final String MOZ_APP_DISPLAYNAME = "@MOZ_APP_DISPLAYNAME@";
// MOZ_APP_UA_NAME is already quoted when it gets substituted, like MOZILLA_VERSION.
public static final String MOZ_APP_UA_NAME = @MOZ_APP_UA_NAME@;
// MOZILLA_VERSION is already quoted when it gets substituted in. If we
// add additional quotes we end up with ""x.y"", which is a syntax error.
public static final String MOZILLA_VERSION = @MOZILLA_VERSION@;
public static final String MOZ_MOZILLA_API_KEY = "@MOZ_MOZILLA_API_KEY@";
public static final boolean MOZ_STUMBLER_BUILD_TIME_ENABLED =
//#ifdef MOZ_ANDROID_MLS_STUMBLER
true;
//#else
false;
//#endif
public static final boolean MOZ_ANDROID_GCM =
//#ifdef MOZ_ANDROID_GCM
true;
//#else
false;
//#endif
public static final String MOZ_ANDROID_GCM_SENDERID =
//#ifdef MOZ_ANDROID_GCM_SENDERID
"@MOZ_ANDROID_GCM_SENDERID@";
//#else
null;
//#endif
public static final String MOZ_CHILD_PROCESS_NAME = "@MOZ_CHILD_PROCESS_NAME@";
public static final String MOZ_UPDATE_CHANNEL = "@MOZ_UPDATE_CHANNEL@";
public static final String OMNIJAR_NAME = "@OMNIJAR_NAME@";
public static final String OS_TARGET = "@OS_TARGET@";
public static final String TARGET_XPCOM_ABI = @TARGET_XPCOM_ABI@;
public static final String USER_AGENT_BOT_LIKE = "Redirector/" + AppConstants.MOZ_APP_VERSION +
" (Android; rv:" + AppConstants.MOZ_APP_VERSION + ")";
public static final String USER_AGENT_FENNEC_MOBILE = "Mozilla/5.0 (Android " +
Build.VERSION.RELEASE + "; Mobile; rv:" +
AppConstants.MOZ_APP_VERSION + ") Gecko/" +
AppConstants.MOZ_APP_VERSION + " Firefox/" +
AppConstants.MOZ_APP_VERSION;
public static final String USER_AGENT_FENNEC_TABLET = "Mozilla/5.0 (Android " +
Build.VERSION.RELEASE + "; Tablet; rv:" +
AppConstants.MOZ_APP_VERSION + ") Gecko/" +
AppConstants.MOZ_APP_VERSION + " Firefox/" +
AppConstants.MOZ_APP_VERSION;
public static final int MOZ_MIN_CPU_VERSION = @MOZ_MIN_CPU_VERSION@;
public static final boolean MOZ_ANDROID_ANR_REPORTER =
//#ifdef MOZ_ANDROID_ANR_REPORTER
true;
//#else
false;
//#endif
public static final String MOZ_PKG_SPECIAL =
//#ifdef MOZ_PKG_SPECIAL
"@MOZ_PKG_SPECIAL@";
//#else
null;
//#endif
public static final boolean MOZ_EXCLUDE_HYPHENATION_DICTIONARIES =
//#ifdef MOZ_EXCLUDE_HYPHENATION_DICTIONARIES
true;
//#else
false;
//#endif
public static final boolean MOZ_SERVICES_HEALTHREPORT =
//#ifdef MOZ_SERVICES_HEALTHREPORT
true;
//#else
false;
//#endif
public static final boolean MOZ_TELEMETRY_ON_BY_DEFAULT =
//#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
true;
//#else
false;
//#endif
public static final String TELEMETRY_PREF_NAME =
"toolkit.telemetry.enabled";
public static final boolean MOZ_TELEMETRY_REPORTING =
//#ifdef MOZ_TELEMETRY_REPORTING
true;
//#else
false;
//#endif
public static final boolean MOZ_DATA_REPORTING =
//#ifdef MOZ_DATA_REPORTING
true;
//#else
false;
//#endif
public static final boolean MOZ_LOCALE_SWITCHER =
//#ifdef MOZ_LOCALE_SWITCHER
true;
//#else
false;
//#endif
public static final boolean MOZ_UPDATER =
//#ifdef MOZ_UPDATER
true;
//#else
false;
//#endif
// Android Beam is only supported on API14+, so we don't even bother building
// it if this APK doesn't include API14 support.
public static final boolean MOZ_ANDROID_BEAM =
//#ifdef MOZ_ANDROID_BEAM
true;
//#else
false;
//#endif
// See this wiki page for more details about channel specific build defines:
// https://wiki.mozilla.org/Platform/Channel-specific_build_defines
public static final boolean RELEASE_OR_BETA =
//#ifdef RELEASE_OR_BETA
true;
//#else
false;
//#endif
public static final boolean NIGHTLY_BUILD =
//#ifdef NIGHTLY_BUILD
true;
//#else
false;
//#endif
public static final boolean DEBUG_BUILD =
//#ifdef MOZ_DEBUG
true;
//#else
false;
//#endif
public static final boolean MOZ_MEDIA_PLAYER =
//#ifdef MOZ_NATIVE_DEVICES
true;
//#else
false;
//#endif
// Official corresponds, roughly, to whether this build is performed on
// Mozilla's continuous integration infrastructure. You should disable
// developer-only functionality when this flag is set.
public static final boolean MOZILLA_OFFICIAL =
//#ifdef MOZILLA_OFFICIAL
true;
//#else
false;
//#endif
public static final boolean ANDROID_DOWNLOADS_INTEGRATION =
//#ifdef MOZ_ANDROID_DOWNLOADS_INTEGRATION
true;
//#else
false;
//#endif
public static final boolean MOZ_DRAGGABLE_URLBAR = false;
public static final boolean MOZ_INSTALL_TRACKING =
//#ifdef MOZ_INSTALL_TRACKING
true;
//#else
false;
//#endif
public static final boolean MOZ_SWITCHBOARD =
//#ifdef MOZ_SWITCHBOARD
true;
//#else
false;
//#endif
/**
* Target CPU architecture: "armeabi-v7a", "x86, "mips", ..
*/
public static final String ANDROID_CPU_ARCH = "@ANDROID_CPU_ARCH@";
public static final boolean MOZ_ANDROID_EXCLUDE_FONTS =
//#ifdef MOZ_ANDROID_EXCLUDE_FONTS
true;
//#else
false;
//#endif
public static final boolean MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE =
//#ifdef MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE
true;
//#else
false;
//#endif
public static final boolean MOZ_ANDROID_CUSTOM_TABS =
//#ifdef MOZ_ANDROID_CUSTOM_TABS
true;
//#else
false;
//#endif
// (bug 1266820) Temporarily disabled since no one is working on it.
public static final boolean SCREENSHOTS_IN_BOOKMARKS_ENABLED = false;
/**
* Enables multidex depending on build flags. For more information,
* see `multiDexEnabled true` in mobile/android/app/build.gradle.
*
* As a method, this shouldn't be in AppConstants, but it's
* the only semi-relevant Java file that we pre-process.
*/
public static void maybeInstallMultiDex(final Context context) {
//#ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
if (BuildConfig.FLAVOR.equals("automation")) {
MultiDex.install(context);
}
//#else
// Do nothing.
//#endif
}
public static final boolean MOZ_ANDROID_ACTIVITY_STREAM =
//#ifdef MOZ_ANDROID_ACTIVITY_STREAM
true;
//#else
false;
//#endif
}

View file

@ -1,65 +0,0 @@
<!-- The bouncer APK and the Fennec APK should define the same set of
<permission>, <uses-permission>, and <uses-feature> elements. This reduces
the likelihood of permission-related surprises when installing the main APK
on top of a pre-installed bouncer APK. Add such elements here, so that
they can be easily shared between the two APKs. -->
#include ../services/manifests/FxAccountAndroidManifest_permissions.xml.in
#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
#include ../search/manifests/SearchAndroidManifest_permissions.xml.in
#endif
<!-- Bug 1261302: we have two new permissions to request,
RECEIVE_BOOT_COMPLETED and the permission for push. We want to ask for
them during the same release, which should be Fennec 48. Therefore we
decouple the push permission from MOZ_ANDROID_GCM to let it ride ahead
(potentially) of the push feature. -->
#include GcmAndroidManifest_permissions.xml.in
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!-- READ_EXTERNAL_STORAGE was added in API 16, and is only enforced in API
19+. We declare it so that the bouncer APK and the main APK have the
same set of permissions. -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"/>
<uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.VIBRATE"/>
#ifdef MOZ_ANDROID_DOWNLOADS_INTEGRATION
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
#endif
<uses-feature android:name="android.hardware.location" android:required="false"/>
<uses-feature android:name="android.hardware.location.gps" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen"/>
<!-- Tab Queue -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
#ifdef MOZ_ANDROID_BEAM
<!-- Android Beam support -->
<uses-permission android:name="android.permission.NFC"/>
<uses-feature android:name="android.hardware.nfc" android:required="false"/>
#endif
#ifdef MOZ_WEBRTC
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-feature android:name="android.hardware.audio.low_latency" android:required="false"/>
<uses-feature android:name="android.hardware.camera.any" android:required="false"/>
<uses-feature android:name="android.hardware.microphone" android:required="false"/>
#endif
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<!-- App requires OpenGL ES 2.0 -->
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

View file

@ -1,4 +0,0 @@
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<!-- Avoid a linter warning by not double-including WAKE_LOCK.
<uses-permission android:name="android.permission.WAKE_LOCK" />
-->

View file

@ -1,29 +0,0 @@
<!-- Handle GCM registration updates from on-device Google Play Services. -->
<service
android:name="org.mozilla.gecko.gcm.GcmInstanceIDListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID"/>
</intent-filter>
</service>
<!-- Provided by on-device Google Play Services. Directs inbound messages to internal listener service. -->
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="@ANDROID_PACKAGE_NAME@" />
</intent-filter>
</receiver>
<!-- Handle messages directed by the GCM receiver. -->
<service
android:name="org.mozilla.gecko.gcm.GcmMessageListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>

View file

@ -1,594 +0,0 @@
# 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/.
# We call mach -> Make -> gradle -> mach, which races to find and
# create .mozconfig files and to generate targets.
ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
.NOTPARALLEL:
endif
MOZ_BUILDID := $(shell awk '{print $$3}' $(DEPTH)/buildid.h)
# Set the appropriate version code, based on the existance of the
# MOZ_APP_ANDROID_VERSION_CODE variable.
ifdef MOZ_APP_ANDROID_VERSION_CODE
ANDROID_VERSION_CODE:=$(MOZ_APP_ANDROID_VERSION_CODE)
else
ANDROID_VERSION_CODE:=$(shell $(PYTHON) \
$(topsrcdir)/python/mozbuild/mozbuild/android_version_code.py \
--verbose \
--with-android-cpu-arch=$(ANDROID_CPU_ARCH) \
$(if $(MOZ_ANDROID_MIN_SDK_VERSION),--with-android-min-sdk=$(MOZ_ANDROID_MIN_SDK_VERSION)) \
$(if $(MOZ_ANDROID_MAX_SDK_VERSION),--with-android-max-sdk=$(MOZ_ANDROID_MAX_SDK_VERSION)) \
$(MOZ_BUILDID))
endif
DEFINES += \
-DANDROID_VERSION_CODE=$(ANDROID_VERSION_CODE) \
-DMOZ_ANDROID_SHARED_ID="$(MOZ_ANDROID_SHARED_ID)" \
-DMOZ_BUILDID=$(MOZ_BUILDID) \
$(NULL)
GARBAGE += \
classes.dex \
gecko.ap_ \
res/values/strings.xml \
res/raw/browsersearch.json \
res/raw/suggestedsites.json \
.aapt.deps \
GeneratedJNINatives.h \
GeneratedJNIWrappers.cpp \
GeneratedJNIWrappers.h \
FennecJNINatives.h \
FennecJNIWrappers.cpp \
FennecJNIWrappers.h \
$(NULL)
GARBAGE_DIRS += classes db jars res sync services generated
# The bootclasspath is functionally identical to the classpath, but allows the
# classes given to redefine classes in core packages, such as java.lang.
# android.jar is here as it provides Android's definition of the Java Standard
# Library. The compatability lib here tweaks a few of the core classes to paint
# over changes in behaviour between versions.
JAVA_BOOTCLASSPATH := \
$(ANDROID_SDK)/android.jar \
$(NULL)
JAVA_BOOTCLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_BOOTCLASSPATH)))
JAVA_CLASSPATH += \
$(ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB) \
$(ANDROID_SUPPORT_V4_AAR_LIB) \
$(ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB) \
$(ANDROID_APPCOMPAT_V7_AAR_LIB) \
$(ANDROID_SUPPORT_VECTOR_DRAWABLE_AAR_LIB) \
$(ANDROID_ANIMATED_VECTOR_DRAWABLE_AAR_LIB) \
$(ANDROID_CARDVIEW_V7_AAR_LIB) \
$(ANDROID_DESIGN_AAR_LIB) \
$(ANDROID_RECYCLERVIEW_V7_AAR_LIB) \
$(ANDROID_CUSTOMTABS_AAR_LIB) \
$(ANDROID_PALETTE_V7_AAR_LIB) \
$(NULL)
# If native devices are enabled, add Google Play Services and some of the v7
# compat libraries.
ifdef MOZ_NATIVE_DEVICES
JAVA_CLASSPATH += \
$(ANDROID_PLAY_SERVICES_BASE_AAR_LIB) \
$(ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB) \
$(ANDROID_PLAY_SERVICES_CAST_AAR_LIB) \
$(ANDROID_MEDIAROUTER_V7_AAR_LIB) \
$(ANDROID_MEDIAROUTER_V7_AAR_INTERNAL_LIB) \
$(NULL)
endif
ifdef MOZ_ANDROID_GCM
JAVA_CLASSPATH += \
$(ANDROID_PLAY_SERVICES_BASE_AAR_LIB) \
$(ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB) \
$(ANDROID_PLAY_SERVICES_GCM_AAR_LIB) \
$(ANDROID_PLAY_SERVICES_MEASUREMENT_AAR_LIB) \
$(NULL)
endif
ifdef MOZ_INSTALL_TRACKING
JAVA_CLASSPATH += \
$(ANDROID_PLAY_SERVICES_ADS_AAR_LIB) \
$(ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB) \
$(NULL)
endif
JAVA_CLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_CLASSPATH)))
# Library jars that we're bundling: these are subject to Proguard before inclusion
# into classes.dex.
java_bundled_libs := \
$(ANDROID_SUPPORT_V4_AAR_LIB) \
$(ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB) \
$(ANDROID_APPCOMPAT_V7_AAR_LIB) \
$(ANDROID_SUPPORT_VECTOR_DRAWABLE_AAR_LIB) \
$(ANDROID_ANIMATED_VECTOR_DRAWABLE_AAR_LIB) \
$(ANDROID_CARDVIEW_V7_AAR_LIB) \
$(ANDROID_DESIGN_AAR_LIB) \
$(ANDROID_RECYCLERVIEW_V7_AAR_LIB) \
$(ANDROID_CUSTOMTABS_AAR_LIB) \
$(ANDROID_PALETTE_V7_AAR_LIB) \
$(NULL)
ifdef MOZ_NATIVE_DEVICES
java_bundled_libs += \
$(ANDROID_PLAY_SERVICES_BASE_AAR_LIB) \
$(ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB) \
$(ANDROID_PLAY_SERVICES_CAST_AAR_LIB) \
$(ANDROID_MEDIAROUTER_V7_AAR_LIB) \
$(ANDROID_MEDIAROUTER_V7_AAR_INTERNAL_LIB) \
$(NULL)
endif
ifdef MOZ_ANDROID_GCM
java_bundled_libs += \
$(ANDROID_PLAY_SERVICES_BASE_AAR_LIB) \
$(ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB) \
$(ANDROID_PLAY_SERVICES_GCM_AAR_LIB) \
$(ANDROID_PLAY_SERVICES_MEASUREMENT_AAR_LIB) \
$(NULL)
endif
ifdef MOZ_INSTALL_TRACKING
java_bundled_libs += \
$(ANDROID_PLAY_SERVICES_ADS_AAR_LIB) \
$(ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB) \
$(NULL)
endif
# uniq purloined from http://stackoverflow.com/a/16151140.
uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1)))
java_bundled_libs := $(call uniq,$(java_bundled_libs))
java_bundled_libs := $(subst $(NULL) ,:,$(strip $(java_bundled_libs)))
GECKOVIEW_JARS = \
constants.jar \
gecko-R.jar \
gecko-mozglue.jar \
gecko-util.jar \
gecko-view.jar \
sync-thirdparty.jar \
$(NULL)
ifdef MOZ_INSTALL_TRACKING
GECKOVIEW_JARS += gecko-thirdparty-adjust_sdk.jar
endif
geckoview_jars_classpath := $(subst $(NULL) ,:,$(strip $(GECKOVIEW_JARS)))
FENNEC_JARS = \
gecko-browser.jar \
gecko-thirdparty.jar \
services.jar \
../javaaddons/javaaddons-1.0.jar \
$(NULL)
ifdef MOZ_WEBRTC
FENNEC_JARS += webrtc.jar
endif
ifdef MOZ_ANDROID_SEARCH_ACTIVITY
FENNEC_JARS += search-activity.jar
endif
ifdef MOZ_ANDROID_MLS_STUMBLER
FENNEC_JARS += ../stumbler/stumbler.jar
endif
# All the jars we're compiling from source. (not to be confused with
# java_bundled_libs, which holds the jars which we're including as binaries).
ALL_JARS = \
$(GECKOVIEW_JARS) \
$(FENNEC_JARS) \
$(NULL)
# The list of jars in Java classpath notation (colon-separated).
all_jars_classpath := $(subst $(NULL) ,:,$(strip $(ALL_JARS)))
include $(topsrcdir)/config/config.mk
library_jars := \
$(ANDROID_SDK)/android.jar \
$(NULL)
# Android 23 (Marshmallow) deprecated a part of the Android platform, namely the
# org.apache.http package. Fennec removed all code that referenced said package
# in order to easily ship to Android 23 devices (and, by extension, all devices
# before Android 23).
#
# Google did not remove all code that referenced said package in their own
# com.google.android.gms namespace! It turns out that the org.apache.http
# package is not removed, only deprecated and hidden by default. Google added a
# a `useLibrary` Gradle directive that allows legacy code to reference the
# package, which is still (hidden) in the Android 23 platform.
#
# Fennec code doesn't need to compile against the deprecated package, since our
# code doesn't reference the package anymore. However, we do need to Proguard
# against the deprecated package. If we don't, Proguard -- which is a global
# optimization -- sees Google libraries referencing "non-existent" libraries and
# complains. The solution is to mimic the `useLibraries` directive by declaring
# the legacy package as a provided library jar.
#
# See https://bugzilla.mozilla.org/show_bug.cgi?id=1233238#c19 for symptoms and
# more discussion.
ifdef MOZ_INSTALL_TRACKING
library_jars += $(ANDROID_SDK)/optional/org.apache.http.legacy.jar
endif # MOZ_INSTALL_TRACKING
library_jars := $(subst $(NULL) ,:,$(strip $(library_jars)))
gradle_dir := $(topobjdir)/gradle/build/mobile/android
ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
.gradle.deps: .aapt.deps FORCE
@$(TOUCH) $@
$(topsrcdir)/mach gradle \
app:assembleAutomationDebug app:assembleAutomationDebugAndroidTest -x lint
classes.dex: .gradle.deps
$(REPORT_BUILD)
cp $(gradle_dir)/app/intermediates/transforms/dex/automation/debug/folders/1000/1f/main/classes.dex $@
else
classes.dex: .proguard.deps
$(REPORT_BUILD)
$(DX) --dex --output=classes.dex jars-proguarded
endif
ifdef MOZ_DISABLE_PROGUARD
PROGUARD_PASSES=0
else
ifdef MOZ_DEBUG
PROGUARD_PASSES=1
else
ifndef MOZILLA_OFFICIAL
PROGUARD_PASSES=1
else
PROGUARD_PASSES=6
endif
endif
endif
proguard_config_dir=$(topsrcdir)/mobile/android/config/proguard
# This stanza ensures that the set of GeckoView classes does not depend on too
# much of Fennec, where "too much" is defined as the set of potentially
# non-GeckoView classes that GeckoView already depended on at a certain point in
# time. The idea is to set a high-water mark that is not to be crossed.
classycle_jar := $(topsrcdir)/mobile/android/build/classycle/classycle-1.4.1.jar
.geckoview.deps: geckoview.ddf $(classycle_jar) $(ALL_JARS)
$(JAVA) -cp $(classycle_jar) \
classycle.dependency.DependencyChecker \
-mergeInnerClasses \
-dependencies=@$< \
$(ALL_JARS)
@$(TOUCH) $@
# First, we delete debugging information from libraries. Having line-number
# information for libraries for which we lack the source isn't useful, so this
# saves us a bit of space. Importantly, Proguard has a bug causing it to
# sometimes corrupt this information if present (which it does for some of the
# included libraries). This corruption prevents dex from completing, so we need
# to get rid of it. This prevents us from seeing line numbers in stack traces
# for stack frames inside libraries.
#
# This step can occur much earlier than the main Proguard pass: it needs only
# gecko-R.jar to have been compiled (as that's where the library R.java files
# end up), but it does block the main Proguard pass.
.bundled.proguard.deps: gecko-R.jar $(proguard_config_dir)/strip-libs.cfg
$(REPORT_BUILD)
@$(TOUCH) $@
$(JAVA) \
-Xmx512m -Xms128m \
-jar $(ANDROID_SDK_ROOT)/tools/proguard/lib/proguard.jar \
@$(proguard_config_dir)/strip-libs.cfg \
-injars $(subst ::,:,$(java_bundled_libs))\
-outjars bundled-jars-nodebug \
-libraryjars $(library_jars):gecko-R.jar
# We touch the target file before invoking Proguard so that Proguard's
# outputs are fresher than the target, preventing a subsequent
# invocation from thinking Proguard's outputs are stale. This is safe
# because Make removes the target file if any recipe command fails.
.proguard.deps: .geckoview.deps .bundled.proguard.deps $(ALL_JARS) $(proguard_config_dir)/proguard.cfg
$(REPORT_BUILD)
@$(TOUCH) $@
$(JAVA) \
-Xmx512m -Xms128m \
-jar $(ANDROID_SDK_ROOT)/tools/proguard/lib/proguard.jar \
@$(proguard_config_dir)/proguard.cfg \
-optimizationpasses $(PROGUARD_PASSES) \
-injars $(subst ::,:,$(all_jars_classpath)):bundled-jars-nodebug \
-outjars jars-proguarded \
-libraryjars $(library_jars)
ANNOTATION_PROCESSOR_JAR_FILES := $(DEPTH)/build/annotationProcessors/annotationProcessors.jar
# This annotation processing step also generates
# GeneratedJNIWrappers.h and GeneratedJNINatives.h
GeneratedJNIWrappers.cpp: $(ANNOTATION_PROCESSOR_JAR_FILES) $(GECKOVIEW_JARS)
$(JAVA) -classpath $(geckoview_jars_classpath):$(JAVA_BOOTCLASSPATH):$(JAVA_CLASSPATH):$(ANNOTATION_PROCESSOR_JAR_FILES) \
org.mozilla.gecko.annotationProcessors.AnnotationProcessor \
Generated $(GECKOVIEW_JARS)
# This annotation processing step also generates
# FennecJNIWrappers.h and FennecJNINatives.h
FennecJNIWrappers.cpp: $(ANNOTATION_PROCESSOR_JAR_FILES) $(FENNEC_JARS)
$(JAVA) -classpath $(all_jars_classpath):$(JAVA_BOOTCLASSPATH):$(JAVA_CLASSPATH):$(ANNOTATION_PROCESSOR_JAR_FILES) \
org.mozilla.gecko.annotationProcessors.AnnotationProcessor \
Fennec $(FENNEC_JARS)
# Certain source files need to be preprocessed. This special rule
# generates these files into generated/org/mozilla/gecko for
# consumption by the build system and IDEs.
# The list in moz.build looks like
# 'preprocessed/org/mozilla/gecko/AppConstants.java'. The list in
# constants_PP_JAVAFILES looks like
# 'generated/preprocessed/org/mozilla/gecko/AppConstants.java'. We
# need to write AppConstants.java.in to
# generated/preprocessed/org/mozilla/gecko.
preprocessed := $(addsuffix .in,$(subst generated/preprocessed/org/mozilla/gecko/,,$(filter generated/preprocessed/org/mozilla/gecko/%,$(constants_PP_JAVAFILES))))
preprocessed_PATH := generated/preprocessed/org/mozilla/gecko
preprocessed_KEEP_PATH := 1
preprocessed_FLAGS := --marker='//\\\#'
PP_TARGETS += preprocessed
include $(topsrcdir)/config/rules.mk
not_android_res_files := \
*.mkdir.done* \
*.DS_Store* \
*\#* \
*.rej \
*.orig \
$(NULL)
# This uses the fact that Android resource directories list all
# resource files one subdirectory below the parent resource directory.
android_res_files := $(filter-out $(not_android_res_files),$(wildcard $(addsuffix /*,$(wildcard $(addsuffix /*,$(ANDROID_RES_DIRS))))))
$(ANDROID_GENERATED_RESFILES): $(call mkdir_deps,$(sort $(dir $(ANDROID_GENERATED_RESFILES))))
# [Comment 1/3] We don't have correct dependencies for strings.xml at
# this point, so we always recursively invoke the submake to check the
# dependencies. Sigh. And, with multilocale builds, there will be
# multiple strings.xml files, and we need to rebuild gecko.ap_ if any
# of them change. But! mobile/android/base/locales does not have
# enough information to actually build res/values/strings.xml during a
# language repack. So rather than adding rules into the main
# makefile, and trying to work around the lack of information, we
# force a rebuild of gecko.ap_ during packaging. See below.
# Since the sub-Make is forced, it doesn't matter that we touch the
# target file before the command. If in the future we stop forcing
# the sub-Make, touching the target file first is better, because the
# sub-Make outputs will be fresher than the target, and not require
# rebuilding. This is all safe because Make removes the target file
# if any recipe command fails. It is crucial that the sub-Make touch
# the target files (those depending on .locales.deps) only when there
# contents have changed; otherwise, this will force rebuild them as
# part of every build.
.locales.deps: FORCE
$(TOUCH) $@
$(MAKE) -C locales
# This .deps pattern saves an invocation of the sub-Make: the single
# invocation generates strings.xml, browsersearch.json, and
# suggestedsites.json. The trailing semi-colon defines an empty
# recipe: defining no recipe at all causes Make to treat the target
# differently, in a way that defeats our dependencies.
res/values/strings.xml: .locales.deps ;
res/raw/browsersearch.json: .locales.deps ;
res/raw/suggestedsites.json: .locales.deps ;
all_resources = \
$(DEPTH)/mobile/android/base/AndroidManifest.xml \
$(android_res_files) \
$(ANDROID_GENERATED_RESFILES) \
$(NULL)
# All of generated/org/mozilla/gecko/R.java, gecko.ap_, and R.txt are
# produced by aapt; this saves aapt invocations. The trailing
# semi-colon defines an empty recipe; defining no recipe at all causes
# Make to treat the target differently, in a way that defeats our
# dependencies.
generated/org/mozilla/gecko/R.java: .aapt.deps ;
# Only add libraries that contain resources here. We (unecessarily) generate an identical R.java which
# is copied into each of these locations, and each of these files contains thousands of fields.
# Each unnecessary copy therefore wastes unnecessary fields in the output dex file.
# Note: usually proguard will help clean this up after the fact, but having too many fields will cause
# dexing to fail, regardless of any later optimisations proguard could later make to bring us back
# under the limit.
# Ideally we would fix our aapt invocations to correctly generate minimal copies of R.java for each
# package, but that seems redundant since gradle builds are able to correctly generate these files.
# If native devices are enabled, add Google Play Services, build their resources
# (no resources) generated/android/support/v4/R.java: .aapt.deps ;
generated/android/support/v7/appcompat/R.java: .aapt.deps ;
# (no resources) generated/android/support/graphics/drawable/animated/R.java: .aapt.deps ;
# (no resources) generated/android/support/graphics/drawable/R.java: .aapt.deps ;
generated/android/support/v7/cardview/R.java: .aapt.deps ;
generated/android/support/design/R.java: .aapt.deps ;
generated/android/support/v7/mediarouter/R.java: .aapt.deps ;
generated/android/support/v7/recyclerview/R.java: .aapt.deps ;
# (no resources) generated/android/support/customtabs/R.java: .aapt.deps ;
# (no resources) generated/android/support/v7/palette/R.java: .aapt.deps ;
generated/com/google/android/gms/R.java: .aapt.deps ;
generated/com/google/android/gms/ads/R.java: .aapt.deps ;
generated/com/google/android/gms/base/R.java: .aapt.deps ;
generated/com/google/android/gms/cast/R.java: .aapt.deps ;
# (no resources) generated/com/google/android/gms/gcm/R.java: .aapt.deps ;
# (no resources) generated/com/google/android/gms/measurement/R.java: .aapt.deps ;
gecko.ap_: .aapt.deps ;
R.txt: .aapt.deps ;
# [Comment 2/3] This tom-foolery provides a target that forces a
# rebuild of gecko.ap_. This is used during packaging to ensure that
# resources are fresh. The alternative would be complicated; see
# [Comment 1/3].
gecko-nodeps/R.java: .aapt.nodeps ;
gecko-nodeps.ap_: .aapt.nodeps ;
gecko-nodeps/R.txt: .aapt.nodeps ;
# This ignores the default set of resources ignored by aapt, plus
# files starting with '#'. (Emacs produces temp files named #temp#.)
# This doesn't actually set the environment variable; it's used as a
# parameter in the aapt invocation below. Consider updating
# not_android_res_files as well.
ANDROID_AAPT_IGNORE := !.svn:!.git:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~:\#*:*.rej:*.orig
# 1: target file.
# 2: dependencies.
# 3: name of ap_ file to write.
# 4: directory to write R.java into.
# 5: directory to write R.txt into.
# We touch the target file before invoking aapt so that aapt's outputs
# are fresher than the target, preventing a subsequent invocation from
# thinking aapt's outputs are stale. This is safe because Make
# removes the target file if any recipe command fails.
define aapt_command
$(1): $$(call mkdir_deps,$(filter-out ./,$(dir $(3) $(4) $(5)))) $(2)
@$$(TOUCH) $$@
$$(AAPT) package -f -m \
-M AndroidManifest.xml \
-I $(ANDROID_SDK)/android.jar \
$(if $(MOZ_ANDROID_MAX_SDK_VERSION),--max-res-version $(MOZ_ANDROID_MAX_SDK_VERSION),) \
--auto-add-overlay \
$$(addprefix -S ,$$(ANDROID_RES_DIRS)) \
$$(addprefix -A ,$$(ANDROID_ASSETS_DIRS)) \
$(if $(ANDROID_EXTRA_PACKAGES),--extra-packages $$(subst $$(NULL) ,:,$$(strip $$(ANDROID_EXTRA_PACKAGES)))) \
$(if $(ANDROID_EXTRA_RES_DIRS),$$(addprefix -S ,$$(ANDROID_EXTRA_RES_DIRS))) \
--custom-package org.mozilla.gecko \
--no-version-vectors \
-F $(3) \
-J $(4) \
--output-text-symbols $(5) \
--ignore-assets "$$(ANDROID_AAPT_IGNORE)"
endef
# [Comment 3/3] The first of these rules is used during regular
# builds. The second writes an ap_ file that is only used during
# packaging. It doesn't write the normal ap_, or R.java, since we
# don't want the packaging step to write anything that would make a
# further no-op build do work. See also
# toolkit/mozapps/installer/packager.mk.
# .aapt.deps: $(all_resources)
$(eval $(call aapt_command,.aapt.deps,$(all_resources),gecko.ap_,generated/,./))
ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
.aapt.nodeps: FORCE
cp $(gradle_dir)/app/intermediates/res/resources-automation-debug.ap_ gecko-nodeps.ap_
else
# .aapt.nodeps: $(DEPTH)/mobile/android/base/AndroidManifest.xml FORCE
$(eval $(call aapt_command,.aapt.nodeps,$(DEPTH)/mobile/android/base/AndroidManifest.xml FORCE,gecko-nodeps.ap_,gecko-nodeps/,gecko-nodeps/))
endif
# Override the Java settings with some specific android settings
include $(topsrcdir)/config/android-common.mk
update-generated-wrappers:
@cp $(CURDIR)/GeneratedJNIWrappers.cpp \
$(CURDIR)/GeneratedJNIWrappers.h \
$(CURDIR)/GeneratedJNINatives.h $(topsrcdir)/widget/android
@echo Updated generated JNI code
update-fennec-wrappers:
@cp $(CURDIR)/FennecJNIWrappers.cpp \
$(CURDIR)/FennecJNIWrappers.h \
$(CURDIR)/FennecJNINatives.h $(topsrcdir)/widget/android/fennec
@echo Updated Fennec JNI code
.PHONY: update-generated-wrappers
# This target is only used by IDE integrations. It rebuilds resources
# that end up in omni.ja using the equivalent of |mach build faster|,
# does most of the packaging step, and then updates omni.ja in
# place. If you're not using an IDE, you should be using |mach build
# mobile/android && mach package|.
$(ABS_DIST)/fennec/$(OMNIJAR_NAME): FORCE
$(REPORT_BUILD)
$(MAKE) -C ../../../faster
$(MAKE) -C ../installer stage-package
$(MKDIR) -p $(@D)
rsync --update $(DIST)/fennec/$(notdir $(OMNIJAR_NAME)) $@
$(RM) $(DIST)/fennec/$(notdir $(OMNIJAR_NAME))
# Targets built very early during a Gradle build.
gradle-targets: $(foreach f,$(constants_PP_JAVAFILES),$(f))
gradle-targets: $(DEPTH)/mobile/android/base/AndroidManifest.xml
gradle-targets: $(ANDROID_GENERATED_RESFILES)
ifndef MOZILLA_OFFICIAL
# Local developers update omni.ja during their builds. There's a
# chicken-and-egg problem here.
gradle-omnijar: $(abspath $(DIST)/fennec/$(OMNIJAR_NAME))
else
# In automation, omni.ja is built only during packaging.
gradle-omnijar:
endif
.PHONY: gradle-targets gradle-omnijar
# GeneratedJNIWrappers.cpp target also generates
# GeneratedJNIWrappers.h and GeneratedJNINatives.h
# FennecJNIWrappers.cpp target also generates
# FennecJNIWrappers.h and FennecJNINatives.h
ifndef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
libs:: GeneratedJNIWrappers.cpp
@(diff GeneratedJNIWrappers.cpp $(topsrcdir)/widget/android/GeneratedJNIWrappers.cpp >/dev/null && \
diff GeneratedJNIWrappers.h $(topsrcdir)/widget/android/GeneratedJNIWrappers.h >/dev/null && \
diff GeneratedJNINatives.h $(topsrcdir)/widget/android/GeneratedJNINatives.h >/dev/null) || \
(echo '*****************************************************' && \
echo '*** Error: The generated JNI code has changed ***' && \
echo '* To update generated code in the tree, please run *' && \
echo && \
echo ' make -C $(CURDIR) update-generated-wrappers' && \
echo && \
echo '* Repeat the build, and check in any changes. *' && \
echo '*****************************************************' && \
exit 1)
libs:: FennecJNIWrappers.cpp
@(diff FennecJNIWrappers.cpp $(topsrcdir)/widget/android/fennec/FennecJNIWrappers.cpp >/dev/null && \
diff FennecJNIWrappers.h $(topsrcdir)/widget/android/fennec/FennecJNIWrappers.h >/dev/null && \
diff FennecJNINatives.h $(topsrcdir)/widget/android/fennec/FennecJNINatives.h >/dev/null) || \
(echo '*****************************************************' && \
echo '*** Error: The Fennec JNI code has changed ***' && \
echo '* To update generated code in the tree, please run *' && \
echo && \
echo ' make -C $(CURDIR) update-fennec-wrappers' && \
echo && \
echo '* Repeat the build, and check in any changes. *' && \
echo '*****************************************************' && \
exit 1)
endif
libs:: classes.dex
$(INSTALL) classes.dex $(FINAL_TARGET)
# Generate Java binder interfaces from AIDL files.
aidl_src_path := $(srcdir)/aidl
aidl_target_path := generated
media_pkg := org/mozilla/gecko/media
$(aidl_target_path)/$(media_pkg)/%.java:$(aidl_src_path)/$(media_pkg)/%.aidl
@echo "Processing AIDL: $< => $@"
$(AIDL) -p$(ANDROID_SDK)/framework.aidl -I$(aidl_src_path) -o$(aidl_target_path) $<

View file

@ -1 +0,0 @@
ABCDEFGHIJKL

View file

@ -1,3 +0,0 @@
//#ifdef MOZ_INSTALL_TRACKING
//#define MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN @MOZ_ADJUST_SDK_KEY@
//#endif

View file

@ -1,7 +0,0 @@
/* 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.media;
parcelable FormatParam;

View file

@ -1,26 +0,0 @@
/* 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.media;
// Non-default types used in interface.
import android.os.Bundle;
import android.view.Surface;
import org.mozilla.gecko.media.FormatParam;
import org.mozilla.gecko.media.ICodecCallbacks;
import org.mozilla.gecko.media.Sample;
interface ICodec {
void setCallbacks(in ICodecCallbacks callbacks);
boolean configure(in FormatParam format, inout Surface surface, int flags);
oneway void start();
oneway void stop();
oneway void flush();
oneway void release();
Sample dequeueInput(int size);
oneway void queueInput(in Sample sample);
oneway void releaseOutput(in Sample sample);
}

View file

@ -1,16 +0,0 @@
/* 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.media;
// Non-default types used in interface.
import org.mozilla.gecko.media.FormatParam;
import org.mozilla.gecko.media.Sample;
interface ICodecCallbacks {
oneway void onInputExhausted();
oneway void onOutputFormatChanged(in FormatParam format);
oneway void onOutput(in Sample sample);
oneway void onError(boolean fatal);
}

View file

@ -1,25 +0,0 @@
/* 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.media;
// Non-default types used in interface.
import org.mozilla.gecko.media.IMediaDrmBridgeCallbacks;
interface IMediaDrmBridge {
void setCallbacks(in IMediaDrmBridgeCallbacks callbacks);
oneway void createSession(int createSessionToken,
int promiseId,
String initDataType,
in byte[] initData);
oneway void updateSession(int promiseId,
String sessionId,
in byte[] response);
oneway void closeSession(int promiseId, String sessionId);
oneway void release();
}

View file

@ -1,31 +0,0 @@
/* 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.media;
// Non-default types used in interface.
import org.mozilla.gecko.media.SessionKeyInfo;
interface IMediaDrmBridgeCallbacks {
oneway void onSessionCreated(int createSessionToken,
int promiseId,
in byte[] sessionId,
in byte[] request);
oneway void onSessionUpdated(int promiseId, in byte[] sessionId);
oneway void onSessionClosed(int promiseId, in byte[] sessionId);
oneway void onSessionMessage(in byte[] sessionId,
int sessionMessageType,
in byte[] request);
oneway void onSessionError(in byte[] sessionId, String message);
oneway void onSessionBatchedKeyChanged(in byte[] sessionId,
in SessionKeyInfo[] keyInfos);
oneway void onRejectPromise(int promiseId, String message);
}

View file

@ -1,18 +0,0 @@
/* 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.media;
// Non-default types used in interface.
import org.mozilla.gecko.media.ICodec;
import org.mozilla.gecko.media.IMediaDrmBridge;
interface IMediaManager {
/** Creates a remote ICodec object. */
ICodec createCodec();
/** Creates a remote IMediaDrmBridge object. */
IMediaDrmBridge createRemoteMediaDrmBridge(in String keySystem,
in String stubId);
}

View file

@ -1,7 +0,0 @@
/* 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.media;
parcelable Sample;

View file

@ -1,7 +0,0 @@
/* 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.media;
parcelable SessionKeyInfo;

View file

@ -780,6 +780,7 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
'background/common/telemetry/TelemetryWrapper.java',
'background/db/CursorDumper.java',
'background/db/Tab.java',
<<<<<<< HEAD
'background/fxa/FxAccount20CreateDelegate.java',
'background/fxa/FxAccount20LoginDelegate.java',
'background/fxa/FxAccountClient.java',
@ -795,6 +796,8 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
'background/fxa/profile/FxAccountProfileClient10.java',
'background/fxa/QuickPasswordStretcher.java',
'background/fxa/SkewHandler.java',
=======
>>>>>>> parent of c9b411d6cd (Issue #1053 - Drop support Android and remove Fennec - Part 1a: Remove mobile/android)
'background/nativecode/NativeCrypto.java',
'background/preferences/PreferenceFragment.java',
'background/preferences/PreferenceManagerCompat.java',
@ -813,6 +816,7 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
'browserid/verifier/BrowserIDVerifierDelegate.java',
'browserid/verifier/BrowserIDVerifierException.java',
'browserid/VerifyingPublicKey.java',
<<<<<<< HEAD
'fxa/AccountLoader.java',
'fxa/activities/CustomColorPreference.java',
'fxa/activities/FxAccountAbstractActivity.java',
@ -859,6 +863,8 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
'fxa/sync/FxAccountSyncStatusHelper.java',
'fxa/sync/SchedulePolicy.java',
'fxa/SyncStatusListener.java',
=======
>>>>>>> parent of c9b411d6cd (Issue #1053 - Drop support Android and remove Fennec - Part 1a: Remove mobile/android)
'push/autopush/AutopushClient.java',
'push/autopush/AutopushClientException.java',
'push/RegisterUserAgentResponse.java',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="true">
<shape>
<solid android:color="@color/textbox_background" />
<stroke android:width="1dp" android:color="@color/textbox_stroke" />
</shape>
</item>
<item android:state_enabled="false">
<shape>
<solid android:color="@color/textbox_background_disabled" />
<stroke android:width="1dp" android:color="@color/textbox_stroke_disabled" />
</shape>
</item>
</selector>

View file

@ -1,126 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/toolbar_grey">
<LinearLayout android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
android:padding="10dp"
android:layout_weight="1">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dip"
android:textSize="30sp"
android:textColor="#000"
android:layout_gravity="center_horizontal"
android:fontFamily="sans-serif-light"
android:text="@string/crash_sorry"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dip"
android:textAppearance="@style/TextAppearance"
android:text="@string/crash_message2"/>
<CheckBox android:id="@+id/send_report"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:textColor="@color/primary_text"
android:layout_marginBottom="10dp"
android:text="@string/crash_send_report_message3"/>
<EditText android:id="@+id/comment"
style="@style/CrashReporter.EditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textMultiLine"
android:lines="5"
android:gravity="top"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="10dp"
android:hint="@string/crash_comment" />
<CheckBox android:id="@+id/include_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/primary_text"
android:textAppearance="@style/TextAppearance"
android:layout_marginBottom="10dp"
android:text="@string/crash_include_url2"/>
<CheckBox android:id="@+id/allow_contact"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/primary_text"
android:textAppearance="@style/TextAppearance"
android:layout_marginBottom="10dp"
android:text="@string/crash_allow_contact2"/>
<org.mozilla.gecko.widget.ClickableWhenDisabledEditText
android:id="@+id/email"
style="@style/CrashReporter.EditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textEmailAddress"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:enabled="false"
android:clickable="true"
android:hint="@string/crash_email" />
</LinearLayout>
<View android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#999" />
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="bottom">
<Button android:id="@+id/close"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:padding="15dp"
android:onClick="onCloseClick"
android:text="@string/crash_close_label"
android:textAppearance="?android:attr/textAppearance"
android:background="@drawable/action_bar_button"/>
<View android:layout_width="1dp"
android:layout_height="match_parent"
android:background="#999" />
<Button android:id="@+id/restart"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:padding="15dp"
android:onClick="onRestartClick"
android:text="@string/crash_restart_label"
android:textAppearance="?android:attr/textAppearance"
android:background="@drawable/action_bar_button"/>
</LinearLayout>
</LinearLayout>
</ScrollView>

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<resources>
<!-- Crash Reporter Colors -->
<color name="textbox_background">#FFF</color>
<color name="textbox_background_disabled">#DDD</color>
<color name="textbox_stroke">#000</color>
<color name="textbox_stroke_disabled">#666</color>
</resources>

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<!-- Crash Reporter Styles -->
<style name="CrashReporter" />
<style name="CrashReporter.EditText">
<item name="android:background">@drawable/textbox_bg</item>
<item name="android:padding">10dp</item>
<item name="android:textAppearance">@style/TextAppearance</item>
</style>
</resources>

View file

@ -1,75 +0,0 @@
# This is a Classycle dependency definition file that asserts that the contents
# of the GeckoView library (Classycle set [lib]) is a dependency (but does not
# depend) on Fennec (Classycle set [main]). The additional Classycle set
# [middle] consists of classes referenced by GeckoView that probably should not
# be referenced. We want this middle set to shrink over time.
show allResults
[lib] = \
org.mozilla.gecko.gfx.* \
org.mozilla.gecko.mozglue.* \
org.mozilla.gecko.sqlite.* \
org.mozilla.gecko.util.* \
org.mozilla.gecko.AndroidGamepadManager \
org.mozilla.gecko.AppConstants \
org.mozilla.gecko.BaseGeckoInterface \
org.mozilla.gecko.ContextGetter \
org.mozilla.gecko.CrashHandler \
org.mozilla.gecko.EventDispatcher \
org.mozilla.gecko.GeckoAccessibility \
org.mozilla.gecko.GeckoAppShell \
org.mozilla.gecko.GeckoBatteryManager \
org.mozilla.gecko.GeckoEditable \
org.mozilla.gecko.GeckoEditableClient \
org.mozilla.gecko.GeckoEditableListener \
org.mozilla.gecko.GeckoEvent \
org.mozilla.gecko.GeckoInputConnection \
org.mozilla.gecko.GeckoJavaSampler \
org.mozilla.gecko.GeckoNetworkManager \
org.mozilla.gecko.GeckoProfile \
org.mozilla.gecko.GeckoScreenOrientation \
org.mozilla.gecko.GeckoSharedPrefs \
org.mozilla.gecko.GeckoThread \
org.mozilla.gecko.GeckoView \
org.mozilla.gecko.GlobalHistory \
org.mozilla.gecko.InputMethods \
org.mozilla.gecko.NSSBridge \
org.mozilla.gecko.NotificationClient \
org.mozilla.gecko.NotificationHandler \
org.mozilla.gecko.PrefsHelper \
org.mozilla.gecko.SysInfo \
org.mozilla.gecko.TouchEventInterceptor \
org.mozilla.gecko.ZoomConstraints
[middle] = \
org.mozilla.gecko.prompts.* \
org.mozilla.gecko.FormAssistPopup \
org.mozilla.gecko.GeckoActivity \
org.mozilla.gecko.GeckoApp \
org.mozilla.gecko.GeckoProfileDirectories \
org.mozilla.gecko.GuestSession \
org.mozilla.gecko.R \
org.mozilla.gecko.Tab \
org.mozilla.gecko.Tabs \
org.mozilla.gecko.Telemetry \
org.mozilla.gecko.TelemetryContract \
org.mozilla.gecko.ThumbnailHelper \
org.mozilla.gecko.db.BrowserDB \
org.mozilla.gecko.db.LocalBrowserDB \
org.mozilla.gecko.distribution.Distribution \
org.mozilla.gecko.icons.*
[main] = org.mozilla.gecko.* excluding [lib] [middle]
check sets [lib] [middle] [main]
# Bug 1107134: it appears that Classycle can be fooled if the Java
# compiler inlines a constant from [main] into [lib]. That is, [main]
# really does depend on [lib] but Classycle only sees the dependency
# with some javac versions. For now, disable the check. Yes, this
# processing is useless without this check.
# check [lib] directlyIndependentOf [main]
# This fails; if this passed, GeckoView would be ready to extract from Fennec.
# check [lib] independentOf [middle]

View file

@ -1,596 +0,0 @@
/* -*- 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 java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.util.Locale;
import java.util.UUID;
import java.util.regex.Pattern;
import org.json.JSONObject;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
public final class ANRReporter extends BroadcastReceiver
{
private static final boolean DEBUG = false;
private static final String LOGTAG = "GeckoANRReporter";
private static final String ANR_ACTION = "android.intent.action.ANR";
// Number of lines to search traces.txt to decide whether it's a Gecko ANR
private static final int LINES_TO_IDENTIFY_TRACES = 10;
// ANRs may happen because of memory pressure,
// so don't use up too much memory here
// Size of buffer to hold one line of text
private static final int TRACES_LINE_SIZE = 100;
// Size of block to use when processing traces.txt
private static final int TRACES_BLOCK_SIZE = 2000;
private static final String TRACES_CHARSET = "utf-8";
private static final String PING_CHARSET = "utf-8";
private static final ANRReporter sInstance = new ANRReporter();
private static int sRegisteredCount;
private Handler mHandler;
private volatile boolean mPendingANR;
@WrapForJNI
private static native boolean requestNativeStack(boolean unwind);
@WrapForJNI
private static native String getNativeStack();
@WrapForJNI
private static native void releaseNativeStack();
public static void register(Context context) {
if (sRegisteredCount++ != 0) {
// Already registered
return;
}
sInstance.start(context);
}
public static void unregister() {
if (sRegisteredCount == 0) {
Log.w(LOGTAG, "register/unregister mismatch");
return;
}
if (--sRegisteredCount != 0) {
// Should still be registered
return;
}
sInstance.stop();
}
private void start(final Context context) {
Thread receiverThread = new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
synchronized (ANRReporter.this) {
mHandler = new Handler();
ANRReporter.this.notify();
}
if (DEBUG) {
Log.d(LOGTAG, "registering receiver");
}
context.registerReceiver(ANRReporter.this,
new IntentFilter(ANR_ACTION),
null,
mHandler);
Looper.loop();
if (DEBUG) {
Log.d(LOGTAG, "unregistering receiver");
}
context.unregisterReceiver(ANRReporter.this);
mHandler = null;
}
}, LOGTAG);
receiverThread.setDaemon(true);
receiverThread.start();
}
private void stop() {
synchronized (this) {
while (mHandler == null) {
try {
wait(1000);
if (mHandler == null) {
// We timed out; just give up. The process is probably
// quitting anyways, so we let the OS do the clean up
Log.w(LOGTAG, "timed out waiting for handler");
return;
}
} catch (InterruptedException e) {
}
}
}
Looper looper = mHandler.getLooper();
looper.quit();
try {
looper.getThread().join();
} catch (InterruptedException e) {
}
}
private ANRReporter() {
}
// Return the "traces.txt" file, or null if there is no such file
private static File getTracesFile() {
// Check most common location first.
File tracesFile = new File("/data/anr/traces.txt");
if (tracesFile.isFile() && tracesFile.canRead()) {
return tracesFile;
}
// Find the traces file name if we can.
try {
// getprop [prop-name [default-value]]
Process propProc = (new ProcessBuilder())
.command("/system/bin/getprop", "dalvik.vm.stack-trace-file")
.redirectErrorStream(true)
.start();
try {
BufferedReader buf = new BufferedReader(
new InputStreamReader(propProc.getInputStream()), TRACES_LINE_SIZE);
String propVal = buf.readLine();
if (DEBUG) {
Log.d(LOGTAG, "getprop returned " + String.valueOf(propVal));
}
// getprop can return empty string when the prop value is empty
// or prop is undefined, treat both cases the same way
if (propVal != null && propVal.length() != 0) {
tracesFile = new File(propVal);
if (tracesFile.isFile() && tracesFile.canRead()) {
return tracesFile;
} else if (DEBUG) {
Log.d(LOGTAG, "cannot access traces file");
}
} else if (DEBUG) {
Log.d(LOGTAG, "empty getprop result");
}
} finally {
propProc.destroy();
}
} catch (IOException e) {
Log.w(LOGTAG, e);
} catch (ClassCastException e) {
Log.w(LOGTAG, e); // Bug 975436
}
return null;
}
private static File getPingFile() {
if (GeckoAppShell.getContext() == null) {
return null;
}
GeckoProfile profile = GeckoAppShell.getGeckoInterface().getProfile();
if (profile == null) {
return null;
}
File profDir = profile.getDir();
if (profDir == null) {
return null;
}
File pingDir = new File(profDir, "saved-telemetry-pings");
pingDir.mkdirs();
if (!(pingDir.exists() && pingDir.isDirectory())) {
return null;
}
return new File(pingDir, UUID.randomUUID().toString());
}
// Return true if the traces file corresponds to a Gecko ANR
private static boolean isGeckoTraces(String pkgName, File tracesFile) {
try {
final String END_OF_PACKAGE_NAME = "([^a-zA-Z0-9_]|$)";
// Regex for finding our package name in the traces file
Pattern pkgPattern = Pattern.compile(Pattern.quote(pkgName) + END_OF_PACKAGE_NAME);
Pattern mangledPattern = null;
if (!AppConstants.MANGLED_ANDROID_PACKAGE_NAME.equals(pkgName)) {
mangledPattern = Pattern.compile(Pattern.quote(
AppConstants.MANGLED_ANDROID_PACKAGE_NAME) + END_OF_PACKAGE_NAME);
}
if (DEBUG) {
Log.d(LOGTAG, "trying to match package: " + pkgName);
}
BufferedReader traces = new BufferedReader(
new FileReader(tracesFile), TRACES_BLOCK_SIZE);
try {
for (int count = 0; count < LINES_TO_IDENTIFY_TRACES; count++) {
String line = traces.readLine();
if (DEBUG) {
Log.d(LOGTAG, "identifying line: " + String.valueOf(line));
}
if (line == null) {
if (DEBUG) {
Log.d(LOGTAG, "reached end of traces file");
}
return false;
}
if (pkgPattern.matcher(line).find()) {
// traces.txt file contains our package
return true;
}
if (mangledPattern != null && mangledPattern.matcher(line).find()) {
// traces.txt file contains our alternate package
return true;
}
}
} finally {
traces.close();
}
} catch (IOException e) {
// meh, can't even read from it right. just return false
}
return false;
}
private static long getUptimeMins() {
long uptimeMins = (new File("/proc/self/stat")).lastModified();
if (uptimeMins != 0L) {
uptimeMins = (System.currentTimeMillis() - uptimeMins) / 1000L / 60L;
if (DEBUG) {
Log.d(LOGTAG, "uptime " + String.valueOf(uptimeMins));
}
return uptimeMins;
}
if (DEBUG) {
Log.d(LOGTAG, "could not get uptime");
}
return 0L;
}
/*
a saved telemetry ping file consists of JSON in the following format,
{
"reason": "android-anr-report",
"slug": "<uuid-string>",
"payload": <json-object>
}
for Android ANR, our JSON payload should look like,
{
"ver": 1,
"simpleMeasurements": {
"uptime": <uptime>
},
"info": {
"reason": "android-anr-report",
"OS": "Android",
...
},
"androidANR": "...",
"androidLogcat": "..."
}
*/
private static int writePingPayload(OutputStream ping,
String payload) throws IOException {
byte [] data = payload.getBytes(PING_CHARSET);
ping.write(data);
return data.length;
}
private static void fillPingHeader(OutputStream ping, String slug)
throws IOException {
// ping file header
byte [] data = ("{" +
"\"reason\":\"android-anr-report\"," +
"\"slug\":" + JSONObject.quote(slug) + "," +
"\"payload\":").getBytes(PING_CHARSET);
ping.write(data);
if (DEBUG) {
Log.d(LOGTAG, "wrote ping header, size = " + String.valueOf(data.length));
}
// payload start
int size = writePingPayload(ping, ("{" +
"\"ver\":1," +
"\"simpleMeasurements\":{" +
"\"uptime\":" + String.valueOf(getUptimeMins()) +
"}," +
"\"info\":{" +
"\"reason\":\"android-anr-report\"," +
"\"OS\":" + JSONObject.quote(SysInfo.getName()) + "," +
"\"version\":\"" + String.valueOf(SysInfo.getVersion()) + "\"," +
"\"appID\":" + JSONObject.quote(AppConstants.MOZ_APP_ID) + "," +
"\"appVersion\":" + JSONObject.quote(AppConstants.MOZ_APP_VERSION) + "," +
"\"appName\":" + JSONObject.quote(AppConstants.MOZ_APP_BASENAME) + "," +
"\"appBuildID\":" + JSONObject.quote(AppConstants.MOZ_APP_BUILDID) + "," +
"\"appUpdateChannel\":" + JSONObject.quote(AppConstants.MOZ_UPDATE_CHANNEL) + "," +
// Technically the platform build ID may be different, but we'll never know
"\"platformBuildID\":" + JSONObject.quote(AppConstants.MOZ_APP_BUILDID) + "," +
"\"locale\":" + JSONObject.quote(Locales.getLanguageTag(Locale.getDefault())) + "," +
"\"cpucount\":" + String.valueOf(SysInfo.getCPUCount()) + "," +
"\"memsize\":" + String.valueOf(SysInfo.getMemSize()) + "," +
"\"arch\":" + JSONObject.quote(SysInfo.getArchABI()) + "," +
"\"kernel_version\":" + JSONObject.quote(SysInfo.getKernelVersion()) + "," +
"\"device\":" + JSONObject.quote(SysInfo.getDevice()) + "," +
"\"manufacturer\":" + JSONObject.quote(SysInfo.getManufacturer()) + "," +
"\"hardware\":" + JSONObject.quote(SysInfo.getHardware()) +
"}," +
"\"androidANR\":\""));
if (DEBUG) {
Log.d(LOGTAG, "wrote metadata, size = " + String.valueOf(size));
}
// We are at the start of ANR data
}
// Block is a section of the larger input stream, and we want to find pattern within
// the stream. This is straightforward if the entire pattern is within one block;
// however, if the pattern spans across two blocks, we have to match both the start of
// the pattern in the first block and the end of the pattern in the second block.
// * If pattern is found in block, this method returns the index at the end of the
// found pattern, which must always be > 0.
// * If pattern is not found, it returns 0.
// * If the start of the pattern matches the end of the block, it returns a number
// < 0, which equals the negated value of how many characters in pattern are already
// matched; when processing the next block, this number is passed in through
// prevIndex, and the rest of the characters in pattern are matched against the
// start of this second block. The method returns value > 0 if the rest of the
// characters match, or 0 if they do not.
private static int getEndPatternIndex(String block, String pattern, int prevIndex) {
if (pattern == null || block.length() < pattern.length()) {
// Nothing to do
return 0;
}
if (prevIndex < 0) {
// Last block ended with a partial start; now match start of block to rest of pattern
if (block.startsWith(pattern.substring(-prevIndex, pattern.length()))) {
// Rest of pattern matches; return index at end of pattern
return pattern.length() + prevIndex;
}
// Not a match; continue with normal search
}
// Did not find pattern in last block; see if entire pattern is inside this block
int index = block.indexOf(pattern);
if (index >= 0) {
// Found pattern; return index at end of the pattern
return index + pattern.length();
}
// Block does not contain the entire pattern, but see if the end of the block
// contains the start of pattern. To do that, we see if block ends with the
// first n-1 characters of pattern, the first n-2 characters of pattern, etc.
for (index = block.length() - pattern.length() + 1; index < block.length(); index++) {
// Using index as a start, see if the rest of block contains the start of pattern
if (block.charAt(index) == pattern.charAt(0) &&
block.endsWith(pattern.substring(0, block.length() - index))) {
// Found partial match; return -(number of characters matched),
// i.e. -1 for 1 character matched, -2 for 2 characters matched, etc.
return index - block.length();
}
}
return 0;
}
// Copy the content of reader to ping;
// copying stops when endPattern is found in the input stream
private static int fillPingBlock(OutputStream ping,
Reader reader, String endPattern)
throws IOException {
int total = 0;
int endIndex = 0;
char [] block = new char[TRACES_BLOCK_SIZE];
for (int size = reader.read(block); size >= 0; size = reader.read(block)) {
String stringBlock = new String(block, 0, size);
endIndex = getEndPatternIndex(stringBlock, endPattern, endIndex);
if (endIndex > 0) {
// Found end pattern; clip the string
stringBlock = stringBlock.substring(0, endIndex);
}
String quoted = JSONObject.quote(stringBlock);
total += writePingPayload(ping, quoted.substring(1, quoted.length() - 1));
if (endIndex > 0) {
// End pattern already found; return now
break;
}
}
return total;
}
private static void fillLogcat(final OutputStream ping) {
if (Versions.preJB) {
// Logcat retrieval is not supported on pre-JB devices.
return;
}
try {
// get the last 200 lines of logcat
Process proc = (new ProcessBuilder())
.command("/system/bin/logcat", "-v", "threadtime", "-t", "200", "-d", "*:D")
.redirectErrorStream(true)
.start();
try {
Reader procOut = new InputStreamReader(proc.getInputStream(), TRACES_CHARSET);
int size = fillPingBlock(ping, procOut, null);
if (DEBUG) {
Log.d(LOGTAG, "wrote logcat, size = " + String.valueOf(size));
}
} finally {
proc.destroy();
}
} catch (IOException e) {
// ignore because logcat is not essential
Log.w(LOGTAG, e);
}
}
private static void fillPingFooter(OutputStream ping,
boolean haveNativeStack)
throws IOException {
// We are at the end of ANR data
int total = writePingPayload(ping, ("\"," +
"\"androidLogcat\":\""));
fillLogcat(ping);
if (haveNativeStack) {
total += writePingPayload(ping, ("\"," +
"\"androidNativeStack\":"));
String nativeStack = String.valueOf(getNativeStack());
int size = writePingPayload(ping, nativeStack);
if (DEBUG) {
Log.d(LOGTAG, "wrote native stack, size = " + String.valueOf(size));
}
total += size + writePingPayload(ping, "}");
} else {
total += writePingPayload(ping, "\"}");
}
byte [] data = (
"}").getBytes(PING_CHARSET);
ping.write(data);
if (DEBUG) {
Log.d(LOGTAG, "wrote ping footer, size = " + String.valueOf(data.length + total));
}
}
private static void processTraces(Reader traces, File pingFile) {
// Only get native stack if Gecko is running.
// Also, unwinding is memory intensive, so only unwind if we have enough memory.
final boolean haveNativeStack =
GeckoThread.isRunning() ?
requestNativeStack(/* unwind */ SysInfo.getMemSize() >= 640) : false;
try {
OutputStream ping = new BufferedOutputStream(
new FileOutputStream(pingFile), TRACES_BLOCK_SIZE);
try {
fillPingHeader(ping, pingFile.getName());
// Traces file has the format
// ----- pid xxx at xxx -----
// Cmd line: org.mozilla.xxx
// * stack trace *
// ----- end xxx -----
// ----- pid xxx at xxx -----
// Cmd line: com.android.xxx
// * stack trace *
// ...
// If we end the stack dump at the first end marker,
// only Fennec stacks will be dumped
int size = fillPingBlock(ping, traces, "\n----- end");
if (DEBUG) {
Log.d(LOGTAG, "wrote traces, size = " + String.valueOf(size));
}
fillPingFooter(ping, haveNativeStack);
if (DEBUG) {
Log.d(LOGTAG, "finished creating ping file");
}
return;
} finally {
ping.close();
if (haveNativeStack) {
releaseNativeStack();
}
}
} catch (IOException e) {
Log.w(LOGTAG, e);
}
// exception; delete ping file
if (pingFile.exists()) {
pingFile.delete();
}
}
private static void processTraces(File tracesFile, File pingFile) {
try {
Reader traces = new InputStreamReader(
new FileInputStream(tracesFile), TRACES_CHARSET);
try {
processTraces(traces, pingFile);
} finally {
traces.close();
}
} catch (IOException e) {
Log.w(LOGTAG, e);
}
}
@Override
public void onReceive(Context context, Intent intent) {
if (mPendingANR) {
// we already processed an ANR without getting unstuck; skip this one
if (DEBUG) {
Log.d(LOGTAG, "skipping duplicate ANR");
}
return;
}
if (ThreadUtils.getUiHandler() != null) {
mPendingANR = true;
// detect when the main thread gets unstuck
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
// okay to reset mPendingANR on main thread
mPendingANR = false;
if (DEBUG) {
Log.d(LOGTAG, "yay we got unstuck!");
}
}
});
}
if (DEBUG) {
Log.d(LOGTAG, "receiving " + String.valueOf(intent));
}
if (!ANR_ACTION.equals(intent.getAction())) {
return;
}
// make sure we have a good save location first
File pingFile = getPingFile();
if (DEBUG) {
Log.d(LOGTAG, "using ping file: " + String.valueOf(pingFile));
}
if (pingFile == null) {
return;
}
File tracesFile = getTracesFile();
if (DEBUG) {
Log.d(LOGTAG, "using traces file: " + String.valueOf(tracesFile));
}
if (tracesFile == null) {
return;
}
// We get ANR intents from all ANRs in the system, but we only want Gecko ANRs
if (!isGeckoTraces(context.getPackageName(), tracesFile)) {
if (DEBUG) {
Log.d(LOGTAG, "traces is not Gecko ANR");
}
return;
}
Log.i(LOGTAG, "processing Gecko ANR");
processTraces(tracesFile, pingFile);
}
}

View file

@ -1,117 +0,0 @@
/* -*- 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;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.home.HomeConfig;
import org.mozilla.gecko.home.HomeConfig.PanelType;
import org.mozilla.gecko.util.StringUtils;
public class AboutPages {
// All of our special pages.
public static final String ACCOUNTS = "about:accounts";
public static final String ADDONS = "about:addons";
public static final String CONFIG = "about:config";
public static final String DOWNLOADS = "about:downloads";
public static final String FIREFOX = "about:firefox";
public static final String HEALTHREPORT = "about:healthreport";
public static final String HOME = "about:home";
public static final String LOGINS = "about:logins";
public static final String PRIVATEBROWSING = "about:privatebrowsing";
public static final String READER = "about:reader";
public static final String UPDATER = "about:";
public static final String URL_FILTER = "about:%";
public static final String PANEL_PARAM = "panel";
public static final boolean isAboutPage(final String url) {
return url != null && url.startsWith("about:");
}
public static final boolean isTitlelessAboutPage(final String url) {
return isAboutHome(url) ||
PRIVATEBROWSING.equals(url);
}
public static final boolean isAboutHome(final String url) {
if (url == null || !url.startsWith(HOME)) {
return false;
}
// We sometimes append a parameter to "about:home" to specify which page to
// show when we open the home pager. Discard this parameter when checking
// whether or not this URL is "about:home".
return HOME.equals(url.split("\\?")[0]);
}
public static final String getPanelIdFromAboutHomeUrl(String aboutHomeUrl) {
return StringUtils.getQueryParameter(aboutHomeUrl, PANEL_PARAM);
}
public static boolean isAboutReader(final String url) {
return isAboutPage(READER, url);
}
public static boolean isAboutConfig(final String url) {
return isAboutPage(CONFIG, url);
}
public static boolean isAboutAddons(final String url) {
return isAboutPage(ADDONS, url);
}
public static boolean isAboutPrivateBrowsing(final String url) {
return isAboutPage(PRIVATEBROWSING, url);
}
public static boolean isAboutPage(String page, String url) {
return url != null && url.toLowerCase().startsWith(page);
}
public static final String[] DEFAULT_ICON_PAGES = new String[] {
HOME,
ACCOUNTS,
ADDONS,
CONFIG,
DOWNLOADS,
FIREFOX,
HEALTHREPORT,
UPDATER
};
public static boolean isBuiltinIconPage(final String url) {
if (url == null ||
!url.startsWith("about:")) {
return false;
}
// about:home uses a separate search built-in icon.
if (isAboutHome(url)) {
return true;
}
// TODO: it'd be quicker to not compare the "about:" part every time.
for (int i = 0; i < DEFAULT_ICON_PAGES.length; ++i) {
if (DEFAULT_ICON_PAGES[i].equals(url)) {
return true;
}
}
return false;
}
/**
* Get a URL that navigates to the specified built-in Home Panel.
*
* @param panelType to navigate to.
* @return URL.
* @throws IllegalArgumentException if the built-in panel type is not a built-in panel.
*/
@RobocopTarget
public static String getURLForBuiltinPanelType(PanelType panelType) throws IllegalArgumentException {
return HOME + "?panel=" + HomeConfig.getIdForBuiltinPanelType(panelType);
}
}

View file

@ -1,318 +0,0 @@
/* -*- 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.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.FxAccountDeviceRegistrator;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.Engaged;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.restrictions.Restrictable;
import org.mozilla.gecko.restrictions.Restrictions;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
/**
* Helper class to manage Android Accounts corresponding to Firefox Accounts.
*/
public class AccountsHelper implements NativeEventListener {
public static final String LOGTAG = "GeckoAccounts";
protected final Context mContext;
protected final GeckoProfile mProfile;
public AccountsHelper(Context context, GeckoProfile profile) {
mContext = context;
mProfile = profile;
EventDispatcher dispatcher = GeckoApp.getEventDispatcher();
if (dispatcher == null) {
Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
return;
}
dispatcher.registerGeckoThreadListener(this,
"Accounts:CreateFirefoxAccountFromJSON",
"Accounts:UpdateFirefoxAccountFromJSON",
"Accounts:Create",
"Accounts:DeleteFirefoxAccount",
"Accounts:Exist",
"Accounts:ProfileUpdated",
"Accounts:ShowSyncPreferences");
}
public synchronized void uninit() {
EventDispatcher dispatcher = GeckoApp.getEventDispatcher();
if (dispatcher == null) {
Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
return;
}
dispatcher.unregisterGeckoThreadListener(this,
"Accounts:CreateFirefoxAccountFromJSON",
"Accounts:UpdateFirefoxAccountFromJSON",
"Accounts:Create",
"Accounts:DeleteFirefoxAccount",
"Accounts:Exist",
"Accounts:ProfileUpdated",
"Accounts:ShowSyncPreferences");
}
@Override
public void handleMessage(String event, NativeJSObject message, final EventCallback callback) {
if (!Restrictions.isAllowed(mContext, Restrictable.MODIFY_ACCOUNTS)) {
// We register for messages in all contexts; we drop, with a log and an error to JavaScript,
// when the profile is restricted. It's better to return errors than silently ignore messages.
Log.e(LOGTAG, "Profile is not allowed to modify accounts! Ignoring event: " + event);
if (callback != null) {
callback.sendError("Profile is not allowed to modify accounts!");
}
return;
}
if ("Accounts:CreateFirefoxAccountFromJSON".equals(event)) {
// As we are about to create a new account, let's ensure our in-memory accounts cache
// is empty so that there are no undesired side-effects.
AndroidFxAccount.invalidateCaches();
AndroidFxAccount fxAccount = null;
try {
final NativeJSObject json = message.getObject("json");
final String email = json.getString("email");
final String uid = json.getString("uid");
final boolean verified = json.optBoolean("verified", false);
final byte[] unwrapkB = Utils.hex2Byte(json.getString("unwrapBKey"));
final byte[] sessionToken = Utils.hex2Byte(json.getString("sessionToken"));
final byte[] keyFetchToken = Utils.hex2Byte(json.getString("keyFetchToken"));
final String authServerEndpoint =
json.optString("authServerEndpoint", FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT);
final String tokenServerEndpoint =
json.optString("tokenServerEndpoint", FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT);
final String profileServerEndpoint =
json.optString("profileServerEndpoint", FxAccountConstants.DEFAULT_PROFILE_SERVER_ENDPOINT);
// TODO: handle choose what to Sync.
State state = new Engaged(email, uid, verified, unwrapkB, sessionToken, keyFetchToken);
fxAccount = AndroidFxAccount.addAndroidAccount(mContext,
email,
mProfile.getName(),
authServerEndpoint,
tokenServerEndpoint,
profileServerEndpoint,
state,
AndroidFxAccount.DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP);
final String[] declinedSyncEngines = json.optStringArray("declinedSyncEngines", null);
if (declinedSyncEngines != null) {
Log.i(LOGTAG, "User has selected engines; storing to prefs.");
final Map<String, Boolean> selectedEngines = new HashMap<String, Boolean>();
for (String enabledSyncEngine : SyncConfiguration.validEngineNames()) {
selectedEngines.put(enabledSyncEngine, true);
}
for (String declinedSyncEngine : declinedSyncEngines) {
selectedEngines.put(declinedSyncEngine, false);
}
// The "forms" engine has the same state as the "history" engine.
selectedEngines.put("forms", selectedEngines.get("history"));
FxAccountUtils.pii(LOGTAG, "User selected engines: " + selectedEngines.toString());
try {
SyncConfiguration.storeSelectedEnginesToPrefs(fxAccount.getSyncPrefs(), selectedEngines);
} catch (UnsupportedEncodingException | GeneralSecurityException e) {
Log.e(LOGTAG, "Got exception storing selected engines; ignoring.", e);
}
}
} catch (URISyntaxException | GeneralSecurityException | UnsupportedEncodingException e) {
Log.w(LOGTAG, "Got exception creating Firefox Account from JSON; ignoring.", e);
if (callback != null) {
callback.sendError("Could not create Firefox Account from JSON: " + e.toString());
return;
}
}
if (callback != null) {
callback.sendSuccess(fxAccount != null);
}
} else if ("Accounts:UpdateFirefoxAccountFromJSON".equals(event)) {
// We might be significantly changing state of the account; let's ensure our in-memory
// accounts cache is empty so that there are no undesired side-effects.
AndroidFxAccount.invalidateCaches();
try {
final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
if (account == null) {
if (callback != null) {
callback.sendError("Could not update Firefox Account since none exists");
}
return;
}
final NativeJSObject json = message.getObject("json");
final String email = json.getString("email");
final String uid = json.getString("uid");
// Protect against cross-connecting accounts.
if (account.name == null || !account.name.equals(email)) {
final String errorMessage = "Cannot update Firefox Account from JSON: datum has different email address!";
Log.e(LOGTAG, errorMessage);
if (callback != null) {
callback.sendError(errorMessage);
}
return;
}
final boolean verified = json.optBoolean("verified", false);
final byte[] unwrapkB = Utils.hex2Byte(json.getString("unwrapBKey"));
final byte[] sessionToken = Utils.hex2Byte(json.getString("sessionToken"));
final byte[] keyFetchToken = Utils.hex2Byte(json.getString("keyFetchToken"));
final State state = new Engaged(email, uid, verified, unwrapkB, sessionToken, keyFetchToken);
final AndroidFxAccount fxAccount = new AndroidFxAccount(mContext, account);
fxAccount.setState(state);
if (callback != null) {
callback.sendSuccess(true);
}
} catch (NativeJSObject.InvalidPropertyException e) {
Log.w(LOGTAG, "Got exception updating Firefox Account from JSON; ignoring.", e);
if (callback != null) {
callback.sendError("Could not update Firefox Account from JSON: " + e.toString());
return;
}
}
} else if ("Accounts:Create".equals(event)) {
// Do exactly the same thing as if you tapped 'Sync' in Settings.
final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final NativeJSObject extras = message.optObject("extras", null);
if (extras != null) {
intent.putExtra("extras", extras.toString());
}
mContext.startActivity(intent);
} else if ("Accounts:DeleteFirefoxAccount".equals(event)) {
try {
final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
if (account == null) {
Log.w(LOGTAG, "Could not delete Firefox Account since none exists!");
if (callback != null) {
callback.sendError("Could not delete Firefox Account since none exists");
}
return;
}
final AccountManagerCallback<Boolean> accountManagerCallback = new AccountManagerCallback<Boolean>() {
@Override
public void run(AccountManagerFuture<Boolean> future) {
try {
final boolean result = future.getResult();
Log.i(LOGTAG, "Account named like " + Utils.obfuscateEmail(account.name) + " removed: " + result);
if (callback != null) {
callback.sendSuccess(result);
}
} catch (OperationCanceledException | IOException | AuthenticatorException e) {
if (callback != null) {
callback.sendError("Could not delete Firefox Account: " + e.toString());
}
}
}
};
AccountManager.get(mContext).removeAccount(account, accountManagerCallback, null);
} catch (Exception e) {
Log.w(LOGTAG, "Got exception updating Firefox Account from JSON; ignoring.", e);
if (callback != null) {
callback.sendError("Could not update Firefox Account from JSON: " + e.toString());
return;
}
}
} else if ("Accounts:Exist".equals(event)) {
if (callback == null) {
Log.w(LOGTAG, "Accounts:Exist requires a callback");
return;
}
final String kind = message.optString("kind", null);
final JSONObject response = new JSONObject();
try {
if ("any".equals(kind)) {
response.put("exists", FirefoxAccounts.firefoxAccountsExist(mContext));
callback.sendSuccess(response);
} else if ("fxa".equals(kind)) {
final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
response.put("exists", account != null);
if (account != null) {
response.put("email", account.name);
// We should always be able to extract the server endpoints.
final AndroidFxAccount fxAccount = new AndroidFxAccount(mContext, account);
response.put("authServerEndpoint", fxAccount.getAccountServerURI());
response.put("profileServerEndpoint", fxAccount.getProfileServerURI());
response.put("tokenServerEndpoint", fxAccount.getTokenServerURI());
try {
// It is possible for the state fetch to fail and us to not be able to provide a UID.
// Long term, the UID (and verification flag) will be attached to the Android account
// user data and not the internal state representation.
final State state = fxAccount.getState();
response.put("uid", state.uid);
} catch (Exception e) {
Log.w(LOGTAG, "Got exception extracting account UID; ignoring.", e);
}
}
callback.sendSuccess(response);
} else {
callback.sendError("Could not query account existence: unknown kind.");
}
} catch (JSONException e) {
Log.w(LOGTAG, "Got exception querying account existence; ignoring.", e);
callback.sendError("Could not query account existence: " + e.toString());
return;
}
} else if ("Accounts:ProfileUpdated".equals(event)) {
final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
if (account == null) {
Log.w(LOGTAG, "Can't change profile of non-existent Firefox Account!; ignored");
return;
}
final AndroidFxAccount androidFxAccount = new AndroidFxAccount(mContext, account);
androidFxAccount.fetchProfileJSON();
} else if ("Accounts:ShowSyncPreferences".equals(event)) {
final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
if (account == null) {
Log.w(LOGTAG, "Can't change show Sync preferences of non-existent Firefox Account!; ignored");
return;
}
// We don't necessarily have an Activity context here, so we always start in a new task.
final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_STATUS);
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
}
}

View file

@ -1,256 +0,0 @@
/* 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.menu.GeckoMenu;
import org.mozilla.gecko.menu.GeckoMenuItem;
import org.mozilla.gecko.util.ResourceDrawableUtils;
import org.mozilla.gecko.text.TextSelection;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.ActionModeCompat.Callback;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.MenuItem;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Timer;
import java.util.TimerTask;
import android.util.Log;
class ActionBarTextSelection implements TextSelection, GeckoEventListener {
private static final String LOGTAG = "GeckoTextSelection";
private static final int SHUTDOWN_DELAY_MS = 250;
private final Context context;
private boolean mDraggingHandles;
private String selectionID; // Unique ID provided for each selection action.
private String mCurrentItems;
private TextSelectionActionModeCallback mCallback;
// These timers are used to avoid flicker caused by selection handles showing/hiding quickly.
// For instance when moving between single handle caret mode and two handle selection mode.
private final Timer mActionModeTimer = new Timer("actionMode");
private class ActionModeTimerTask extends TimerTask {
@Override
public void run() {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
endActionMode();
}
});
}
};
private ActionModeTimerTask mActionModeTimerTask;
ActionBarTextSelection(Context context) {
this.context = context;
}
@Override
public void create() {
// Only register listeners if we have valid start/middle/end handles
if (context == null) {
Log.e(LOGTAG, "Failed to initialize text selection because at least one context is null");
} else {
GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
"TextSelection:ActionbarInit",
"TextSelection:ActionbarStatus",
"TextSelection:ActionbarUninit",
"TextSelection:Update");
}
}
@Override
public boolean dismiss() {
// We do not call endActionMode() here because this is already handled by the activity.
return false;
}
@Override
public void destroy() {
if (context == null) {
Log.e(LOGTAG, "Do not unregister TextSelection:* listeners since context is null");
} else {
GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
"TextSelection:ActionbarInit",
"TextSelection:ActionbarStatus",
"TextSelection:ActionbarUninit",
"TextSelection:Update");
}
}
@Override
public void handleMessage(final String event, final JSONObject message) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
try {
if (event.equals("TextSelection:Update")) {
if (mActionModeTimerTask != null)
mActionModeTimerTask.cancel();
showActionMode(message.getJSONArray("actions"));
} else if (event.equals("TextSelection:ActionbarInit")) {
// Init / Open the action bar. Note the current selectionID,
// cancel any pending actionBar close.
Telemetry.sendUIEvent(TelemetryContract.Event.SHOW,
TelemetryContract.Method.CONTENT, "text_selection");
selectionID = message.getString("selectionID");
mCurrentItems = null;
if (mActionModeTimerTask != null) {
mActionModeTimerTask.cancel();
}
} else if (event.equals("TextSelection:ActionbarStatus")) {
// Ensure async updates from SearchService for example are valid.
if (selectionID != message.optString("selectionID")) {
return;
}
// Update the actionBar actions as provided by Gecko.
showActionMode(message.getJSONArray("actions"));
} else if (event.equals("TextSelection:ActionbarUninit")) {
// Uninit the actionbar. Schedule a cancellable close
// action to avoid UI jank. (During SelectionAll for ex).
mCurrentItems = null;
mActionModeTimerTask = new ActionModeTimerTask();
mActionModeTimer.schedule(mActionModeTimerTask, SHUTDOWN_DELAY_MS);
}
} catch (JSONException e) {
Log.e(LOGTAG, "JSON exception", e);
}
}
});
}
private void showActionMode(final JSONArray items) {
String itemsString = items.toString();
if (itemsString.equals(mCurrentItems)) {
return;
}
mCurrentItems = itemsString;
if (mCallback != null) {
mCallback.updateItems(items);
return;
}
if (context instanceof ActionModeCompat.Presenter) {
final ActionModeCompat.Presenter presenter = (ActionModeCompat.Presenter) context;
mCallback = new TextSelectionActionModeCallback(items);
presenter.startActionModeCompat(mCallback);
mCallback.animateIn();
}
}
private void endActionMode() {
if (context instanceof ActionModeCompat.Presenter) {
final ActionModeCompat.Presenter presenter = (ActionModeCompat.Presenter) context;
presenter.endActionModeCompat();
}
mCurrentItems = null;
}
private class TextSelectionActionModeCallback implements Callback {
private JSONArray mItems;
private ActionModeCompat mActionMode;
public TextSelectionActionModeCallback(JSONArray items) {
mItems = items;
}
public void updateItems(JSONArray items) {
mItems = items;
if (mActionMode != null) {
mActionMode.invalidate();
}
}
public void animateIn() {
if (mActionMode != null) {
mActionMode.animateIn();
}
}
@Override
public boolean onPrepareActionMode(final ActionModeCompat mode, final GeckoMenu menu) {
// Android would normally expect us to only update the state of menu items here
// To make the js-java interaction a bit simpler, we just wipe out the menu here and recreate all
// the javascript menu items in onPrepare instead. This will be called any time invalidate() is called on the
// action mode.
menu.clear();
int length = mItems.length();
for (int i = 0; i < length; i++) {
try {
final JSONObject obj = mItems.getJSONObject(i);
final GeckoMenuItem menuitem = (GeckoMenuItem) menu.add(0, i, 0, obj.optString("label"));
final int actionEnum = obj.optBoolean("showAsAction") ? GeckoMenuItem.SHOW_AS_ACTION_ALWAYS : GeckoMenuItem.SHOW_AS_ACTION_NEVER;
menuitem.setShowAsAction(actionEnum, R.attr.menuItemActionModeStyle);
final String iconString = obj.optString("icon");
ResourceDrawableUtils.getDrawable(context, iconString, new ResourceDrawableUtils.BitmapLoader() {
@Override
public void onBitmapFound(Drawable d) {
if (d != null) {
menuitem.setIcon(d);
}
}
});
} catch (Exception ex) {
Log.i(LOGTAG, "Exception building menu", ex);
}
}
return true;
}
@Override
public boolean onCreateActionMode(ActionModeCompat mode, GeckoMenu unused) {
mActionMode = mode;
return true;
}
@Override
public boolean onActionItemClicked(ActionModeCompat mode, MenuItem item) {
try {
final JSONObject obj = mItems.getJSONObject(item.getItemId());
GeckoAppShell.notifyObservers("TextSelection:Action", obj.optString("id"));
return true;
} catch (Exception ex) {
Log.i(LOGTAG, "Exception calling action", ex);
}
return false;
}
// Called when the user exits the action mode
@Override
public void onDestroyActionMode(ActionModeCompat mode) {
mActionMode = null;
mCallback = null;
final JSONObject args = new JSONObject();
try {
args.put("selectionID", selectionID);
} catch (JSONException e) {
Log.e(LOGTAG, "Error building JSON arguments for TextSelection:End", e);
return;
}
GeckoAppShell.notifyObservers("TextSelection:End", args.toString());
}
}
}

View file

@ -1,135 +0,0 @@
/* 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.menu.GeckoMenu;
import org.mozilla.gecko.menu.GeckoMenuItem;
import org.mozilla.gecko.widget.GeckoPopupMenu;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
class ActionModeCompat implements GeckoPopupMenu.OnMenuItemClickListener,
GeckoPopupMenu.OnMenuItemLongClickListener,
View.OnClickListener {
private final String LOGTAG = "GeckoActionModeCompat";
private final Callback mCallback;
private final ActionModeCompatView mView;
private final Presenter mPresenter;
/* A set of callbacks to be called during this ActionMode's lifecycle. These will control the
* creation, interaction with, and destruction of menuitems for the view */
public static interface Callback {
/* Called when action mode is first created. Implementors should use this to inflate menu resources. */
public boolean onCreateActionMode(ActionModeCompat mode, GeckoMenu menu);
/* Called to refresh an action mode's action menu. Called whenever the mode is invalidated. Implementors
* should use this to enable/disable/show/hide menu items. */
public boolean onPrepareActionMode(ActionModeCompat mode, GeckoMenu menu);
/* Called to report a user click on an action button. */
public boolean onActionItemClicked(ActionModeCompat mode, MenuItem item);
/* Called when an action mode is about to be exited and destroyed. */
public void onDestroyActionMode(ActionModeCompat mode);
}
/* Presenters handle the actual showing/hiding of the action mode UI in the app. Its their responsibility
* to create an action mode, and assign it Callbacks and ActionModeCompatView's. */
public static interface Presenter {
/* Called when an action mode should be shown */
public void startActionModeCompat(final Callback callback);
/* Called when whatever action mode is showing should be hidden */
public void endActionModeCompat();
}
public ActionModeCompat(Presenter presenter, Callback callback, ActionModeCompatView view) {
mPresenter = presenter;
mCallback = callback;
mView = view;
mView.initForMode(this);
}
public void finish() {
// Clearing the menu will also clear the ActionItemBar
final GeckoMenu menu = mView.getMenu();
menu.clear();
menu.close();
if (mCallback != null) {
mCallback.onDestroyActionMode(this);
}
}
public CharSequence getTitle() {
return mView.getTitle();
}
public void setTitle(CharSequence title) {
mView.setTitle(title);
}
public void setTitle(int resId) {
mView.setTitle(resId);
}
public GeckoMenu getMenu() {
return mView.getMenu();
}
public void invalidate() {
if (mCallback != null) {
mCallback.onPrepareActionMode(this, mView.getMenu());
}
mView.invalidate();
}
public void animateIn() {
mView.animateIn();
}
/* GeckoPopupMenu.OnMenuItemClickListener */
@Override
public boolean onMenuItemClick(MenuItem item) {
if (mCallback != null) {
return mCallback.onActionItemClicked(this, item);
}
return false;
}
/* GeckoPopupMenu.onMenuItemLongClickListener */
@Override
public boolean onMenuItemLongClick(MenuItem item) {
showTooltip((GeckoMenuItem) item);
return true;
}
/* View.OnClickListener*/
@Override
public void onClick(View v) {
mPresenter.endActionModeCompat();
}
private void showTooltip(GeckoMenuItem item) {
// Computes the tooltip toast screen position (shown when long-tapping the menu item) with regards to the
// menu item's position (i.e below the item and slightly to the left)
int[] location = new int[2];
final View view = item.getActionView();
view.getLocationOnScreen(location);
int xOffset = location[0] - view.getWidth();
int yOffset = location[1] + view.getHeight() / 2;
Toast toast = Toast.makeText(view.getContext(), item.getTitle(), Toast.LENGTH_SHORT);
toast.setGravity(Gravity.TOP | Gravity.LEFT, xOffset, yOffset);
toast.show();
}
}

View file

@ -1,202 +0,0 @@
/* 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.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import org.mozilla.gecko.animation.AnimationUtils;
import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.widget.GeckoPopupMenu;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
class ActionModeCompatView extends LinearLayout implements GeckoMenu.ActionItemBarPresenter {
private final String LOGTAG = "GeckoActionModeCompatPresenter";
private static final int SPEC = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
private Button mTitleView;
private ImageButton mMenuButton;
private ViewGroup mActionButtonBar;
private GeckoPopupMenu mPopupMenu;
// Maximum number of items to show as actions
private static final int MAX_ACTION_ITEMS = 4;
private int mActionButtonsWidth;
private Paint mBottomDividerPaint;
private int mBottomDividerOffset;
public ActionModeCompatView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public ActionModeCompatView(Context context, AttributeSet attrs, int style) {
super(context, attrs, style);
init(context, attrs, style);
}
public void init(final Context context, final AttributeSet attrs, final int defStyle) {
LayoutInflater.from(context).inflate(R.layout.actionbar, this);
mTitleView = (Button) findViewById(R.id.actionmode_title);
mMenuButton = (ImageButton) findViewById(R.id.actionbar_menu);
mActionButtonBar = (ViewGroup) findViewById(R.id.actionbar_buttons);
mPopupMenu = new GeckoPopupMenu(getContext(), mMenuButton);
mPopupMenu.getMenu().setActionItemBarPresenter(this);
mMenuButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openMenu();
}
});
// The built-in action bar uses colorAccent for the divider so we duplicate that here.
final TypedArray arr = context.obtainStyledAttributes(attrs, new int[] { R.attr.colorAccent }, defStyle, 0);
final int bottomDividerColor = arr.getColor(0, 0);
arr.recycle();
mBottomDividerPaint = new Paint();
mBottomDividerPaint.setColor(bottomDividerColor);
mBottomDividerOffset = getResources().getDimensionPixelSize(R.dimen.action_bar_divider_height);
}
public void initForMode(final ActionModeCompat mode) {
mTitleView.setOnClickListener(mode);
mPopupMenu.setOnMenuItemClickListener(mode);
mPopupMenu.setOnMenuItemLongClickListener(mode);
}
public CharSequence getTitle() {
return mTitleView.getText();
}
public void setTitle(CharSequence title) {
mTitleView.setText(title);
}
public void setTitle(int resId) {
mTitleView.setText(resId);
}
public GeckoMenu getMenu() {
return mPopupMenu.getMenu();
}
@Override
public void invalidate() {
// onFinishInflate may not have been called yet on some versions of Android
if (mPopupMenu != null && mMenuButton != null) {
mMenuButton.setVisibility(mPopupMenu.getMenu().hasVisibleItems() ? View.VISIBLE : View.GONE);
}
super.invalidate();
}
/* GeckoMenu.ActionItemBarPresenter */
@Override
public boolean addActionItem(View actionItem) {
final int count = mActionButtonBar.getChildCount();
if (count >= MAX_ACTION_ITEMS) {
return false;
}
int maxWidth = mActionButtonBar.getMeasuredWidth();
if (maxWidth == 0) {
mActionButtonBar.measure(SPEC, SPEC);
maxWidth = mActionButtonBar.getMeasuredWidth();
}
// If the menu button is already visible, no need to account for it
if (mMenuButton.getVisibility() == View.GONE) {
// Since we don't know how many items will be added, we always reserve space for the overflow menu
mMenuButton.measure(SPEC, SPEC);
maxWidth -= mMenuButton.getMeasuredWidth();
}
if (mActionButtonsWidth <= 0) {
mActionButtonsWidth = 0;
// Loop over child views, measure them, and add their width to the taken width
for (int i = 0; i < count; i++) {
View v = mActionButtonBar.getChildAt(i);
v.measure(SPEC, SPEC);
mActionButtonsWidth += v.getMeasuredWidth();
}
}
actionItem.measure(SPEC, SPEC);
int w = actionItem.getMeasuredWidth();
if (mActionButtonsWidth + w < maxWidth) {
// We cache the new width of our children.
mActionButtonsWidth += w;
mActionButtonBar.addView(actionItem);
return true;
}
return false;
}
/* GeckoMenu.ActionItemBarPresenter */
@Override
public void removeActionItem(View actionItem) {
actionItem.measure(SPEC, SPEC);
mActionButtonsWidth -= actionItem.getMeasuredWidth();
mActionButtonBar.removeView(actionItem);
}
public void openMenu() {
mPopupMenu.openMenu();
}
public void closeMenu() {
mPopupMenu.dismiss();
}
public void animateIn() {
long duration = AnimationUtils.getShortDuration(getContext());
TranslateAnimation t = new TranslateAnimation(Animation.RELATIVE_TO_SELF, -0.5f, Animation.RELATIVE_TO_SELF, 0f,
Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f);
t.setDuration(duration);
ScaleAnimation s = new ScaleAnimation(1f, 1f, 0f, 1f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
s.setDuration((long) (duration * 1.5f));
mTitleView.startAnimation(t);
mActionButtonBar.startAnimation(s);
if ((mMenuButton.getVisibility() == View.VISIBLE) &&
(mPopupMenu.getMenu().size() > 0)) {
mMenuButton.startAnimation(s);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw the divider at the bottom of the screen. We could do this with a layer-list
// but then we'd have overdraw (http://stackoverflow.com/a/13509472).
final int bottom = getHeight();
final int top = bottom - mBottomDividerOffset;
canvas.drawRect(0, top, getWidth(), bottom, mBottomDividerPaint);
}
}

View file

@ -1,61 +0,0 @@
/* 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.util.ActivityResultHandler;
import org.mozilla.gecko.util.ActivityResultHandlerMap;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class ActivityHandlerHelper {
private static final String LOGTAG = "GeckoActivityHandlerHelper";
private static final ActivityResultHandlerMap mActivityResultHandlerMap = new ActivityResultHandlerMap();
private static int makeRequestCode(ActivityResultHandler aHandler) {
return mActivityResultHandlerMap.put(aHandler);
}
public static void startIntent(Intent intent, ActivityResultHandler activityResultHandler) {
startIntentForActivity(GeckoAppShell.getGeckoInterface().getActivity(), intent, activityResultHandler);
}
/**
* Starts the Activity, catching & logging if the Activity fails to start.
*
* We catch to prevent callers from passing in invalid Intents and crashing the browser.
*
* @return true if the Activity is successfully started, false otherwise.
*/
public static boolean startIntentAndCatch(final String logtag, final Context context, final Intent intent) {
try {
context.startActivity(intent);
return true;
} catch (final ActivityNotFoundException e) {
Log.w(logtag, "Activity not found.", e);
return false;
} catch (final SecurityException e) {
Log.w(logtag, "Forbidden to launch activity.", e);
return false;
}
}
public static void startIntentForActivity(Activity activity, Intent intent, ActivityResultHandler activityResultHandler) {
activity.startActivityForResult(intent, mActivityResultHandlerMap.put(activityResultHandler));
}
public static boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
ActivityResultHandler handler = mActivityResultHandlerMap.getAndRemove(requestCode);
if (handler != null) {
handler.onActivityResult(resultCode, data);
return true;
}
return false;
}
}

View file

@ -1,27 +0,0 @@
/* -*- 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.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.mozilla.gecko.feeds.FeedService;
/**
* This broadcast receiver receives ACTION_BOOT_COMPLETED broadcasts and starts components that should
* run after the device has booted.
*/
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || !intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
return; // This is not the broadcast you are looking for.
}
FeedService.setup(context);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,439 +0,0 @@
/* -*- 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;
import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.annotation.ReflectionTarget;
import org.mozilla.gecko.util.GeckoJarReader;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.Log;
/**
* This class manages persistence, application, and otherwise handling of
* user-specified locales.
*
* Of note:
*
* * It's a singleton, because its scope extends to that of the application,
* and definitionally all changes to the locale of the app must go through
* this.
* * It's lazy.
* * It has ties into the Gecko event system, because it has to tell Gecko when
* to switch locale.
* * It relies on using the SharedPreferences file owned by the browser (in
* Fennec's case, "GeckoApp") for performance.
*/
public class BrowserLocaleManager implements LocaleManager {
private static final String LOG_TAG = "GeckoLocales";
private static final String EVENT_LOCALE_CHANGED = "Locale:Changed";
private static final String PREF_LOCALE = "locale";
private static final String FALLBACK_LOCALE_TAG = "en-US";
// These are volatile because we don't impose restrictions
// over which thread calls our methods.
private volatile Locale currentLocale;
private volatile Locale systemLocale = Locale.getDefault();
private final AtomicBoolean inited = new AtomicBoolean(false);
private boolean systemLocaleDidChange;
private BroadcastReceiver receiver;
private static final AtomicReference<LocaleManager> instance = new AtomicReference<LocaleManager>();
@ReflectionTarget
public static LocaleManager getInstance() {
LocaleManager localeManager = instance.get();
if (localeManager != null) {
return localeManager;
}
localeManager = new BrowserLocaleManager();
if (instance.compareAndSet(null, localeManager)) {
return localeManager;
} else {
return instance.get();
}
}
@Override
public boolean isEnabled() {
return AppConstants.MOZ_LOCALE_SWITCHER;
}
/**
* Ensure that you call this early in your application startup,
* and with a context that's sufficiently long-lived (typically
* the application context).
*
* Calling multiple times is harmless.
*/
@Override
public void initialize(final Context context) {
if (!inited.compareAndSet(false, true)) {
return;
}
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final Locale current = systemLocale;
// We don't trust Locale.getDefault() here, because we make a
// habit of mutating it! Use the one Android supplies, because
// that gets regularly reset.
// The default value of systemLocale is fine, because we haven't
// yet swizzled Locale during static initialization.
systemLocale = context.getResources().getConfiguration().locale;
systemLocaleDidChange = true;
Log.d(LOG_TAG, "System locale changed from " + current + " to " + systemLocale);
}
};
context.registerReceiver(receiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
}
@Override
public boolean systemLocaleDidChange() {
return systemLocaleDidChange;
}
/**
* Every time the system gives us a new configuration, it
* carries the external locale. Fix it.
*/
@Override
public void correctLocale(Context context, Resources res, Configuration config) {
final Locale current = getCurrentLocale(context);
if (current == null) {
Log.d(LOG_TAG, "No selected locale. No correction needed.");
return;
}
// I know it's tempting to short-circuit here if the config seems to be
// up-to-date, but the rest is necessary.
config.locale = current;
// The following two lines are heavily commented in case someone
// decides to chase down performance improvements and decides to
// question what's going on here.
// Both lines should be cheap, *but*...
// This is unnecessary for basic string choice, but it almost
// certainly comes into play when rendering numbers, deciding on RTL,
// etc. Take it out if you can prove that's not the case.
Locale.setDefault(current);
// This seems to be a no-op, but every piece of documentation under the
// sun suggests that it's necessary, and it certainly makes sense.
res.updateConfiguration(config, null);
}
/**
* We can be in one of two states.
*
* If the user has not explicitly chosen a Firefox-specific locale, we say
* we are "mirroring" the system locale.
*
* When we are not mirroring, system locale changes do not impact Firefox
* and are essentially ignored; the user's locale selection is the only
* thing we care about, and we actively correct incoming configuration
* changes to reflect the user's chosen locale.
*
* By contrast, when we are mirroring, system locale changes cause Firefox
* to reflect the new system locale, as if the user picked the new locale.
*
* If we're currently mirroring the system locale, this method returns the
* supplied configuration's locale, unless the current activity locale is
* correct. If we're not currently mirroring, this method updates the
* configuration object to match the user's currently selected locale, and
* returns that, unless the current activity locale is correct.
*
* If the current activity locale is correct, returns null.
*
* The caller is expected to redisplay themselves accordingly.
*
* This method is intended to be called from inside
* <code>onConfigurationChanged(Configuration)</code> as part of a strategy
* to detect and either apply or undo system locale changes.
*/
@Override
public Locale onSystemConfigurationChanged(final Context context, final Resources resources, final Configuration configuration, final Locale currentActivityLocale) {
if (!isMirroringSystemLocale(context)) {
correctLocale(context, resources, configuration);
}
final Locale changed = configuration.locale;
if (changed.equals(currentActivityLocale)) {
return null;
}
return changed;
}
/**
* Gecko needs to know the OS locale to compute a useful Accept-Language
* header. If it changed since last time, send a message to Gecko and
* persist the new value. If unchanged, returns immediately.
*
* @param prefs the SharedPreferences instance to use. Cannot be null.
* @param osLocale the new locale instance. Safe if null.
*/
public static void storeAndNotifyOSLocale(final SharedPreferences prefs,
final Locale osLocale) {
if (osLocale == null) {
return;
}
final String lastOSLocale = prefs.getString("osLocale", null);
final String osLocaleString = osLocale.toString();
if (osLocaleString.equals(lastOSLocale)) {
return;
}
// Store the Java-native form.
prefs.edit().putString("osLocale", osLocaleString).apply();
// The value we send to Gecko should be a language tag, not
// a Java locale string.
final String osLanguageTag = Locales.getLanguageTag(osLocale);
GeckoAppShell.notifyObservers("Locale:OS", osLanguageTag);
}
@Override
public String getAndApplyPersistedLocale(Context context) {
initialize(context);
final long t1 = android.os.SystemClock.uptimeMillis();
final String localeCode = getPersistedLocale(context);
if (localeCode == null) {
return null;
}
// Note that we don't tell Gecko about this. We notify Gecko when the
// locale is set, not when we update Java.
final String resultant = updateLocale(context, localeCode);
if (resultant == null) {
// Update the configuration anyway.
updateConfiguration(context, currentLocale);
}
final long t2 = android.os.SystemClock.uptimeMillis();
Log.i(LOG_TAG, "Locale read and update took: " + (t2 - t1) + "ms.");
return resultant;
}
/**
* Returns the set locale if it changed.
*
* Always persists and notifies Gecko.
*/
@Override
public String setSelectedLocale(Context context, String localeCode) {
final String resultant = updateLocale(context, localeCode);
// We always persist and notify Gecko, even if nothing seemed to
// change. This might happen if you're picking a locale that's the same
// as the current OS locale. The OS locale might change next time we
// launch, and we need the Gecko pref and persisted locale to have been
// set by the time that happens.
persistLocale(context, localeCode);
// Tell Gecko.
GeckoAppShell.notifyObservers(EVENT_LOCALE_CHANGED, Locales.getLanguageTag(getCurrentLocale(context)));
return resultant;
}
@Override
public void resetToSystemLocale(Context context) {
// Wipe the pref.
final SharedPreferences settings = getSharedPreferences(context);
settings.edit().remove(PREF_LOCALE).apply();
// Apply the system locale.
updateLocale(context, systemLocale);
// Tell Gecko.
GeckoAppShell.notifyObservers(EVENT_LOCALE_CHANGED, "");
}
/**
* This is public to allow for an activity to force the
* current locale to be applied if necessary (e.g., when
* a new activity launches).
*/
@Override
public void updateConfiguration(Context context, Locale locale) {
Resources res = context.getResources();
Configuration config = res.getConfiguration();
// We should use setLocale, but it's unexpectedly missing
// on real devices.
config.locale = locale;
res.updateConfiguration(config, null);
}
private SharedPreferences getSharedPreferences(Context context) {
return GeckoSharedPrefs.forApp(context);
}
/**
* @return the persisted locale in Java format: "en_US".
*/
private String getPersistedLocale(Context context) {
final SharedPreferences settings = getSharedPreferences(context);
final String locale = settings.getString(PREF_LOCALE, "");
if ("".equals(locale)) {
return null;
}
return locale;
}
private void persistLocale(Context context, String localeCode) {
final SharedPreferences settings = getSharedPreferences(context);
settings.edit().putString(PREF_LOCALE, localeCode).apply();
}
@Override
public Locale getCurrentLocale(Context context) {
if (currentLocale != null) {
return currentLocale;
}
final String current = getPersistedLocale(context);
if (current == null) {
return null;
}
return currentLocale = Locales.parseLocaleCode(current);
}
/**
* Updates the Java locale and the Android configuration.
*
* Returns the persisted locale if it differed.
*
* Does not notify Gecko.
*
* @param localeCode a locale string in Java format: "en_US".
* @return if it differed, a locale string in Java format: "en_US".
*/
private String updateLocale(Context context, String localeCode) {
// Fast path.
final Locale defaultLocale = Locale.getDefault();
if (defaultLocale.toString().equals(localeCode)) {
return null;
}
final Locale locale = Locales.parseLocaleCode(localeCode);
return updateLocale(context, locale);
}
/**
* @return the Java locale string: e.g., "en_US".
*/
private String updateLocale(Context context, final Locale locale) {
// Fast path.
if (Locale.getDefault().equals(locale)) {
return null;
}
Locale.setDefault(locale);
currentLocale = locale;
// Update resources.
updateConfiguration(context, locale);
return locale.toString();
}
private boolean isMirroringSystemLocale(final Context context) {
return getPersistedLocale(context) == null;
}
/**
* Examines <code>multilocale.json</code>, returning the included list of
* locale codes.
*
* If <code>multilocale.json</code> is not present, returns
* <code>null</code>. In that case, consider {@link #getFallbackLocaleTag()}.
*
* multilocale.json currently looks like this:
*
* <code>
* {"locales": ["en-US", "be", "ca", "cs", "da", "de", "en-GB",
* "en-ZA", "es-AR", "es-ES", "es-MX", "et", "fi",
* "fr", "ga-IE", "hu", "id", "it", "ja", "ko",
* "lt", "lv", "nb-NO", "nl", "pl", "pt-BR",
* "pt-PT", "ro", "ru", "sk", "sl", "sv-SE", "th",
* "tr", "uk", "zh-CN", "zh-TW", "en-US"]}
* </code>
*/
public static Collection<String> getPackagedLocaleTags(final Context context) {
final String resPath = "res/multilocale.json";
final String jarURL = GeckoJarReader.getJarURL(context, resPath);
final String contents = GeckoJarReader.getText(context, jarURL);
if (contents == null) {
// GeckoJarReader logs and swallows exceptions.
return null;
}
try {
final JSONObject multilocale = new JSONObject(contents);
final JSONArray locales = multilocale.getJSONArray("locales");
if (locales == null) {
Log.e(LOG_TAG, "No 'locales' array in multilocales.json!");
return null;
}
final Set<String> out = new HashSet<String>(locales.length());
for (int i = 0; i < locales.length(); ++i) {
// If any item in the array is invalid, this will throw,
// and the entire clause will fail, being caught below
// and returning null.
out.add(locales.getString(i));
}
return out;
} catch (JSONException e) {
Log.e(LOG_TAG, "Unable to parse multilocale.json.", e);
return null;
}
}
/**
* @return the single default locale baked into this application.
* Applicable when there is no multilocale.json present.
*/
@SuppressWarnings("static-method")
public String getFallbackLocaleTag() {
return FALLBACK_LOCALE_TAG;
}
}

View file

@ -1,112 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* vim: ts=4 sw=4 expandtab:
* 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.json.JSONObject;
import org.json.JSONException;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.EventCallback;
import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.CastRemoteDisplayLocalService;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.Status;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.util.Log;
public class ChromeCastDisplay implements GeckoPresentationDisplay {
static final String REMOTE_DISPLAY_APP_ID = "4574A331";
private static final String LOGTAG = "GeckoChromeCastDisplay";
private final Context context;
private final RouteInfo route;
private CastDevice castDevice;
public ChromeCastDisplay(Context context, RouteInfo route) {
int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
if (status != ConnectionResult.SUCCESS) {
throw new IllegalStateException("Play services are required for Chromecast support (got status code " + status + ")");
}
this.context = context;
this.route = route;
this.castDevice = CastDevice.getFromBundle(route.getExtras());
}
public JSONObject toJSON() {
final JSONObject obj = new JSONObject();
try {
if (castDevice == null) {
return null;
}
obj.put("uuid", route.getId());
obj.put("friendlyName", castDevice.getFriendlyName());
obj.put("type", "chromecast");
} catch (JSONException ex) {
Log.d(LOGTAG, "Error building route", ex);
}
return obj;
}
@Override
public void start(final EventCallback callback) {
if (CastRemoteDisplayLocalService.getInstance() != null) {
Log.d(LOGTAG, "CastRemoteDisplayLocalService already existed.");
GeckoAppShell.notifyObservers("presentation-view-ready", route.getId());
callback.sendSuccess("Succeed to start presentation.");
return;
}
Intent intent = new Intent(context, RemotePresentationService.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent notificationPendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
CastRemoteDisplayLocalService.NotificationSettings settings =
new CastRemoteDisplayLocalService.NotificationSettings.Builder()
.setNotificationPendingIntent(notificationPendingIntent).build();
CastRemoteDisplayLocalService.startService(
context,
RemotePresentationService.class,
REMOTE_DISPLAY_APP_ID,
castDevice,
settings,
new CastRemoteDisplayLocalService.Callbacks() {
@Override
public void onServiceCreated(CastRemoteDisplayLocalService service) {
((RemotePresentationService) service).setDeviceId(route.getId());
}
@Override
public void onRemoteDisplaySessionStarted(CastRemoteDisplayLocalService service) {
Log.d(LOGTAG, "Remote presentation launched!");
callback.sendSuccess("Succeed to start presentation.");
}
@Override
public void onRemoteDisplaySessionError(Status errorReason) {
int code = errorReason.getStatusCode();
callback.sendError("Fail to start presentation. Error code: " + code);
}
});
}
@Override
public void stop(EventCallback callback) {
CastRemoteDisplayLocalService.stopService();
callback.sendSuccess("Succeed to stop presentation.");
}
}

View file

@ -1,509 +0,0 @@
/* -*- 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;
import java.io.IOException;
import org.mozilla.gecko.util.EventCallback;
import org.json.JSONObject;
import org.json.JSONException;
import com.google.android.gms.cast.Cast.MessageReceivedCallback;
import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.gms.cast.Cast;
import com.google.android.gms.cast.Cast.ApplicationConnectionResult;
import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.android.gms.cast.MediaStatus;
import com.google.android.gms.cast.RemoteMediaPlayer;
import com.google.android.gms.cast.RemoteMediaPlayer.MediaChannelResult;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.common.GooglePlayServicesUtil;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.util.Log;
/* Implementation of GeckoMediaPlayer for talking to ChromeCast devices */
class ChromeCastPlayer implements GeckoMediaPlayer {
private static final boolean SHOW_DEBUG = false;
static final String MIRROR_RECEIVER_APP_ID = "08FF1091";
private final Context context;
private final RouteInfo route;
private GoogleApiClient apiClient;
private RemoteMediaPlayer remoteMediaPlayer;
private final boolean canMirror;
private String mSessionId;
private MirrorChannel mMirrorChannel;
private boolean mApplicationStarted = false;
// EventCallback which is actually a GeckoEventCallback is sometimes being invoked more
// than once. That causes the IllegalStateException to be thrown. To prevent a crash,
// catch the exception and report it as an error to the log.
private static void sendSuccess(final EventCallback callback, final String msg) {
try {
callback.sendSuccess(msg);
} catch (final IllegalStateException e) {
Log.e(LOGTAG, "Attempting to invoke callback.sendSuccess more than once.", e);
}
}
private static void sendError(final EventCallback callback, final String msg) {
try {
callback.sendError(msg);
} catch (final IllegalStateException e) {
Log.e(LOGTAG, "Attempting to invoke callback.sendError more than once.", e);
}
}
// Callback to start playback of a url on a remote device
private class VideoPlayCallback implements ResultCallback<ApplicationConnectionResult>,
RemoteMediaPlayer.OnStatusUpdatedListener,
RemoteMediaPlayer.OnMetadataUpdatedListener {
private final String url;
private final String type;
private final String title;
private final EventCallback callback;
public VideoPlayCallback(String url, String type, String title, EventCallback callback) {
this.url = url;
this.type = type;
this.title = title;
this.callback = callback;
}
@Override
public void onStatusUpdated() {
MediaStatus mediaStatus = remoteMediaPlayer.getMediaStatus();
switch (mediaStatus.getPlayerState()) {
case MediaStatus.PLAYER_STATE_PLAYING:
GeckoAppShell.notifyObservers("MediaPlayer:Playing", null);
break;
case MediaStatus.PLAYER_STATE_PAUSED:
GeckoAppShell.notifyObservers("MediaPlayer:Paused", null);
break;
case MediaStatus.PLAYER_STATE_IDLE:
// TODO: Do we want to shutdown when there are errors?
if (mediaStatus.getIdleReason() == MediaStatus.IDLE_REASON_FINISHED) {
GeckoAppShell.notifyObservers("Casting:Stop", null);
}
break;
default:
// TODO: Do we need to handle other status such as buffering / unknown?
break;
}
}
@Override
public void onMetadataUpdated() { }
@Override
public void onResult(ApplicationConnectionResult result) {
Status status = result.getStatus();
debug("ApplicationConnectionResultCallback.onResult: statusCode" + status.getStatusCode());
if (status.isSuccess()) {
remoteMediaPlayer = new RemoteMediaPlayer();
remoteMediaPlayer.setOnStatusUpdatedListener(this);
remoteMediaPlayer.setOnMetadataUpdatedListener(this);
mSessionId = result.getSessionId();
if (!verifySession(callback)) {
return;
}
try {
Cast.CastApi.setMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace(), remoteMediaPlayer);
} catch (IOException e) {
debug("Exception while creating media channel", e);
}
startPlayback();
} else {
sendError(callback, status.toString());
}
}
private void startPlayback() {
MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
mediaMetadata.putString(MediaMetadata.KEY_TITLE, title);
MediaInfo mediaInfo = new MediaInfo.Builder(url)
.setContentType(type)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setMetadata(mediaMetadata)
.build();
try {
remoteMediaPlayer.load(apiClient, mediaInfo, true).setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
@Override
public void onResult(MediaChannelResult result) {
if (result.getStatus().isSuccess()) {
sendSuccess(callback, null);
debug("Media loaded successfully");
return;
}
debug("Media load failed " + result.getStatus());
sendError(callback, result.getStatus().toString());
}
});
return;
} catch (IllegalStateException e) {
debug("Problem occurred with media during loading", e);
} catch (Exception e) {
debug("Problem opening media during loading", e);
}
sendError(callback, "");
}
}
public ChromeCastPlayer(Context context, RouteInfo route) {
int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
if (status != ConnectionResult.SUCCESS) {
throw new IllegalStateException("Play services are required for Chromecast support (got status code " + status + ")");
}
this.context = context;
this.route = route;
this.canMirror = route.supportsControlCategory(CastMediaControlIntent.categoryForCast(MIRROR_RECEIVER_APP_ID));
}
/**
* This dumps everything we can find about the device into JSON. This will hopefully make it
* easier to filter out duplicate devices from different sources in JS.
* Returns null if the device can't be found.
*/
@Override
public JSONObject toJSON() {
final JSONObject obj = new JSONObject();
try {
final CastDevice device = CastDevice.getFromBundle(route.getExtras());
if (device == null) {
return null;
}
obj.put("uuid", route.getId());
obj.put("version", device.getDeviceVersion());
obj.put("friendlyName", device.getFriendlyName());
obj.put("location", device.getIpAddress().toString());
obj.put("modelName", device.getModelName());
obj.put("mirror", canMirror);
// For now we just assume all of these are Google devices
obj.put("manufacturer", "Google Inc.");
} catch (JSONException ex) {
debug("Error building route", ex);
}
return obj;
}
@Override
public void load(final String title, final String url, final String type, final EventCallback callback) {
final CastDevice device = CastDevice.getFromBundle(route.getExtras());
Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(device, new Cast.Listener() {
@Override
public void onApplicationStatusChanged() { }
@Override
public void onVolumeChanged() { }
@Override
public void onApplicationDisconnected(int errorCode) { }
});
apiClient = new GoogleApiClient.Builder(context)
.addApi(Cast.API, apiOptionsBuilder.build())
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle connectionHint) {
// Sometimes apiClient is null here. See bug 1061032
if (apiClient != null && !apiClient.isConnected()) {
debug("Connection failed");
sendError(callback, "Not connected");
return;
}
// Launch the media player app and launch this url once its loaded
try {
Cast.CastApi.launchApplication(apiClient, CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID, true)
.setResultCallback(new VideoPlayCallback(url, type, title, callback));
} catch (Exception e) {
debug("Failed to launch application", e);
}
}
@Override
public void onConnectionSuspended(int cause) {
debug("suspended");
}
}).build();
apiClient.connect();
}
@Override
public void start(final EventCallback callback) {
// Nothing to be done here
sendSuccess(callback, null);
}
@Override
public void stop(final EventCallback callback) {
// Nothing to be done here
sendSuccess(callback, null);
}
public boolean verifySession(final EventCallback callback) {
String msg = null;
if (apiClient == null || !apiClient.isConnected()) {
msg = "Not connected";
}
if (mSessionId == null) {
msg = "No session";
}
if (msg != null) {
debug(msg);
if (callback != null) {
sendError(callback, msg);
}
return false;
}
return true;
}
@Override
public void play(final EventCallback callback) {
if (!verifySession(callback)) {
return;
}
try {
remoteMediaPlayer.play(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
@Override
public void onResult(MediaChannelResult result) {
Status status = result.getStatus();
if (!status.isSuccess()) {
debug("Unable to play: " + status.getStatusCode());
sendError(callback, status.toString());
} else {
sendSuccess(callback, null);
}
}
});
} catch (IllegalStateException ex) {
// The media player may throw if the session has been killed. For now, we're just catching this here.
sendError(callback, "Error playing");
}
}
@Override
public void pause(final EventCallback callback) {
if (!verifySession(callback)) {
return;
}
try {
remoteMediaPlayer.pause(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
@Override
public void onResult(MediaChannelResult result) {
Status status = result.getStatus();
if (!status.isSuccess()) {
debug("Unable to pause: " + status.getStatusCode());
sendError(callback, status.toString());
} else {
sendSuccess(callback, null);
}
}
});
} catch (IllegalStateException ex) {
// The media player may throw if the session has been killed. For now, we're just catching this here.
sendError(callback, "Error pausing");
}
}
@Override
public void end(final EventCallback callback) {
if (!verifySession(callback)) {
return;
}
try {
Cast.CastApi.stopApplication(apiClient).setResultCallback(new ResultCallback<Status>() {
@Override
public void onResult(Status result) {
if (result.isSuccess()) {
try {
Cast.CastApi.removeMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace());
remoteMediaPlayer = null;
mSessionId = null;
apiClient.disconnect();
apiClient = null;
if (callback != null) {
sendSuccess(callback, null);
}
return;
} catch (Exception ex) {
debug("Error ending", ex);
}
}
if (callback != null) {
sendError(callback, result.getStatus().toString());
}
}
});
} catch (IllegalStateException ex) {
// The media player may throw if the session has been killed. For now, we're just catching this here.
sendError(callback, "Error stopping");
}
}
class MirrorChannel implements MessageReceivedCallback {
/**
* @return custom namespace
*/
public String getNamespace() {
return "urn:x-cast:org.mozilla.mirror";
}
/*
* Receive message from the receiver app
*/
@Override
public void onMessageReceived(CastDevice castDevice, String namespace,
String message) {
GeckoAppShell.notifyObservers("MediaPlayer:Response", message);
}
public void sendMessage(String message) {
if (apiClient != null && mMirrorChannel != null) {
try {
Cast.CastApi.sendMessage(apiClient, mMirrorChannel.getNamespace(), message)
.setResultCallback(
new ResultCallback<Status>() {
@Override
public void onResult(Status result) {
}
});
} catch (Exception e) {
Log.e(LOGTAG, "Exception while sending message", e);
}
}
}
}
private class MirrorCallback implements ResultCallback<ApplicationConnectionResult> {
final EventCallback callback;
MirrorCallback(final EventCallback callback) {
this.callback = callback;
}
@Override
public void onResult(ApplicationConnectionResult result) {
Status status = result.getStatus();
if (status.isSuccess()) {
ApplicationMetadata applicationMetadata = result.getApplicationMetadata();
mSessionId = result.getSessionId();
String applicationStatus = result.getApplicationStatus();
boolean wasLaunched = result.getWasLaunched();
mApplicationStarted = true;
// Create the custom message
// channel
mMirrorChannel = new MirrorChannel();
try {
Cast.CastApi.setMessageReceivedCallbacks(apiClient,
mMirrorChannel
.getNamespace(),
mMirrorChannel);
sendSuccess(callback, null);
} catch (IOException e) {
Log.e(LOGTAG, "Exception while creating channel", e);
}
GeckoAppShell.notifyObservers("Casting:Mirror", route.getId());
} else {
sendError(callback, status.toString());
}
}
}
@Override
public void message(String msg, final EventCallback callback) {
if (mMirrorChannel != null) {
mMirrorChannel.sendMessage(msg);
}
}
@Override
public void mirror(final EventCallback callback) {
final CastDevice device = CastDevice.getFromBundle(route.getExtras());
Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(device, new Cast.Listener() {
@Override
public void onApplicationStatusChanged() { }
@Override
public void onVolumeChanged() { }
@Override
public void onApplicationDisconnected(int errorCode) { }
});
apiClient = new GoogleApiClient.Builder(context)
.addApi(Cast.API, apiOptionsBuilder.build())
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle connectionHint) {
// Sometimes apiClient is null here. See bug 1061032
if (apiClient == null || !apiClient.isConnected()) {
return;
}
// Launch the media player app and launch this url once its loaded
try {
Cast.CastApi.launchApplication(apiClient, MIRROR_RECEIVER_APP_ID, true)
.setResultCallback(new MirrorCallback(callback));
} catch (Exception e) {
debug("Failed to launch application", e);
}
}
@Override
public void onConnectionSuspended(int cause) {
debug("suspended");
}
}).build();
apiClient.connect();
}
private static final String LOGTAG = "GeckoChromeCastPlayer";
private void debug(String msg, Exception e) {
if (SHOW_DEBUG) {
Log.e(LOGTAG, msg, e);
}
}
private void debug(String msg) {
if (SHOW_DEBUG) {
Log.d(LOGTAG, msg);
}
}
}

View file

@ -1,480 +0,0 @@
/* -*- Mode: Java; 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 java.util.HashMap;
import java.util.Map;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.util.zip.GZIPOutputStream;
import org.mozilla.gecko.AppConstants.Versions;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
@SuppressLint("Registered") // This activity is only registered in the manifest if MOZ_CRASHREPORTER is set
public class CrashReporter extends AppCompatActivity
{
private static final String LOGTAG = "GeckoCrashReporter";
private static final String PASSED_MINI_DUMP_KEY = "minidumpPath";
private static final String PASSED_MINI_DUMP_SUCCESS_KEY = "minidumpSuccess";
private static final String MINI_DUMP_PATH_KEY = "upload_file_minidump";
private static final String PAGE_URL_KEY = "URL";
private static final String NOTES_KEY = "Notes";
private static final String SERVER_URL_KEY = "ServerURL";
private static final String CRASH_REPORT_SUFFIX = "/mozilla/Crash Reports/";
private static final String PENDING_SUFFIX = CRASH_REPORT_SUFFIX + "pending";
private static final String SUBMITTED_SUFFIX = CRASH_REPORT_SUFFIX + "submitted";
private static final String PREFS_SEND_REPORT = "sendReport";
private static final String PREFS_INCLUDE_URL = "includeUrl";
private static final String PREFS_ALLOW_CONTACT = "allowContact";
private static final String PREFS_CONTACT_EMAIL = "contactEmail";
private Handler mHandler;
private ProgressDialog mProgressDialog;
private File mPendingMinidumpFile;
private File mPendingExtrasFile;
private HashMap<String, String> mExtrasStringMap;
private boolean mMinidumpSucceeded;
private boolean moveFile(File inFile, File outFile) {
Log.i(LOGTAG, "moving " + inFile + " to " + outFile);
if (inFile.renameTo(outFile))
return true;
try {
outFile.createNewFile();
Log.i(LOGTAG, "couldn't rename minidump file");
// so copy it instead
FileChannel inChannel = new FileInputStream(inFile).getChannel();
FileChannel outChannel = new FileOutputStream(outFile).getChannel();
long transferred = inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
outChannel.close();
if (transferred > 0)
inFile.delete();
} catch (Exception e) {
Log.e(LOGTAG, "exception while copying minidump file: ", e);
return false;
}
return true;
}
private void doFinish() {
if (mHandler != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
finish();
}
});
}
}
@Override
public void finish() {
try {
if (mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
} catch (Exception e) {
Log.e(LOGTAG, "exception while closing progress dialog: ", e);
}
super.finish();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// mHandler is created here so runnables can be run on the main thread
mHandler = new Handler();
setContentView(R.layout.crash_reporter);
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage(getString(R.string.sending_crash_report));
mMinidumpSucceeded = getIntent().getBooleanExtra(PASSED_MINI_DUMP_SUCCESS_KEY, false);
if (!mMinidumpSucceeded) {
Log.i(LOGTAG, "Failed to get minidump.");
}
String passedMinidumpPath = getIntent().getStringExtra(PASSED_MINI_DUMP_KEY);
File passedMinidumpFile = new File(passedMinidumpPath);
File pendingDir = new File(getFilesDir(), PENDING_SUFFIX);
pendingDir.mkdirs();
mPendingMinidumpFile = new File(pendingDir, passedMinidumpFile.getName());
moveFile(passedMinidumpFile, mPendingMinidumpFile);
File extrasFile = new File(passedMinidumpPath.replaceAll("\\.dmp", ".extra"));
mPendingExtrasFile = new File(pendingDir, extrasFile.getName());
moveFile(extrasFile, mPendingExtrasFile);
mExtrasStringMap = new HashMap<String, String>();
readStringsFromFile(mPendingExtrasFile.getPath(), mExtrasStringMap);
// Notify GeckoApp that we've crashed, so it can react appropriately during the next start.
try {
File crashFlag = new File(GeckoProfileDirectories.getMozillaDirectory(this), "CRASHED");
crashFlag.createNewFile();
} catch (GeckoProfileDirectories.NoMozillaDirectoryException | IOException e) {
Log.e(LOGTAG, "Cannot set crash flag: ", e);
}
final CheckBox allowContactCheckBox = (CheckBox) findViewById(R.id.allow_contact);
final CheckBox includeUrlCheckBox = (CheckBox) findViewById(R.id.include_url);
final CheckBox sendReportCheckBox = (CheckBox) findViewById(R.id.send_report);
final EditText commentsEditText = (EditText) findViewById(R.id.comment);
final EditText emailEditText = (EditText) findViewById(R.id.email);
// Load CrashReporter preferences to avoid redundant user input.
SharedPreferences prefs = GeckoSharedPrefs.forCrashReporter(this);
final boolean sendReport = prefs.getBoolean(PREFS_SEND_REPORT, true);
final boolean includeUrl = prefs.getBoolean(PREFS_INCLUDE_URL, false);
final boolean allowContact = prefs.getBoolean(PREFS_ALLOW_CONTACT, false);
final String contactEmail = prefs.getString(PREFS_CONTACT_EMAIL, "");
allowContactCheckBox.setChecked(allowContact);
includeUrlCheckBox.setChecked(includeUrl);
sendReportCheckBox.setChecked(sendReport);
emailEditText.setText(contactEmail);
sendReportCheckBox.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton checkbox, boolean isChecked) {
commentsEditText.setEnabled(isChecked);
commentsEditText.requestFocus();
includeUrlCheckBox.setEnabled(isChecked);
allowContactCheckBox.setEnabled(isChecked);
emailEditText.setEnabled(isChecked && allowContactCheckBox.isChecked());
}
});
allowContactCheckBox.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton checkbox, boolean isChecked) {
// We need to check isEnabled() here because this listener is
// fired on rotation -- even when the checkbox is disabled.
emailEditText.setEnabled(checkbox.isEnabled() && isChecked);
emailEditText.requestFocus();
}
});
emailEditText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Even if the email EditText is disabled, allow it to be
// clicked and focused.
if (sendReportCheckBox.isChecked() && !v.isEnabled()) {
allowContactCheckBox.setChecked(true);
v.setEnabled(true);
v.requestFocus();
}
}
});
}
@Override
public void onBackPressed() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.crash_closing_alert);
builder.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
CrashReporter.this.finish();
}
});
builder.show();
}
private void backgroundSendReport() {
final CheckBox sendReportCheckbox = (CheckBox) findViewById(R.id.send_report);
if (!sendReportCheckbox.isChecked()) {
doFinish();
return;
}
// Persist settings to avoid redundant user input.
savePrefs();
mProgressDialog.show();
new Thread(new Runnable() {
@Override
public void run() {
sendReport(mPendingMinidumpFile, mExtrasStringMap, mPendingExtrasFile);
}
}, "CrashReporter Thread").start();
}
private void savePrefs() {
SharedPreferences.Editor editor = GeckoSharedPrefs.forCrashReporter(this).edit();
final boolean allowContact = ((CheckBox) findViewById(R.id.allow_contact)).isChecked();
final boolean includeUrl = ((CheckBox) findViewById(R.id.include_url)).isChecked();
final boolean sendReport = ((CheckBox) findViewById(R.id.send_report)).isChecked();
final String contactEmail = ((EditText) findViewById(R.id.email)).getText().toString();
editor.putBoolean(PREFS_ALLOW_CONTACT, allowContact);
editor.putBoolean(PREFS_INCLUDE_URL, includeUrl);
editor.putBoolean(PREFS_SEND_REPORT, sendReport);
editor.putString(PREFS_CONTACT_EMAIL, contactEmail);
// A slight performance improvement via async apply() vs. blocking on commit().
editor.apply();
}
public void onCloseClick(View v) { // bound via crash_reporter.xml
backgroundSendReport();
}
public void onRestartClick(View v) { // bound via crash_reporter.xml
doRestart();
backgroundSendReport();
}
private boolean readStringsFromFile(String filePath, Map<String, String> stringMap) {
try {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
return readStringsFromReader(reader, stringMap);
} catch (Exception e) {
Log.e(LOGTAG, "exception while reading strings: ", e);
return false;
}
}
private boolean readStringsFromReader(BufferedReader reader, Map<String, String> stringMap) throws IOException {
String line;
while ((line = reader.readLine()) != null) {
int equalsPos = -1;
if ((equalsPos = line.indexOf('=')) != -1) {
String key = line.substring(0, equalsPos);
String val = unescape(line.substring(equalsPos + 1));
stringMap.put(key, val);
}
}
reader.close();
return true;
}
private String generateBoundary() {
// Generate some random numbers to fill out the boundary
int r0 = (int)(Integer.MAX_VALUE * Math.random());
int r1 = (int)(Integer.MAX_VALUE * Math.random());
return String.format("---------------------------%08X%08X", r0, r1);
}
private void sendPart(OutputStream os, String boundary, String name, String data) {
try {
os.write(("--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"" + name + "\"\r\n" +
"\r\n" +
data + "\r\n"
).getBytes());
} catch (Exception ex) {
Log.e(LOGTAG, "Exception when sending \"" + name + "\"", ex);
}
}
private void sendFile(OutputStream os, String boundary, String name, File file) throws IOException {
os.write(("--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"" + name + "\"; " +
"filename=\"" + file.getName() + "\"\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n"
).getBytes());
FileChannel fc = new FileInputStream(file).getChannel();
fc.transferTo(0, fc.size(), Channels.newChannel(os));
fc.close();
}
private String readLogcat() {
final String crashReporterProc = " " + android.os.Process.myPid() + ' ';
BufferedReader br = null;
try {
// get at most the last 400 lines of logcat
Process proc = Runtime.getRuntime().exec(new String[] {
"logcat", "-v", "threadtime", "-t", "400", "-d", "*:D"
});
StringBuilder sb = new StringBuilder();
br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
for (String s = br.readLine(); s != null; s = br.readLine()) {
if (s.contains(crashReporterProc)) {
// Don't include logs from the crash reporter's process.
break;
}
sb.append(s).append('\n');
}
return sb.toString();
} catch (Exception e) {
return "Unable to get logcat: " + e.toString();
} finally {
if (br != null) {
try {
br.close();
} catch (Exception e) {
// ignore
}
}
}
}
private void sendReport(File minidumpFile, Map<String, String> extras, File extrasFile) {
Log.i(LOGTAG, "sendReport: " + minidumpFile.getPath());
final CheckBox includeURLCheckbox = (CheckBox) findViewById(R.id.include_url);
String spec = extras.get(SERVER_URL_KEY);
if (spec == null) {
doFinish();
return;
}
Log.i(LOGTAG, "server url: " + spec);
try {
URL url = new URL(spec);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("POST");
String boundary = generateBoundary();
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
conn.setRequestProperty("Content-Encoding", "gzip");
OutputStream os = new GZIPOutputStream(conn.getOutputStream());
for (String key : extras.keySet()) {
if (key.equals(PAGE_URL_KEY)) {
if (includeURLCheckbox.isChecked())
sendPart(os, boundary, key, extras.get(key));
} else if (!key.equals(SERVER_URL_KEY) && !key.equals(NOTES_KEY)) {
sendPart(os, boundary, key, extras.get(key));
}
}
// Add some extra information to notes so its displayed by
// crash-stats.mozilla.org. Remove this when bug 607942 is fixed.
StringBuilder sb = new StringBuilder();
sb.append(extras.containsKey(NOTES_KEY) ? extras.get(NOTES_KEY) + "\n" : "");
if (AppConstants.MOZ_MIN_CPU_VERSION < 7) {
sb.append("nothumb Build\n");
}
sb.append(Build.MANUFACTURER).append(' ')
.append(Build.MODEL).append('\n')
.append(Build.FINGERPRINT);
sendPart(os, boundary, NOTES_KEY, sb.toString());
sendPart(os, boundary, "Min_ARM_Version", Integer.toString(AppConstants.MOZ_MIN_CPU_VERSION));
sendPart(os, boundary, "Android_Manufacturer", Build.MANUFACTURER);
sendPart(os, boundary, "Android_Model", Build.MODEL);
sendPart(os, boundary, "Android_Board", Build.BOARD);
sendPart(os, boundary, "Android_Brand", Build.BRAND);
sendPart(os, boundary, "Android_Device", Build.DEVICE);
sendPart(os, boundary, "Android_Display", Build.DISPLAY);
sendPart(os, boundary, "Android_Fingerprint", Build.FINGERPRINT);
sendPart(os, boundary, "Android_APP_ABI", AppConstants.MOZ_APP_ABI);
sendPart(os, boundary, "Android_CPU_ABI", Build.CPU_ABI);
sendPart(os, boundary, "Android_MIN_SDK", Integer.toString(AppConstants.Versions.MIN_SDK_VERSION));
sendPart(os, boundary, "Android_MAX_SDK", Integer.toString(AppConstants.Versions.MAX_SDK_VERSION));
try {
sendPart(os, boundary, "Android_CPU_ABI2", Build.CPU_ABI2);
sendPart(os, boundary, "Android_Hardware", Build.HARDWARE);
} catch (Exception ex) {
Log.e(LOGTAG, "Exception while sending SDK version 8 keys", ex);
}
sendPart(os, boundary, "Android_Version", Build.VERSION.SDK_INT + " (" + Build.VERSION.CODENAME + ")");
if (Versions.feature16Plus && includeURLCheckbox.isChecked()) {
sendPart(os, boundary, "Android_Logcat", readLogcat());
}
String comment = ((EditText) findViewById(R.id.comment)).getText().toString();
if (!TextUtils.isEmpty(comment)) {
sendPart(os, boundary, "Comments", comment);
}
if (((CheckBox) findViewById(R.id.allow_contact)).isChecked()) {
String email = ((EditText) findViewById(R.id.email)).getText().toString();
sendPart(os, boundary, "Email", email);
}
sendPart(os, boundary, PASSED_MINI_DUMP_SUCCESS_KEY, mMinidumpSucceeded ? "True" : "False");
sendFile(os, boundary, MINI_DUMP_PATH_KEY, minidumpFile);
os.write(("\r\n--" + boundary + "--\r\n").getBytes());
os.flush();
os.close();
BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
HashMap<String, String> responseMap = new HashMap<String, String>();
readStringsFromReader(br, responseMap);
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
File submittedDir = new File(getFilesDir(),
SUBMITTED_SUFFIX);
submittedDir.mkdirs();
minidumpFile.delete();
extrasFile.delete();
String crashid = responseMap.get("CrashID");
File file = new File(submittedDir, crashid + ".txt");
FileOutputStream fos = new FileOutputStream(file);
fos.write("Crash ID: ".getBytes());
fos.write(crashid.getBytes());
fos.close();
} else {
Log.i(LOGTAG, "Received failure HTTP response code from server: " + conn.getResponseCode());
}
} catch (IOException e) {
Log.e(LOGTAG, "exception during send: ", e);
}
doFinish();
}
private void doRestart() {
try {
String action = "android.intent.action.MAIN";
Intent intent = new Intent(action);
intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
intent.putExtra("didRestart", true);
Log.i(LOGTAG, intent.toString());
startActivity(intent);
} catch (Exception e) {
Log.e(LOGTAG, "error while trying to restart", e);
}
}
private String unescape(String string) {
return string.replaceAll("\\\\\\\\", "\\").replaceAll("\\\\n", "\n").replaceAll("\\\\t", "\t");
}
}

View file

@ -1,89 +0,0 @@
/* -*- 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.support.v4.content.ContextCompat;
import org.mozilla.gecko.widget.themed.ThemedEditText;
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
public class CustomEditText extends ThemedEditText {
private OnKeyPreImeListener mOnKeyPreImeListener;
private OnSelectionChangedListener mOnSelectionChangedListener;
private OnWindowFocusChangeListener mOnWindowFocusChangeListener;
private int mHighlightColor;
public CustomEditText(Context context, AttributeSet attrs) {
super(context, attrs);
setPrivateMode(false); // Initialize mHighlightColor.
}
public interface OnKeyPreImeListener {
public boolean onKeyPreIme(View v, int keyCode, KeyEvent event);
}
public void setOnKeyPreImeListener(OnKeyPreImeListener listener) {
mOnKeyPreImeListener = listener;
}
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (mOnKeyPreImeListener != null)
return mOnKeyPreImeListener.onKeyPreIme(this, keyCode, event);
return false;
}
public interface OnSelectionChangedListener {
public void onSelectionChanged(int selStart, int selEnd);
}
public void setOnSelectionChangedListener(OnSelectionChangedListener listener) {
mOnSelectionChangedListener = listener;
}
@Override
protected void onSelectionChanged(int selStart, int selEnd) {
if (mOnSelectionChangedListener != null)
mOnSelectionChangedListener.onSelectionChanged(selStart, selEnd);
super.onSelectionChanged(selStart, selEnd);
}
public interface OnWindowFocusChangeListener {
public void onWindowFocusChanged(boolean hasFocus);
}
public void setOnWindowFocusChangeListener(OnWindowFocusChangeListener listener) {
mOnWindowFocusChangeListener = listener;
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (mOnWindowFocusChangeListener != null)
mOnWindowFocusChangeListener.onWindowFocusChanged(hasFocus);
}
// Provide a getHighlightColor implementation for API level < 16.
@Override
public int getHighlightColor() {
return mHighlightColor;
}
@Override
public void setPrivateMode(boolean isPrivate) {
super.setPrivateMode(isPrivate);
mHighlightColor = ContextCompat.getColor(getContext(), isPrivate
? R.color.url_bar_text_highlight_pb : R.color.fennec_ui_orange);
// android:textColorHighlight cannot support a ColorStateList.
setHighlightColor(mHighlightColor);
}
}

View file

@ -1,133 +0,0 @@
/* -*- 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;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.preferences.GeckoPreferences;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.support.v4.app.NotificationCompat;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.StyleSpan;
public class DataReportingNotification {
private static final String LOGTAG = "DataReportNotification";
public static final String ALERT_NAME_DATAREPORTING_NOTIFICATION = "datareporting-notification";
private static final String PREFS_POLICY_NOTIFIED_TIME = "datareporting.policy.dataSubmissionPolicyNotifiedTime";
private static final String PREFS_POLICY_VERSION = "datareporting.policy.dataSubmissionPolicyVersion";
private static final int DATA_REPORTING_VERSION = 2;
public static void checkAndNotifyPolicy(Context context) {
SharedPreferences dataPrefs = GeckoSharedPrefs.forApp(context);
final int currentVersion = dataPrefs.getInt(PREFS_POLICY_VERSION, -1);
if (currentVersion < 1) {
// This is a first run, so notify user about data policy.
notifyDataPolicy(context, dataPrefs);
// If healthreport is enabled, set default preference value.
if (AppConstants.MOZ_SERVICES_HEALTHREPORT) {
SharedPreferences.Editor editor = dataPrefs.edit();
editor.putBoolean(GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true);
editor.apply();
}
return;
}
if (currentVersion == 1) {
// Redisplay notification only for Beta because version 2 updates Beta policy and update version.
if (TextUtils.equals("beta", AppConstants.MOZ_UPDATE_CHANNEL)) {
notifyDataPolicy(context, dataPrefs);
} else {
// Silently update the version.
SharedPreferences.Editor editor = dataPrefs.edit();
editor.putInt(PREFS_POLICY_VERSION, DATA_REPORTING_VERSION);
editor.apply();
}
return;
}
if (currentVersion >= DATA_REPORTING_VERSION) {
// Do nothing, we're at a current (or future) version.
return;
}
}
/**
* Launch a notification of the data policy, and record notification time and version.
*/
public static void notifyDataPolicy(Context context, SharedPreferences sharedPrefs) {
boolean result = false;
try {
// Launch main App to launch Data choices when notification is clicked.
Intent prefIntent = new Intent(GeckoApp.ACTION_LAUNCH_SETTINGS);
prefIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
GeckoPreferences.setResourceToOpen(prefIntent, "preferences_privacy");
prefIntent.putExtra(ALERT_NAME_DATAREPORTING_NOTIFICATION, true);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, prefIntent, PendingIntent.FLAG_UPDATE_CURRENT);
final Resources resources = context.getResources();
// Create and send notification.
String notificationTitle = resources.getString(R.string.datareporting_notification_title);
String notificationSummary;
if (Versions.preJB) {
notificationSummary = resources.getString(R.string.datareporting_notification_action);
} else {
// Display partial version of Big Style notification for supporting devices.
notificationSummary = resources.getString(R.string.datareporting_notification_summary);
}
String notificationAction = resources.getString(R.string.datareporting_notification_action);
String notificationBigSummary = resources.getString(R.string.datareporting_notification_summary);
// Make styled ticker text for display in notification bar.
String tickerString = resources.getString(R.string.datareporting_notification_ticker_text);
SpannableString tickerText = new SpannableString(tickerString);
// Bold the notification title of the ticker text, which is the same string as notificationTitle.
tickerText.setSpan(new StyleSpan(Typeface.BOLD), 0, notificationTitle.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
Notification notification = new NotificationCompat.Builder(context)
.setContentTitle(notificationTitle)
.setContentText(notificationSummary)
.setSmallIcon(R.drawable.ic_status_logo)
.setAutoCancel(true)
.setContentIntent(contentIntent)
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(notificationBigSummary))
.addAction(R.drawable.firefox_settings_alert, notificationAction, contentIntent)
.setTicker(tickerText)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
int notificationID = ALERT_NAME_DATAREPORTING_NOTIFICATION.hashCode();
notificationManager.notify(notificationID, notification);
// Record version and notification time.
SharedPreferences.Editor editor = sharedPrefs.edit();
long now = System.currentTimeMillis();
editor.putLong(PREFS_POLICY_NOTIFIED_TIME, now);
editor.putInt(PREFS_POLICY_VERSION, DATA_REPORTING_VERSION);
editor.apply();
result = true;
} finally {
// We want to track any errors, so record notification outcome.
Telemetry.sendUIEvent(TelemetryContract.Event.POLICY_NOTIFICATION_SUCCESS, result);
}
}
}

View file

@ -1,52 +0,0 @@
/* -*- 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.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.util.Log;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.InputOptionsUtils;
/**
* Supports the DevTools WiFi debugging authentication flow by invoking a QR decoder.
*/
public class DevToolsAuthHelper {
private static final String LOGTAG = "GeckoDevToolsAuthHelper";
public static void scan(Context context, final EventCallback callback) {
final Intent intent = InputOptionsUtils.createQRCodeReaderIntent();
intent.putExtra("PROMPT_MESSAGE", context.getString(R.string.devtools_auth_scan_header));
// Check ahead of time if an activity exists for the intent. This
// avoids a case where we get both an ActivityNotFoundException *and*
// an activity result when the activity is missing.
PackageManager pm = context.getPackageManager();
if (pm.resolveActivity(intent, 0) == null) {
Log.w(LOGTAG, "PackageManager can't resolve the activity.");
callback.sendError("PackageManager can't resolve the activity.");
return;
}
ActivityHandlerHelper.startIntent(intent, new ActivityResultHandler() {
@Override
public void onActivityResult(int resultCode, Intent intent) {
if (resultCode == Activity.RESULT_OK) {
String text = intent.getStringExtra("SCAN_RESULT");
callback.sendSuccess(text);
} else {
callback.sendError(resultCode);
}
}
});
}
}

View file

@ -1,361 +0,0 @@
/* -*- 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 java.util.HashSet;
import android.text.TextUtils;
import android.widget.PopupWindow;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONArray;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.AnchoredPopup;
import org.mozilla.gecko.widget.DoorHanger;
import android.content.Context;
import android.util.Log;
import android.view.View;
import org.mozilla.gecko.widget.DoorhangerConfig;
public class DoorHangerPopup extends AnchoredPopup
implements GeckoEventListener,
Tabs.OnTabsChangedListener,
PopupWindow.OnDismissListener,
DoorHanger.OnButtonClickListener {
private static final String LOGTAG = "GeckoDoorHangerPopup";
// Stores a set of all active DoorHanger notifications. A DoorHanger is
// uniquely identified by its tabId and value.
private final HashSet<DoorHanger> mDoorHangers;
// Whether or not the doorhanger popup is disabled.
private boolean mDisabled;
public DoorHangerPopup(Context context) {
super(context);
mDoorHangers = new HashSet<DoorHanger>();
GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
"Doorhanger:Add",
"Doorhanger:Remove");
Tabs.registerOnTabsChangedListener(this);
setOnDismissListener(this);
}
void destroy() {
GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
"Doorhanger:Add",
"Doorhanger:Remove");
Tabs.unregisterOnTabsChangedListener(this);
}
/**
* Temporarily disables the doorhanger popup. If the popup is disabled,
* it will not be shown to the user, but it will continue to process
* calls to add/remove doorhanger notifications.
*/
void disable() {
mDisabled = true;
updatePopup();
}
/**
* Re-enables the doorhanger popup.
*/
void enable() {
mDisabled = false;
updatePopup();
}
@Override
public void handleMessage(String event, JSONObject geckoObject) {
try {
if (event.equals("Doorhanger:Add")) {
final DoorhangerConfig config = makeConfigFromJSON(geckoObject);
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
addDoorHanger(config);
}
});
} else if (event.equals("Doorhanger:Remove")) {
final int tabId = geckoObject.getInt("tabID");
final String value = geckoObject.getString("value");
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
DoorHanger doorHanger = getDoorHanger(tabId, value);
if (doorHanger == null)
return;
removeDoorHanger(doorHanger);
updatePopup();
}
});
}
} catch (Exception e) {
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
}
}
private DoorhangerConfig makeConfigFromJSON(JSONObject json) throws JSONException {
final int tabId = json.getInt("tabID");
final String id = json.getString("value");
final String typeString = json.optString("category");
DoorHanger.Type doorhangerType = DoorHanger.Type.DEFAULT;
if (DoorHanger.Type.LOGIN.toString().equals(typeString)) {
doorhangerType = DoorHanger.Type.LOGIN;
} else if (DoorHanger.Type.GEOLOCATION.toString().equals(typeString)) {
doorhangerType = DoorHanger.Type.GEOLOCATION;
} else if (DoorHanger.Type.DESKTOPNOTIFICATION2.toString().equals(typeString)) {
doorhangerType = DoorHanger.Type.DESKTOPNOTIFICATION2;
} else if (DoorHanger.Type.WEBRTC.toString().equals(typeString)) {
doorhangerType = DoorHanger.Type.WEBRTC;
} else if (DoorHanger.Type.VIBRATION.toString().equals(typeString)) {
doorhangerType = DoorHanger.Type.VIBRATION;
}
final DoorhangerConfig config = new DoorhangerConfig(tabId, id, doorhangerType, this);
config.setMessage(json.getString("message"));
config.setOptions(json.getJSONObject("options"));
final JSONArray buttonArray = json.getJSONArray("buttons");
int numButtons = buttonArray.length();
if (numButtons > 2) {
Log.e(LOGTAG, "Doorhanger can have a maximum of two buttons!");
numButtons = 2;
}
for (int i = 0; i < numButtons; i++) {
final JSONObject buttonJSON = buttonArray.getJSONObject(i);
final boolean isPositive = buttonJSON.optBoolean("positive", false);
config.setButton(buttonJSON.getString("label"), buttonJSON.getInt("callback"), isPositive);
}
return config;
}
// This callback is automatically executed on the UI thread.
@Override
public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final String data) {
switch (msg) {
case CLOSED:
// Remove any doorhangers for a tab when it's closed (make
// a temporary set to avoid a ConcurrentModificationException)
removeTabDoorHangers(tab.getId(), true);
break;
case LOCATION_CHANGE:
// Only remove doorhangers if the popup is hidden or if we're navigating to a new URL
if (!isShowing() || !data.equals(tab.getURL()))
removeTabDoorHangers(tab.getId(), false);
// Update the popup if the location change was on the current tab
if (Tabs.getInstance().isSelectedTab(tab))
updatePopup();
break;
case SELECTED:
// Always update the popup when a new tab is selected. This will cover cases
// where a different tab was closed, since we always need to select a new tab.
updatePopup();
break;
}
}
/**
* Adds a doorhanger.
*
* This method must be called on the UI thread.
*/
void addDoorHanger(DoorhangerConfig config) {
final int tabId = config.getTabId();
// Don't add a doorhanger for a tab that doesn't exist
if (Tabs.getInstance().getTab(tabId) == null) {
return;
}
// Replace the doorhanger if it already exists
DoorHanger oldDoorHanger = getDoorHanger(tabId, config.getId());
if (oldDoorHanger != null) {
removeDoorHanger(oldDoorHanger);
}
if (!mInflated) {
init();
}
final DoorHanger newDoorHanger = DoorHanger.Get(mContext, config);
mDoorHangers.add(newDoorHanger);
mContent.addView(newDoorHanger);
// Only update the popup if we're adding a notification to the selected tab
if (tabId == Tabs.getInstance().getSelectedTab().getId())
updatePopup();
}
/*
* DoorHanger.OnButtonClickListener implementation
*/
@Override
public void onButtonClick(JSONObject response, DoorHanger doorhanger) {
GeckoAppShell.notifyObservers("Doorhanger:Reply", response.toString());
removeDoorHanger(doorhanger);
updatePopup();
}
/**
* Gets a doorhanger.
*
* This method must be called on the UI thread.
*/
DoorHanger getDoorHanger(int tabId, String value) {
for (DoorHanger dh : mDoorHangers) {
if (dh.getTabId() == tabId && dh.getIdentifier().equals(value))
return dh;
}
// If there's no doorhanger for the given tabId and value, return null
return null;
}
/**
* Removes a doorhanger.
*
* This method must be called on the UI thread.
*/
void removeDoorHanger(final DoorHanger doorHanger) {
mDoorHangers.remove(doorHanger);
mContent.removeView(doorHanger);
}
/**
* Removes doorhangers for a given tab.
* @param tabId identifier of the tab to remove doorhangers from
* @param forceRemove boolean for force-removing tabs. If true, all doorhangers associated
* with the tab specified are removed; if false, only remove the doorhangers
* that are not persistent, as specified by the doorhanger options.
*
* This method must be called on the UI thread.
*/
void removeTabDoorHangers(int tabId, boolean forceRemove) {
// Make a temporary set to avoid a ConcurrentModificationException
HashSet<DoorHanger> doorHangersToRemove = new HashSet<DoorHanger>();
for (DoorHanger dh : mDoorHangers) {
// Only remove transient doorhangers for the given tab
if (dh.getTabId() == tabId
&& (forceRemove || (!forceRemove && dh.shouldRemove(isShowing())))) {
doorHangersToRemove.add(dh);
}
}
for (DoorHanger dh : doorHangersToRemove) {
removeDoorHanger(dh);
}
}
/**
* Updates the popup state.
*
* This method must be called on the UI thread.
*/
void updatePopup() {
// Bail if the selected tab is null, if there are no active doorhangers,
// if we haven't inflated the layout yet (this can happen if updatePopup()
// is called before the runnable from addDoorHanger() runs), or if the
// doorhanger popup is temporarily disabled.
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab == null || mDoorHangers.size() == 0 || !mInflated || mDisabled) {
dismiss();
return;
}
// Show doorhangers for the selected tab
int tabId = tab.getId();
boolean shouldShowPopup = false;
DoorHanger firstDoorhanger = null;
for (DoorHanger dh : mDoorHangers) {
if (dh.getTabId() == tabId) {
dh.setVisibility(View.VISIBLE);
shouldShowPopup = true;
if (firstDoorhanger == null) {
firstDoorhanger = dh;
} else {
dh.hideTitle();
}
} else {
dh.setVisibility(View.GONE);
}
}
// Dismiss the popup if there are no doorhangers to show for this tab
if (!shouldShowPopup) {
dismiss();
return;
}
showDividers();
final String baseDomain = tab.getBaseDomain();
if (TextUtils.isEmpty(baseDomain)) {
firstDoorhanger.hideTitle();
} else {
firstDoorhanger.showTitle(tab.getFavicon(), baseDomain);
}
if (isShowing()) {
show();
return;
}
setFocusable(true);
show();
}
//Show all inter-DoorHanger dividers (ie. Dividers on all visible DoorHangers except the last one)
private void showDividers() {
int count = mContent.getChildCount();
DoorHanger lastVisibleDoorHanger = null;
for (int i = 0; i < count; i++) {
DoorHanger dh = (DoorHanger) mContent.getChildAt(i);
dh.showDivider();
if (dh.getVisibility() == View.VISIBLE) {
lastVisibleDoorHanger = dh;
}
}
if (lastVisibleDoorHanger != null) {
lastVisibleDoorHanger.hideDivider();
}
}
@Override
public void onDismiss() {
final int tabId = Tabs.getInstance().getSelectedTab().getId();
removeTabDoorHangers(tabId, true);
}
@Override
public void dismiss() {
// If the popup is focusable while it is hidden, we run into crashes
// on pre-ICS devices when the popup gets focus before it is shown.
setFocusable(false);
super.dismiss();
}
}

View file

@ -1,235 +0,0 @@
/* -*- 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;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.permissions.Permissions;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.EventCallback;
import java.io.File;
import java.lang.IllegalArgumentException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import android.app.DownloadManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.media.MediaScannerConnection;
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
public class DownloadsIntegration implements NativeEventListener
{
private static final String LOGTAG = "GeckoDownloadsIntegration";
private static final List<String> UNKNOWN_MIME_TYPES;
static {
final ArrayList<String> tempTypes = new ArrayList<>(3);
tempTypes.add("unknown/unknown"); // This will be used as a default mime type for unknown files
tempTypes.add("application/unknown");
tempTypes.add("application/octet-stream"); // Github uses this for APK files
UNKNOWN_MIME_TYPES = Collections.unmodifiableList(tempTypes);
}
private static final String DOWNLOAD_REMOVE = "Download:Remove";
private DownloadsIntegration() {
EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener)this, DOWNLOAD_REMOVE);
}
private static DownloadsIntegration sInstance;
private static class Download {
final File file;
final long id;
final private static int UNKNOWN_ID = -1;
public Download(final String path) {
this(path, UNKNOWN_ID);
}
public Download(final String path, final long id) {
file = new File(path);
this.id = id;
}
public static Download fromJSON(final NativeJSObject obj) {
final String path = obj.getString("path");
return new Download(path);
}
public static Download fromCursor(final Cursor c) {
final String path = c.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_FILENAME));
final long id = c.getLong(c.getColumnIndexOrThrow(DownloadManager.COLUMN_ID));
return new Download(path, id);
}
public boolean equals(final Download download) {
return file.equals(download.file);
}
}
public static void init() {
if (sInstance == null) {
sInstance = new DownloadsIntegration();
}
}
@Override
public void handleMessage(final String event, final NativeJSObject message,
final EventCallback callback) {
if (DOWNLOAD_REMOVE.equals(event)) {
final Download d = Download.fromJSON(message);
removeDownload(d);
}
}
private static boolean useSystemDownloadManager() {
if (!AppConstants.ANDROID_DOWNLOADS_INTEGRATION) {
return false;
}
int state = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
try {
state = GeckoAppShell.getContext().getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads");
} catch (IllegalArgumentException e) {
// Download Manager package does not exist
return false;
}
return (PackageManager.COMPONENT_ENABLED_STATE_ENABLED == state ||
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT == state);
}
@WrapForJNI(calledFrom = "gecko")
public static String getTemporaryDownloadDirectory() {
Context context = GeckoAppShell.getApplicationContext();
if (Permissions.has(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// We do have the STORAGE permission, so we can save the file directly to the public
// downloads directory.
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.getAbsolutePath();
} else {
// Without the permission we are going to start to download the file to the cache
// directory. Later in the process we will ask for the permission and the download
// process will move the file to the actual downloads directory. If we do not get the
// permission then the download will be cancelled.
return context.getCacheDir().getAbsolutePath();
}
}
@WrapForJNI(calledFrom = "gecko")
public static void scanMedia(final String aFile, String aMimeType) {
String mimeType = aMimeType;
if (UNKNOWN_MIME_TYPES.contains(mimeType)) {
// If this is a generic undefined mimetype, erase it so that we can try to determine
// one from the file extension below.
mimeType = "";
}
// If the platform didn't give us a mimetype, try to guess one from the filename
if (TextUtils.isEmpty(mimeType)) {
final int extPosition = aFile.lastIndexOf(".");
if (extPosition > 0 && extPosition < aFile.length() - 1) {
mimeType = GeckoAppShell.getMimeTypeFromExtension(aFile.substring(extPosition + 1));
}
}
// addCompletedDownload will throw if it received any null parameters. Use aMimeType or a default
// if we still don't have one.
if (TextUtils.isEmpty(mimeType)) {
if (TextUtils.isEmpty(aMimeType)) {
mimeType = UNKNOWN_MIME_TYPES.get(0);
} else {
mimeType = aMimeType;
}
}
if (useSystemDownloadManager()) {
final File f = new File(aFile);
final DownloadManager dm = (DownloadManager) GeckoAppShell.getContext().getSystemService(Context.DOWNLOAD_SERVICE);
dm.addCompletedDownload(f.getName(),
f.getName(),
true, // Media scanner should scan this
mimeType,
f.getAbsolutePath(),
Math.max(1, f.length()), // Some versions of Android require downloads to be at least length 1
false); // Don't show a notification.
} else {
final Context context = GeckoAppShell.getContext();
final GeckoMediaScannerClient client = new GeckoMediaScannerClient(context, aFile, mimeType);
client.connect();
}
}
public static void removeDownload(final Download download) {
if (!useSystemDownloadManager()) {
return;
}
final DownloadManager dm = (DownloadManager) GeckoAppShell.getContext().getSystemService(Context.DOWNLOAD_SERVICE);
Cursor c = null;
try {
c = dm.query((new DownloadManager.Query()).setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL));
if (c == null || !c.moveToFirst()) {
return;
}
do {
final Download d = Download.fromCursor(c);
// Try hard as we can to verify this download is the one we think it is
if (download.equals(d)) {
dm.remove(d.id);
}
} while (c.moveToNext());
} finally {
if (c != null) {
c.close();
}
}
}
private static final class GeckoMediaScannerClient implements MediaScannerConnectionClient {
private final String mFile;
private final String mMimeType;
private MediaScannerConnection mScanner;
public GeckoMediaScannerClient(Context context, String file, String mimeType) {
mFile = file;
mMimeType = mimeType;
mScanner = new MediaScannerConnection(context, this);
}
public void connect() {
mScanner.connect();
}
@Override
public void onMediaScannerConnected() {
mScanner.scanFile(mFile, mMimeType);
}
@Override
public void onScanCompleted(String path, Uri uri) {
if (path.equals(mFile)) {
mScanner.disconnect();
mScanner = null;
}
}
}
}

View file

@ -1,218 +0,0 @@
package org.mozilla.gecko;
import org.mozilla.gecko.PrefsHelper.PrefHandlerBase;
import org.mozilla.gecko.gfx.DynamicToolbarAnimator.PinReason;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.util.ThreadUtils;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
public class DynamicToolbar {
private static final String LOGTAG = "DynamicToolbar";
private static final String STATE_ENABLED = "dynamic_toolbar";
private static final String CHROME_PREF = "browser.chrome.dynamictoolbar";
// DynamicToolbar is enabled iff prefEnabled is true *and* accessibilityEnabled is false,
// so it is disabled by default on startup. We do not enable it until we explicitly get
// the pref from Gecko telling us to turn it on.
private volatile boolean prefEnabled;
private boolean accessibilityEnabled;
// On some device we have to force-disable the dynamic toolbar because of
// bugs in the Android code. See bug 1231554.
private final boolean forceDisabled;
private final PrefsHelper.PrefHandler prefObserver;
private LayerView layerView;
private OnEnabledChangedListener enabledChangedListener;
private boolean temporarilyVisible;
public enum VisibilityTransition {
IMMEDIATE,
ANIMATE
}
/**
* Listener for changes to the dynamic toolbar's enabled state.
*/
public interface OnEnabledChangedListener {
/**
* This callback is executed on the UI thread.
*/
public void onEnabledChanged(boolean enabled);
}
public DynamicToolbar() {
// Listen to the dynamic toolbar pref
prefObserver = new PrefHandler();
PrefsHelper.addObserver(new String[] { CHROME_PREF }, prefObserver);
forceDisabled = isForceDisabled();
if (forceDisabled) {
Log.i(LOGTAG, "Force-disabling dynamic toolbar for " + Build.MODEL + " (" + Build.DEVICE + "/" + Build.PRODUCT + ")");
}
}
public static boolean isForceDisabled() {
// Force-disable dynamic toolbar on the variants of the Galaxy Note 10.1
// and Note 8.0 running Android 4.1.2. (Bug 1231554). This includes
// the following model numbers:
// GT-N8000, GT-N8005, GT-N8010, GT-N8013, GT-N8020
// GT-N5100, GT-N5110, GT-N5120
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN
&& (Build.MODEL.startsWith("GT-N80") ||
Build.MODEL.startsWith("GT-N51"))) {
return true;
}
// Also disable variants of the Galaxy Note 4 on Android 5.0.1 (Bug 1301593)
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP
&& (Build.MODEL.startsWith("SM-N910"))) {
return true;
}
return false;
}
public void destroy() {
PrefsHelper.removeObserver(prefObserver);
}
public void setLayerView(LayerView layerView) {
ThreadUtils.assertOnUiThread();
this.layerView = layerView;
}
public void setEnabledChangedListener(OnEnabledChangedListener listener) {
ThreadUtils.assertOnUiThread();
enabledChangedListener = listener;
}
public void onSaveInstanceState(Bundle outState) {
ThreadUtils.assertOnUiThread();
outState.putBoolean(STATE_ENABLED, prefEnabled);
}
public void onRestoreInstanceState(Bundle savedInstanceState) {
ThreadUtils.assertOnUiThread();
if (savedInstanceState != null) {
prefEnabled = savedInstanceState.getBoolean(STATE_ENABLED);
}
}
public boolean isEnabled() {
ThreadUtils.assertOnUiThread();
if (forceDisabled) {
return false;
}
return prefEnabled && !accessibilityEnabled;
}
public void setAccessibilityEnabled(boolean enabled) {
ThreadUtils.assertOnUiThread();
if (accessibilityEnabled == enabled) {
return;
}
// Disable the dynamic toolbar when accessibility features are enabled,
// and re-read the preference when they're disabled.
accessibilityEnabled = enabled;
if (prefEnabled) {
triggerEnabledListener();
}
}
public void setVisible(boolean visible, VisibilityTransition transition) {
ThreadUtils.assertOnUiThread();
if (layerView == null) {
return;
}
// Don't hide the ActionBar/Toolbar, if it's pinned open by TextSelection.
if (visible == false &&
layerView.getDynamicToolbarAnimator().isPinnedBy(PinReason.ACTION_MODE)) {
return;
}
final boolean isImmediate = transition == VisibilityTransition.IMMEDIATE;
if (visible) {
layerView.getDynamicToolbarAnimator().showToolbar(isImmediate);
} else {
layerView.getDynamicToolbarAnimator().hideToolbar(isImmediate);
}
}
public void setTemporarilyVisible(boolean visible, VisibilityTransition transition) {
ThreadUtils.assertOnUiThread();
if (layerView == null) {
return;
}
if (visible == temporarilyVisible) {
// nothing to do
return;
}
temporarilyVisible = visible;
final boolean isImmediate = transition == VisibilityTransition.IMMEDIATE;
if (visible) {
layerView.getDynamicToolbarAnimator().showToolbar(isImmediate);
} else {
layerView.getDynamicToolbarAnimator().hideToolbar(isImmediate);
}
}
public void persistTemporaryVisibility() {
ThreadUtils.assertOnUiThread();
if (temporarilyVisible) {
temporarilyVisible = false;
setVisible(true, VisibilityTransition.IMMEDIATE);
}
}
public void setPinned(boolean pinned, PinReason reason) {
ThreadUtils.assertOnUiThread();
if (layerView == null) {
return;
}
layerView.getDynamicToolbarAnimator().setPinned(pinned, reason);
}
private void triggerEnabledListener() {
if (enabledChangedListener != null) {
enabledChangedListener.onEnabledChanged(isEnabled());
}
}
private class PrefHandler extends PrefHandlerBase {
@Override
public void prefValue(String pref, boolean value) {
if (value == prefEnabled) {
return;
}
prefEnabled = value;
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
// If accessibility is enabled, the dynamic toolbar is
// forced to be off.
if (!accessibilityEnabled) {
triggerEnabledListener();
}
}
});
}
}
}

View file

@ -1,252 +0,0 @@
/* -*- 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.db.BrowserDB;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UIAsyncTask;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.database.Cursor;
import android.support.design.widget.Snackbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
/**
* A dialog that allows editing a bookmarks url, title, or keywords
* <p>
* Invoked by calling one of the {@link org.mozilla.gecko.EditBookmarkDialog#show(String)}
* methods.
*/
public class EditBookmarkDialog {
private final Context mContext;
public EditBookmarkDialog(Context context) {
mContext = context;
}
/**
* A private struct to make it easier to pass bookmark data across threads
*/
private class Bookmark {
final int id;
final String title;
final String url;
final String keyword;
public Bookmark(int aId, String aTitle, String aUrl, String aKeyword) {
id = aId;
title = aTitle;
url = aUrl;
keyword = aKeyword;
}
}
/**
* This text watcher to enable or disable the OK button if the dialog contains
* valid information. This class is overridden to do data checking on different fields.
* By itself, it always enables the button.
*
* Callers can also assign a paired partner to the TextWatcher, and callers will check
* that both are enabled before enabling the ok button.
*/
private class EditBookmarkTextWatcher implements TextWatcher {
// A stored reference to the dialog containing the text field being watched
protected AlertDialog mDialog;
// A stored text watcher to do the real verification of a field
protected EditBookmarkTextWatcher mPairedTextWatcher;
// Whether or not the ok button should be enabled.
protected boolean mEnabled = true;
public EditBookmarkTextWatcher(AlertDialog aDialog) {
mDialog = aDialog;
}
public void setPairedTextWatcher(EditBookmarkTextWatcher aTextWatcher) {
mPairedTextWatcher = aTextWatcher;
}
public boolean isEnabled() {
return mEnabled;
}
// Textwatcher interface
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Disable if the we're disabled or the paired partner is disabled
boolean enabled = mEnabled && (mPairedTextWatcher == null || mPairedTextWatcher.isEnabled());
mDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled);
}
@Override
public void afterTextChanged(Editable s) {}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
}
/**
* A version of the EditBookmarkTextWatcher for the url field of the dialog.
* Only checks if the field is empty or not.
*/
private class LocationTextWatcher extends EditBookmarkTextWatcher {
public LocationTextWatcher(AlertDialog aDialog) {
super(aDialog);
}
// Disables the ok button if the location field is empty.
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mEnabled = (s.toString().trim().length() > 0);
super.onTextChanged(s, start, before, count);
}
}
/**
* A version of the EditBookmarkTextWatcher for the keyword field of the dialog.
* Checks if the field has any (non leading or trailing) spaces.
*/
private class KeywordTextWatcher extends EditBookmarkTextWatcher {
public KeywordTextWatcher(AlertDialog aDialog) {
super(aDialog);
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Disable if the keyword contains spaces
mEnabled = (s.toString().trim().indexOf(' ') == -1);
super.onTextChanged(s, start, before, count);
}
}
/**
* Show the Edit bookmark dialog for a particular url. If the url is bookmarked multiple times
* this will just edit the first instance it finds.
*
* @param url The url of the bookmark to edit. The dialog will look up other information like the id,
* current title, or keywords associated with this url. If the url isn't bookmarked, the
* dialog will fail silently. If the url is bookmarked multiple times, this will only show
* information about the first it finds.
*/
public void show(final String url) {
final ContentResolver cr = mContext.getContentResolver();
final BrowserDB db = BrowserDB.from(mContext);
(new UIAsyncTask.WithoutParams<Bookmark>(ThreadUtils.getBackgroundHandler()) {
@Override
public Bookmark doInBackground() {
final Cursor cursor = db.getBookmarkForUrl(cr, url);
if (cursor == null) {
return null;
}
Bookmark bookmark = null;
try {
cursor.moveToFirst();
bookmark = new Bookmark(cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID)),
cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.TITLE)),
cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.URL)),
cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.KEYWORD)));
} finally {
cursor.close();
}
return bookmark;
}
@Override
public void onPostExecute(Bookmark bookmark) {
if (bookmark == null) {
return;
}
show(bookmark.id, bookmark.title, bookmark.url, bookmark.keyword);
}
}).execute();
}
/**
* Show the Edit bookmark dialog for a set of data. This will show the dialog whether
* a bookmark with this url exists or not, but the results will NOT be saved if the id
* is not a valid bookmark id.
*
* @param id The id of the bookmark to change. If there is no bookmark with this ID, the dialog
* will fail silently.
* @param title The initial title to show in the dialog
* @param url The initial url to show in the dialog
* @param keyword The initial keyword to show in the dialog
*/
public void show(final int id, final String title, final String url, final String keyword) {
final Context context = mContext;
AlertDialog.Builder editPrompt = new AlertDialog.Builder(context);
final View editView = LayoutInflater.from(context).inflate(R.layout.bookmark_edit, null);
editPrompt.setTitle(R.string.bookmark_edit_title);
editPrompt.setView(editView);
final EditText nameText = ((EditText) editView.findViewById(R.id.edit_bookmark_name));
final EditText locationText = ((EditText) editView.findViewById(R.id.edit_bookmark_location));
final EditText keywordText = ((EditText) editView.findViewById(R.id.edit_bookmark_keyword));
nameText.setText(title);
locationText.setText(url);
keywordText.setText(keyword);
final BrowserDB db = BrowserDB.from(mContext);
editPrompt.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
(new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) {
@Override
public Void doInBackground() {
String newUrl = locationText.getText().toString().trim();
String newKeyword = keywordText.getText().toString().trim();
db.updateBookmark(context.getContentResolver(), id, newUrl, nameText.getText().toString(), newKeyword);
return null;
}
@Override
public void onPostExecute(Void result) {
SnackbarBuilder.builder((Activity) context)
.message(R.string.bookmark_updated)
.duration(Snackbar.LENGTH_LONG)
.buildAndShow();
}
}).execute();
}
});
editPrompt.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
// do nothing
}
});
final AlertDialog dialog = editPrompt.create();
// Create our TextWatchers
LocationTextWatcher locationTextWatcher = new LocationTextWatcher(dialog);
KeywordTextWatcher keywordTextWatcher = new KeywordTextWatcher(dialog);
// Cross reference the TextWatchers
locationTextWatcher.setPairedTextWatcher(keywordTextWatcher);
keywordTextWatcher.setPairedTextWatcher(locationTextWatcher);
// Add the TextWatcher Listeners
locationText.addTextChangedListener(locationTextWatcher);
keywordText.addTextChangedListener(keywordTextWatcher);
dialog.show();
}
}

View file

@ -1,119 +0,0 @@
/* 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.content.Context;
import android.util.Log;
import android.text.TextUtils;
import com.keepsafe.switchboard.Preferences;
import com.keepsafe.switchboard.SwitchBoard;
import java.util.LinkedList;
import java.util.List;
/**
* This class should reflect the experiment names found in the Switchboard experiments config here:
* https://github.com/mozilla-services/switchboard-experiments
*/
public class Experiments {
private static final String LOGTAG = "GeckoExperiments";
// Show a system notification linking to a "What's New" page on app update.
public static final String WHATSNEW_NOTIFICATION = "whatsnew-notification";
// Subscribe to known, bookmarked sites and show a notification if new content is available.
public static final String CONTENT_NOTIFICATIONS_12HRS = "content-notifications-12hrs";
public static final String CONTENT_NOTIFICATIONS_8AM = "content-notifications-8am";
public static final String CONTENT_NOTIFICATIONS_5PM = "content-notifications-5pm";
// Onboarding: "Features and Story". These experiments are determined
// on the client, they are not part of the server config.
public static final String ONBOARDING3_A = "onboarding3-a"; // Control: No first run
public static final String ONBOARDING3_B = "onboarding3-b"; // 4 static Feature + 1 dynamic slides
public static final String ONBOARDING3_C = "onboarding3-c"; // Differentiating features slides
// Synchronizing the catalog of downloadable content from Kinto
public static final String DOWNLOAD_CONTENT_CATALOG_SYNC = "download-content-catalog-sync";
// Promotion for "Add to homescreen"
public static final String PROMOTE_ADD_TO_HOMESCREEN = "promote-add-to-homescreen";
public static final String PREF_ONBOARDING_VERSION = "onboarding_version";
// Promotion to bookmark reader-view items after entering reader view three times (Bug 1247689)
public static final String TRIPLE_READERVIEW_BOOKMARK_PROMPT = "triple-readerview-bookmark-prompt";
// Only show origin in URL bar instead of full URL (Bug 1236431)
public static final String URLBAR_SHOW_ORIGIN_ONLY = "urlbar-show-origin-only";
// Show name of organization (EV cert) instead of full URL in URL bar (Bug 1249594).
public static final String URLBAR_SHOW_EV_CERT_OWNER = "urlbar-show-ev-cert-owner";
// Play HLS videos in a VideoView (Bug 1313391)
public static final String HLS_VIDEO_PLAYBACK = "hls-video-playback";
// Make new activity stream panel available (to replace top sites) (Bug 1313316)
public static final String ACTIVITY_STREAM = "activity-stream";
/**
* Returns if a user is in certain local experiment.
* @param experiment Name of experiment to look up
* @return returns value for experiment or false if experiment does not exist.
*/
public static boolean isInExperimentLocal(Context context, String experiment) {
if (SwitchBoard.isInBucket(context, 0, 20)) {
return Experiments.ONBOARDING3_A.equals(experiment);
} else if (SwitchBoard.isInBucket(context, 20, 60)) {
return Experiments.ONBOARDING3_B.equals(experiment);
} else if (SwitchBoard.isInBucket(context, 60, 100)) {
return Experiments.ONBOARDING3_C.equals(experiment);
} else {
return false;
}
}
/**
* Returns list of all active experiments, remote and local.
* @return List of experiment names Strings
*/
public static List<String> getActiveExperiments(Context c) {
final List<String> experiments = new LinkedList<>();
experiments.addAll(SwitchBoard.getActiveExperiments(c));
// Add onboarding version.
final String onboardingExperiment = GeckoSharedPrefs.forProfile(c).getString(Experiments.PREF_ONBOARDING_VERSION, null);
if (!TextUtils.isEmpty(onboardingExperiment)) {
experiments.add(onboardingExperiment);
}
return experiments;
}
/**
* Sets an override to force an experiment to be enabled or disabled. This value
* will be read and used before reading the switchboard server configuration.
*
* @param c Context
* @param experimentName Experiment name
* @param isEnabled Whether or not the experiment should be enabled
*/
public static void setOverride(Context c, String experimentName, boolean isEnabled) {
Log.d(LOGTAG, "setOverride: " + experimentName + " = " + isEnabled);
Preferences.setOverrideValue(c, experimentName, isEnabled);
}
/**
* Clears the override value for an experiment.
*
* @param c Context
* @param experimentName Experiment name
*/
public static void clearOverride(Context c, String experimentName) {
Log.d(LOGTAG, "clearOverride: " + experimentName);
Preferences.clearOverrideValue(c, experimentName);
}
}

View file

@ -1,227 +0,0 @@
/* 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.GeckoAppShell;
import org.mozilla.gecko.util.GeckoEventListener;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Environment;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class FilePicker implements GeckoEventListener {
private static final String LOGTAG = "GeckoFilePicker";
private static FilePicker sFilePicker;
private final Context context;
public interface ResultHandler {
public void gotFile(String filename);
}
public static void init(Context context) {
if (sFilePicker == null) {
sFilePicker = new FilePicker(context.getApplicationContext());
}
}
protected FilePicker(Context context) {
this.context = context;
EventDispatcher.getInstance().registerGeckoThreadListener(this, "FilePicker:Show");
}
@Override
public void handleMessage(String event, final JSONObject message) {
if (event.equals("FilePicker:Show")) {
String mimeType = "*/*";
final String mode = message.optString("mode");
final int tabId = message.optInt("tabId", -1);
final String title = message.optString("title");
if ("mimeType".equals(mode))
mimeType = message.optString("mimeType");
else if ("extension".equals(mode))
mimeType = GeckoAppShell.getMimeTypeFromExtensions(message.optString("extensions"));
showFilePickerAsync(title, mimeType, new ResultHandler() {
@Override
public void gotFile(String filename) {
try {
message.put("file", filename);
} catch (JSONException ex) {
Log.i(LOGTAG, "Can't add filename to message " + filename);
}
GeckoAppShell.notifyObservers("FilePicker:Result", message.toString());
}
}, tabId);
}
}
private void addActivities(Intent intent, HashMap<String, Intent> intents, HashMap<String, Intent> filters) {
PackageManager pm = context.getPackageManager();
List<ResolveInfo> lri = pm.queryIntentActivities(intent, 0);
for (ResolveInfo ri : lri) {
ComponentName cn = new ComponentName(ri.activityInfo.applicationInfo.packageName, ri.activityInfo.name);
if (filters != null && !filters.containsKey(cn.toString())) {
Intent rintent = new Intent(intent);
rintent.setComponent(cn);
intents.put(cn.toString(), rintent);
}
}
}
private Intent getIntent(String mimeType) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType(mimeType);
intent.addCategory(Intent.CATEGORY_OPENABLE);
return intent;
}
private List<Intent> getIntentsForFilePicker(final String mimeType,
final FilePickerResultHandler fileHandler) {
// The base intent to use for the file picker. Even if this is an implicit intent, Android will
// still show a list of Activities that match this action/type.
Intent baseIntent;
// A HashMap of Activities the base intent will show in the chooser. This is used
// to filter activities from other intents so that we don't show duplicates.
HashMap<String, Intent> baseIntents = new HashMap<String, Intent>();
// A list of other activities to shwo in the picker (and the intents to launch them).
HashMap<String, Intent> intents = new HashMap<String, Intent> ();
if ("audio/*".equals(mimeType)) {
// For audio the only intent is the mimetype
baseIntent = getIntent(mimeType);
addActivities(baseIntent, baseIntents, null);
} else if ("image/*".equals(mimeType)) {
// For images the base is a capture intent
baseIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
baseIntent.putExtra(MediaStore.EXTRA_OUTPUT,
Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
fileHandler.generateImageName())));
addActivities(baseIntent, baseIntents, null);
// We also add the mimetype intent
addActivities(getIntent(mimeType), intents, baseIntents);
} else if ("video/*".equals(mimeType)) {
// For videos the base is a capture intent
baseIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
addActivities(baseIntent, baseIntents, null);
// We also add the mimetype intent
addActivities(getIntent(mimeType), intents, baseIntents);
} else {
baseIntent = getIntent("*/*");
addActivities(baseIntent, baseIntents, null);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT,
Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
fileHandler.generateImageName())));
addActivities(intent, intents, baseIntents);
intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
addActivities(intent, intents, baseIntents);
}
// If we didn't find any activities, we fall back to the */* mimetype intent
if (baseIntents.size() == 0 && intents.size() == 0) {
intents.clear();
baseIntent = getIntent("*/*");
addActivities(baseIntent, baseIntents, null);
}
ArrayList<Intent> vals = new ArrayList<Intent>(intents.values());
vals.add(0, baseIntent);
return vals;
}
private String getFilePickerTitle(String mimeType) {
if (mimeType.equals("audio/*")) {
return context.getString(R.string.filepicker_audio_title);
} else if (mimeType.equals("image/*")) {
return context.getString(R.string.filepicker_image_title);
} else if (mimeType.equals("video/*")) {
return context.getString(R.string.filepicker_video_title);
} else {
return context.getString(R.string.filepicker_title);
}
}
private interface IntentHandler {
public void gotIntent(Intent intent);
}
/* Gets an intent that can open a particular mimetype. Will show a prompt with a list
* of Activities that can handle the mietype. Asynchronously calls the handler when
* one of the intents is selected. If the caller passes in null for the handler, will still
* prompt for the activity, but will throw away the result.
*/
private void getFilePickerIntentAsync(String title,
final String mimeType,
final FilePickerResultHandler fileHandler,
final IntentHandler handler) {
List<Intent> intents = getIntentsForFilePicker(mimeType, fileHandler);
if (intents.size() == 0) {
Log.i(LOGTAG, "no activities for the file picker!");
handler.gotIntent(null);
return;
}
Intent base = intents.remove(0);
if (intents.size() == 0) {
handler.gotIntent(base);
return;
}
if (TextUtils.isEmpty(title)) {
title = getFilePickerTitle(mimeType);
}
Intent chooser = Intent.createChooser(base, title);
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[intents.size()]));
handler.gotIntent(chooser);
}
/* Allows the user to pick an activity to load files from using a list prompt. Then opens the activity and
* sends the file returned to the passed in handler. If a null handler is passed in, will still
* pick and launch the file picker, but will throw away the result.
*/
protected void showFilePickerAsync(final String title, final String mimeType, final ResultHandler handler, final int tabId) {
final FilePickerResultHandler fileHandler = new FilePickerResultHandler(handler, context, tabId);
getFilePickerIntentAsync(title, mimeType, fileHandler, new IntentHandler() {
@Override
public void gotIntent(Intent intent) {
if (handler == null) {
return;
}
if (intent == null) {
handler.gotFile("");
return;
}
ActivityHandlerHelper.startIntent(intent, fileHandler);
}
});
}
}

View file

@ -1,282 +0,0 @@
/* 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.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Process;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.text.format.Time;
import android.util.Log;
class FilePickerResultHandler implements ActivityResultHandler {
private static final String LOGTAG = "GeckoFilePickerResultHandler";
private static final String UPLOADS_DIR = "uploads";
private final FilePicker.ResultHandler handler;
private final int tabId;
private final File cacheDir;
// this code is really hacky and doesn't belong anywhere so I'm putting it here for now
// until I can come up with a better solution.
private String mImageName = "";
/* Use this constructor to asynchronously listen for results */
public FilePickerResultHandler(final FilePicker.ResultHandler handler, final Context context, final int tabId) {
this.tabId = tabId;
this.cacheDir = new File(context.getCacheDir(), UPLOADS_DIR);
this.handler = handler;
}
void sendResult(String res) {
if (handler != null) {
handler.gotFile(res);
}
}
@Override
public void onActivityResult(int resultCode, Intent intent) {
if (resultCode != Activity.RESULT_OK) {
sendResult("");
return;
}
// Camera results won't return an Intent. Use the file name we passed to the original intent.
// In Android M, camera results return an empty Intent rather than null.
if (intent == null || (intent.getAction() == null && intent.getData() == null)) {
if (mImageName != null) {
File file = new File(Environment.getExternalStorageDirectory(), mImageName);
sendResult(file.getAbsolutePath());
} else {
sendResult("");
}
return;
}
Uri uri = intent.getData();
if (uri == null) {
sendResult("");
return;
}
// Some file pickers may return a file uri
if ("file".equals(uri.getScheme())) {
String path = uri.getPath();
sendResult(path == null ? "" : path);
return;
}
final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
final LoaderManager lm = fa.getSupportLoaderManager();
// Finally, Video pickers and some file pickers may return a content provider.
final ContentResolver cr = fa.getContentResolver();
final Cursor cursor = cr.query(uri, new String[] { MediaStore.Video.Media.DATA }, null, null, null);
if (cursor != null) {
try {
// Try a query to make sure the expected columns exist
int index = cursor.getColumnIndex(MediaStore.Video.Media.DATA);
if (index >= 0) {
lm.initLoader(intent.hashCode(), null, new VideoLoaderCallbacks(uri));
return;
}
} catch (Exception ex) {
// We'll try a different loader below
} finally {
cursor.close();
}
}
lm.initLoader(uri.hashCode(), null, new FileLoaderCallbacks(uri, cacheDir, tabId));
}
public String generateImageName() {
Time now = new Time();
now.setToNow();
mImageName = now.format("%Y-%m-%d %H.%M.%S") + ".jpg";
return mImageName;
}
private class VideoLoaderCallbacks implements LoaderCallbacks<Cursor> {
final private Uri uri;
public VideoLoaderCallbacks(Uri uri) {
this.uri = uri;
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
return new CursorLoader(fa,
uri,
new String[] { MediaStore.Video.Media.DATA },
null, // selection
null, // selectionArgs
null); // sortOrder
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if (cursor.moveToFirst()) {
String res = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));
// Some pickers (the KitKat Documents one for instance) won't return a temporary file here.
// Fall back to the normal FileLoader if we didn't find anything.
if (TextUtils.isEmpty(res)) {
tryFileLoaderCallback();
return;
}
sendResult(res);
} else {
tryFileLoaderCallback();
}
}
private void tryFileLoaderCallback() {
final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
final LoaderManager lm = fa.getSupportLoaderManager();
lm.initLoader(uri.hashCode(), null, new FileLoaderCallbacks(uri, cacheDir, tabId));
}
@Override
public void onLoaderReset(Loader<Cursor> loader) { }
}
/**
* This class's only dependency on FilePickerResultHandler is sendResult.
*/
private class FileLoaderCallbacks implements LoaderCallbacks<Cursor>,
Tabs.OnTabsChangedListener {
private final Uri uri;
private final File cacheDir;
private final int tabId;
String tempFile;
public FileLoaderCallbacks(Uri uri, File cacheDir, int tabId) {
this.uri = uri;
this.cacheDir = cacheDir;
this.tabId = tabId;
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
return new CursorLoader(fa,
uri,
new String[] { OpenableColumns.DISPLAY_NAME },
null, // selection
null, // selectionArgs
null); // sortOrder
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if (cursor.moveToFirst()) {
String name = cursor.getString(0);
// tmp filenames must be at least 3 characters long. Add a prefix to make sure that happens
String fileName = "tmp_" + Process.myPid() + "-";
String fileExt;
int period;
final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
final ContentResolver cr = fa.getContentResolver();
// Generate an extension if we don't already have one
if (name == null || (period = name.lastIndexOf('.')) == -1) {
String mimeType = cr.getType(uri);
fileExt = "." + GeckoAppShell.getExtensionFromMimeType(mimeType);
} else {
fileExt = name.substring(period);
fileName += name.substring(0, period);
}
// Now write the data to the temp file
FileOutputStream fos = null;
try {
cacheDir.mkdir();
File file = File.createTempFile(fileName, fileExt, cacheDir);
fos = new FileOutputStream(file);
InputStream is = cr.openInputStream(uri);
byte[] buf = new byte[4096];
int len = is.read(buf);
while (len != -1) {
fos.write(buf, 0, len);
len = is.read(buf);
}
fos.close();
is.close();
tempFile = file.getAbsolutePath();
sendResult((tempFile == null) ? "" : tempFile);
if (tabId > -1 && !TextUtils.isEmpty(tempFile)) {
Tabs.registerOnTabsChangedListener(this);
}
} catch (IOException ex) {
Log.i(LOGTAG, "Error writing file", ex);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) { /* not much to do here */ }
}
}
} else {
sendResult("");
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) { }
/*Tabs.OnTabsChangedListener*/
// This cleans up our temp file. If it doesn't run, we just hope that Android
// will eventually does the cleanup for us.
@Override
public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
if ((tab == null) || (tab.getId() != tabId)) {
return;
}
if (msg == Tabs.TabEvents.LOCATION_CHANGE ||
msg == Tabs.TabEvents.CLOSED) {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
File f = new File(tempFile);
f.delete();
}
});
// Tabs' listener array is safe to modify during use: its
// iteration pattern is based on snapshots.
Tabs.unregisterOnTabsChangedListener(this);
}
}
}
}

View file

@ -1,256 +0,0 @@
/* 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.util.GeckoEventListener;
import org.mozilla.gecko.util.GeckoRequest;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;
import org.json.JSONObject;
import android.content.Context;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
import android.widget.TextView;
public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnClickListener, GeckoEventListener {
private static final String LOGTAG = "GeckoFindInPageBar";
private static final String REQUEST_ID = "FindInPageBar";
private final Context mContext;
private CustomEditText mFindText;
private TextView mStatusText;
private boolean mInflated;
public FindInPageBar(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setFocusable(true);
}
public void inflateContent() {
LayoutInflater inflater = LayoutInflater.from(mContext);
View content = inflater.inflate(R.layout.find_in_page_content, this);
content.findViewById(R.id.find_prev).setOnClickListener(this);
content.findViewById(R.id.find_next).setOnClickListener(this);
content.findViewById(R.id.find_close).setOnClickListener(this);
// Capture clicks on the rest of the view to prevent them from
// leaking into other views positioned below.
content.setOnClickListener(this);
mFindText = (CustomEditText) content.findViewById(R.id.find_text);
mFindText.addTextChangedListener(this);
mFindText.setOnKeyPreImeListener(new CustomEditText.OnKeyPreImeListener() {
@Override
public boolean onKeyPreIme(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
hide();
return true;
}
return false;
}
});
mStatusText = (TextView) content.findViewById(R.id.find_status);
mInflated = true;
GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
"FindInPage:MatchesCountResult",
"TextSelection:Data");
}
public void show() {
if (!mInflated)
inflateContent();
setVisibility(VISIBLE);
mFindText.requestFocus();
// handleMessage() receives response message and determines initial state of softInput
GeckoAppShell.notifyObservers("TextSelection:Get", REQUEST_ID);
GeckoAppShell.notifyObservers("FindInPage:Opened", null);
}
public void hide() {
if (!mInflated || getVisibility() == View.GONE) {
// There's nothing to hide yet.
return;
}
// Always clear the Find string, primarily for privacy.
mFindText.setText("");
// Only close the IMM if its EditText is the one with focus.
if (mFindText.isFocused()) {
getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
}
// Close the FIPB / FindHelper state.
setVisibility(GONE);
GeckoAppShell.notifyObservers("FindInPage:Closed", null);
}
private InputMethodManager getInputMethodManager(View view) {
Context context = view.getContext();
return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
}
public void onDestroy() {
if (!mInflated) {
return;
}
GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
"FindInPage:MatchesCountResult",
"TextSelection:Data");
}
private void onMatchesCountResult(final int total, final int current, final int limit, final String searchString) {
if (total == -1) {
updateResult(Integer.toString(limit) + "+");
} else if (total > 0) {
updateResult(Integer.toString(current) + "/" + Integer.toString(total));
} else if (TextUtils.isEmpty(searchString)) {
updateResult("");
} else {
// We display 0/0, when there were no
// matches found, or if matching has been turned off by setting
// pref accessibility.typeaheadfind.matchesCountLimit to 0.
updateResult("0/0");
}
}
private void updateResult(final String statusText) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
mStatusText.setVisibility(statusText.isEmpty() ? View.GONE : View.VISIBLE);
mStatusText.setText(statusText);
}
});
}
// TextWatcher implementation
@Override
public void afterTextChanged(Editable s) {
sendRequestToFinderHelper("FindInPage:Find", s.toString());
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// ignore
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// ignore
}
// View.OnClickListener implementation
@Override
public void onClick(View v) {
final int viewId = v.getId();
String extras = getResources().getResourceEntryName(viewId);
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, extras);
if (viewId == R.id.find_prev) {
sendRequestToFinderHelper("FindInPage:Prev", mFindText.getText().toString());
getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
return;
}
if (viewId == R.id.find_next) {
sendRequestToFinderHelper("FindInPage:Next", mFindText.getText().toString());
getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
return;
}
if (viewId == R.id.find_close) {
hide();
}
}
// GeckoEventListener implementation
@Override
public void handleMessage(String event, JSONObject message) {
if (event.equals("FindInPage:MatchesCountResult")) {
onMatchesCountResult(message.optInt("total", 0),
message.optInt("current", 0),
message.optInt("limit", 0),
message.optString("searchString"));
return;
}
if (!event.equals("TextSelection:Data") || !REQUEST_ID.equals(message.optString("requestId"))) {
return;
}
final String text = message.optString("text");
// Populate an initial find string, virtual keyboard not required.
if (!TextUtils.isEmpty(text)) {
// Populate initial selection
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
mFindText.setText(text);
}
});
return;
}
// Show the virtual keyboard.
if (mFindText.hasWindowFocus()) {
getInputMethodManager(mFindText).showSoftInput(mFindText, 0);
} else {
// showSoftInput won't work until after the window is focused.
mFindText.setOnWindowFocusChangeListener(new CustomEditText.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean hasFocus) {
if (!hasFocus)
return;
mFindText.setOnWindowFocusChangeListener(null);
getInputMethodManager(mFindText).showSoftInput(mFindText, 0);
}
});
}
}
/**
* Request find operation, and update matchCount results (current count and total).
*/
private void sendRequestToFinderHelper(final String request, final String searchString) {
GeckoAppShell.sendRequestToGecko(new GeckoRequest(request, searchString) {
@Override
public void onResponse(NativeJSObject nativeJSObject) {
// We don't care about the return value, because `onMatchesCountResult`
// does the heavy lifting.
}
@Override
public void onError(NativeJSObject error) {
// Gecko didn't respond due to state change, javascript error, etc.
Log.d(LOGTAG, "No response from Gecko on request to match string: [" +
searchString + "]");
updateResult("");
}
});
}
}

View file

@ -1,459 +0,0 @@
/* -*- 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.animation.ViewHelper;
import org.mozilla.gecko.gfx.FloatSize;
import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.SwipeDismissListViewTouchListener;
import org.mozilla.gecko.widget.SwipeDismissListViewTouchListener.OnDismissCallback;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams;
import android.widget.TextView;
import java.util.Arrays;
import java.util.Collection;
public class FormAssistPopup extends RelativeLayout implements GeckoEventListener {
private final Context mContext;
private final Animation mAnimation;
private ListView mAutoCompleteList;
private RelativeLayout mValidationMessage;
private TextView mValidationMessageText;
private ImageView mValidationMessageArrow;
private ImageView mValidationMessageArrowInverted;
private double mX;
private double mY;
private double mW;
private double mH;
private enum PopupType {
AUTOCOMPLETE,
VALIDATIONMESSAGE;
}
private PopupType mPopupType;
private static final int MAX_VISIBLE_ROWS = 5;
private static int sAutoCompleteMinWidth;
private static int sAutoCompleteRowHeight;
private static int sValidationMessageHeight;
private static int sValidationTextMarginTop;
private static LayoutParams sValidationTextLayoutNormal;
private static LayoutParams sValidationTextLayoutInverted;
private static final String LOGTAG = "GeckoFormAssistPopup";
// The blocklist is so short that ArrayList is probably cheaper than HashSet.
private static final Collection<String> sInputMethodBlocklist = Arrays.asList(
InputMethods.METHOD_GOOGLE_JAPANESE_INPUT, // bug 775850
InputMethods.METHOD_OPENWNN_PLUS, // bug 768108
InputMethods.METHOD_SIMEJI, // bug 768108
InputMethods.METHOD_SWYPE, // bug 755909
InputMethods.METHOD_SWYPE_BETA // bug 755909
);
public FormAssistPopup(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mAnimation = AnimationUtils.loadAnimation(context, R.anim.grow_fade_in);
mAnimation.setDuration(75);
setFocusable(false);
GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
"FormAssist:AutoComplete",
"FormAssist:ValidationMessage",
"FormAssist:Hide");
}
void destroy() {
GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
"FormAssist:AutoComplete",
"FormAssist:ValidationMessage",
"FormAssist:Hide");
}
@Override
public void handleMessage(String event, JSONObject message) {
try {
if (event.equals("FormAssist:AutoComplete")) {
handleAutoCompleteMessage(message);
} else if (event.equals("FormAssist:ValidationMessage")) {
handleValidationMessage(message);
} else if (event.equals("FormAssist:Hide")) {
handleHideMessage(message);
}
} catch (Exception e) {
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
}
}
private void handleAutoCompleteMessage(JSONObject message) throws JSONException {
final JSONArray suggestions = message.getJSONArray("suggestions");
final JSONObject rect = message.getJSONObject("rect");
final boolean isEmpty = message.getBoolean("isEmpty");
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
showAutoCompleteSuggestions(suggestions, rect, isEmpty);
}
});
}
private void handleValidationMessage(JSONObject message) throws JSONException {
final String validationMessage = message.getString("validationMessage");
final JSONObject rect = message.getJSONObject("rect");
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
showValidationMessage(validationMessage, rect);
}
});
}
private void handleHideMessage(JSONObject message) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
hide();
}
});
}
private void showAutoCompleteSuggestions(JSONArray suggestions, JSONObject rect, boolean isEmpty) {
final String inputMethod = InputMethods.getCurrentInputMethod(mContext);
if (!isEmpty && sInputMethodBlocklist.contains(inputMethod)) {
// Don't display the form auto-complete popup after the user starts typing
// to avoid confusing somes IME. See bug 758820 and bug 632744.
hide();
return;
}
if (mAutoCompleteList == null) {
LayoutInflater inflater = LayoutInflater.from(mContext);
mAutoCompleteList = (ListView) inflater.inflate(R.layout.autocomplete_list, null);
mAutoCompleteList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parentView, View view, int position, long id) {
// Use the value stored with the autocomplete view, not the label text,
// since they can be different.
TextView textView = (TextView) view;
String value = (String) textView.getTag();
broadcastGeckoEvent("FormAssist:AutoComplete", value);
hide();
}
});
// Create a ListView-specific touch listener. ListViews are given special treatment because
// by default they handle touches for their list items... i.e. they're in charge of drawing
// the pressed state (the list selector), handling list item clicks, etc.
final SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener(mAutoCompleteList, new OnDismissCallback() {
@Override
public void onDismiss(ListView listView, final int position) {
// Use the value stored with the autocomplete view, not the label text,
// since they can be different.
AutoCompleteListAdapter adapter = (AutoCompleteListAdapter) listView.getAdapter();
Pair<String, String> item = adapter.getItem(position);
// Remove the item from form history.
broadcastGeckoEvent("FormAssist:Remove", item.second);
// Update the list
adapter.remove(item);
adapter.notifyDataSetChanged();
positionAndShowPopup();
}
});
mAutoCompleteList.setOnTouchListener(touchListener);
// Setting this scroll listener is required to ensure that during ListView scrolling,
// we don't look for swipes.
mAutoCompleteList.setOnScrollListener(touchListener.makeScrollListener());
// Setting this recycler listener is required to make sure animated views are reset.
mAutoCompleteList.setRecyclerListener(touchListener.makeRecyclerListener());
addView(mAutoCompleteList);
}
AutoCompleteListAdapter adapter = new AutoCompleteListAdapter(mContext, R.layout.autocomplete_list_item);
adapter.populateSuggestionsList(suggestions);
mAutoCompleteList.setAdapter(adapter);
if (setGeckoPositionData(rect, true)) {
positionAndShowPopup();
}
}
private void showValidationMessage(String validationMessage, JSONObject rect) {
if (mValidationMessage == null) {
LayoutInflater inflater = LayoutInflater.from(mContext);
mValidationMessage = (RelativeLayout) inflater.inflate(R.layout.validation_message, null);
addView(mValidationMessage);
mValidationMessageText = (TextView) mValidationMessage.findViewById(R.id.validation_message_text);
sValidationTextMarginTop = (int) (mContext.getResources().getDimension(R.dimen.validation_message_margin_top));
sValidationTextLayoutNormal = new LayoutParams(mValidationMessageText.getLayoutParams());
sValidationTextLayoutNormal.setMargins(0, sValidationTextMarginTop, 0, 0);
sValidationTextLayoutInverted = new LayoutParams((ViewGroup.MarginLayoutParams) sValidationTextLayoutNormal);
sValidationTextLayoutInverted.setMargins(0, 0, 0, 0);
mValidationMessageArrow = (ImageView) mValidationMessage.findViewById(R.id.validation_message_arrow);
mValidationMessageArrowInverted = (ImageView) mValidationMessage.findViewById(R.id.validation_message_arrow_inverted);
}
mValidationMessageText.setText(validationMessage);
// We need to set the text as selected for the marquee text to work.
mValidationMessageText.setSelected(true);
if (setGeckoPositionData(rect, false)) {
positionAndShowPopup();
}
}
private boolean setGeckoPositionData(JSONObject rect, boolean isAutoComplete) {
try {
mX = rect.getDouble("x");
mY = rect.getDouble("y");
mW = rect.getDouble("w");
mH = rect.getDouble("h");
} catch (JSONException e) {
// Bail if we can't get the correct dimensions for the popup.
Log.e(LOGTAG, "Error getting FormAssistPopup dimensions", e);
return false;
}
mPopupType = (isAutoComplete ?
PopupType.AUTOCOMPLETE : PopupType.VALIDATIONMESSAGE);
return true;
}
private void positionAndShowPopup() {
positionAndShowPopup(GeckoAppShell.getLayerView().getViewportMetrics());
}
private void positionAndShowPopup(ImmutableViewportMetrics aMetrics) {
ThreadUtils.assertOnUiThread();
// Don't show the form assist popup when using fullscreen VKB
InputMethodManager imm =
(InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm.isFullscreenMode()) {
return;
}
// Hide/show the appropriate popup contents
if (mAutoCompleteList != null) {
mAutoCompleteList.setVisibility((mPopupType == PopupType.AUTOCOMPLETE) ? VISIBLE : GONE);
}
if (mValidationMessage != null) {
mValidationMessage.setVisibility((mPopupType == PopupType.AUTOCOMPLETE) ? GONE : VISIBLE);
}
if (sAutoCompleteMinWidth == 0) {
Resources res = mContext.getResources();
sAutoCompleteMinWidth = (int) (res.getDimension(R.dimen.autocomplete_min_width));
sAutoCompleteRowHeight = (int) (res.getDimension(R.dimen.autocomplete_row_height));
sValidationMessageHeight = (int) (res.getDimension(R.dimen.validation_message_height));
}
float zoom = aMetrics.zoomFactor;
// These values correspond to the input box for which we want to
// display the FormAssistPopup.
int left = (int) (mX * zoom - aMetrics.viewportRectLeft);
int top = (int) (mY * zoom - aMetrics.viewportRectTop + GeckoAppShell.getLayerView().getSurfaceTranslation());
int width = (int) (mW * zoom);
int height = (int) (mH * zoom);
int popupWidth = LayoutParams.MATCH_PARENT;
int popupLeft = left < 0 ? 0 : left;
FloatSize viewport = aMetrics.getSize();
// For autocomplete suggestions, if the input is smaller than the screen-width,
// shrink the popup's width. Otherwise, keep it as MATCH_PARENT.
if ((mPopupType == PopupType.AUTOCOMPLETE) && (left + width) < viewport.width) {
popupWidth = left < 0 ? left + width : width;
// Ensure the popup has a minimum width.
if (popupWidth < sAutoCompleteMinWidth) {
popupWidth = sAutoCompleteMinWidth;
// Move the popup to the left if there isn't enough room for it.
if ((popupLeft + popupWidth) > viewport.width) {
popupLeft = (int) (viewport.width - popupWidth);
}
}
}
int popupHeight;
if (mPopupType == PopupType.AUTOCOMPLETE) {
// Limit the amount of visible rows.
int rows = mAutoCompleteList.getAdapter().getCount();
if (rows > MAX_VISIBLE_ROWS) {
rows = MAX_VISIBLE_ROWS;
}
popupHeight = sAutoCompleteRowHeight * rows;
} else {
popupHeight = sValidationMessageHeight;
}
int popupTop = top + height;
if (mPopupType == PopupType.VALIDATIONMESSAGE) {
mValidationMessageText.setLayoutParams(sValidationTextLayoutNormal);
mValidationMessageArrow.setVisibility(VISIBLE);
mValidationMessageArrowInverted.setVisibility(GONE);
}
// If the popup doesn't fit below the input box, shrink its height, or
// see if we can place it above the input instead.
if ((popupTop + popupHeight) > viewport.height) {
// Find where the maximum space is, and put the popup there.
if ((viewport.height - popupTop) > top) {
// Shrink the height to fit it below the input box.
popupHeight = (int) (viewport.height - popupTop);
} else {
if (popupHeight < top) {
// No shrinking needed to fit on top.
popupTop = (top - popupHeight);
} else {
// Shrink to available space on top.
popupTop = 0;
popupHeight = top;
}
if (mPopupType == PopupType.VALIDATIONMESSAGE) {
mValidationMessageText.setLayoutParams(sValidationTextLayoutInverted);
mValidationMessageArrow.setVisibility(GONE);
mValidationMessageArrowInverted.setVisibility(VISIBLE);
}
}
}
LayoutParams layoutParams = new LayoutParams(popupWidth, popupHeight);
layoutParams.setMargins(popupLeft, popupTop, 0, 0);
setLayoutParams(layoutParams);
requestLayout();
if (!isShown()) {
setVisibility(VISIBLE);
startAnimation(mAnimation);
}
}
public void hide() {
if (isShown()) {
setVisibility(GONE);
broadcastGeckoEvent("FormAssist:Hidden", null);
}
}
void onTranslationChanged() {
ThreadUtils.assertOnUiThread();
if (!isShown()) {
return;
}
positionAndShowPopup();
}
void onMetricsChanged(final ImmutableViewportMetrics aMetrics) {
if (!isShown()) {
return;
}
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
positionAndShowPopup(aMetrics);
}
});
}
private static void broadcastGeckoEvent(String eventName, String eventData) {
GeckoAppShell.notifyObservers(eventName, eventData);
}
private class AutoCompleteListAdapter extends ArrayAdapter<Pair<String, String>> {
private final LayoutInflater mInflater;
private final int mTextViewResourceId;
public AutoCompleteListAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mTextViewResourceId = textViewResourceId;
}
// This method takes an array of autocomplete suggestions with label/value properties
// and adds label/value Pair objects to the array that backs the adapter.
public void populateSuggestionsList(JSONArray suggestions) {
try {
for (int i = 0; i < suggestions.length(); i++) {
JSONObject suggestion = suggestions.getJSONObject(i);
String label = suggestion.getString("label");
String value = suggestion.getString("value");
add(new Pair<String, String>(label, value));
}
} catch (JSONException e) {
Log.e(LOGTAG, "JSONException", e);
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(mTextViewResourceId, null);
}
Pair<String, String> item = getItem(position);
TextView itemView = (TextView) convertView;
// Set the text with the suggestion label
itemView.setText(item.first);
// Set a tag with the suggestion value
itemView.setTag(item.second);
return convertView;
}
}
}

View file

@ -1,100 +0,0 @@
/* 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.content.ComponentName;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
public abstract class GeckoActivity extends AppCompatActivity implements GeckoActivityStatus {
// has this activity recently started another Gecko activity?
private boolean mGeckoActivityOpened;
/**
* Display any resources that show strings or encompass locale-specific
* representations.
*
* onLocaleReady must always be called on the UI thread.
*/
public void onLocaleReady(final String locale) {
}
@Override
public void onPause() {
super.onPause();
if (getApplication() instanceof GeckoApplication) {
((GeckoApplication) getApplication()).onActivityPause(this);
}
}
@Override
public void onResume() {
super.onResume();
if (getApplication() instanceof GeckoApplication) {
((GeckoApplication) getApplication()).onActivityResume(this);
mGeckoActivityOpened = false;
}
}
@Override
public void onCreate(android.os.Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (AppConstants.MOZ_ANDROID_ANR_REPORTER) {
ANRReporter.register(getApplicationContext());
}
}
@Override
public void onDestroy() {
if (AppConstants.MOZ_ANDROID_ANR_REPORTER) {
ANRReporter.unregister();
}
super.onDestroy();
}
@Override
public void startActivity(Intent intent) {
mGeckoActivityOpened = checkIfGeckoActivity(intent);
super.startActivity(intent);
}
@Override
public void startActivityForResult(Intent intent, int request) {
mGeckoActivityOpened = checkIfGeckoActivity(intent);
super.startActivityForResult(intent, request);
}
private static boolean checkIfGeckoActivity(Intent intent) {
// Whenever we call our own activity, the component and its package name is set.
// If we call an activity from another package, or an open intent (leaving android to resolve)
// component has a different package name or it is null.
ComponentName component = intent.getComponent();
return (component != null &&
AppConstants.ANDROID_PACKAGE_NAME.equals(component.getPackageName()));
}
@Override
public boolean isGeckoActivityOpened() {
return mGeckoActivityOpened;
}
public boolean isApplicationInBackground() {
return ((GeckoApplication) getApplication()).isApplicationInBackground();
}
@Override
public void onLowMemory() {
MemoryMonitor.getInstance().onLowMemory();
super.onLowMemory();
}
@Override
public void onTrimMemory(int level) {
MemoryMonitor.getInstance().onTrimMemory(level);
super.onTrimMemory(level);
}
}

View file

@ -1,10 +0,0 @@
/* 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 GeckoActivityStatus {
public boolean isGeckoActivityOpened();
public boolean isFinishing(); // typically from android.app.Activity
};

File diff suppressed because it is too large Load diff

View file

@ -1,314 +0,0 @@
/* 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.Application;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.LocalBrowserDB;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.dlc.DownloadContentService;
import org.mozilla.gecko.home.HomePanelsManager;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.mdns.MulticastDNSManager;
import org.mozilla.gecko.media.AudioFocusAgent;
import org.mozilla.gecko.notifications.NotificationClient;
import org.mozilla.gecko.notifications.NotificationHelper;
import org.mozilla.gecko.preferences.DistroSharedPrefsImport;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.ThreadUtils;
import java.io.File;
import java.lang.reflect.Method;
public class GeckoApplication extends Application
implements ContextGetter {
private static final String LOG_TAG = "GeckoApplication";
private static volatile GeckoApplication instance;
private boolean mInBackground;
private boolean mPausedGecko;
private LightweightTheme mLightweightTheme;
private RefWatcher mRefWatcher;
public GeckoApplication() {
super();
instance = this;
}
public static GeckoApplication get() {
return instance;
}
public static RefWatcher getRefWatcher(Context context) {
GeckoApplication app = (GeckoApplication) context.getApplicationContext();
return app.mRefWatcher;
}
public static void watchReference(Context context, Object object) {
if (context == null) {
return;
}
getRefWatcher(context).watch(object);
}
@Override
public Context getContext() {
return this;
}
@Override
public SharedPreferences getSharedPreferences() {
return GeckoSharedPrefs.forApp(this);
}
/**
* We need to do locale work here, because we need to intercept
* each hit to onConfigurationChanged.
*/
@Override
public void onConfigurationChanged(Configuration config) {
Log.d(LOG_TAG, "onConfigurationChanged: " + config.locale +
", background: " + mInBackground);
// Do nothing if we're in the background. It'll simply cause a loop
// (Bug 936756 Comment 11), and it's not necessary.
if (mInBackground) {
super.onConfigurationChanged(config);
return;
}
// Otherwise, correct the locale. This catches some cases that GeckoApp
// doesn't get a chance to.
try {
BrowserLocaleManager.getInstance().correctLocale(this, getResources(), config);
} catch (IllegalStateException ex) {
// GeckoApp hasn't started, so we have no ContextGetter in BrowserLocaleManager.
Log.w(LOG_TAG, "Couldn't correct locale.", ex);
}
super.onConfigurationChanged(config);
}
public void onActivityPause(GeckoActivityStatus activity) {
mInBackground = true;
if ((activity.isFinishing() == false) &&
(activity.isGeckoActivityOpened() == false)) {
// Notify Gecko that we are pausing; the cache service will be
// shutdown, closing the disk cache cleanly. If the android
// low memory killer subsequently kills us, the disk cache will
// be left in a consistent state, avoiding costly cleanup and
// re-creation.
GeckoThread.onPause();
mPausedGecko = true;
final BrowserDB db = BrowserDB.from(this);
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
db.expireHistory(getContentResolver(), BrowserContract.ExpirePriority.NORMAL);
}
});
}
GeckoNetworkManager.getInstance().stop();
}
public void onActivityResume(GeckoActivityStatus activity) {
if (mPausedGecko) {
GeckoThread.onResume();
mPausedGecko = false;
}
GeckoBatteryManager.getInstance().start(this);
GeckoNetworkManager.getInstance().start(this);
mInBackground = false;
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
AppConstants.maybeInstallMultiDex(base);
}
@Override
public void onCreate() {
Log.i(LOG_TAG, "zerdatime " + SystemClock.uptimeMillis() + " - Fennec application start");
mRefWatcher = LeakCanary.install(this);
final Context context = getApplicationContext();
GeckoAppShell.setApplicationContext(context);
HardwareUtils.init(context);
Clipboard.init(context);
FilePicker.init(context);
DownloadsIntegration.init();
HomePanelsManager.getInstance().init(context);
GlobalPageMetadata.getInstance().init();
// We need to set the notification client before launching Gecko, since Gecko could start
// sending notifications immediately after startup, which we don't want to lose/crash on.
GeckoAppShell.setNotificationListener(new NotificationClient(context));
// This getInstance call will force initialization of the NotificationHelper, but does nothing with the result
NotificationHelper.getInstance(context).init();
MulticastDNSManager.getInstance(context).init();
GeckoService.register();
EventDispatcher.getInstance().registerBackgroundThreadListener(new EventListener(),
"Profile:Create");
super.onCreate();
}
public void onDelayedStartup() {
if (AppConstants.MOZ_ANDROID_GCM) {
// TODO: only run in main process.
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
// It's fine to throw GCM initialization onto a background thread; the registration process requires
// network access, so is naturally asynchronous. This, of course, races against Gecko page load of
// content requiring GCM-backed services, like Web Push. There's nothing to be done here.
try {
final Class<?> clazz = Class.forName("org.mozilla.gecko.push.PushService");
final Method onCreate = clazz.getMethod("onCreate", Context.class);
onCreate.invoke(null, getApplicationContext()); // Method is static.
} catch (Exception e) {
Log.e(LOG_TAG, "Got exception during startup; ignoring.", e);
return;
}
}
});
}
if (AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) {
DownloadContentService.startStudy(this);
}
GeckoAccessibility.setAccessibilityManagerListeners(this);
AudioFocusAgent.getInstance().attachToContext(this);
}
private class EventListener implements BundleEventListener
{
private void onProfileCreate(final String name, final String path) {
// Add everything when we're done loading the distribution.
final Context context = GeckoApplication.this;
final GeckoProfile profile = GeckoProfile.get(context, name);
final Distribution distribution = Distribution.getInstance(context);
distribution.addOnDistributionReadyCallback(new Distribution.ReadyCallback() {
@Override
public void distributionNotFound() {
this.distributionFound(null);
}
@Override
public void distributionFound(final Distribution distribution) {
Log.d(LOG_TAG, "Running post-distribution task: bookmarks.");
// Because we are running in the background, we want to synchronize on the
// GeckoProfile instance so that we don't race with main thread operations
// such as locking/unlocking/removing the profile.
synchronized (profile.getLock()) {
distributionFoundLocked(distribution);
}
}
@Override
public void distributionArrivedLate(final Distribution distribution) {
Log.d(LOG_TAG, "Running late distribution task: bookmarks.");
// Recover as best we can.
synchronized (profile.getLock()) {
distributionArrivedLateLocked(distribution);
}
}
private void distributionFoundLocked(final Distribution distribution) {
// Skip initialization if the profile directory has been removed.
if (!(new File(path)).exists()) {
return;
}
final ContentResolver cr = context.getContentResolver();
final LocalBrowserDB db = new LocalBrowserDB(profile.getName());
// We pass the number of added bookmarks to ensure that the
// indices of the distribution and default bookmarks are
// contiguous. Because there are always at least as many
// bookmarks as there are favicons, we can also guarantee that
// the favicon IDs won't overlap.
final int offset = distribution == null ? 0 :
db.addDistributionBookmarks(cr, distribution, 0);
db.addDefaultBookmarks(context, cr, offset);
Log.d(LOG_TAG, "Running post-distribution task: android preferences.");
DistroSharedPrefsImport.importPreferences(context, distribution);
}
private void distributionArrivedLateLocked(final Distribution distribution) {
// Skip initialization if the profile directory has been removed.
if (!(new File(path)).exists()) {
return;
}
final ContentResolver cr = context.getContentResolver();
final LocalBrowserDB db = new LocalBrowserDB(profile.getName());
// We assume we've been called very soon after startup, and so our offset
// into "Mobile Bookmarks" is the number of bookmarks in the DB.
final int offset = db.getCount(cr, "bookmarks");
db.addDistributionBookmarks(cr, distribution, offset);
Log.d(LOG_TAG, "Running late distribution task: android preferences.");
DistroSharedPrefsImport.importPreferences(context, distribution);
}
});
}
@Override // BundleEventListener
public void handleMessage(final String event, final Bundle message,
final EventCallback callback) {
if ("Profile:Create".equals(event)) {
onProfileCreate(message.getCharSequence("name").toString(),
message.getCharSequence("path").toString());
}
}
}
public boolean isApplicationInBackground() {
return mInBackground;
}
public LightweightTheme getLightweightTheme() {
return mLightweightTheme;
}
public void prepareLightweightTheme() {
mLightweightTheme = new LightweightTheme(this);
}
}

View file

@ -1,211 +0,0 @@
/* -*- 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;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import org.mozilla.gecko.annotation.WrapForJNI;
import java.lang.Thread;
import java.util.Set;
public class GeckoJavaSampler {
private static final String LOGTAG = "JavaSampler";
private static Thread sSamplingThread;
private static SamplingThread sSamplingRunnable;
private static Thread sMainThread;
// Use the same timer primitive as the profiler
// to get a perfect sample syncing.
@WrapForJNI
private static native double getProfilerTime();
private static class Sample {
public Frame[] mFrames;
public double mTime;
public long mJavaTime; // non-zero if Android system time is used
public Sample(StackTraceElement[] aStack) {
mFrames = new Frame[aStack.length];
if (GeckoThread.isStateAtLeast(GeckoThread.State.LIBS_READY)) {
mTime = getProfilerTime();
}
if (mTime == 0.0d) {
// getProfilerTime is not available yet; either libs are not loaded,
// or profiling hasn't started on the Gecko side yet
mJavaTime = SystemClock.elapsedRealtime();
}
for (int i = 0; i < aStack.length; i++) {
mFrames[aStack.length - 1 - i] = new Frame();
mFrames[aStack.length - 1 - i].fileName = aStack[i].getFileName();
mFrames[aStack.length - 1 - i].lineNo = aStack[i].getLineNumber();
mFrames[aStack.length - 1 - i].methodName = aStack[i].getMethodName();
mFrames[aStack.length - 1 - i].className = aStack[i].getClassName();
}
}
}
private static class Frame {
public String fileName;
public int lineNo;
public String methodName;
public String className;
}
private static class SamplingThread implements Runnable {
private final int mInterval;
private final int mSampleCount;
private boolean mPauseSampler;
private boolean mStopSampler;
private final SparseArray<Sample[]> mSamples = new SparseArray<Sample[]>();
private int mSamplePos;
public SamplingThread(final int aInterval, final int aSampleCount) {
// If we sample faster then 10ms we get to many missed samples
mInterval = Math.max(10, aInterval);
mSampleCount = aSampleCount;
}
@Override
public void run() {
synchronized (GeckoJavaSampler.class) {
mSamples.put(0, new Sample[mSampleCount]);
mSamplePos = 0;
// Find the main thread
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
for (Thread t : threadSet) {
if (t.getName().compareToIgnoreCase("main") == 0) {
sMainThread = t;
break;
}
}
if (sMainThread == null) {
Log.e(LOGTAG, "Main thread not found");
return;
}
}
while (true) {
try {
Thread.sleep(mInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (GeckoJavaSampler.class) {
if (!mPauseSampler) {
StackTraceElement[] bt = sMainThread.getStackTrace();
mSamples.get(0)[mSamplePos] = new Sample(bt);
mSamplePos = (mSamplePos + 1) % mSamples.get(0).length;
}
if (mStopSampler) {
break;
}
}
}
}
private Sample getSample(int aThreadId, int aSampleId) {
if (aThreadId < mSamples.size() && aSampleId < mSamples.get(aThreadId).length &&
mSamples.get(aThreadId)[aSampleId] != null) {
int startPos = 0;
if (mSamples.get(aThreadId)[mSamplePos] != null) {
startPos = mSamplePos;
}
int readPos = (startPos + aSampleId) % mSamples.get(aThreadId).length;
return mSamples.get(aThreadId)[readPos];
}
return null;
}
}
@WrapForJNI
public synchronized static String getThreadName(int aThreadId) {
if (aThreadId == 0 && sMainThread != null) {
return sMainThread.getName();
}
return null;
}
private synchronized static Sample getSample(int aThreadId, int aSampleId) {
return sSamplingRunnable.getSample(aThreadId, aSampleId);
}
@WrapForJNI
public synchronized static double getSampleTime(int aThreadId, int aSampleId) {
Sample sample = getSample(aThreadId, aSampleId);
if (sample != null) {
if (sample.mJavaTime != 0) {
return (sample.mJavaTime -
SystemClock.elapsedRealtime()) + getProfilerTime();
}
System.out.println("Sample: " + sample.mTime);
return sample.mTime;
}
return 0;
}
@WrapForJNI
public synchronized static String getFrameName(int aThreadId, int aSampleId, int aFrameId) {
Sample sample = getSample(aThreadId, aSampleId);
if (sample != null && aFrameId < sample.mFrames.length) {
Frame frame = sample.mFrames[aFrameId];
if (frame == null) {
return null;
}
return frame.className + "." + frame.methodName + "()";
}
return null;
}
@WrapForJNI
public static void start(int aInterval, int aSamples) {
synchronized (GeckoJavaSampler.class) {
if (sSamplingRunnable != null) {
return;
}
sSamplingRunnable = new SamplingThread(aInterval, aSamples);
sSamplingThread = new Thread(sSamplingRunnable, "Java Sampler");
sSamplingThread.start();
}
}
@WrapForJNI
public static void pause() {
synchronized (GeckoJavaSampler.class) {
sSamplingRunnable.mPauseSampler = true;
}
}
@WrapForJNI
public static void unpause() {
synchronized (GeckoJavaSampler.class) {
sSamplingRunnable.mPauseSampler = false;
}
}
@WrapForJNI
public static void stop() {
synchronized (GeckoJavaSampler.class) {
if (sSamplingThread == null) {
return;
}
sSamplingRunnable.mStopSampler = true;
try {
sSamplingThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
sSamplingThread = null;
sSamplingRunnable = null;
}
}
}

View file

@ -1,27 +0,0 @@
/* -*- 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;
import org.json.JSONObject;
import org.mozilla.gecko.util.EventCallback;
/**
* Wrapper for MediaRouter types supported by Android, such as Chromecast, Miracast, etc.
*/
interface GeckoMediaPlayer {
/**
* Can return null.
*/
JSONObject toJSON();
void load(String title, String url, String type, EventCallback callback);
void play(EventCallback callback);
void pause(EventCallback callback);
void stop(EventCallback callback);
void start(EventCallback callback);
void end(EventCallback callback);
void mirror(EventCallback callback);
void message(String message, EventCallback callback);
}

View file

@ -1,19 +0,0 @@
/* 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.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class GeckoMessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (GeckoApp.ACTION_INIT_PW.equals(action)) {
GeckoAppShell.notifyObservers("Passwords:Init", null);
}
}
}

View file

@ -1,22 +0,0 @@
/* -*- 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;
import org.json.JSONObject;
import org.mozilla.gecko.util.EventCallback;
/**
* Wrapper for MediaRouter types supported by Android to use for
* Presentation API, such as Chromecast, Miracast, etc.
*/
interface GeckoPresentationDisplay {
/**
* Can return null.
*/
JSONObject toJSON();
void start(EventCallback callback);
void stop(EventCallback callback);
}

View file

@ -1,149 +0,0 @@
/* 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.io.File;
import java.util.Map;
import java.util.Map.Entry;
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
import org.mozilla.gecko.db.BrowserContract;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.util.Log;
/**
* This is not a per-profile provider. This provider allows read-only,
* restricted access to certain attributes of Fennec profiles.
*/
public class GeckoProfilesProvider extends ContentProvider {
private static final String LOG_TAG = "GeckoProfilesProvider";
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int PROFILES = 100;
private static final int PROFILES_NAME = 101;
private static final int PROFILES_DEFAULT = 200;
private static final String[] DEFAULT_ARGS = {
BrowserContract.Profiles.NAME,
BrowserContract.Profiles.PATH,
};
static {
URI_MATCHER.addURI(BrowserContract.PROFILES_AUTHORITY, "profiles", PROFILES);
URI_MATCHER.addURI(BrowserContract.PROFILES_AUTHORITY, "profiles/*", PROFILES_NAME);
URI_MATCHER.addURI(BrowserContract.PROFILES_AUTHORITY, "default", PROFILES_DEFAULT);
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public boolean onCreate() {
// Successfully loaded.
return true;
}
private String[] profileValues(final String name, final String path, int len, int nameIndex, int pathIndex) {
final String[] values = new String[len];
if (nameIndex >= 0) {
values[nameIndex] = name;
}
if (pathIndex >= 0) {
values[pathIndex] = path;
}
return values;
}
protected void addRowForProfile(final MatrixCursor cursor, final int len, final int nameIndex, final int pathIndex, final String name, final String path) {
if (path == null || name == null) {
return;
}
cursor.addRow(profileValues(name, path, len, nameIndex, pathIndex));
}
protected Cursor getCursorForProfiles(final String[] args, Map<String, String> profiles) {
// Compute the projection.
int nameIndex = -1;
int pathIndex = -1;
for (int i = 0; i < args.length; ++i) {
if (BrowserContract.Profiles.NAME.equals(args[i])) {
nameIndex = i;
} else if (BrowserContract.Profiles.PATH.equals(args[i])) {
pathIndex = i;
}
}
final MatrixCursor cursor = new MatrixCursor(args);
for (Entry<String, String> entry : profiles.entrySet()) {
addRowForProfile(cursor, args.length, nameIndex, pathIndex, entry.getKey(), entry.getValue());
}
return cursor;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
final String[] args = (projection == null) ? DEFAULT_ARGS : projection;
final File mozillaDir;
try {
mozillaDir = GeckoProfileDirectories.getMozillaDirectory(getContext());
} catch (NoMozillaDirectoryException e) {
Log.d(LOG_TAG, "No Mozilla directory; cannot query for profiles. Assuming there are none.");
return new MatrixCursor(projection);
}
final Map<String, String> matchingProfiles;
final int match = URI_MATCHER.match(uri);
switch (match) {
case PROFILES:
// Return all profiles.
matchingProfiles = GeckoProfileDirectories.getAllProfiles(mozillaDir);
break;
case PROFILES_NAME:
// Return data about the specified profile.
final String name = uri.getLastPathSegment();
matchingProfiles = GeckoProfileDirectories.getProfilesNamed(mozillaDir,
name);
break;
case PROFILES_DEFAULT:
matchingProfiles = GeckoProfileDirectories.getDefaultProfile(mozillaDir);
break;
default:
throw new UnsupportedOperationException("Unknown query URI " + uri);
}
return getCursorForProfiles(args, matchingProfiles);
}
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new IllegalStateException("Inserts not supported.");
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new IllegalStateException("Deletes not supported.");
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
throw new IllegalStateException("Updates not supported.");
}
}

View file

@ -1,236 +0,0 @@
/* -*- 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.AlarmManager;
import android.app.Service;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.io.File;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.EventCallback;
public class GeckoService extends Service {
private static final String LOGTAG = "GeckoService";
private static final boolean DEBUG = false;
private static final String INTENT_PROFILE_NAME = "org.mozilla.gecko.intent.PROFILE_NAME";
private static final String INTENT_PROFILE_DIR = "org.mozilla.gecko.intent.PROFILE_DIR";
private static final String INTENT_ACTION_UPDATE_ADDONS = "update-addons";
private static final String INTENT_ACTION_CREATE_SERVICES = "create-services";
private static final String INTENT_SERVICE_CATEGORY = "category";
private static final String INTENT_SERVICE_DATA = "data";
private static class EventListener implements NativeEventListener {
@Override // NativeEventListener
public void handleMessage(final String event,
final NativeJSObject message,
final EventCallback callback) {
final Context context = GeckoAppShell.getApplicationContext();
switch (event) {
case "Gecko:ScheduleRun":
if (DEBUG) {
Log.d(LOGTAG, "Scheduling " + message.getString("action") +
" @ " + message.getInt("interval") + "ms");
}
final Intent intent = getIntentForAction(context, message.getString("action"));
final PendingIntent pendingIntent = PendingIntent.getService(
context, /* requestCode */ 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
final AlarmManager am = (AlarmManager)
context.getSystemService(Context.ALARM_SERVICE);
// Cancel any previous alarm and schedule a new one.
am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
message.getInt("trigger"),
message.getInt("interval"),
pendingIntent);
break;
default:
throw new UnsupportedOperationException(event);
}
}
}
private static final EventListener EVENT_LISTENER = new EventListener();
public static void register() {
if (DEBUG) {
Log.d(LOGTAG, "Registered listener");
}
EventDispatcher.getInstance().registerGeckoThreadListener(EVENT_LISTENER,
"Gecko:ScheduleRun");
}
public static void unregister() {
if (DEBUG) {
Log.d(LOGTAG, "Unregistered listener");
}
EventDispatcher.getInstance().unregisterGeckoThreadListener(EVENT_LISTENER,
"Gecko:ScheduleRun");
}
@Override // Service
public void onCreate() {
GeckoAppShell.ensureCrashHandling();
GeckoThread.onResume();
super.onCreate();
if (DEBUG) {
Log.d(LOGTAG, "Created");
}
}
@Override // Service
public void onDestroy() {
GeckoThread.onPause();
// We want to block here if we can, so we don't get killed when Gecko is in the
// middle of handling onPause().
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
GeckoThread.waitOnGecko();
}
if (DEBUG) {
Log.d(LOGTAG, "Destroyed");
}
super.onDestroy();
}
private static Intent getIntentForAction(final Context context, final String action) {
final Intent intent = new Intent(action, /* uri */ null, context, GeckoService.class);
final GeckoProfile profile = GeckoThread.getActiveProfile();
if (profile != null) {
setIntentProfile(intent, profile.getName(), profile.getDir().getAbsolutePath());
}
return intent;
}
public static Intent getIntentToCreateServices(final Context context, final String category, final String data) {
final Intent intent = getIntentForAction(context, INTENT_ACTION_CREATE_SERVICES);
intent.putExtra(INTENT_SERVICE_CATEGORY, category);
intent.putExtra(INTENT_SERVICE_DATA, data);
return intent;
}
public static Intent getIntentToCreateServices(final Context context, final String category) {
return getIntentToCreateServices(context, category, /* data */ null);
}
public static void setIntentProfile(final Intent intent, final String profileName,
final String profileDir) {
intent.putExtra(INTENT_PROFILE_NAME, profileName);
intent.putExtra(INTENT_PROFILE_DIR, profileDir);
}
private int handleIntent(final Intent intent, final int startId) {
if (DEBUG) {
Log.d(LOGTAG, "Handling " + intent.getAction());
}
final String profileName = intent.getStringExtra(INTENT_PROFILE_NAME);
final String profileDir = intent.getStringExtra(INTENT_PROFILE_DIR);
if (profileName == null) {
throw new IllegalArgumentException("Intent must specify profile.");
}
if (!GeckoThread.initWithProfile(profileName != null ? profileName : "",
profileDir != null ? new File(profileDir) : null)) {
Log.w(LOGTAG, "Ignoring due to profile mismatch: " +
profileName + " [" + profileDir + ']');
final GeckoProfile profile = GeckoThread.getActiveProfile();
if (profile != null) {
Log.w(LOGTAG, "Current profile is " + profile.getName() +
" [" + profile.getDir().getAbsolutePath() + ']');
}
stopSelf(startId);
return Service.START_NOT_STICKY;
}
GeckoThread.launch();
switch (intent.getAction()) {
case INTENT_ACTION_UPDATE_ADDONS:
// Run the add-on update service. Because the service is automatically invoked
// when loading Gecko, we don't have to do anything else here.
break;
case INTENT_ACTION_CREATE_SERVICES:
final String category = intent.getStringExtra(INTENT_SERVICE_CATEGORY);
final String data = intent.getStringExtra(INTENT_SERVICE_DATA);
if (category == null) {
break;
}
GeckoThread.createServices(category, data);
break;
default:
Log.w(LOGTAG, "Unknown request: " + intent);
}
stopSelf(startId);
return Service.START_NOT_STICKY;
}
@Override // Service
public int onStartCommand(final Intent intent, final int flags, final int startId) {
if (intent == null) {
return Service.START_NOT_STICKY;
}
try {
return handleIntent(intent, startId);
} catch (final Throwable e) {
Log.e(LOGTAG, "Cannot handle intent: " + intent, e);
return Service.START_NOT_STICKY;
}
}
@Override // Service
public IBinder onBind(final Intent intent) {
return null;
}
public static void startGecko(final GeckoProfile profile, final String args, final Context context) {
if (GeckoThread.isLaunched()) {
if (DEBUG) {
Log.v(LOGTAG, "already launched");
}
return;
}
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
GeckoAppShell.ensureCrashHandling();
GeckoAppShell.setApplicationContext(context);
GeckoThread.onResume();
GeckoThread.init(profile, args, null, false);
GeckoThread.launch();
if (DEBUG) {
Log.v(LOGTAG, "warmed up (launched)");
}
}
});
}
}

View file

@ -1,25 +0,0 @@
/* -*- 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.updater.UpdateServiceHelper;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class GeckoUpdateReceiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent) {
if (UpdateServiceHelper.ACTION_CHECK_UPDATE_RESULT.equals(intent.getAction())) {
String result = intent.getStringExtra("result");
if (GeckoAppShell.getGeckoInterface() != null && result != null) {
GeckoAppShell.getGeckoInterface().notifyCheckUpdateResult(result);
}
}
}
}

View file

@ -1,178 +0,0 @@
/* -*- 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 java.lang.ref.SoftReference;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.reader.ReaderModeUtils;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
class GlobalHistory {
private static final String LOGTAG = "GeckoGlobalHistory";
public static final String EVENT_URI_AVAILABLE_IN_HISTORY = "URI_INSERTED_TO_HISTORY";
public static final String EVENT_PARAM_URI = "uri";
private static final String TELEMETRY_HISTOGRAM_ADD = "FENNEC_GLOBALHISTORY_ADD_MS";
private static final String TELEMETRY_HISTOGRAM_UPDATE = "FENNEC_GLOBALHISTORY_UPDATE_MS";
private static final String TELEMETRY_HISTOGRAM_BUILD_VISITED_LINK = "FENNEC_GLOBALHISTORY_VISITED_BUILD_MS";
private static final GlobalHistory sInstance = new GlobalHistory();
static GlobalHistory getInstance() {
return sInstance;
}
// this is the delay between receiving a URI check request and processing it.
// this allows batching together multiple requests and processing them together,
// which is more efficient.
private static final long BATCHING_DELAY_MS = 100;
private final Handler mHandler; // a background thread on which we can process requests
// Note: These fields are accessed through the NotificationRunnable inner class.
final Queue<String> mPendingUris; // URIs that need to be checked
SoftReference<Set<String>> mVisitedCache; // cache of the visited URI list
boolean mProcessing; // = false // whether or not the runnable is queued/working
private class NotifierRunnable implements Runnable {
private final ContentResolver mContentResolver;
private final BrowserDB mDB;
public NotifierRunnable(final Context context) {
mContentResolver = context.getContentResolver();
mDB = BrowserDB.from(context);
}
@Override
public void run() {
Set<String> visitedSet = mVisitedCache.get();
if (visitedSet == null) {
// The cache was wiped. Repopulate it.
Log.w(LOGTAG, "Rebuilding visited link set...");
final long start = SystemClock.uptimeMillis();
final Cursor c = mDB.getAllVisitedHistory(mContentResolver);
if (c == null) {
return;
}
try {
visitedSet = new HashSet<String>();
if (c.moveToFirst()) {
do {
visitedSet.add(c.getString(0));
} while (c.moveToNext());
}
mVisitedCache = new SoftReference<Set<String>>(visitedSet);
final long end = SystemClock.uptimeMillis();
final long took = end - start;
Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_BUILD_VISITED_LINK, (int) Math.min(took, Integer.MAX_VALUE));
} finally {
c.close();
}
}
// This runs on the same handler thread as the checkUriVisited code,
// so no synchronization is needed.
while (true) {
final String uri = mPendingUris.poll();
if (uri == null) {
break;
}
if (visitedSet.contains(uri)) {
GeckoAppShell.notifyUriVisited(uri);
}
}
mProcessing = false;
}
};
private GlobalHistory() {
mHandler = ThreadUtils.getBackgroundHandler();
mPendingUris = new LinkedList<String>();
mVisitedCache = new SoftReference<Set<String>>(null);
}
public void addToGeckoOnly(String uri) {
Set<String> visitedSet = mVisitedCache.get();
if (visitedSet != null) {
visitedSet.add(uri);
}
GeckoAppShell.notifyUriVisited(uri);
}
public void add(final Context context, final BrowserDB db, String uri) {
ThreadUtils.assertOnBackgroundThread();
final long start = SystemClock.uptimeMillis();
// stripAboutReaderUrl only removes about:reader if present, in all other cases the original string is returned
final String uriToStore = ReaderModeUtils.stripAboutReaderUrl(uri);
db.updateVisitedHistory(context.getContentResolver(), uriToStore);
final long end = SystemClock.uptimeMillis();
final long took = end - start;
Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_ADD, (int) Math.min(took, Integer.MAX_VALUE));
addToGeckoOnly(uriToStore);
dispatchUriAvailableMessage(uri);
}
@SuppressWarnings("static-method")
public void update(final ContentResolver cr, final BrowserDB db, String uri, String title) {
ThreadUtils.assertOnBackgroundThread();
final long start = SystemClock.uptimeMillis();
final String uriToStore = ReaderModeUtils.stripAboutReaderUrl(uri);
db.updateHistoryTitle(cr, uriToStore, title);
final long end = SystemClock.uptimeMillis();
final long took = end - start;
Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_UPDATE, (int) Math.min(took, Integer.MAX_VALUE));
}
public void checkUriVisited(final String uri) {
final String storedURI = ReaderModeUtils.stripAboutReaderUrl(uri);
final NotifierRunnable runnable = new NotifierRunnable(GeckoAppShell.getContext());
mHandler.post(new Runnable() {
@Override
public void run() {
// this runs on the same handler thread as the processing loop,
// so no synchronization needed
mPendingUris.add(storedURI);
if (mProcessing) {
// there's already a runnable queued up or working away, so
// no need to post another
return;
}
mProcessing = true;
mHandler.postDelayed(runnable, BATCHING_DELAY_MS);
}
});
}
private void dispatchUriAvailableMessage(String uri) {
final Bundle message = new Bundle();
message.putString(EVENT_PARAM_URI, uri);
EventDispatcher.getInstance().dispatch(EVENT_URI_AVAILABLE_IN_HISTORY, message);
}
}

View file

@ -1,182 +0,0 @@
/* 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.content.ContentProviderClient;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.ThreadUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Provides access to metadata information about websites.
*
* While storing, in case of timing issues preventing us from looking up History GUID by a given uri,
* we queue up metadata and wait for GlobalHistory to let us know history record is now available.
*
* TODO Bug 1313515: selection of metadata for a given uri/history_GUID
*
* @author grisha
*/
/* package-local */ class GlobalPageMetadata implements BundleEventListener {
private static final String LOG_TAG = "GeckoGlobalPageMetadata";
private static final GlobalPageMetadata instance = new GlobalPageMetadata();
private static final String KEY_HAS_IMAGE = "hasImage";
private static final String KEY_METADATA_JSON = "metadataJSON";
private static final int MAX_METADATA_QUEUE_SIZE = 15;
private final Map<String, Bundle> queuedMetadata = Collections.synchronizedMap(new LimitedLinkedHashMap<String, Bundle>());
public static GlobalPageMetadata getInstance() {
return instance;
}
private static class LimitedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = 6359725112736360244L;
@Override
protected boolean removeEldestEntry(Entry<K, V> eldest) {
if (size() > MAX_METADATA_QUEUE_SIZE) {
Log.w(LOG_TAG, "Page metadata queue is full. Dropping oldest metadata.");
return true;
}
return false;
}
}
private GlobalPageMetadata() {}
public void init() {
EventDispatcher
.getInstance()
.registerBackgroundThreadListener(this, GlobalHistory.EVENT_URI_AVAILABLE_IN_HISTORY);
}
public void add(BrowserDB db, ContentProviderClient contentProviderClient, String uri, boolean hasImage, @NonNull String metadataJSON) {
ThreadUtils.assertOnBackgroundThread();
// NB: Other than checking that JSON is valid and trimming it,
// we do not process metadataJSON in any way, trusting our source.
doAddOrQueue(db, contentProviderClient, uri, hasImage, metadataJSON);
}
@VisibleForTesting
/*package-local */ void doAddOrQueue(BrowserDB db, ContentProviderClient contentProviderClient, String uri, boolean hasImage, @NonNull String metadataJSON) {
final String preparedMetadataJSON;
try {
preparedMetadataJSON = prepareJSON(metadataJSON);
} catch (JSONException e) {
Log.e(LOG_TAG, "Couldn't process metadata JSON", e);
return;
}
// Don't bother queuing this if deletions fails to find a corresponding history record.
// If we can't delete metadata because it didn't exist yet, that's OK.
if (preparedMetadataJSON.equals("{}")) {
final int deleted = db.deletePageMetadata(contentProviderClient, uri);
// We could delete none if history record for uri isn't present.
// We must delete one if history record for uri is present.
if (deleted != 0 && deleted != 1) {
throw new IllegalStateException("Deleted unexpected number of page metadata records: " + deleted);
}
return;
}
// If we could insert page metadata, we're done.
if (db.insertPageMetadata(contentProviderClient, uri, hasImage, preparedMetadataJSON)) {
return;
}
// Otherwise, we need to queue it for future insertion when history record is available.
Bundle bundledMetadata = new Bundle();
bundledMetadata.putBoolean(KEY_HAS_IMAGE, hasImage);
bundledMetadata.putString(KEY_METADATA_JSON, preparedMetadataJSON);
queuedMetadata.put(uri, bundledMetadata);
}
@VisibleForTesting
/* package-local */ int getMetadataQueueSize() {
return queuedMetadata.size();
}
@Override
public void handleMessage(String event, Bundle message, EventCallback callback) {
ThreadUtils.assertOnBackgroundThread();
if (!GlobalHistory.EVENT_URI_AVAILABLE_IN_HISTORY.equals(event)) {
return;
}
final String uri = message.getString(GlobalHistory.EVENT_PARAM_URI);
if (TextUtils.isEmpty(uri)) {
return;
}
final Bundle bundledMetadata;
synchronized (queuedMetadata) {
if (!queuedMetadata.containsKey(uri)) {
return;
}
bundledMetadata = queuedMetadata.get(uri);
queuedMetadata.remove(uri);
}
insertMetadataBundleForUri(uri, bundledMetadata);
}
private void insertMetadataBundleForUri(String uri, Bundle bundledMetadata) {
final boolean hasImage = bundledMetadata.getBoolean(KEY_HAS_IMAGE);
final String metadataJSON = bundledMetadata.getString(KEY_METADATA_JSON);
// Acquire CPC, must be released in this function.
final ContentProviderClient contentProviderClient = GeckoAppShell.getApplicationContext()
.getContentResolver()
.acquireContentProviderClient(BrowserContract.PageMetadata.CONTENT_URI);
// Pre-conditions...
if (contentProviderClient == null) {
Log.e(LOG_TAG, "Couldn't acquire content provider client");
return;
}
if (TextUtils.isEmpty(metadataJSON)) {
Log.e(LOG_TAG, "Metadata bundle contained empty metadata json");
return;
}
// Insert!
try {
add(
BrowserDB.from(GeckoThread.getActiveProfile()),
contentProviderClient,
uri, hasImage, metadataJSON
);
} finally {
contentProviderClient.release();
}
}
private String prepareJSON(String json) throws JSONException {
return (new JSONObject(json)).toString();
}
}

View file

@ -1,51 +0,0 @@
/* -*- 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.KeyguardManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.support.v4.app.NotificationCompat;
import android.view.Window;
import android.view.WindowManager;
// Utility methods for entering/exiting guest mode.
public final class GuestSession {
private static final String LOGTAG = "GeckoGuestSession";
public static final String NOTIFICATION_INTENT = "org.mozilla.gecko.GUEST_SESSION_INPROGRESS";
private static PendingIntent getNotificationIntent(Context context) {
Intent intent = new Intent(NOTIFICATION_INTENT);
intent.setClassName(context, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
public static void showNotification(Context context) {
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
final Resources res = context.getResources();
builder.setContentTitle(res.getString(R.string.guest_browsing_notification_title))
.setContentText(res.getString(R.string.guest_browsing_notification_text))
.setSmallIcon(R.drawable.alert_guest)
.setOngoing(true)
.setContentIntent(getNotificationIntent(context));
final NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(R.id.guestNotification, builder.build());
}
public static void hideNotification(Context context) {
final NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.cancel(R.id.guestNotification);
}
public static void onNotificationIntentReceived(BrowserApp context) {
context.showGuestModeDialog(BrowserApp.GuestModeDialog.LEAVING);
}
}

View file

@ -1,599 +0,0 @@
/* -*- 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;
import org.mozilla.gecko.overlays.ui.ShareDialog;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.JSONUtils;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.widget.ExternalIntentDuringPrivateBrowsingPromptFragment;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.provider.Browser;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
public final class IntentHelper implements GeckoEventListener,
NativeEventListener {
private static final String LOGTAG = "GeckoIntentHelper";
private static final String[] EVENTS = {
"Intent:GetHandlers",
"Intent:Open",
"Intent:OpenForResult",
};
private static final String[] NATIVE_EVENTS = {
"Intent:OpenNoHandler",
};
// via http://developer.android.com/distribute/tools/promote/linking.html
private static String MARKET_INTENT_URI_PACKAGE_PREFIX = "market://details?id=";
private static String EXTRA_BROWSER_FALLBACK_URL = "browser_fallback_url";
/** A partial URI to an error page - the encoded error URI should be appended before loading. */
private static String UNKNOWN_PROTOCOL_URI_PREFIX = "about:neterror?e=unknownProtocolFound&u=";
private static IntentHelper instance;
private final FragmentActivity activity;
private IntentHelper(final FragmentActivity activity) {
this.activity = activity;
EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener) this, EVENTS);
EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this, NATIVE_EVENTS);
}
public static IntentHelper init(final FragmentActivity activity) {
if (instance == null) {
instance = new IntentHelper(activity);
} else {
Log.w(LOGTAG, "IntentHelper.init() called twice, ignoring.");
}
return instance;
}
public static void destroy() {
if (instance != null) {
EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) instance, EVENTS);
EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) instance, NATIVE_EVENTS);
instance = null;
}
}
/**
* Given the inputs to <code>getOpenURIIntent</code>, plus an optional
* package name and class name, create and fire an intent to open the
* provided URI. If a class name is specified but a package name is not,
* we will default to using the current fennec package.
*
* @param targetURI the string spec of the URI to open.
* @param mimeType an optional MIME type string.
* @param packageName an optional app package name.
* @param className an optional intent class name.
* @param action an Android action specifier, such as
* <code>Intent.ACTION_SEND</code>.
* @param title the title to use in <code>ACTION_SEND</code> intents.
* @param showPromptInPrivateBrowsing whether or not the user should be prompted when opening
* this uri from private browsing. This should be true
* when the user doesn't explicitly choose to open an an
* external app (e.g. just clicked a link).
* @return true if the activity started successfully or the user was prompted to open the
* application; false otherwise.
*/
public static boolean openUriExternal(String targetURI,
String mimeType,
String packageName,
String className,
String action,
String title,
final boolean showPromptInPrivateBrowsing) {
final GeckoAppShell.GeckoInterface gi = GeckoAppShell.getGeckoInterface();
final Context activityContext = gi != null ? gi.getActivity() : null;
final Context context = activityContext != null ? activityContext : GeckoAppShell.getApplicationContext();
final Intent intent = getOpenURIIntent(context, targetURI,
mimeType, action, title);
if (intent == null) {
return false;
}
if (!TextUtils.isEmpty(className)) {
if (!TextUtils.isEmpty(packageName)) {
intent.setClassName(packageName, className);
} else {
// Default to using the fennec app context.
intent.setClassName(context, className);
}
}
if (!showPromptInPrivateBrowsing || activityContext == null) {
if (activityContext == null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
return ActivityHandlerHelper.startIntentAndCatch(LOGTAG, context, intent);
} else {
// Ideally we retrieve the Activity from the calling args, rather than
// statically, but since this method is called from Gecko and I'm
// unfamiliar with that code, this is a simpler solution.
final FragmentActivity fragmentActivity = (FragmentActivity) activityContext;
return ExternalIntentDuringPrivateBrowsingPromptFragment.showDialogOrAndroidChooser(
context, fragmentActivity.getSupportFragmentManager(), intent);
}
}
public static boolean hasHandlersForIntent(Intent intent) {
try {
return !GeckoAppShell.queryIntentActivities(intent).isEmpty();
} catch (Exception ex) {
Log.e(LOGTAG, "Exception in hasHandlersForIntent");
return false;
}
}
public static String[] getHandlersForIntent(Intent intent) {
final PackageManager pm = GeckoAppShell.getApplicationContext().getPackageManager();
try {
final List<ResolveInfo> list = GeckoAppShell.queryIntentActivities(intent);
int numAttr = 4;
final String[] ret = new String[list.size() * numAttr];
for (int i = 0; i < list.size(); i++) {
ResolveInfo resolveInfo = list.get(i);
ret[i * numAttr] = resolveInfo.loadLabel(pm).toString();
if (resolveInfo.isDefault)
ret[i * numAttr + 1] = "default";
else
ret[i * numAttr + 1] = "";
ret[i * numAttr + 2] = resolveInfo.activityInfo.applicationInfo.packageName;
ret[i * numAttr + 3] = resolveInfo.activityInfo.name;
}
return ret;
} catch (Exception ex) {
Log.e(LOGTAG, "Exception in getHandlersForIntent");
return new String[0];
}
}
public static Intent getIntentForActionString(String aAction) {
// Default to the view action if no other action as been specified.
if (TextUtils.isEmpty(aAction)) {
return new Intent(Intent.ACTION_VIEW);
}
return new Intent(aAction);
}
/**
* Given a URI, a MIME type, and a title,
* produce a share intent which can be used to query all activities
* than can open the specified URI.
*
* @param context a <code>Context</code> instance.
* @param targetURI the string spec of the URI to open.
* @param mimeType an optional MIME type string.
* @param title the title to use in <code>ACTION_SEND</code> intents.
* @return an <code>Intent</code>, or <code>null</code> if none could be
* produced.
*/
public static Intent getShareIntent(final Context context,
final String targetURI,
final String mimeType,
final String title) {
Intent shareIntent = getIntentForActionString(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, targetURI);
shareIntent.putExtra(Intent.EXTRA_SUBJECT, title);
shareIntent.putExtra(ShareDialog.INTENT_EXTRA_DEVICES_ONLY, true);
// Note that EXTRA_TITLE is intended to be used for share dialog
// titles. Common usage (e.g., Pocket) suggests that it's sometimes
// interpreted as an alternate to EXTRA_SUBJECT, so we include it.
shareIntent.putExtra(Intent.EXTRA_TITLE, title);
if (mimeType != null && mimeType.length() > 0) {
shareIntent.setType(mimeType);
}
return shareIntent;
}
/**
* Given a URI, a MIME type, an Android intent "action", and a title,
* produce an intent which can be used to start an activity to open
* the specified URI.
*
* @param context a <code>Context</code> instance.
* @param targetURI the string spec of the URI to open.
* @param mimeType an optional MIME type string.
* @param action an Android action specifier, such as
* <code>Intent.ACTION_SEND</code>.
* @param title the title to use in <code>ACTION_SEND</code> intents.
* @return an <code>Intent</code>, or <code>null</code> if none could be
* produced.
*/
static Intent getOpenURIIntent(final Context context,
final String targetURI,
final String mimeType,
final String action,
final String title) {
// The resultant chooser can return non-exported activities in 4.1 and earlier.
// https://code.google.com/p/android/issues/detail?id=29535
final Intent intent = getOpenURIIntentInner(context, targetURI, mimeType, action, title);
if (intent != null) {
// Some applications use this field to return to the same browser after processing the
// Intent. While there is some danger (e.g. denial of service), other major browsers already
// use it and so it's the norm.
intent.putExtra(Browser.EXTRA_APPLICATION_ID, AppConstants.ANDROID_PACKAGE_NAME);
}
return intent;
}
private static Intent getOpenURIIntentInner(final Context context, final String targetURI,
final String mimeType, final String action, final String title) {
if (action.equalsIgnoreCase(Intent.ACTION_SEND)) {
Intent shareIntent = getShareIntent(context, targetURI, mimeType, title);
return Intent.createChooser(shareIntent,
context.getResources().getString(R.string.share_title));
}
Uri uri = normalizeUriScheme(targetURI.indexOf(':') >= 0 ? Uri.parse(targetURI) : new Uri.Builder().scheme(targetURI).build());
if (!TextUtils.isEmpty(mimeType)) {
Intent intent = getIntentForActionString(action);
intent.setDataAndType(uri, mimeType);
return intent;
}
if (!GeckoAppShell.isUriSafeForScheme(uri)) {
return null;
}
final String scheme = uri.getScheme();
if ("intent".equals(scheme) || "android-app".equals(scheme)) {
final Intent intent;
try {
intent = Intent.parseUri(targetURI, 0);
} catch (final URISyntaxException e) {
Log.e(LOGTAG, "Unable to parse URI - " + e);
return null;
}
final Uri data = intent.getData();
if (data != null && "file".equals(data.normalizeScheme().getScheme())) {
Log.w(LOGTAG, "Blocked intent with \"file://\" data scheme.");
return null;
}
// Only open applications which can accept arbitrary data from a browser.
intent.addCategory(Intent.CATEGORY_BROWSABLE);
// Prevent site from explicitly opening our internal activities, which can leak data.
intent.setComponent(null);
nullIntentSelector(intent);
return intent;
}
// Compute our most likely intent, then check to see if there are any
// custom handlers that would apply.
// Start with the original URI. If we end up modifying it, we'll
// overwrite it.
final String extension = MimeTypeMap.getFileExtensionFromUrl(targetURI);
final Intent intent = getIntentForActionString(action);
intent.setData(uri);
if ("file".equals(scheme)) {
// Only set explicit mimeTypes on file://.
final String mimeType2 = GeckoAppShell.getMimeTypeFromExtension(extension);
intent.setType(mimeType2);
return intent;
}
// Have a special handling for SMS based schemes, as the query parameters
// are not extracted from the URI automatically.
if (!"sms".equals(scheme) && !"smsto".equals(scheme) && !"mms".equals(scheme) && !"mmsto".equals(scheme)) {
return intent;
}
final String query = uri.getEncodedQuery();
if (TextUtils.isEmpty(query)) {
return intent;
}
// It is common to see sms*/mms* uris on the web without '//', it is W3C standard not to have the slashes,
// but android's Uri builder & Uri require the slashes and will interpret those without as malformed.
String currentUri = uri.toString();
String correctlyFormattedDataURIScheme = scheme + "://";
if (!currentUri.contains(correctlyFormattedDataURIScheme)) {
uri = Uri.parse(currentUri.replaceFirst(scheme + ":", correctlyFormattedDataURIScheme));
}
final String[] fields = query.split("&");
boolean shouldUpdateIntent = false;
String resultQuery = "";
for (String field : fields) {
if (field.startsWith("body=")) {
final String body = Uri.decode(field.substring(5));
intent.putExtra("sms_body", body);
shouldUpdateIntent = true;
} else if (field.startsWith("subject=")) {
final String subject = Uri.decode(field.substring(8));
intent.putExtra("subject", subject);
shouldUpdateIntent = true;
} else if (field.startsWith("cc=")) {
final String ccNumber = Uri.decode(field.substring(3));
String phoneNumber = uri.getAuthority();
if (phoneNumber != null) {
uri = uri.buildUpon().encodedAuthority(phoneNumber + ";" + ccNumber).build();
}
shouldUpdateIntent = true;
} else {
resultQuery = resultQuery.concat(resultQuery.length() > 0 ? "&" + field : field);
}
}
if (!shouldUpdateIntent) {
// No need to rewrite the URI, then.
return intent;
}
// Form a new URI without the extracted fields in the query part, and
// push that into the new Intent.
final String newQuery = resultQuery.length() > 0 ? "?" + resultQuery : "";
final Uri pruned = uri.buildUpon().encodedQuery(newQuery).build();
intent.setData(pruned);
return intent;
}
// We create a separate method to better encapsulate the @TargetApi use.
@TargetApi(15)
private static void nullIntentSelector(final Intent intent) {
intent.setSelector(null);
}
/**
* Return a <code>Uri</code> instance which is equivalent to <code>u</code>,
* but with a guaranteed-lowercase scheme as if the API level 16 method
* <code>u.normalizeScheme</code> had been called.
*
* @param u the <code>Uri</code> to normalize.
* @return a <code>Uri</code>, which might be <code>u</code>.
*/
private static Uri normalizeUriScheme(final Uri u) {
final String scheme = u.getScheme();
final String lower = scheme.toLowerCase(Locale.US);
if (lower.equals(scheme)) {
return u;
}
// Otherwise, return a new URI with a normalized scheme.
return u.buildUpon().scheme(lower).build();
}
@Override
public void handleMessage(final String event, final NativeJSObject message, final EventCallback callback) {
if (event.equals("Intent:OpenNoHandler")) {
openNoHandler(message, callback);
}
}
@Override
public void handleMessage(String event, JSONObject message) {
try {
if (event.equals("Intent:GetHandlers")) {
getHandlers(message);
} else if (event.equals("Intent:Open")) {
open(message);
} else if (event.equals("Intent:OpenForResult")) {
openForResult(message);
}
} catch (JSONException e) {
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
}
}
private void getHandlers(JSONObject message) throws JSONException {
final Intent intent = getOpenURIIntent(activity,
message.optString("url"),
message.optString("mime"),
message.optString("action"),
message.optString("title"));
final List<String> appList = Arrays.asList(getHandlersForIntent(intent));
final JSONObject response = new JSONObject();
response.put("apps", new JSONArray(appList));
EventDispatcher.sendResponse(message, response);
}
private void open(JSONObject message) throws JSONException {
openUriExternal(message.optString("url"),
message.optString("mime"),
message.optString("packageName"),
message.optString("className"),
message.optString("action"),
message.optString("title"), false);
}
private void openForResult(final JSONObject message) throws JSONException {
Intent intent = getOpenURIIntent(activity,
message.optString("url"),
message.optString("mime"),
message.optString("action"),
message.optString("title"));
intent.setClassName(message.optString("packageName"), message.optString("className"));
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
final ResultHandler handler = new ResultHandler(message);
try {
ActivityHandlerHelper.startIntentForActivity(activity, intent, handler);
} catch (SecurityException e) {
Log.w(LOGTAG, "Forbidden to launch activity.", e);
}
}
/**
* Opens a URI without any valid handlers on device. In the best case, a package is specified
* and we can bring the user directly to the application page in an app market. If a package is
* not specified and there is a fallback url in the intent extras, we open that url. If neither
* is present, we alert the user that we were unable to open the link.
*
* @param msg A message with the uri with no handlers as the value for the "uri" key
* @param callback A callback that will be called with success & no params if Java loads a page, or with error and
* the uri to load if Java does not load a page
*/
private void openNoHandler(final NativeJSObject msg, final EventCallback callback) {
final String uri = msg.getString("uri");
if (TextUtils.isEmpty(uri)) {
Log.w(LOGTAG, "Received empty URL - loading about:neterror");
callback.sendError(getUnknownProtocolErrorPageUri(""));
return;
}
final Intent intent;
try {
// TODO (bug 1173626): This will not handle android-app uris on non 5.1 devices.
intent = Intent.parseUri(uri, 0);
} catch (final URISyntaxException e) {
String errorUri;
try {
errorUri = getUnknownProtocolErrorPageUri(URLEncoder.encode(uri, "UTF-8"));
} catch (final UnsupportedEncodingException encodingE) {
errorUri = getUnknownProtocolErrorPageUri("");
}
// Don't log the exception to prevent leaking URIs.
Log.w(LOGTAG, "Unable to parse Intent URI - loading about:neterror");
callback.sendError(errorUri);
return;
}
// For this flow, we follow Chrome's lead:
// https://developer.chrome.com/multidevice/android/intents
final String fallbackUrl = intent.getStringExtra(EXTRA_BROWSER_FALLBACK_URL);
if (isFallbackUrlValid(fallbackUrl)) {
// Opens the page in JS.
callback.sendError(fallbackUrl);
} else if (intent.getPackage() != null) {
// Note on alternative flows: we could get the intent package from a component, however, for
// security reasons, components are ignored when opening URIs (bug 1168998) so we should
// ignore it here too.
//
// Our old flow used to prompt the user to search for their app in the market by scheme and
// while this could help the user find a new app, there is not always a correlation in
// scheme to application name and we could end up steering the user wrong (potentially to
// malicious software). Better to leave that one alone.
final String marketUri = MARKET_INTENT_URI_PACKAGE_PREFIX + intent.getPackage();
final Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(marketUri));
marketIntent.addCategory(Intent.CATEGORY_BROWSABLE);
marketIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// (Bug 1192436) We don't know if marketIntent matches any Activities (e.g. non-Play
// Store devices). If it doesn't, clicking the link will cause no action to occur.
ExternalIntentDuringPrivateBrowsingPromptFragment.showDialogOrAndroidChooser(
activity, activity.getSupportFragmentManager(), marketIntent);
callback.sendSuccess(null);
} else {
// We return the error page here, but it will only be shown if we think the load did
// not come from clicking a link. Chrome does not show error pages in that case, and
// many websites have catered to this behavior. For example, the site might set a timeout and load a play
// store url for their app if the intent link fails to load, i.e. the app is not installed.
// These work-arounds would often end with our users seeing about:neterror instead of the intended experience.
// While I feel showing about:neterror is a better solution for users (when not hacked around),
// we should match the status quo for the good of our users.
//
// Don't log the URI to prevent leaking it.
Log.w(LOGTAG, "Unable to open URI, maybe showing neterror");
callback.sendError(getUnknownProtocolErrorPageUri(intent.getData().toString()));
}
}
private static boolean isFallbackUrlValid(@Nullable final String fallbackUrl) {
if (fallbackUrl == null) {
return false;
}
try {
final String anyCaseScheme = new URI(fallbackUrl).getScheme();
final String scheme = (anyCaseScheme == null) ? null : anyCaseScheme.toLowerCase(Locale.US);
if ("http".equals(scheme) || "https".equals(scheme)) {
return true;
} else {
Log.w(LOGTAG, "Fallback URI uses unsupported scheme: " + scheme + ". Try http or https.");
}
} catch (final URISyntaxException e) {
// Do not include Exception to avoid leaking uris.
Log.w(LOGTAG, "URISyntaxException parsing fallback URI");
}
return false;
}
/**
* Returns an about:neterror uri with the unknownProtocolFound text as a parameter.
* @param encodedUri The encoded uri. While the page does not open correctly without specifying
* a uri parameter, it happily accepts the empty String so this argument may
* be the empty String.
*/
private String getUnknownProtocolErrorPageUri(final String encodedUri) {
return UNKNOWN_PROTOCOL_URI_PREFIX + encodedUri;
}
private static class ResultHandler implements ActivityResultHandler {
private final JSONObject message;
public ResultHandler(JSONObject message) {
this.message = message;
}
@Override
public void onActivityResult(int resultCode, Intent data) {
JSONObject response = new JSONObject();
try {
if (data != null) {
if (data.getExtras() != null) {
response.put("extras", JSONUtils.bundleToJSON(data.getExtras()));
}
if (data.getData() != null) {
response.put("uri", data.getData().toString());
}
}
response.put("resultCode", resultCode);
} catch (JSONException e) {
Log.w(LOGTAG, "Error building JSON response.", e);
}
EventDispatcher.sendResponse(message, response);
}
}
}

View file

@ -1,110 +0,0 @@
/* -*- 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;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.customtabs.CustomTabsIntent;
import org.mozilla.gecko.customtabs.CustomTabsActivity;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.mozglue.SafeIntent;
import org.mozilla.gecko.preferences.GeckoPreferences;
import org.mozilla.gecko.tabqueue.TabQueueHelper;
import org.mozilla.gecko.tabqueue.TabQueueService;
/**
* Activity that receives incoming Intents and dispatches them to the appropriate activities (e.g. browser, custom tabs, web app).
*/
public class LauncherActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeckoAppShell.ensureCrashHandling();
final SafeIntent safeIntent = new SafeIntent(getIntent());
// If it's not a view intent, it won't be a custom tabs intent either. Just launch!
if (!isViewIntentWithURL(safeIntent)) {
dispatchNormalIntent();
// Is this a custom tabs intent, and are custom tabs enabled?
} else if (AppConstants.MOZ_ANDROID_CUSTOM_TABS && isCustomTabsIntent(safeIntent)
&& isCustomTabsEnabled()) {
dispatchCustomTabsIntent();
// Can we dispatch this VIEW action intent to the tab queue service?
} else if (!safeIntent.getBooleanExtra(BrowserContract.SKIP_TAB_QUEUE_FLAG, false)
&& TabQueueHelper.TAB_QUEUE_ENABLED
&& TabQueueHelper.isTabQueueEnabled(this)) {
dispatchTabQueueIntent();
// Dispatch this VIEW action intent to the browser.
} else {
dispatchNormalIntent();
}
finish();
}
/**
* Launch tab queue service to display overlay.
*/
private void dispatchTabQueueIntent() {
Intent intent = new Intent(getIntent());
intent.setClass(getApplicationContext(), TabQueueService.class);
startService(intent);
}
/**
* Launch the browser activity.
*/
private void dispatchNormalIntent() {
Intent intent = new Intent(getIntent());
intent.setClassName(getApplicationContext(), AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
filterFlags(intent);
startActivity(intent);
}
private void dispatchCustomTabsIntent() {
Intent intent = new Intent(getIntent());
intent.setClassName(getApplicationContext(), CustomTabsActivity.class.getName());
filterFlags(intent);
startActivity(intent);
}
private static void filterFlags(Intent intent) {
// Explicitly remove the new task and clear task flags (Our browser activity is a single
// task activity and we never want to start a second task here). See bug 1280112.
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_CLEAR_TASK);
// LauncherActivity is started with the "exclude from recents" flag (set in manifest). We do
// not want to propagate this flag from the launcher activity to the browser.
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
}
private static boolean isViewIntentWithURL(@NonNull final SafeIntent safeIntent) {
return Intent.ACTION_VIEW.equals(safeIntent.getAction())
&& safeIntent.getDataString() != null;
}
private static boolean isCustomTabsIntent(@NonNull final SafeIntent safeIntent) {
return isViewIntentWithURL(safeIntent)
&& safeIntent.hasExtra(CustomTabsIntent.EXTRA_SESSION);
}
private boolean isCustomTabsEnabled() {
return GeckoSharedPrefs.forApp(this).getBoolean(GeckoPreferences.PREFS_CUSTOM_TABS, false);
}
}

View file

@ -1,42 +0,0 @@
/* 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.Locale;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
/**
* Implement this interface to provide Fennec's locale switching functionality.
*
* The LocaleManager is responsible for persisting and applying selected locales,
* and correcting configurations after Android has changed them.
*/
public interface LocaleManager {
void initialize(Context context);
/**
* @return true if locale switching is enabled.
*/
boolean isEnabled();
Locale getCurrentLocale(Context context);
String getAndApplyPersistedLocale(Context context);
void correctLocale(Context context, Resources resources, Configuration newConfig);
void updateConfiguration(Context context, Locale locale);
String setSelectedLocale(Context context, String localeCode);
boolean systemLocaleDidChange();
void resetToSystemLocale(Context context);
/**
* Call this in your onConfigurationChanged handler. This method is expected
* to do the appropriate thing: if the user has selected a locale, it
* corrects the incoming configuration; if not, it signals the new locale to
* use.
*/
Locale onSystemConfigurationChanged(Context context, Resources resources, Configuration configuration, Locale currentActivityLocale);
String getFallbackLocaleTag();
}

View file

@ -1,136 +0,0 @@
/* 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.lang.reflect.Method;
import java.util.Locale;
import org.mozilla.gecko.LocaleManager;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.StrictMode;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AppCompatActivity;
/**
* This is a helper class to do typical locale switching operations without
* hitting StrictMode errors or adding boilerplate to common activity
* subclasses.
*
* Either call {@link Locales#initializeLocale(Context)} in your
* <code>onCreate</code> method, or inherit from
* <code>LocaleAwareFragmentActivity</code> or <code>LocaleAwareActivity</code>.
*/
public class Locales {
public static LocaleManager getLocaleManager() {
try {
final Class<?> clazz = Class.forName("org.mozilla.gecko.BrowserLocaleManager");
final Method getInstance = clazz.getMethod("getInstance");
final LocaleManager localeManager = (LocaleManager) getInstance.invoke(null);
return localeManager;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void initializeLocale(Context context) {
final LocaleManager localeManager = getLocaleManager();
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
StrictMode.allowThreadDiskWrites();
try {
localeManager.getAndApplyPersistedLocale(context);
} finally {
StrictMode.setThreadPolicy(savedPolicy);
}
}
public static abstract class LocaleAwareAppCompatActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
Locales.initializeLocale(getApplicationContext());
super.onCreate(savedInstanceState);
}
}
public static abstract class LocaleAwareFragmentActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
Locales.initializeLocale(getApplicationContext());
super.onCreate(savedInstanceState);
}
}
public static abstract class LocaleAwareActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
Locales.initializeLocale(getApplicationContext());
super.onCreate(savedInstanceState);
}
}
/**
* Sometimes we want just the language for a locale, not the entire language
* tag. But Java's .getLanguage method is wrong.
*
* This method is equivalent to the first part of
* {@link Locales#getLanguageTag(Locale)}.
*
* @return a language string, such as "he" for the Hebrew locales.
*/
public static String getLanguage(final Locale locale) {
// Can, but should never be, an empty string.
final String language = locale.getLanguage();
// Modernize certain language codes.
if (language.equals("iw")) {
return "he";
}
if (language.equals("in")) {
return "id";
}
if (language.equals("ji")) {
return "yi";
}
return language;
}
/**
* Gecko uses locale codes like "es-ES", whereas a Java {@link Locale}
* stringifies as "es_ES".
*
* This method approximates the Java 7 method
* <code>Locale#toLanguageTag()</code>.
*
* @return a locale string suitable for passing to Gecko.
*/
public static String getLanguageTag(final Locale locale) {
// If this were Java 7:
// return locale.toLanguageTag();
final String language = getLanguage(locale);
final String country = locale.getCountry(); // Can be an empty string.
if (country.equals("")) {
return language;
}
return language + "-" + country;
}
public static Locale parseLocaleCode(final String localeCode) {
int index;
if ((index = localeCode.indexOf('-')) != -1 ||
(index = localeCode.indexOf('_')) != -1) {
final String langCode = localeCode.substring(0, index);
final String countryCode = localeCode.substring(index + 1);
return new Locale(langCode, countryCode);
}
return new Locale(localeCode);
}
}

View file

@ -1,131 +0,0 @@
/* 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.GeckoApp;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
import org.json.JSONObject;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class MediaCastingBar extends RelativeLayout implements View.OnClickListener, GeckoEventListener {
private static final String LOGTAG = "GeckoMediaCastingBar";
private TextView mCastingTo;
private ImageButton mMediaPlay;
private ImageButton mMediaPause;
private ImageButton mMediaStop;
private boolean mInflated;
public MediaCastingBar(Context context, AttributeSet attrs) {
super(context, attrs);
GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
"Casting:Started",
"Casting:Paused",
"Casting:Playing",
"Casting:Stopped");
}
public void inflateContent() {
LayoutInflater inflater = LayoutInflater.from(getContext());
View content = inflater.inflate(R.layout.media_casting, this);
mMediaPlay = (ImageButton) content.findViewById(R.id.media_play);
mMediaPlay.setOnClickListener(this);
mMediaPause = (ImageButton) content.findViewById(R.id.media_pause);
mMediaPause.setOnClickListener(this);
mMediaStop = (ImageButton) content.findViewById(R.id.media_stop);
mMediaStop.setOnClickListener(this);
mCastingTo = (TextView) content.findViewById(R.id.media_sending_to);
// Capture clicks on the rest of the view to prevent them from
// leaking into other views positioned below.
content.setOnClickListener(this);
mInflated = true;
}
public void show() {
if (!mInflated)
inflateContent();
setVisibility(VISIBLE);
}
public void hide() {
setVisibility(GONE);
}
public void onDestroy() {
GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
"Casting:Started",
"Casting:Paused",
"Casting:Playing",
"Casting:Stopped");
}
// View.OnClickListener implementation
@Override
public void onClick(View v) {
final int viewId = v.getId();
if (viewId == R.id.media_play) {
GeckoAppShell.notifyObservers("Casting:Play", "");
mMediaPlay.setVisibility(GONE);
mMediaPause.setVisibility(VISIBLE);
} else if (viewId == R.id.media_pause) {
GeckoAppShell.notifyObservers("Casting:Pause", "");
mMediaPause.setVisibility(GONE);
mMediaPlay.setVisibility(VISIBLE);
} else if (viewId == R.id.media_stop) {
GeckoAppShell.notifyObservers("Casting:Stop", "");
}
}
// GeckoEventListener implementation
@Override
public void handleMessage(final String event, final JSONObject message) {
final String device = message.optString("device");
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
if (event.equals("Casting:Started")) {
show();
if (!TextUtils.isEmpty(device)) {
mCastingTo.setText(device);
} else {
// Should not happen
mCastingTo.setText("");
Log.d(LOGTAG, "Device name is empty.");
}
mMediaPlay.setVisibility(GONE);
mMediaPause.setVisibility(VISIBLE);
} else if (event.equals("Casting:Paused")) {
mMediaPause.setVisibility(GONE);
mMediaPlay.setVisibility(VISIBLE);
} else if (event.equals("Casting:Playing")) {
mMediaPlay.setVisibility(GONE);
mMediaPause.setVisibility(VISIBLE);
} else if (event.equals("Casting:Stopped")) {
hide();
}
}
});
}
}

View file

@ -1,323 +0,0 @@
/* -*- 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;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.media.MediaControlIntent;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.util.Log;
import com.google.android.gms.cast.CastMediaControlIntent;
import org.json.JSONObject;
import org.mozilla.gecko.annotation.JNITarget;
import org.mozilla.gecko.annotation.ReflectionTarget;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Manages a list of GeckoMediaPlayers methods (i.e. Chromecast/Miracast). Routes messages
* from Gecko to the correct caster based on the id of the display
*/
public class MediaPlayerManager extends Fragment implements NativeEventListener {
/**
* Create a new instance of DetailsFragment, initialized to
* show the text at 'index'.
*/
private static MediaPlayerManager instance = null;
@ReflectionTarget
public static MediaPlayerManager getInstance() {
if (instance != null) {
return instance;
}
if (Versions.feature17Plus) {
instance = (MediaPlayerManager) new PresentationMediaPlayerManager();
} else {
instance = new MediaPlayerManager();
}
return instance;
}
private static final String LOGTAG = "GeckoMediaPlayerManager";
protected boolean isPresentationMode = false; // Used to prevent mirroring when Presentation API is used.
@ReflectionTarget
public static final String MEDIA_PLAYER_TAG = "MPManagerFragment";
private static final boolean SHOW_DEBUG = false;
// Simplified debugging interfaces
private static void debug(String msg, Exception e) {
if (SHOW_DEBUG) {
Log.e(LOGTAG, msg, e);
}
}
private static void debug(String msg) {
if (SHOW_DEBUG) {
Log.d(LOGTAG, msg);
}
}
protected MediaRouter mediaRouter = null;
protected final Map<String, GeckoMediaPlayer> players = new HashMap<String, GeckoMediaPlayer>();
protected final Map<String, GeckoPresentationDisplay> displays = new HashMap<String, GeckoPresentationDisplay>(); // used for Presentation API
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
"MediaPlayer:Load",
"MediaPlayer:Start",
"MediaPlayer:Stop",
"MediaPlayer:Play",
"MediaPlayer:Pause",
"MediaPlayer:End",
"MediaPlayer:Mirror",
"MediaPlayer:Message",
"AndroidCastDevice:Start",
"AndroidCastDevice:Stop",
"AndroidCastDevice:SyncDevice");
}
@Override
@JNITarget
public void onDestroy() {
super.onDestroy();
GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
"MediaPlayer:Load",
"MediaPlayer:Start",
"MediaPlayer:Stop",
"MediaPlayer:Play",
"MediaPlayer:Pause",
"MediaPlayer:End",
"MediaPlayer:Mirror",
"MediaPlayer:Message",
"AndroidCastDevice:Start",
"AndroidCastDevice:Stop",
"AndroidCastDevice:SyncDevice");
}
// GeckoEventListener implementation
@Override
public void handleMessage(String event, final NativeJSObject message, final EventCallback callback) {
debug(event);
if (event.startsWith("MediaPlayer:")) {
final GeckoMediaPlayer player = players.get(message.getString("id"));
if (player == null) {
Log.e(LOGTAG, "Couldn't find a player for this id: " + message.getString("id") + " for message: " + event);
if (callback != null) {
callback.sendError(null);
}
return;
}
if ("MediaPlayer:Play".equals(event)) {
player.play(callback);
} else if ("MediaPlayer:Start".equals(event)) {
player.start(callback);
} else if ("MediaPlayer:Stop".equals(event)) {
player.stop(callback);
} else if ("MediaPlayer:Pause".equals(event)) {
player.pause(callback);
} else if ("MediaPlayer:End".equals(event)) {
player.end(callback);
} else if ("MediaPlayer:Mirror".equals(event)) {
player.mirror(callback);
} else if ("MediaPlayer:Message".equals(event) && message.has("data")) {
player.message(message.getString("data"), callback);
} else if ("MediaPlayer:Load".equals(event)) {
final String url = message.optString("source", "");
final String type = message.optString("type", "video/mp4");
final String title = message.optString("title", "");
player.load(title, url, type, callback);
}
}
if (event.startsWith("AndroidCastDevice:")) {
if ("AndroidCastDevice:Start".equals(event)) {
final GeckoPresentationDisplay display = displays.get(message.getString("id"));
if (display == null) {
Log.e(LOGTAG, "Couldn't find a display for this id: " + message.getString("id") + " for message: " + event);
return;
}
display.start(callback);
} else if ("AndroidCastDevice:Stop".equals(event)) {
final GeckoPresentationDisplay display = displays.get(message.getString("id"));
if (display == null) {
Log.e(LOGTAG, "Couldn't find a display for this id: " + message.getString("id") + " for message: " + event);
return;
}
display.stop(callback);
} else if ("AndroidCastDevice:SyncDevice".equals(event)) {
for (Map.Entry<String, GeckoPresentationDisplay> entry : displays.entrySet()) {
GeckoPresentationDisplay display = entry.getValue();
JSONObject json = display.toJSON();
if (json == null) {
break;
}
GeckoAppShell.notifyObservers("AndroidCastDevice:Added", json.toString());
}
}
}
}
private final MediaRouter.Callback callback =
new MediaRouter.Callback() {
@Override
public void onRouteRemoved(MediaRouter router, RouteInfo route) {
debug("onRouteRemoved: route=" + route);
// Remove from media player list.
players.remove(route.getId());
GeckoAppShell.notifyObservers("MediaPlayer:Removed", route.getId());
updatePresentation();
// Remove from presentation display list.
displays.remove(route.getId());
GeckoAppShell.notifyObservers("AndroidCastDevice:Removed", route.getId());
}
@SuppressWarnings("unused")
public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo route) {
updatePresentation();
}
// These methods aren't used by the support version Media Router
@SuppressWarnings("unused")
public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) {
updatePresentation();
}
@Override
public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
updatePresentation();
}
@Override
public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
}
@Override
public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) {
debug("onRouteAdded: route=" + route);
final GeckoMediaPlayer player = getMediaPlayerForRoute(route);
saveAndNotifyOfPlayer("MediaPlayer:Added", route, player);
updatePresentation();
final GeckoPresentationDisplay display = getPresentationDisplayForRoute(route);
saveAndNotifyOfDisplay("AndroidCastDevice:Added", route, display);
}
@Override
public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
debug("onRouteChanged: route=" + route);
final GeckoMediaPlayer player = players.get(route.getId());
saveAndNotifyOfPlayer("MediaPlayer:Changed", route, player);
updatePresentation();
final GeckoPresentationDisplay display = displays.get(route.getId());
saveAndNotifyOfDisplay("AndroidCastDevice:Changed", route, display);
}
private void saveAndNotifyOfPlayer(final String eventName,
MediaRouter.RouteInfo route,
final GeckoMediaPlayer player) {
if (player == null) {
return;
}
final JSONObject json = player.toJSON();
if (json == null) {
return;
}
players.put(route.getId(), player);
GeckoAppShell.notifyObservers(eventName, json.toString());
}
private void saveAndNotifyOfDisplay(final String eventName,
MediaRouter.RouteInfo route,
final GeckoPresentationDisplay display) {
if (display == null) {
return;
}
final JSONObject json = display.toJSON();
if (json == null) {
return;
}
displays.put(route.getId(), display);
GeckoAppShell.notifyObservers(eventName, json.toString());
}
};
private GeckoMediaPlayer getMediaPlayerForRoute(MediaRouter.RouteInfo route) {
try {
if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
return new ChromeCastPlayer(getActivity(), route);
}
} catch (Exception ex) {
debug("Error handling presentation", ex);
}
return null;
}
private GeckoPresentationDisplay getPresentationDisplayForRoute(MediaRouter.RouteInfo route) {
try {
if (route.supportsControlCategory(CastMediaControlIntent.categoryForCast(ChromeCastDisplay.REMOTE_DISPLAY_APP_ID))) {
return new ChromeCastDisplay(getActivity(), route);
}
} catch (Exception ex) {
debug("Error handling presentation", ex);
}
return null;
}
@Override
public void onPause() {
super.onPause();
mediaRouter.removeCallback(callback);
mediaRouter = null;
}
@Override
public void onResume() {
super.onResume();
// The mediaRouter shouldn't exist here, but this is a nice safety check.
if (mediaRouter != null) {
return;
}
mediaRouter = MediaRouter.getInstance(getActivity());
final MediaRouteSelector selectorBuilder = new MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.addControlCategory(CastMediaControlIntent.categoryForCast(ChromeCastPlayer.MIRROR_RECEIVER_APP_ID))
.addControlCategory(CastMediaControlIntent.categoryForCast(ChromeCastDisplay.REMOTE_DISPLAY_APP_ID))
.build();
mediaRouter.addCallback(selectorBuilder, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
}
public void setPresentationMode(boolean isPresentationMode) {
this.isPresentationMode = isPresentationMode;
}
protected void updatePresentation() { /* Overridden in sub-classes. */ }
}

View file

@ -1,279 +0,0 @@
/* -*- 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.WrapForJNI;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserProvider;
import org.mozilla.gecko.home.ImageLoader;
import org.mozilla.gecko.icons.storage.MemoryStorage;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
/**
* This is a utility class to keep track of how much memory and disk-space pressure
* the system is under. It receives input from GeckoActivity via the onLowMemory() and
* onTrimMemory() functions, and also listens for some system intents related to
* disk-space notifications. Internally it will track how much memory and disk pressure
* the system is under, and perform various actions to help alleviate the pressure.
*
* Note that since there is no notification for when the system has lots of free memory
* again, this class also assumes that, over time, the system will free up memory. This
* assumption is implemented using a timer that slowly lowers the internal memory
* pressure state if no new low-memory notifications are received.
*
* Synchronization note: MemoryMonitor contains an inner class PressureDecrementer. Both
* of these classes may be accessed from various threads, and have both been designed to
* be thread-safe. In terms of lock ordering, code holding the PressureDecrementer lock
* is allowed to pick up the MemoryMonitor lock, but not vice-versa.
*/
class MemoryMonitor extends BroadcastReceiver {
private static final String LOGTAG = "GeckoMemoryMonitor";
private static final String ACTION_MEMORY_DUMP = "org.mozilla.gecko.MEMORY_DUMP";
private static final String ACTION_FORCE_PRESSURE = "org.mozilla.gecko.FORCE_MEMORY_PRESSURE";
// Memory pressure levels. Keep these in sync with those in AndroidJavaWrappers.h
private static final int MEMORY_PRESSURE_NONE = 0;
private static final int MEMORY_PRESSURE_CLEANUP = 1;
private static final int MEMORY_PRESSURE_LOW = 2;
private static final int MEMORY_PRESSURE_MEDIUM = 3;
private static final int MEMORY_PRESSURE_HIGH = 4;
private static final MemoryMonitor sInstance = new MemoryMonitor();
static MemoryMonitor getInstance() {
return sInstance;
}
private Context mAppContext;
private final PressureDecrementer mPressureDecrementer;
private int mMemoryPressure; // Synchronized access only.
private volatile boolean mStoragePressure; // Accessed via UI thread intent, background runnables.
private boolean mInited;
private MemoryMonitor() {
mPressureDecrementer = new PressureDecrementer();
mMemoryPressure = MEMORY_PRESSURE_NONE;
}
public void init(final Context context) {
if (mInited) {
return;
}
mAppContext = context.getApplicationContext();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
filter.addAction(ACTION_MEMORY_DUMP);
filter.addAction(ACTION_FORCE_PRESSURE);
mAppContext.registerReceiver(this, filter);
mInited = true;
}
public void onLowMemory() {
Log.d(LOGTAG, "onLowMemory() notification received");
if (increaseMemoryPressure(MEMORY_PRESSURE_HIGH)) {
// We need to wait on Gecko here, because if we haven't reduced
// memory usage enough when we return from this, Android will kill us.
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
GeckoThread.waitOnGecko();
}
}
}
public void onTrimMemory(int level) {
Log.d(LOGTAG, "onTrimMemory() notification received with level " + level);
if (level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
// We seem to get this just by entering the task switcher or hitting the home button.
// Seems bogus, because we are the foreground app, or at least not at the end of the LRU list.
// Just ignore it, and if there is a real memory pressure event (CRITICAL, MODERATE, etc),
// we'll respond appropriately.
return;
}
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
// TRIM_MEMORY_MODERATE is the highest level we'll respond to while backgrounded
increaseMemoryPressure(MEMORY_PRESSURE_HIGH);
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
increaseMemoryPressure(MEMORY_PRESSURE_MEDIUM);
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
increaseMemoryPressure(MEMORY_PRESSURE_LOW);
break;
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
increaseMemoryPressure(MEMORY_PRESSURE_CLEANUP);
break;
default:
Log.d(LOGTAG, "Unhandled onTrimMemory() level " + level);
break;
}
}
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) {
Log.d(LOGTAG, "Device storage is low");
mStoragePressure = true;
ThreadUtils.postToBackgroundThread(new StorageReducer(context));
} else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction())) {
Log.d(LOGTAG, "Device storage is ok");
mStoragePressure = false;
} else if (ACTION_MEMORY_DUMP.equals(intent.getAction())) {
String label = intent.getStringExtra("label");
if (label == null) {
label = "default";
}
GeckoAppShell.notifyObservers("Memory:Dump", label);
} else if (ACTION_FORCE_PRESSURE.equals(intent.getAction())) {
increaseMemoryPressure(MEMORY_PRESSURE_HIGH);
}
}
@WrapForJNI(calledFrom = "ui")
private static native void dispatchMemoryPressure();
private boolean increaseMemoryPressure(int level) {
int oldLevel;
synchronized (this) {
// bump up our level if we're not already higher
if (mMemoryPressure > level) {
return false;
}
oldLevel = mMemoryPressure;
mMemoryPressure = level;
}
Log.d(LOGTAG, "increasing memory pressure to " + level);
// since we don't get notifications for when memory pressure is off,
// we schedule our own timer to slowly back off the memory pressure level.
// note that this will reset the time to next decrement if the decrementer
// is already running, which is the desired behaviour because we just got
// a new low-mem notification.
mPressureDecrementer.start();
if (oldLevel == level) {
// if we're not going to a higher level we probably don't
// need to run another round of the same memory reductions
// we did on the last memory pressure increase.
return false;
}
// TODO hook in memory-reduction stuff for different levels here
if (level >= MEMORY_PRESSURE_MEDIUM) {
//Only send medium or higher events because that's all that is used right now
if (GeckoThread.isRunning()) {
dispatchMemoryPressure();
}
MemoryStorage.get().evictAll();
ImageLoader.clearLruCache();
LocalBroadcastManager.getInstance(mAppContext)
.sendBroadcast(new Intent(BrowserProvider.ACTION_SHRINK_MEMORY));
}
return true;
}
/**
* Thread-safe due to mStoragePressure's volatility.
*/
boolean isUnderStoragePressure() {
return mStoragePressure;
}
private boolean decreaseMemoryPressure() {
int newLevel;
synchronized (this) {
if (mMemoryPressure <= 0) {
return false;
}
newLevel = --mMemoryPressure;
}
Log.d(LOGTAG, "Decreased memory pressure to " + newLevel);
return true;
}
class PressureDecrementer implements Runnable {
private static final int DECREMENT_DELAY = 5 * 60 * 1000; // 5 minutes
private boolean mPosted;
synchronized void start() {
if (mPosted) {
// cancel the old one before scheduling a new one
ThreadUtils.getBackgroundHandler().removeCallbacks(this);
}
ThreadUtils.getBackgroundHandler().postDelayed(this, DECREMENT_DELAY);
mPosted = true;
}
@Override
public synchronized void run() {
if (!decreaseMemoryPressure()) {
// done decrementing, bail out
mPosted = false;
return;
}
// need to keep decrementing
ThreadUtils.getBackgroundHandler().postDelayed(this, DECREMENT_DELAY);
}
}
private static class StorageReducer implements Runnable {
private final Context mContext;
private final BrowserDB mDB;
public StorageReducer(final Context context) {
this.mContext = context;
// Since this may be called while Fennec is in the background, we don't want to risk accidentally
// using the wrong context. If the profile we get is a guest profile, use the default profile instead.
GeckoProfile profile = GeckoProfile.get(mContext);
if (profile.inGuestMode()) {
// If it was the guest profile, switch to the default one.
profile = GeckoProfile.get(mContext, GeckoProfile.DEFAULT_PROFILE);
}
mDB = BrowserDB.from(profile);
}
@Override
public void run() {
// this might get run right on startup, if so wait 10 seconds and try again
if (!GeckoThread.isRunning()) {
ThreadUtils.getBackgroundHandler().postDelayed(this, 10000);
return;
}
if (!MemoryMonitor.getInstance().isUnderStoragePressure()) {
// Pressure is off, so we can abort.
return;
}
final ContentResolver cr = mContext.getContentResolver();
mDB.expireHistory(cr, BrowserContract.ExpirePriority.AGGRESSIVE);
mDB.removeThumbnails(cr);
// TODO: drop or shrink disk caches
}
}
}

View file

@ -1,13 +0,0 @@
/* -*- 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 MotionEventInterceptor {
public boolean onInterceptMotionEvent(View view, MotionEvent event);
}

View file

@ -1,38 +0,0 @@
/* -*- 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.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.mozilla.gecko.mozglue.GeckoLoader;
/**
* This broadcast receiver receives ACTION_MY_PACKAGE_REPLACED broadcasts and
* starts procedures that should run after the APK has been updated.
*/
public class PackageReplacedReceiver extends BroadcastReceiver {
public static final String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED";
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || !ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())) {
// This is not the broadcast we are looking for.
return;
}
// Extract Gecko libs to allow them to be loaded from cache on startup.
extractGeckoLibs(context);
}
private static void extractGeckoLibs(final Context context) {
final String resourcePath = context.getPackageResourcePath();
GeckoLoader.loadMozGlue(context);
GeckoLoader.extractGeckoLibs(context, resourcePath);
}
}

View file

@ -1,149 +0,0 @@
/* -*- 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;
import android.annotation.TargetApi;
import android.app.Presentation;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.media.MediaRouter;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.view.WindowManager;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.annotation.WrapForJNI;
/**
* A MediaPlayerManager with API 17+ Presentation support.
*/
@TargetApi(17)
public class PresentationMediaPlayerManager extends MediaPlayerManager {
private static final String LOGTAG = "Gecko" + PresentationMediaPlayerManager.class.getSimpleName();
private GeckoPresentation presentation;
public PresentationMediaPlayerManager() {
if (!Versions.feature17Plus) {
throw new IllegalStateException(PresentationMediaPlayerManager.class.getSimpleName() +
" does not support < API 17");
}
}
@Override
public void onStop() {
super.onStop();
if (presentation != null) {
presentation.dismiss();
presentation = null;
}
}
@Override
protected void updatePresentation() {
if (mediaRouter == null) {
return;
}
if (isPresentationMode) {
return;
}
MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
Display display = route != null ? route.getPresentationDisplay() : null;
if (display != null) {
if ((presentation != null) && (presentation.getDisplay() != display)) {
presentation.dismiss();
presentation = null;
}
if (presentation == null) {
final GeckoView geckoView = (GeckoView) getActivity().findViewById(R.id.layer_view);
presentation = new GeckoPresentation(getActivity(), display, geckoView);
try {
presentation.show();
} catch (WindowManager.InvalidDisplayException ex) {
Log.w(LOGTAG, "Couldn't show presentation! Display was removed in "
+ "the meantime.", ex);
presentation = null;
}
}
} else if (presentation != null) {
presentation.dismiss();
presentation = null;
}
}
@WrapForJNI(calledFrom = "ui")
/* protected */ static native void invalidateAndScheduleComposite(GeckoView geckoView);
@WrapForJNI(calledFrom = "ui")
/* protected */ static native void addPresentationSurface(GeckoView geckoView, Surface surface);
@WrapForJNI(calledFrom = "ui")
/* protected */ static native void removePresentationSurface();
private static final class GeckoPresentation extends Presentation {
private SurfaceView mView;
private GeckoView mGeckoView;
public GeckoPresentation(Context context, Display display, GeckoView geckoView) {
super(context, display);
mGeckoView = geckoView;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mView = new SurfaceView(getContext());
setContentView(mView, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mView.getHolder().addCallback(new SurfaceListener(mGeckoView));
}
}
private static final class SurfaceListener implements SurfaceHolder.Callback {
private GeckoView mGeckoView;
public SurfaceListener(GeckoView geckoView) {
mGeckoView = geckoView;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// Surface changed so force a composite
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
invalidateAndScheduleComposite(mGeckoView);
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
addPresentationSurface(mGeckoView, holder.getSurface());
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
removePresentationSurface();
}
}
}
}

View file

@ -1,27 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* vim: ts=4 sw=4 expandtab:
* 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.WrapForJNI;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.GeckoView;
import org.mozilla.gecko.ScreenManagerHelper;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
public class PresentationView extends GeckoView {
private static final String LOGTAG = "PresentationView";
private static final String presentationViewURI = "chrome://browser/content/PresentationView.xul";
public PresentationView(Context context, String deviceId, int screenId) {
super(context);
this.chromeURI = presentationViewURI + "#" + deviceId;
this.screenId = screenId;
}
}

View file

@ -1,124 +0,0 @@
/* -*- 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.AppConstants.Versions;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.util.GeckoRequest;
import org.mozilla.gecko.util.IOUtils;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import android.content.Context;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentAdapter.LayoutResultCallback;
import android.print.PrintDocumentAdapter.WriteResultCallback;
import android.print.PrintDocumentInfo;
import android.print.PrintManager;
import android.print.PageRange;
import android.util.Log;
public class PrintHelper {
private static final String LOGTAG = "GeckoPrintUtils";
public static void printPDF(final Context context) {
GeckoAppShell.sendRequestToGecko(new GeckoRequest("Print:PDF", new JSONObject()) {
@Override
public void onResponse(NativeJSObject nativeJSObject) {
final String filePath = nativeJSObject.getString("file");
final String title = nativeJSObject.getString("title");
finish(context, filePath, title);
}
@Override
public void onError(NativeJSObject error) {
// Gecko didn't respond due to state change, javascript error, etc.
Log.d(LOGTAG, "No response from Gecko on request to generate a PDF");
}
private void finish(final Context context, final String filePath, final String title) {
PrintManager printManager = (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
String jobName = title;
// The adapter methods are all called on the UI thread by the PrintManager. Put the heavyweight code
// in onWrite on the background thread.
PrintDocumentAdapter pda = new PrintDocumentAdapter() {
@Override
public void onWrite(final PageRange[] pages, final ParcelFileDescriptor destination, final CancellationSignal cancellationSignal, final WriteResultCallback callback) {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
InputStream input = null;
OutputStream output = null;
try {
File pdfFile = new File(filePath);
input = new FileInputStream(pdfFile);
output = new FileOutputStream(destination.getFileDescriptor());
byte[] buf = new byte[8192];
int bytesRead;
while ((bytesRead = input.read(buf)) > 0) {
output.write(buf, 0, bytesRead);
}
callback.onWriteFinished(new PageRange[] { PageRange.ALL_PAGES });
} catch (FileNotFoundException ee) {
Log.d(LOGTAG, "Unable to find the temporary PDF file.");
} catch (IOException ioe) {
Log.e(LOGTAG, "IOException while transferring temporary PDF file: ", ioe);
} finally {
IOUtils.safeStreamClose(input);
IOUtils.safeStreamClose(output);
}
}
});
}
@Override
public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras) {
if (cancellationSignal.isCanceled()) {
callback.onLayoutCancelled();
return;
}
PrintDocumentInfo pdi = new PrintDocumentInfo.Builder(filePath).setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).build();
callback.onLayoutFinished(pdi, true);
}
@Override
public void onFinish() {
// Remove the temporary file when the printing system is finished.
try {
File pdfFile = new File(filePath);
pdfFile.delete();
} catch (NullPointerException npe) {
// Silence the exception. We only want to delete a real file. We don't
// care if the file doesn't exist.
}
}
};
printManager.print(jobName, pda, null);
}
});
}
}

View file

@ -1,28 +0,0 @@
/* -*- 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.content.Context;
import org.json.JSONObject;
import org.mozilla.gecko.db.BrowserDB;
public class PrivateTab extends Tab {
public PrivateTab(Context context, int id, String url, boolean external, int parentId, String title) {
super(context, id, url, external, parentId, title);
}
@Override
protected void saveThumbnailToDB(final BrowserDB db) {}
@Override
public void setMetadata(JSONObject metadata) {}
@Override
public boolean isPrivate() {
return true;
}
}

View file

@ -1,133 +0,0 @@
/* -*- 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 java.util.ArrayList;
import java.util.List;
import org.mozilla.gecko.db.RemoteClient;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.util.SparseBooleanArray;
/**
* A dialog fragment that displays a list of remote clients.
* <p>
* The dialog allows both single (one tap) and multiple (checkbox) selection.
* The dialog's results are communicated via the {@link RemoteClientsListener}
* interface. Either the dialog fragment's <i>target fragment</i> (see
* {@link Fragment#setTargetFragment(Fragment, int)}), or the containing
* <i>activity</i>, must implement that interface. See
* {@link #notifyListener(List)} for details.
*/
public class RemoteClientsDialogFragment extends DialogFragment {
private static final String KEY_TITLE = "title";
private static final String KEY_CHOICE_MODE = "choice_mode";
private static final String KEY_POSITIVE_BUTTON_TEXT = "positive_button_text";
private static final String KEY_CLIENTS = "clients";
public interface RemoteClientsListener {
// Always called on the main UI thread.
public void onClients(List<RemoteClient> clients);
}
public enum ChoiceMode {
SINGLE,
MULTIPLE,
}
public static RemoteClientsDialogFragment newInstance(String title, String positiveButtonText, ChoiceMode choiceMode, ArrayList<RemoteClient> clients) {
final RemoteClientsDialogFragment dialog = new RemoteClientsDialogFragment();
final Bundle args = new Bundle();
args.putString(KEY_TITLE, title);
args.putString(KEY_POSITIVE_BUTTON_TEXT, positiveButtonText);
args.putInt(KEY_CHOICE_MODE, choiceMode.ordinal());
args.putParcelableArrayList(KEY_CLIENTS, clients);
dialog.setArguments(args);
return dialog;
}
public RemoteClientsDialogFragment() {
// Empty constructor is required for DialogFragment.
}
@Override
public void onDestroy() {
super.onDestroy();
GeckoApplication.watchReference(getActivity(), this);
}
protected void notifyListener(List<RemoteClient> clients) {
RemoteClientsListener listener;
try {
listener = (RemoteClientsListener) getTargetFragment();
} catch (ClassCastException e) {
try {
listener = (RemoteClientsListener) getActivity();
} catch (ClassCastException f) {
throw new ClassCastException(getTargetFragment() + " or " + getActivity()
+ " must implement RemoteClientsListener");
}
}
listener.onClients(clients);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final String title = getArguments().getString(KEY_TITLE);
final String positiveButtonText = getArguments().getString(KEY_POSITIVE_BUTTON_TEXT);
final ChoiceMode choiceMode = ChoiceMode.values()[getArguments().getInt(KEY_CHOICE_MODE)];
final ArrayList<RemoteClient> clients = getArguments().getParcelableArrayList(KEY_CLIENTS);
final Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(title);
final String[] clientNames = new String[clients.size()];
for (int i = 0; i < clients.size(); i++) {
clientNames[i] = clients.get(i).name;
}
if (choiceMode == ChoiceMode.MULTIPLE) {
builder.setMultiChoiceItems(clientNames, null, null);
builder.setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int which) {
if (which != Dialog.BUTTON_POSITIVE) {
return;
}
final AlertDialog dialog = (AlertDialog) dialogInterface;
final SparseBooleanArray checkedItemPositions = dialog.getListView().getCheckedItemPositions();
final ArrayList<RemoteClient> checked = new ArrayList<RemoteClient>();
for (int i = 0; i < clients.size(); i++) {
if (checkedItemPositions.get(i)) {
checked.add(clients.get(i));
}
}
notifyListener(checked);
}
});
} else {
builder.setItems(clientNames, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int index) {
final ArrayList<RemoteClient> checked = new ArrayList<RemoteClient>();
checked.add(clients.get(index));
notifyListener(checked);
}
});
}
return builder.create();
}
}

View file

@ -1,150 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* vim: ts=4 sw=4 expandtab:
* 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.json.JSONObject;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.PresentationView;
import org.mozilla.gecko.R;
import org.mozilla.gecko.ScreenManagerHelper;
import org.mozilla.gecko.annotation.JNITarget;
import org.mozilla.gecko.annotation.ReflectionTarget;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.cast.CastPresentation;
import com.google.android.gms.cast.CastRemoteDisplayLocalService;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.media.MediaControlIntent;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.support.v7.media.MediaRouter;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.widget.RelativeLayout;
import java.util.HashMap;
import java.util.Map;
/*
* Service to keep the remote display running even when the app goes into the background
*/
public class RemotePresentationService extends CastRemoteDisplayLocalService {
private static final String LOGTAG = "RemotePresentationService";
private CastPresentation presentation;
private String deviceId;
private int screenId;
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getDeviceId() {
return deviceId;
}
@Override
public void onCreatePresentation(Display display) {
createPresentation();
}
@Override
public void onDismissPresentation() {
dismissPresentation();
}
private void dismissPresentation() {
if (presentation != null) {
presentation.dismiss();
presentation = null;
ScreenManagerHelper.removeDisplay(screenId);
MediaPlayerManager.getInstance().setPresentationMode(false);
}
}
private void createPresentation() {
dismissPresentation();
MediaPlayerManager.getInstance().setPresentationMode(true);
DisplayMetrics metrics = new DisplayMetrics();
getDisplay().getMetrics(metrics);
screenId = ScreenManagerHelper.addDisplay(ScreenManagerHelper.DISPLAY_VIRTUAL,
metrics.widthPixels,
metrics.heightPixels,
metrics.density);
VirtualPresentation virtualPresentation = new VirtualPresentation(this, getDisplay());
virtualPresentation.setDeviceId(deviceId);
virtualPresentation.setScreenId(screenId);
presentation = (CastPresentation) virtualPresentation;
try {
presentation.show();
} catch (WindowManager.InvalidDisplayException ex) {
Log.e(LOGTAG, "Unable to show presentation, display was removed.", ex);
dismissPresentation();
}
}
}
class VirtualPresentation extends CastPresentation {
private final String LOGTAG = "VirtualPresentation";
private RelativeLayout layout;
private PresentationView view;
private String deviceId;
private int screenId;
public VirtualPresentation(Context context, Display display) {
super(context, display);
}
public void setDeviceId(String deviceId) { this.deviceId = deviceId; }
public void setScreenId(int screenId) { this.screenId = screenId; }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
* NOTICE: The context get from getContext() is different to the context
* of the application. Presentaion has its own context to get correct
* resources.
*/
// Create new PresentationView
view = new PresentationView(getContext(), deviceId, screenId);
view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
// Create new layout to put the GeckoView
layout = new RelativeLayout(getContext());
layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
layout.addView(view);
setContentView(layout);
}
}

View file

@ -1,50 +0,0 @@
/* -*- 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.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
import android.util.Log;
public class Restarter extends Service {
private static final String LOGTAG = "GeckoRestarter";
private void doRestart(Intent intent) {
final int oldProc = intent.getIntExtra("pid", -1);
if (oldProc < 0) {
return;
}
Process.killProcess(oldProc);
Log.d(LOGTAG, "Killed " + oldProc);
try {
Thread.sleep(100);
} catch (final InterruptedException e) {
}
final Intent restartIntent = (Intent)intent.getParcelableExtra(Intent.EXTRA_INTENT);
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra("didRestart", true)
.setClassName(getApplicationContext(),
AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
startActivity(restartIntent);
Log.d(LOGTAG, "Launched " + restartIntent);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
doRestart(intent);
stopSelf(startId);
return Service.START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

View file

@ -1,43 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* vim: ts=4 sw=4 expandtab:
* 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.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.
/**
* 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);
}

View file

@ -1,146 +0,0 @@
/* -*- 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;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.permissions.Permissions;
import org.mozilla.gecko.util.ThreadUtils;
import android.Manifest;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
public class ScreenshotObserver {
private static final String LOGTAG = "GeckoScreenshotObserver";
public Context context;
/**
* Listener for screenshot changes.
*/
public interface OnScreenshotListener {
/**
* This callback is executed on the UI thread.
*/
public void onScreenshotTaken(String data, String title);
}
private OnScreenshotListener listener;
public ScreenshotObserver() {
}
public void setListener(Context context, OnScreenshotListener listener) {
this.context = context;
this.listener = listener;
}
private MediaObserver mediaObserver;
private String[] mediaProjections = new String[] {
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DISPLAY_NAME,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
MediaStore.Images.ImageColumns.DATE_TAKEN,
MediaStore.Images.ImageColumns.TITLE
};
/**
* Start ScreenshotObserver if this device is supported and all required runtime permissions
* have been granted by the user. Calling this method will not prompt for permissions.
*/
public void start() {
Permissions.from(context)
.withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.doNotPrompt()
.run(startObserverRunnable());
}
private Runnable startObserverRunnable() {
return new Runnable() {
@Override
public void run() {
try {
if (mediaObserver == null) {
mediaObserver = new MediaObserver();
context.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, mediaObserver);
}
} catch (Exception e) {
Log.e(LOGTAG, "Failure to start watching media: ", e);
}
}
};
}
public void stop() {
if (mediaObserver == null) {
return;
}
try {
context.getContentResolver().unregisterContentObserver(mediaObserver);
mediaObserver = null;
} catch (Exception e) {
Log.e(LOGTAG, "Failure to stop watching media: ", e);
}
}
public void onMediaChange(final Uri uri) {
// Make sure we are on not on the main thread.
final ContentResolver cr = context.getContentResolver();
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
// Find the most recent image added to the MediaStore and see if it's a screenshot.
final Cursor cursor = cr.query(uri, mediaProjections, null, null, MediaStore.Images.ImageColumns.DATE_ADDED + " DESC LIMIT 1");
try {
if (cursor == null) {
return;
}
while (cursor.moveToNext()) {
String data = cursor.getString(0);
Log.i(LOGTAG, "data: " + data);
String display = cursor.getString(1);
Log.i(LOGTAG, "display: " + display);
String album = cursor.getString(2);
Log.i(LOGTAG, "album: " + album);
long date = cursor.getLong(3);
String title = cursor.getString(4);
Log.i(LOGTAG, "title: " + title);
if (album != null && album.toLowerCase().contains("screenshot")) {
if (listener != null) {
listener.onScreenshotTaken(data, title);
break;
}
}
}
} catch (Exception e) {
Log.e(LOGTAG, "Failure to process media change: ", e);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
});
}
private class MediaObserver extends ContentObserver {
public MediaObserver() {
super(null);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
onMediaChange(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
}
}
}

View file

@ -1,140 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
*
* 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/.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko;
import java.util.LinkedList;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;
public abstract class SessionParser {
private static final String LOGTAG = "GeckoSessionParser";
public class SessionTab {
final private String mTitle;
final private String mUrl;
final private JSONObject mTabObject;
private boolean mIsSelected;
private SessionTab(String title, String url, boolean isSelected, JSONObject tabObject) {
mTitle = title;
mUrl = url;
mIsSelected = isSelected;
mTabObject = tabObject;
}
public String getTitle() {
return mTitle;
}
public String getUrl() {
return mUrl;
}
public boolean isSelected() {
return mIsSelected;
}
public JSONObject getTabObject() {
return mTabObject;
}
/**
* Is this tab pointing to about:home and does not contain any other history?
*/
public boolean isAboutHomeWithoutHistory() {
JSONArray entries = mTabObject.optJSONArray("entries");
return entries != null && entries.length() == 1 && AboutPages.isAboutHome(mUrl);
}
};
abstract public void onTabRead(SessionTab tab);
/**
* Placeholder method that must be overloaded to handle closedTabs while parsing session data.
*
* @param closedTabs, JSONArray of recently closed tab entries.
* @throws JSONException
*/
public void onClosedTabsRead(final JSONArray closedTabs) throws JSONException {
}
/**
* Parses the provided session store data and calls onTabRead for each tab that has been found.
*
* @param sessionStrings One or more strings containing session store data.
* @return False if any of the session strings provided didn't contain valid session store data.
*/
public boolean parse(String... sessionStrings) {
final LinkedList<SessionTab> sessionTabs = new LinkedList<SessionTab>();
int totalCount = 0;
int selectedIndex = -1;
try {
for (String sessionString : sessionStrings) {
final JSONArray windowsArray = new JSONObject(sessionString).getJSONArray("windows");
if (windowsArray.length() == 0) {
// Session json can be empty if the user has opted out of session restore.
Log.d(LOGTAG, "Session restore file is empty, no session entries found.");
continue;
}
final JSONObject window = windowsArray.getJSONObject(0);
final JSONArray tabs = window.getJSONArray("tabs");
final int optSelected = window.optInt("selected", -1);
final JSONArray closedTabs = window.optJSONArray("closedTabs");
if (closedTabs != null) {
onClosedTabsRead(closedTabs);
}
for (int i = 0; i < tabs.length(); i++) {
final JSONObject tab = tabs.getJSONObject(i);
final int index = tab.getInt("index");
final JSONArray entries = tab.getJSONArray("entries");
if (index < 1 || entries.length() < index) {
Log.w(LOGTAG, "Session entries and index don't agree.");
continue;
}
final JSONObject entry = entries.getJSONObject(index - 1);
final String url = entry.getString("url");
String title = entry.optString("title");
if (title.length() == 0) {
title = url;
}
totalCount++;
boolean selected = false;
if (optSelected == i + 1) {
selected = true;
selectedIndex = totalCount;
}
sessionTabs.add(new SessionTab(title, url, selected, tab));
}
}
} catch (JSONException e) {
Log.e(LOGTAG, "JSON error", e);
return false;
}
// If no selected index was found, select the first tab.
if (selectedIndex == -1 && sessionTabs.size() > 0) {
sessionTabs.getFirst().mIsSelected = true;
}
for (SessionTab tab : sessionTabs) {
onTabRead(tab);
}
return true;
}
}

Some files were not shown because too many files have changed in this diff Show more