mirror of
https://github.com/k0shk0sh/FastHub.git
synced 2025-12-08 19:05:54 +00:00
releasing 4.4.0
This commit is contained in:
parent
e095e990f7
commit
e6230fe91f
3
.gitignore
vendored
3
.gitignore
vendored
@ -8,5 +8,4 @@
|
||||
/app/google-services.json
|
||||
/app/build/
|
||||
/app/src/main/res/values/secrets.xml
|
||||
/app/fastaccess-key
|
||||
/jobdispatcher/build/
|
||||
/app/fastaccess-key
|
||||
@ -29,8 +29,8 @@ android {
|
||||
applicationId "com.fastaccess.github"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 26
|
||||
versionCode 430
|
||||
versionName "4.3.0"
|
||||
versionCode 440
|
||||
versionName "4.4.0"
|
||||
buildConfigString "GITHUB_CLIENT_ID", (buildProperties.secrets['github_client_id'] | buildProperties.notThere['github_client_id']).string
|
||||
buildConfigString "GITHUB_SECRET", (buildProperties.secrets['github_secret'] | buildProperties.notThere['github_secret']).string
|
||||
buildConfigString "IMGUR_CLIENT_ID", (buildProperties.secrets['imgur_client_id'] | buildProperties.notThere['imgur_client_id']).string
|
||||
@ -167,7 +167,7 @@ dependencies {
|
||||
implementation 'com.jaredrummler:android-device-names:1.1.4'
|
||||
implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:2.1.0'
|
||||
implementation 'com.airbnb.android:lottie:2.2.0'
|
||||
implementation project(path: ':jobdispatcher')
|
||||
implementation 'com.firebase:firebase-jobdispatcher:0.8.2'
|
||||
compileOnly "org.projectlombok:lombok:${lombokVersion}"
|
||||
kapt "org.projectlombok:lombok:${lombokVersion}"
|
||||
kapt "com.evernote:android-state-processor:${state_version}"
|
||||
|
||||
@ -4,6 +4,7 @@ import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import com.fastaccess.helper.Logger;
|
||||
import com.fastaccess.ui.widgets.SpannableBuilder;
|
||||
|
||||
import net.nightwhistler.htmlspanner.TagNodeHandler;
|
||||
@ -42,10 +43,11 @@ import lombok.NoArgsConstructor;
|
||||
return node.getParent() == null ? null : node.getParent().getName();
|
||||
}
|
||||
|
||||
@Override public void beforeChildren(TagNode node, SpannableStringBuilder builder) {
|
||||
@Override public void beforeChildren(TagNode node, SpannableStringBuilder builder) {
|
||||
TodoItems todoItem = null;
|
||||
if (node.getChildTags() != null && node.getChildTags().length > 0) {
|
||||
for (TagNode tagNode : node.getChildTags()) {
|
||||
Logger.e(tagNode.getName(), tagNode.getText());
|
||||
if (tagNode.getName() != null && tagNode.getName().equals("input")) {
|
||||
todoItem = new TodoItems();
|
||||
todoItem.isChecked = tagNode.getAttributeByName("checked") != null;
|
||||
|
||||
@ -20,10 +20,8 @@ public class MarginHandler extends TagNodeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void handleTagNode(TagNode node, SpannableStringBuilder builder, int start, int end) {
|
||||
builder.setSpan(new LeadingMarginSpan.Standard(30), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
this.appendNewLine(builder);
|
||||
this.appendNewLine(builder);
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@ import com.fastaccess.BuildConfig
|
||||
import com.fastaccess.R
|
||||
import com.fastaccess.helper.AppHelper
|
||||
import com.fastaccess.helper.InputHelper
|
||||
import com.fastaccess.helper.PrefGetter
|
||||
import com.fastaccess.helper.ViewHelper
|
||||
import com.fastaccess.provider.fabric.FabricProvider
|
||||
import com.fastaccess.ui.base.BaseActivity
|
||||
@ -76,7 +75,7 @@ class PremiumActivity : BaseActivity<PremiumMvp.View, PremiumPresenter>(), Premi
|
||||
return true
|
||||
}
|
||||
|
||||
@OnClick(R.id.close) fun onClose(): Unit = finish()
|
||||
@OnClick(R.id.close) fun onClose() = finish()
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
@ -97,8 +96,6 @@ class PremiumActivity : BaseActivity<PremiumMvp.View, PremiumPresenter>(), Premi
|
||||
override fun onAnimationRepeat(p0: Animator?) {}
|
||||
override fun onAnimationEnd(p0: Animator?) {
|
||||
FabricProvider.logPurchase(InputHelper.toString(editText))
|
||||
PrefGetter.setProItems()
|
||||
PrefGetter.setEnterpriseItem()
|
||||
showMessage(R.string.success, R.string.success)
|
||||
successResult()
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.fastaccess.ui.modules.main.premium
|
||||
|
||||
import com.fastaccess.helper.Logger
|
||||
import com.fastaccess.helper.PrefGetter
|
||||
import com.fastaccess.helper.RxHelper
|
||||
import com.fastaccess.ui.base.mvp.presenter.BasePresenter
|
||||
import com.github.b3er.rxfirebase.database.data
|
||||
@ -26,13 +26,20 @@ class PremiumPresenter : BasePresenter<PremiumMvp.View>(), PremiumMvp.Presenter
|
||||
val map = it.getValue(gti)
|
||||
exists = map?.contains(promo)
|
||||
}
|
||||
Logger.e(it.children, it.childrenCount, exists)
|
||||
return@flatMap Observable.just(exists)
|
||||
}
|
||||
.doOnComplete { sendToView { it.hideProgress() } }
|
||||
.subscribe({
|
||||
when (it) {
|
||||
true -> sendToView { it.onSuccessfullyActivated() }
|
||||
true -> sendToView {
|
||||
if (promo.contains("student")) {
|
||||
PrefGetter.setProItems()
|
||||
} else {
|
||||
PrefGetter.setProItems()
|
||||
PrefGetter.setEnterpriseItem()
|
||||
}
|
||||
it.onSuccessfullyActivated()
|
||||
}
|
||||
else -> sendToView { it.onNoMatch() }
|
||||
}
|
||||
}, ::println))
|
||||
|
||||
@ -8,28 +8,22 @@
|
||||
<body id="preview">
|
||||
<h2><a id="FastHub_changelog_0"></a>FastHub changelog
|
||||
</h2>
|
||||
<h3><a id="Version__420_Create_Edit__Delete_files_make_Commits_2"></a>Version 4.3.0 (Project Columns and Cards)
|
||||
<h3><a id="Version__420_Create_Edit__Delete_files_make_Commits_2"></a>Version 4.4.0 (Org Project Columns and Cards)
|
||||
</h3>
|
||||
<blockquote>
|
||||
<p>Reporting Issues or Feature Requests in Google Play review section, will be ignored or might even get your account to be blocked from
|
||||
FastHub. You are using an app for GitHub, which provides a proper way to report issues.
|
||||
<br>
|
||||
Please report the issues in FastHub repo instead, by opening the Drawer Menu and clicking on “Report an Issue”<strong>
|
||||
PLEASE USE IT</st§rong>.
|
||||
<p>Please report the issues in FastHub repo instead, by opening the Drawer Menu and clicking on “Report an Issue”<strong>
|
||||
PLEASE USE IT</strong>.
|
||||
</p>
|
||||
</blockquote>
|
||||
<h4><a id="Bugs__Enhancements__new_Features_320_7"></a>Bugs , Enhancements & new Features (4.3.0)
|
||||
<h4><a id="Bugs__Enhancements__new_Features_320_7"></a>Bugs , Enhancements & new Features (4.4.0)
|
||||
</h4>
|
||||
<ul>
|
||||
<li>Project Columns & Cards (Edit, Create & Delete)</li>
|
||||
<li>(New) Repo Collaborators now can Edit, delete & create files.</li>
|
||||
<li>(New) Repo Collaborators now can Edit, delete & Comments.</li>
|
||||
<li>(New) Long press in Feeds to navigate directly to Repo.</li>
|
||||
<li>(Enhancement) Made Search to have minimum 2 chars.</li>
|
||||
<li>(Enhancement) Adding blockable progress when adding new comment.</li>
|
||||
<li>(Fix) Crash in PRs & Issues comments due to Table rendering!.</li>
|
||||
<li>(Fix) ReadMe scrolling when Device Animation is turned off.</li>
|
||||
<li>(Fix) Closed/Opened Issue pagination where most of the issues in the page are PRs (GitHub API!!!!).</li>
|
||||
<li>(New) Org Project Columns & Cards (Edit, Create & Delete)</li>
|
||||
<li>(New) Displaying Labels under Issue/Pr description.</li>
|
||||
<li>(Enhancement) Removal of PR review limit.</li>
|
||||
<li>(Enhancement) Selected text will be taken in consideration when adding Image/Link.</li>
|
||||
<li>(Enhancement) Removed Loading background.</li>
|
||||
<li>(Enhancement) More markdown enhancement.</li>
|
||||
<li>(Fix) Lots of bug fixes.</li>
|
||||
<li>There are more stuff are not mentioned, find them out :stuck_out_tongue:</li>
|
||||
</ul>
|
||||
|
||||
@ -592,6 +592,19 @@
|
||||
<h5>• I\'m having this issue or I want this & that!!</h5>
|
||||
<p>Head to https://github.com/k0shk0sh/FastHub/issues/new and create new issue for bugs or feature requests, I really do encourage you to
|
||||
search before opening a ticket. Any duplicate request will result in it being closed immediately.</p>
|
||||
|
||||
<h5>• How do I get PROMO CODE?</h5>
|
||||
<p>If you are a student, you\'ll have to provide me via Email that you are student, you will need below documents:</p>
|
||||
<ul>
|
||||
<li>Your university identity card & your identity card (that shows your name & your face to compare it!)</li>
|
||||
<li>Your university start & end date</li>
|
||||
<li>Rate FastHub in the Play Store</li>
|
||||
</ul>
|
||||
<p>If you aren\'t a student and you can\'t afford to pay for PRO, you\'ll need:</p>
|
||||
<ul>
|
||||
<li>Write an article about FastHub in social media such as (Medium)</li>
|
||||
<li>Rate FastHub in the Play Store</li>
|
||||
</ul>
|
||||
]]></string>
|
||||
<string name="faq">FAQ</string>
|
||||
<string name="comments_added_successfully">Comments added successfully</string>
|
||||
|
||||
@ -1,79 +0,0 @@
|
||||
apply plugin: "com.android.library"
|
||||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
buildToolsVersion "26.0.1"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 26
|
||||
versionCode 1
|
||||
versionName "0.8.0"
|
||||
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
|
||||
defaultPublishConfig "release"
|
||||
publishNonDefault true
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
testCoverageEnabled true
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt')
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
// A set of testing helpers that are shared across test types
|
||||
testLib { java.srcDir("src/main") }
|
||||
test { java.srcDir("src/testLib") } // Robolectric tests
|
||||
androidTest { java.srcDir("src/testLib") } // Android (e2e) tests
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// The main library only depends on the Android support lib
|
||||
compile "com.android.support:support-v4:26.0.1"
|
||||
|
||||
def junit = 'junit:junit:4.12'
|
||||
def robolectric = 'org.robolectric:robolectric:3.3.2'
|
||||
|
||||
// The common test library uses JUnit
|
||||
testLibCompile junit
|
||||
|
||||
// The unit tests are written using JUnit, Robolectric, and Mockito
|
||||
testCompile junit
|
||||
testCompile robolectric
|
||||
testCompile 'org.mockito:mockito-core:2.2.5'
|
||||
|
||||
// The Android (e2e) tests are written using JUnit and the test support lib
|
||||
androidTestCompile junit
|
||||
androidTestCompile 'com.android.support.test:runner:0.5'
|
||||
}
|
||||
|
||||
task javadocs(type: Javadoc) {
|
||||
description "Generate Javadocs"
|
||||
source = android.sourceSets.main.java.sourceFiles
|
||||
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
|
||||
classpath += configurations.compile
|
||||
failOnError false
|
||||
}
|
||||
|
||||
task javadocsJar(type: Jar, dependsOn: javadocs) {
|
||||
description "Package Javadocs into a jar"
|
||||
classifier = "javadoc"
|
||||
from javadocs.destinationDir
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
description "Package sources into a jar"
|
||||
classifier = "sources"
|
||||
from android.sourceSets.main.java.sourceFiles
|
||||
}
|
||||
|
||||
task aar(dependsOn: "assembleRelease") {
|
||||
group "artifact"
|
||||
description "Builds the library AARs"
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
apply plugin: "jacoco"
|
||||
|
||||
jacoco {
|
||||
// see https://github.com/jacoco/jacoco/pull/288 and the top build.gradle
|
||||
toolVersion "0.7.6.201602180812"
|
||||
}
|
||||
|
||||
android {
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
systemProperty "robolectric.logging.enabled", true
|
||||
systemProperty "robolectric.logging", "stdout"
|
||||
|
||||
jacoco {
|
||||
includeNoLocationClasses = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ignore these when generating coverage
|
||||
def ignoredPrefixes = ['R$', 'R.class', 'BuildConfig.class']
|
||||
|
||||
task coverage(type: JacocoReport, dependsOn: ["testDebugUnitTest"]) {
|
||||
group = "Reports"
|
||||
description = "Generate a coverage report"
|
||||
|
||||
classDirectories = fileTree(
|
||||
dir: "${project.buildDir}/intermediates/classes/debug/com/firebase/",
|
||||
exclude: { d -> ignoredPrefixes.any { p -> d.file.name.startsWith(p) } }
|
||||
)
|
||||
sourceDirectories = files(["src/main/java/com/firebase/"])
|
||||
executionData = files("${project.buildDir}/jacoco/testDebugUnitTest.exec")
|
||||
|
||||
reports {
|
||||
xml.enabled = true
|
||||
html.enabled = true
|
||||
html.destination "${buildDir}/coverage_html"
|
||||
}
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2017 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.firebase.jobdispatcher">
|
||||
|
||||
<uses-sdk android:minSdkVersion="14" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
|
||||
<instrumentation
|
||||
android:targetPackage="com.firebase.jobdispatcher"
|
||||
android:name="android.test.InstrumentationTestRunner" />
|
||||
|
||||
<application>
|
||||
<service android:exported="false" android:name=".TestJobService">
|
||||
<intent-filter>
|
||||
<action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
@ -1,69 +0,0 @@
|
||||
// Copyright 2017 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Basic end to end test for the JobDispatcher. Requires Google Play services be installed and
|
||||
* available.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class EndToEndTest {
|
||||
private Context appContext;
|
||||
private FirebaseJobDispatcher dispatcher;
|
||||
|
||||
@Before public void setUp() {
|
||||
appContext = InstrumentationRegistry.getTargetContext();
|
||||
dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(appContext));
|
||||
TestJobService.reset();
|
||||
}
|
||||
|
||||
@Test public void basicImmediateJob() throws InterruptedException {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
TestJobService.setProxy(new TestJobService.JobServiceProxy() {
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters params) {
|
||||
latch.countDown();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters params) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
dispatcher.mustSchedule(
|
||||
dispatcher.newJobBuilder()
|
||||
.setService(TestJobService.class)
|
||||
.setTrigger(Trigger.NOW)
|
||||
.setTag("basic-immediate-job")
|
||||
.build());
|
||||
|
||||
assertTrue("Latch wasn't counted down as expected", latch.await(120, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2016 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.firebase.jobdispatcher">
|
||||
|
||||
<application>
|
||||
<!-- Receives GooglePlay execution requests and forwards them to the
|
||||
appropriate internal service. -->
|
||||
<service
|
||||
android:name=".GooglePlayReceiver"
|
||||
android:exported="true"
|
||||
android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.gms.gcm.ACTION_TASK_READY" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
@ -1,49 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
final class BundleProtocol {
|
||||
static final String PACKED_PARAM_BUNDLE_PREFIX = "com.firebase.jobdispatcher.";
|
||||
|
||||
// PACKED_PARAM values are only read on the client side, so as long as the
|
||||
// extraction process gets the same changes then it's fine.
|
||||
static final String PACKED_PARAM_CONSTRAINTS = "constraints";
|
||||
static final String PACKED_PARAM_LIFETIME = "persistent";
|
||||
static final String PACKED_PARAM_RECURRING = "recurring";
|
||||
static final String PACKED_PARAM_SERVICE = "service";
|
||||
static final String PACKED_PARAM_TAG = "tag";
|
||||
static final String PACKED_PARAM_EXTRAS = "extras";
|
||||
static final String PACKED_PARAM_TRIGGER_TYPE = "trigger_type";
|
||||
static final String PACKED_PARAM_TRIGGER_WINDOW_END = "window_end";
|
||||
static final String PACKED_PARAM_TRIGGER_WINDOW_START = "window_start";
|
||||
static final int TRIGGER_TYPE_EXECUTION_WINDOW = 1;
|
||||
static final int TRIGGER_TYPE_IMMEDIATE = 2;
|
||||
static final int TRIGGER_TYPE_CONTENT_URI = 3;
|
||||
static final String PACKED_PARAM_RETRY_STRATEGY_INITIAL_BACKOFF_SECONDS =
|
||||
"initial_backoff_seconds";
|
||||
static final String PACKED_PARAM_RETRY_STRATEGY_MAXIMUM_BACKOFF_SECONDS =
|
||||
"maximum_backoff_seconds";
|
||||
static final String PACKED_PARAM_RETRY_STRATEGY_POLICY = "retry_policy";
|
||||
static final String PACKED_PARAM_REPLACE_CURRENT = "replace_current";
|
||||
static final String PACKED_PARAM_CONTENT_URI_FLAGS_ARRAY = "content_uri_flags_array";
|
||||
static final String PACKED_PARAM_CONTENT_URI_ARRAY = "content_uri_array";
|
||||
static final String PACKED_PARAM_TRIGGERED_URIS = "triggered_uris";
|
||||
static final String PACKED_PARAM_OBSERVED_URI = "observed_uris";
|
||||
|
||||
BundleProtocol() {
|
||||
}
|
||||
}
|
||||
@ -1,108 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* A Constraint is a runtime requirement for a job. A job only becomes eligible to run once its
|
||||
* trigger has been activated and all constraints are satisfied.
|
||||
*/
|
||||
public final class Constraint {
|
||||
/**
|
||||
* Only run the job when an unmetered network is available.
|
||||
*/
|
||||
public static final int ON_UNMETERED_NETWORK = 1;
|
||||
|
||||
/**
|
||||
* Only run the job when a network connection is available. If both this and
|
||||
* {@link #ON_UNMETERED_NETWORK} is provided, {@link #ON_UNMETERED_NETWORK} will take
|
||||
* precedence.
|
||||
*/
|
||||
public static final int ON_ANY_NETWORK = 1 << 1;
|
||||
|
||||
/**
|
||||
* Only run the job when the device is currently charging.
|
||||
*/
|
||||
public static final int DEVICE_CHARGING = 1 << 2;
|
||||
|
||||
/**
|
||||
* Only run the job when the device is idle. This is ignored for devices that don't expose the
|
||||
* concept of an idle state.
|
||||
*/
|
||||
public static final int DEVICE_IDLE = 1 << 3;
|
||||
|
||||
@VisibleForTesting
|
||||
static final int[] ALL_CONSTRAINTS = {
|
||||
ON_ANY_NETWORK, ON_UNMETERED_NETWORK, DEVICE_CHARGING, DEVICE_IDLE};
|
||||
|
||||
/** Constraint shouldn't ever be instantiated. */
|
||||
private Constraint() {}
|
||||
|
||||
/**
|
||||
* A tooling type-hint for any of the valid constraint values.
|
||||
*/
|
||||
@IntDef(flag = true, value = {
|
||||
ON_ANY_NETWORK,
|
||||
ON_UNMETERED_NETWORK,
|
||||
DEVICE_CHARGING,
|
||||
DEVICE_IDLE,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface JobConstraint {}
|
||||
|
||||
/**
|
||||
* Compact a provided array of constraints into a single int.
|
||||
*
|
||||
* @see #uncompact(int)
|
||||
*/
|
||||
static int compact(@JobConstraint int[] constraints) {
|
||||
int result = 0;
|
||||
if (constraints == null) {
|
||||
return result;
|
||||
}
|
||||
for (int c : constraints) {
|
||||
result |= c;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack a single int into an array of constraints.
|
||||
*
|
||||
* @see #compact(int[])
|
||||
*/
|
||||
static int[] uncompact(int compactConstraints) {
|
||||
int length = 0;
|
||||
for (int c : ALL_CONSTRAINTS) {
|
||||
length += (compactConstraints & c) == c ? 1 : 0;
|
||||
}
|
||||
int[] list = new int[length];
|
||||
|
||||
int i = 0;
|
||||
for (int c : ALL_CONSTRAINTS) {
|
||||
if ((compactConstraints & c) == c) {
|
||||
list[i++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@ -1,288 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static com.firebase.jobdispatcher.RetryStrategy.RETRY_POLICY_EXPONENTIAL;
|
||||
import static com.firebase.jobdispatcher.RetryStrategy.RETRY_POLICY_LINEAR;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Validates Jobs according to some safe standards.
|
||||
* <p>
|
||||
* Custom JobValidators should typically extend from this.
|
||||
*/
|
||||
public class DefaultJobValidator implements JobValidator {
|
||||
|
||||
/**
|
||||
* The maximum length of a tag, in characters (i.e. String.length()). Strings longer than this
|
||||
* will cause validation to fail.
|
||||
*/
|
||||
public static final int MAX_TAG_LENGTH = 100;
|
||||
|
||||
/**
|
||||
* The maximum size, in bytes, that the provided extras bundle can be. Corresponds to
|
||||
* {@link Parcel#dataSize()}.
|
||||
*/
|
||||
public final static int MAX_EXTRAS_SIZE_BYTES = 10 * 1024;
|
||||
|
||||
/** Private ref to the Context. Necessary to check that the manifest is configured correctly. */
|
||||
private final Context context;
|
||||
|
||||
public DefaultJobValidator(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/** @see {@link #MAX_EXTRAS_SIZE_BYTES}. */
|
||||
private static int measureBundleSize(Bundle extras) {
|
||||
Parcel p = Parcel.obtain();
|
||||
extras.writeToParcel(p, 0);
|
||||
int sizeInBytes = p.dataSize();
|
||||
p.recycle();
|
||||
|
||||
return sizeInBytes;
|
||||
}
|
||||
|
||||
/** Combines two {@literal List<String>s} together. */
|
||||
@Nullable
|
||||
private static List<String> mergeErrorLists(@Nullable List<String> errors,
|
||||
@Nullable List<String> newErrors) {
|
||||
if (errors == null) {
|
||||
return newErrors;
|
||||
}
|
||||
if (newErrors == null) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
errors.addAll(newErrors);
|
||||
return errors;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static List<String> addError(@Nullable List<String> errors, String newError) {
|
||||
if (newError == null) {
|
||||
return errors;
|
||||
}
|
||||
if (errors == null) {
|
||||
return getMutableSingletonList(newError);
|
||||
}
|
||||
|
||||
Collections.addAll(errors, newError);
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static List<String> addErrorsIf(boolean condition, List<String> errors, String newErr) {
|
||||
if (condition) {
|
||||
return addError(errors, newErr);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to validate the provided {@code JobParameters}. If the JobParameters is valid, null will be
|
||||
* returned. If the JobParameters has errors, a list of those errors will be returned.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
@CallSuper
|
||||
public List<String> validate(JobParameters job) {
|
||||
List<String> errors = null;
|
||||
|
||||
errors = mergeErrorLists(errors, validate(job.getTrigger()));
|
||||
errors = mergeErrorLists(errors, validate(job.getRetryStrategy()));
|
||||
|
||||
if (job.isRecurring() && job.getTrigger() == Trigger.NOW) {
|
||||
errors = addError(errors, "ImmediateTriggers can't be used with recurring jobs");
|
||||
}
|
||||
|
||||
errors = mergeErrorLists(errors, validateForTransport(job.getExtras()));
|
||||
if (job.getLifetime() > Lifetime.UNTIL_NEXT_BOOT) {
|
||||
//noinspection ConstantConditions
|
||||
errors = mergeErrorLists(errors, validateForPersistence(job.getExtras()));
|
||||
}
|
||||
|
||||
errors = mergeErrorLists(errors, validateTag(job.getTag()));
|
||||
errors = mergeErrorLists(errors, validateService(job.getService()));
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to validate the provided Trigger. If valid, null is returned. Otherwise a list of
|
||||
* errors will be returned.
|
||||
* <p>
|
||||
* Note that a Trigger that passes validation here is not necessarily valid in all permutations
|
||||
* of a JobParameters. For example, an Immediate is never valid for a recurring job.
|
||||
* @param trigger
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
@CallSuper
|
||||
public List<String> validate(JobTrigger trigger) {
|
||||
if (trigger != Trigger.NOW
|
||||
&& !(trigger instanceof JobTrigger.ExecutionWindowTrigger)
|
||||
&& !(trigger instanceof JobTrigger.ContentUriTrigger)) {
|
||||
return getMutableSingletonList("Unknown trigger provided");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to validate the provided RetryStrategy. If valid, null is returned. Otherwise a list
|
||||
* of errors will be returned.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
@CallSuper
|
||||
public List<String> validate(RetryStrategy retryStrategy) {
|
||||
List<String> errors = null;
|
||||
|
||||
int policy = retryStrategy.getPolicy();
|
||||
int initial = retryStrategy.getInitialBackoff();
|
||||
int maximum = retryStrategy.getMaximumBackoff();
|
||||
|
||||
errors = addErrorsIf(policy != RETRY_POLICY_EXPONENTIAL && policy != RETRY_POLICY_LINEAR,
|
||||
errors, "Unknown retry policy provided");
|
||||
errors = addErrorsIf(maximum < initial,
|
||||
errors, "Maximum backoff must be greater than or equal to initial backoff");
|
||||
errors = addErrorsIf(300 > maximum,
|
||||
errors, "Maximum backoff must be greater than 300s (5 minutes)");
|
||||
errors = addErrorsIf(initial < 30,
|
||||
errors, "Initial backoff must be at least 30s");
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private List<String> validateForPersistence(Bundle extras) {
|
||||
List<String> errors = null;
|
||||
|
||||
if (extras != null) {
|
||||
// check the types to make sure they're persistable
|
||||
for (String k : extras.keySet()) {
|
||||
errors = addError(errors, validateExtrasType(extras, k));
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private List<String> validateForTransport(Bundle extras) {
|
||||
if (extras == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int bundleSizeInBytes = measureBundleSize(extras);
|
||||
if (bundleSizeInBytes > MAX_EXTRAS_SIZE_BYTES) {
|
||||
return getMutableSingletonList(String.format(Locale.US,
|
||||
"Extras too large: %d bytes is > the max (%d bytes)",
|
||||
bundleSizeInBytes, MAX_EXTRAS_SIZE_BYTES));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String validateExtrasType(Bundle extras, String key) {
|
||||
Object o = extras.get(key);
|
||||
|
||||
if (o == null
|
||||
|| o instanceof Integer
|
||||
|| o instanceof Long
|
||||
|| o instanceof Double
|
||||
|| o instanceof String
|
||||
|| o instanceof Boolean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return String.format(Locale.US,
|
||||
"Received value of type '%s' for key '%s', but only the"
|
||||
+ " following extra parameter types are supported:"
|
||||
+ " Integer, Long, Double, String, and Boolean",
|
||||
o == null ? null : o.getClass(), key);
|
||||
}
|
||||
|
||||
private List<String> validateService(String service) {
|
||||
if (service == null || service.isEmpty()) {
|
||||
return getMutableSingletonList("Service can't be empty");
|
||||
}
|
||||
|
||||
if (context == null) {
|
||||
return getMutableSingletonList("Context is null, can't query PackageManager");
|
||||
}
|
||||
|
||||
PackageManager pm = context.getPackageManager();
|
||||
if (pm == null) {
|
||||
return getMutableSingletonList("PackageManager is null, can't validate service");
|
||||
}
|
||||
|
||||
final String msg = "Couldn't find a registered service with the name " + service
|
||||
+ ". Is it declared in the manifest with the right intent-filter?";
|
||||
|
||||
Intent executeIntent = new Intent(JobService.ACTION_EXECUTE);
|
||||
executeIntent.setClassName(context, service);
|
||||
List<ResolveInfo> intentServices = pm.queryIntentServices(executeIntent, 0);
|
||||
if (intentServices == null || intentServices.isEmpty()) {
|
||||
return getMutableSingletonList(msg);
|
||||
}
|
||||
|
||||
for (ResolveInfo info : intentServices) {
|
||||
if (info.serviceInfo != null && info.serviceInfo.enabled) {
|
||||
// found a match!
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return getMutableSingletonList(msg);
|
||||
}
|
||||
|
||||
private List<String> validateTag(String tag) {
|
||||
if (tag == null) {
|
||||
return getMutableSingletonList("Tag can't be null");
|
||||
}
|
||||
|
||||
if (tag.length() > MAX_TAG_LENGTH) {
|
||||
return getMutableSingletonList("Tag must be shorter than " + MAX_TAG_LENGTH);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static List<String> getMutableSingletonList(String msg) {
|
||||
ArrayList<String> strings = new ArrayList<>();
|
||||
strings.add(msg);
|
||||
return strings;
|
||||
}
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import com.firebase.jobdispatcher.FirebaseJobDispatcher.CancelResult;
|
||||
import com.firebase.jobdispatcher.FirebaseJobDispatcher.ScheduleResult;
|
||||
|
||||
/**
|
||||
* Driver represents a component that understands how to schedule, validate, and execute jobs.
|
||||
*/
|
||||
public interface Driver {
|
||||
|
||||
/**
|
||||
* Schedules the provided Job.
|
||||
*
|
||||
* @return one of the SCHEDULE_RESULT_ constants
|
||||
*/
|
||||
@ScheduleResult
|
||||
int schedule(@NonNull Job job);
|
||||
|
||||
/**
|
||||
* Cancels the job with the provided tag and class.
|
||||
*
|
||||
* @return one of the CANCEL_RESULT_ constants.
|
||||
*/
|
||||
@CancelResult
|
||||
int cancel(@NonNull String tag);
|
||||
|
||||
/**
|
||||
* Cancels all jobs registered with this Driver.
|
||||
*
|
||||
* @return one of the CANCEL_RESULT_ constants.
|
||||
*/
|
||||
@CancelResult
|
||||
int cancelAll();
|
||||
|
||||
/**
|
||||
* Returns a JobValidator configured for this backend.
|
||||
*/
|
||||
@NonNull
|
||||
JobValidator getValidator();
|
||||
|
||||
/**
|
||||
* Indicates whether the backend is available.
|
||||
*/
|
||||
boolean isAvailable();
|
||||
}
|
||||
@ -1,160 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static android.content.Context.BIND_AUTO_CREATE;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.support.v4.util.SimpleArrayMap;
|
||||
import android.util.Log;
|
||||
import com.firebase.jobdispatcher.JobService.JobResult;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* ExecutionDelegator tracks local Binder connections to client JobServices and handles
|
||||
* communication with those services.
|
||||
*/
|
||||
/* package */ class ExecutionDelegator {
|
||||
@VisibleForTesting
|
||||
static final int JOB_FINISHED = 1;
|
||||
|
||||
static final String TAG = "FJD.ExternalReceiver";
|
||||
|
||||
interface JobFinishedCallback {
|
||||
void onJobFinished(@NonNull JobInvocation jobInvocation, @JobResult int result);
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapping of {@link JobInvocation} to (local) binder connections.
|
||||
* Synchronized by itself.
|
||||
*/
|
||||
private final SimpleArrayMap<JobInvocation, JobServiceConnection> serviceConnections =
|
||||
new SimpleArrayMap<>();
|
||||
private final ResponseHandler responseHandler =
|
||||
new ResponseHandler(Looper.getMainLooper(), new WeakReference<>(this));
|
||||
private final Context context;
|
||||
private final JobFinishedCallback jobFinishedCallback;
|
||||
|
||||
ExecutionDelegator(Context context, JobFinishedCallback jobFinishedCallback) {
|
||||
this.context = context;
|
||||
this.jobFinishedCallback = jobFinishedCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the provided {@code jobInvocation} by kicking off the creation of a new Binder
|
||||
* connection to the Service.
|
||||
*
|
||||
* @return true if the service was bound successfully.
|
||||
*/
|
||||
boolean executeJob(JobInvocation jobInvocation) {
|
||||
if (jobInvocation == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JobServiceConnection conn = new JobServiceConnection(jobInvocation,
|
||||
responseHandler.obtainMessage(JOB_FINISHED));
|
||||
|
||||
synchronized (serviceConnections) {
|
||||
JobServiceConnection oldConnection = serviceConnections.put(jobInvocation, conn);
|
||||
if (oldConnection != null) {
|
||||
Log.e(TAG, "Received execution request for already running job");
|
||||
}
|
||||
return context.bindService(createBindIntent(jobInvocation), conn, BIND_AUTO_CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Intent createBindIntent(JobParameters jobParameters) {
|
||||
Intent execReq = new Intent(JobService.ACTION_EXECUTE);
|
||||
execReq.setClassName(context, jobParameters.getService());
|
||||
return execReq;
|
||||
}
|
||||
|
||||
void stopJob(JobInvocation job) {
|
||||
synchronized (serviceConnections) {
|
||||
JobServiceConnection jobServiceConnection = serviceConnections.remove(job);
|
||||
if (jobServiceConnection != null) {
|
||||
jobServiceConnection.onStop();
|
||||
safeUnbindService(jobServiceConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void safeUnbindService(JobServiceConnection connection) {
|
||||
if (connection != null && connection.isBound()) {
|
||||
try {
|
||||
context.unbindService(connection);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, "Error unbinding service: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onJobFinishedMessage(JobInvocation jobInvocation, int result) {
|
||||
synchronized (serviceConnections) {
|
||||
JobServiceConnection connection = serviceConnections.remove(jobInvocation);
|
||||
safeUnbindService(connection);
|
||||
}
|
||||
|
||||
jobFinishedCallback.onJobFinished(jobInvocation, result);
|
||||
}
|
||||
|
||||
private static class ResponseHandler extends Handler {
|
||||
|
||||
/**
|
||||
* We hold a WeakReference to the ExecutionDelegator because it holds a reference to a
|
||||
* Service Context and Handlers are often kept in memory longer than you'd expect because
|
||||
* any pending Messages can maintain references to them.
|
||||
*/
|
||||
private final WeakReference<ExecutionDelegator> executionDelegatorReference;
|
||||
|
||||
ResponseHandler(Looper looper, WeakReference<ExecutionDelegator> executionDelegator) {
|
||||
super(looper);
|
||||
this.executionDelegatorReference = executionDelegator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case JOB_FINISHED:
|
||||
if (msg.obj instanceof JobInvocation) {
|
||||
ExecutionDelegator delegator = this.executionDelegatorReference.get();
|
||||
if (delegator == null) {
|
||||
Log.wtf(TAG, "handleMessage: service was unexpectedly GC'd"
|
||||
+ ", can't send job result");
|
||||
return;
|
||||
}
|
||||
|
||||
delegator.onJobFinishedMessage((JobInvocation) msg.obj, msg.arg1);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.wtf(TAG, "handleMessage: unknown obj returned");
|
||||
return;
|
||||
|
||||
default:
|
||||
Log.wtf(TAG, "handleMessage: unknown message type received: " + msg.what);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,211 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.NonNull;
|
||||
import com.firebase.jobdispatcher.RetryStrategy.RetryPolicy;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* The FirebaseJobDispatcher provides a driver-agnostic API for scheduling and cancelling Jobs.
|
||||
*
|
||||
* @see #FirebaseJobDispatcher(Driver)
|
||||
* @see Driver
|
||||
* @see JobParameters
|
||||
*/
|
||||
public final class FirebaseJobDispatcher {
|
||||
/**
|
||||
* Indicates the schedule request seems to have been successful.
|
||||
*/
|
||||
public final static int SCHEDULE_RESULT_SUCCESS = 0;
|
||||
|
||||
/**
|
||||
* Indicates the schedule request encountered an unknown error.
|
||||
*/
|
||||
public final static int SCHEDULE_RESULT_UNKNOWN_ERROR = 1;
|
||||
|
||||
/**
|
||||
* Indicates the schedule request failed because the driver was unavailable.
|
||||
*/
|
||||
public final static int SCHEDULE_RESULT_NO_DRIVER_AVAILABLE = 2;
|
||||
|
||||
/**
|
||||
* Indicates the schedule request failed because the Trigger was unsupported.
|
||||
*/
|
||||
public final static int SCHEDULE_RESULT_UNSUPPORTED_TRIGGER = 3;
|
||||
|
||||
/**
|
||||
* Indicates the schedule request failed because the service is not exposed or configured
|
||||
* correctly.
|
||||
*/
|
||||
public final static int SCHEDULE_RESULT_BAD_SERVICE = 4;
|
||||
|
||||
/**
|
||||
* Indicates the cancel request seems to have been successful.
|
||||
*/
|
||||
public final static int CANCEL_RESULT_SUCCESS = 0;
|
||||
/**
|
||||
* Indicates the cancel request encountered an unknown error.
|
||||
*/
|
||||
public final static int CANCEL_RESULT_UNKNOWN_ERROR = 1;
|
||||
/**
|
||||
* Indicates the cancel request failed because the driver was unavailable.
|
||||
*/
|
||||
public final static int CANCEL_RESULT_NO_DRIVER_AVAILABLE = 2;
|
||||
/**
|
||||
* The backing Driver for this instance.
|
||||
*/
|
||||
private final Driver mDriver;
|
||||
/**
|
||||
* The ValidationEnforcer configured for the current Driver.
|
||||
*/
|
||||
private final ValidationEnforcer mValidator;
|
||||
/**
|
||||
* Single instance of a RetryStrategy.Builder, configured with the current driver's validation
|
||||
* settings. We can do this because the RetryStrategy.Builder is stateless.
|
||||
*/
|
||||
private RetryStrategy.Builder mRetryStrategyBuilder;
|
||||
|
||||
/**
|
||||
* Instantiates a new FirebaseJobDispatcher using the provided Driver.
|
||||
*/
|
||||
public FirebaseJobDispatcher(Driver driver) {
|
||||
mDriver = driver;
|
||||
mValidator = new ValidationEnforcer(mDriver.getValidator());
|
||||
mRetryStrategyBuilder = new RetryStrategy.Builder(mValidator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to schedule the provided Job.
|
||||
* <p>
|
||||
* Returns one of the SCHEDULE_RESULT_ constants.
|
||||
*/
|
||||
@ScheduleResult
|
||||
public int schedule(@NonNull Job job) {
|
||||
if (!mDriver.isAvailable()) {
|
||||
return SCHEDULE_RESULT_NO_DRIVER_AVAILABLE;
|
||||
}
|
||||
|
||||
return mDriver.schedule(job);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to cancel the Job that matches the provided tag and endpoint.
|
||||
* <p>
|
||||
* Returns one of the CANCEL_RESULT_ constants.
|
||||
*/
|
||||
@CancelResult
|
||||
public int cancel(@NonNull String tag) {
|
||||
if (!mDriver.isAvailable()) {
|
||||
return CANCEL_RESULT_NO_DRIVER_AVAILABLE;
|
||||
}
|
||||
|
||||
return mDriver.cancel(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to cancel all Jobs registered for this package.
|
||||
* <p>
|
||||
* Returns one of the CANCEL_RESULT_ constants.
|
||||
*/
|
||||
@CancelResult
|
||||
public int cancelAll() {
|
||||
if (!mDriver.isAvailable()) {
|
||||
return CANCEL_RESULT_NO_DRIVER_AVAILABLE;
|
||||
}
|
||||
|
||||
return mDriver.cancelAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to schedule the provided Job, throwing an exception if it fails.
|
||||
*
|
||||
* @throws ScheduleFailedException
|
||||
*/
|
||||
public void mustSchedule(Job job) {
|
||||
if (schedule(job) != SCHEDULE_RESULT_SUCCESS) {
|
||||
throw new ScheduleFailedException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ValidationEnforcer configured for the current Driver.
|
||||
*/
|
||||
public ValidationEnforcer getValidator() {
|
||||
return mValidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Job.Builder, configured with the current driver's validation settings.
|
||||
*/
|
||||
@NonNull
|
||||
public Job.Builder newJobBuilder() {
|
||||
return new Job.Builder(mValidator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RetryStrategy from the provided parameters, validated with the current driver's
|
||||
* {@link JobValidator}.
|
||||
*
|
||||
* @param policy the backoff policy to use. One of the {@link RetryPolicy} constants.
|
||||
* @param initialBackoff the initial backoff, in seconds.
|
||||
* @param maximumBackoff the maximum backoff, in seconds.
|
||||
* @throws ValidationEnforcer.ValidationException
|
||||
* @see RetryStrategy
|
||||
*/
|
||||
public RetryStrategy newRetryStrategy(@RetryPolicy int policy, int initialBackoff,
|
||||
int maximumBackoff) {
|
||||
|
||||
return mRetryStrategyBuilder.build(policy, initialBackoff, maximumBackoff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Results that can legally be returned from {@link #schedule(Job)} calls.
|
||||
*/
|
||||
@IntDef({
|
||||
SCHEDULE_RESULT_SUCCESS,
|
||||
SCHEDULE_RESULT_UNKNOWN_ERROR,
|
||||
SCHEDULE_RESULT_NO_DRIVER_AVAILABLE,
|
||||
SCHEDULE_RESULT_UNSUPPORTED_TRIGGER,
|
||||
SCHEDULE_RESULT_BAD_SERVICE,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ScheduleResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Results that can legally be returned from {@link #cancel(String)} or {@link #cancelAll()}
|
||||
* calls.
|
||||
*/
|
||||
@IntDef({
|
||||
CANCEL_RESULT_SUCCESS,
|
||||
CANCEL_RESULT_UNKNOWN_ERROR,
|
||||
CANCEL_RESULT_NO_DRIVER_AVAILABLE,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface CancelResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when a {@link FirebaseJobDispatcher#schedule(com.firebase.jobdispatcher.Job)} call
|
||||
* fails.
|
||||
*/
|
||||
public final static class ScheduleFailedException extends RuntimeException {
|
||||
}
|
||||
}
|
||||
@ -1,247 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Responsible for extracting a JobCallback from a given Bundle.
|
||||
*
|
||||
* <p>Google Play services will send the Binder packed inside a simple strong Binder wrapper ({@link
|
||||
* #PENDING_CALLBACK_CLASS}) under the key {@link #BUNDLE_KEY_CALLBACK "callback"}.
|
||||
*/
|
||||
/* package */ final class GooglePlayCallbackExtractor {
|
||||
|
||||
private static final String TAG = GooglePlayReceiver.TAG;
|
||||
private static final String ERROR_NULL_CALLBACK = "No callback received, terminating";
|
||||
private static final String ERROR_INVALID_CALLBACK = "Bad callback received, terminating";
|
||||
|
||||
/** The Parcelable class that wraps the Binder we need to access. */
|
||||
private static final String PENDING_CALLBACK_CLASS =
|
||||
"com.google.android.gms.gcm.PendingCallback";
|
||||
/** The key for the wrapped Binder. */
|
||||
private static final String BUNDLE_KEY_CALLBACK = "callback";
|
||||
/** A magic number that indicates the following bytes belong to a Bundle. */
|
||||
private static final int BUNDLE_MAGIC = 0x4C444E42;
|
||||
/** A magic number that indicates the following value is a Parcelable. */
|
||||
private static final int VAL_PARCELABLE = 4;
|
||||
|
||||
// GuardedBy("GooglePlayCallbackExtractor.class")
|
||||
private static Boolean shouldReadKeysAsStringsCached = null;
|
||||
|
||||
public Pair<JobCallback, Bundle> extractCallback(@Nullable Bundle data) {
|
||||
if (data == null) {
|
||||
Log.e(TAG, ERROR_NULL_CALLBACK);
|
||||
return null;
|
||||
}
|
||||
|
||||
return extractWrappedBinderFromParcel(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundles are written out in the following format:
|
||||
* A header, which consists of:
|
||||
* <ol>
|
||||
* <li>length (int)</li>
|
||||
* <li>magic number ({@link #BUNDLE_MAGIC}) (int)</li>
|
||||
* <li>number of entries (int)</li>
|
||||
* </ol>
|
||||
* <p/>
|
||||
* Then the map values, each of which looks like this:
|
||||
* <ol>
|
||||
* <li>string key</li>
|
||||
* <li>int type marker</li>
|
||||
* <li>(any) parceled value</li>
|
||||
* </ol>
|
||||
* <p/>
|
||||
* We're just going to iterate over the map looking for the right key (BUNDLE_KEY_CALLBACK)
|
||||
* and try and read the IBinder straight from the parcelled data. This is entirely dependent
|
||||
* on the implementation of Parcel, but these specific parts of Parcel / Bundle haven't
|
||||
* changed since 2008 and newer versions of Android will ship with newer versions of Google
|
||||
* Play services which embed the IBinder directly into the Bundle (no need to deal with the
|
||||
* Parcelable issues).
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressLint("ParcelClassLoader")
|
||||
private Pair<JobCallback, Bundle> extractWrappedBinderFromParcel(Bundle data) {
|
||||
Bundle cleanBundle = new Bundle();
|
||||
Parcel serialized = toParcel(data);
|
||||
JobCallback callback = null;
|
||||
|
||||
try {
|
||||
int length = serialized.readInt();
|
||||
if (length <= 0) {
|
||||
// Empty Bundle
|
||||
Log.w(TAG, ERROR_NULL_CALLBACK);
|
||||
return null;
|
||||
}
|
||||
|
||||
int magic = serialized.readInt();
|
||||
if (magic != BUNDLE_MAGIC) {
|
||||
// Not a Bundle
|
||||
Log.w(TAG, ERROR_NULL_CALLBACK);
|
||||
return null;
|
||||
}
|
||||
|
||||
int numEntries = serialized.readInt();
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
String entryKey = readKey(serialized);
|
||||
if (entryKey == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(callback == null && BUNDLE_KEY_CALLBACK.equals(entryKey))) {
|
||||
// If it's not the 'callback' key, we can just read it using the standard
|
||||
// mechanisms because we're not afraid of rogue BadParcelableExceptions.
|
||||
Object value = serialized.readValue(null /* class loader */);
|
||||
if (value instanceof String) {
|
||||
cleanBundle.putString(entryKey, (String) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
cleanBundle.putBoolean(entryKey, (boolean) value);
|
||||
} else if (value instanceof Integer) {
|
||||
cleanBundle.putInt(entryKey, (int) value);
|
||||
} else if (value instanceof ArrayList) {
|
||||
// The only acceptable ArrayList in a Bundle is one that consists entirely
|
||||
// of Parcelables, so this cast is safe.
|
||||
@SuppressWarnings("unchecked") // safe by specification
|
||||
ArrayList<Parcelable> arrayList = (ArrayList<Parcelable>) value;
|
||||
cleanBundle.putParcelableArrayList(entryKey, arrayList);
|
||||
} else if (value instanceof Bundle) {
|
||||
cleanBundle.putBundle(entryKey, (Bundle) value);
|
||||
} else if (value instanceof Parcelable) {
|
||||
cleanBundle.putParcelable(entryKey, (Parcelable) value);
|
||||
}
|
||||
|
||||
// Move to the next key
|
||||
continue;
|
||||
}
|
||||
|
||||
int typeTag = serialized.readInt();
|
||||
if (typeTag != VAL_PARCELABLE) {
|
||||
// If the key is correct ("callback"), but it's not a Parcelable then something
|
||||
// went wrong and we should bail.
|
||||
Log.w(TAG, ERROR_INVALID_CALLBACK);
|
||||
return null;
|
||||
}
|
||||
|
||||
String clsname = serialized.readString();
|
||||
if (!PENDING_CALLBACK_CLASS.equals(clsname)) {
|
||||
// If it's a Parcelable, but not one we recognize then we should not try and
|
||||
// unpack it.
|
||||
Log.w(TAG, ERROR_INVALID_CALLBACK);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Instead of trying to instantiate clsname, we'll just read its single member.
|
||||
IBinder remote = serialized.readStrongBinder();
|
||||
callback = new GooglePlayJobCallback(remote);
|
||||
}
|
||||
|
||||
if (callback == null) {
|
||||
Log.w(TAG, ERROR_NULL_CALLBACK);
|
||||
return null;
|
||||
}
|
||||
return Pair.create(callback, cleanBundle);
|
||||
} finally {
|
||||
serialized.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private static Parcel toParcel(Bundle data) {
|
||||
Parcel serialized = Parcel.obtain();
|
||||
data.writeToParcel(serialized, 0);
|
||||
serialized.setDataPosition(0);
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next key (String) from the provided {@code serialized} Parcel.
|
||||
*
|
||||
* <p>Naively using {@link Parcel#readString()} fails on versions of Android older than L,
|
||||
* whereas {@link Parcel#readValue(ClassLoader)} works on older versions but fails on anything L
|
||||
* or newer.
|
||||
*/
|
||||
private String readKey(Parcel serialized) {
|
||||
if (shouldReadKeysAsStrings()) {
|
||||
return serialized.readString();
|
||||
}
|
||||
|
||||
// Older platforms require readValue
|
||||
Object entryKeyObj = serialized.readValue(null /* Use the system ClassLoader */);
|
||||
if (!(entryKeyObj instanceof String)) {
|
||||
// Should never happen (Bundle keys are always Strings)
|
||||
Log.w(TAG, ERROR_INVALID_CALLBACK);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (String) entryKeyObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether {@link Parcel#readString()} or {@link Parcel#readValue()} should be used to
|
||||
* access Bundle keys from a serialized Parcel. Commit {@link
|
||||
* https://android.googlesource.com/platform/frameworks/base/+/9c3e74f
|
||||
* I57bda9eb79ceaaa9c1b94ad49d9e462b52102149} (which only officially landed in Lollipop) changed
|
||||
* from using writeValue to writeString for Bundle keys. Some OEMs have pulled this change into
|
||||
* their KitKat fork, so we can't trust the SDK version check. Instead, we'll write a dummy
|
||||
* Bundle to a Parcel and figure it out using that.
|
||||
*
|
||||
* The check is cached because the result can't change during runtime.
|
||||
*/
|
||||
private static synchronized boolean shouldReadKeysAsStrings() {
|
||||
// We're pretty sure that readString() should always be used on L+, but if we shortcircuit
|
||||
// this check then we have no evidence that this code is functioning correctly on KitKat
|
||||
// devices that have the corresponding writeString() change.
|
||||
if (shouldReadKeysAsStringsCached == null) {
|
||||
final String expectedKey = "key";
|
||||
Bundle testBundle = new Bundle();
|
||||
testBundle.putString(expectedKey, "value");
|
||||
Parcel testParcel = toParcel(testBundle);
|
||||
try {
|
||||
// length
|
||||
checkCondition(testParcel.readInt() > 0);
|
||||
// magic
|
||||
checkCondition(testParcel.readInt() == BUNDLE_MAGIC);
|
||||
// num entries
|
||||
checkCondition(testParcel.readInt() == 1);
|
||||
|
||||
shouldReadKeysAsStringsCached = expectedKey.equals(testParcel.readString());
|
||||
} catch (RuntimeException e) {
|
||||
shouldReadKeysAsStringsCached = Boolean.FALSE;
|
||||
} finally {
|
||||
testParcel.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
return shouldReadKeysAsStringsCached;
|
||||
}
|
||||
|
||||
/** Throws an {@code IllegalStateException} if {@code condition} is false. */
|
||||
private static void checkCondition(boolean condition) {
|
||||
if (!condition) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,162 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.firebase.jobdispatcher.FirebaseJobDispatcher.ScheduleResult;
|
||||
|
||||
/**
|
||||
* GooglePlayDriver provides an implementation of Driver for devices with Google Play
|
||||
* services installed. This backend does not do any availability checks and any uses should be
|
||||
* guarded with a call to {@code GoogleApiAvailability#isGooglePlayServicesAvailable(android.content.Context)}
|
||||
*
|
||||
* @see
|
||||
* <a href="https://developers.google.com/android/reference/com/google/android/gms/common/GoogleApiAvailability#isGooglePlayServicesAvailable(android.content.Context)">GoogleApiAvailability</a>
|
||||
*/
|
||||
public final class GooglePlayDriver implements Driver {
|
||||
static final String BACKEND_PACKAGE = "com.google.android.gms";
|
||||
private final static String ACTION_SCHEDULE = "com.google.android.gms.gcm.ACTION_SCHEDULE";
|
||||
|
||||
private final static String BUNDLE_PARAM_SCHEDULER_ACTION = "scheduler_action";
|
||||
private final static String BUNDLE_PARAM_TAG = "tag";
|
||||
private final static String BUNDLE_PARAM_TOKEN = "app";
|
||||
private final static String BUNDLE_PARAM_COMPONENT = "component";
|
||||
|
||||
private final static String SCHEDULER_ACTION_SCHEDULE_TASK = "SCHEDULE_TASK";
|
||||
private final static String SCHEDULER_ACTION_CANCEL_TASK = "CANCEL_TASK";
|
||||
private final static String SCHEDULER_ACTION_CANCEL_ALL = "CANCEL_ALL";
|
||||
private static final String INTENT_PARAM_SOURCE = "source";
|
||||
private static final String INTENT_PARAM_SOURCE_VERSION = "source_version";
|
||||
|
||||
private static final int JOB_DISPATCHER_SOURCE_CODE = 1 << 3;
|
||||
private static final int JOB_DISPATCHER_SOURCE_VERSION_CODE = 1;
|
||||
|
||||
private final JobValidator mValidator;
|
||||
/**
|
||||
* The application Context. Used to send broadcasts.
|
||||
*/
|
||||
private final Context mContext;
|
||||
/**
|
||||
* A PendingIntent from this package. Passed inside the broadcast so the receiver can verify the
|
||||
* sender's package.
|
||||
*/
|
||||
private final PendingIntent mToken;
|
||||
/**
|
||||
* Turns Jobs into Bundles.
|
||||
*/
|
||||
private final GooglePlayJobWriter mWriter;
|
||||
|
||||
/**
|
||||
* Instantiates a new GooglePlayDriver.
|
||||
*/
|
||||
public GooglePlayDriver(Context context) {
|
||||
mContext = context;
|
||||
mToken = PendingIntent.getBroadcast(context, 0, new Intent(), 0);
|
||||
mWriter = new GooglePlayJobWriter();
|
||||
mValidator = new DefaultJobValidator(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
ApplicationInfo applicationInfo = null;
|
||||
try {
|
||||
applicationInfo = mContext.getPackageManager().getApplicationInfo(BACKEND_PACKAGE, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return applicationInfo != null && applicationInfo.enabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Schedules the provided Job.
|
||||
*/
|
||||
@Override
|
||||
@ScheduleResult
|
||||
public int schedule(@NonNull Job job) {
|
||||
mContext.sendBroadcast(createScheduleRequest(job));
|
||||
|
||||
return FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int cancel(@NonNull String tag) {
|
||||
mContext.sendBroadcast(createCancelRequest(tag));
|
||||
|
||||
return FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int cancelAll() {
|
||||
mContext.sendBroadcast(createBatchCancelRequest());
|
||||
|
||||
return FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Intent createCancelRequest(@NonNull String tag) {
|
||||
Intent cancelReq = createSchedulerIntent(SCHEDULER_ACTION_CANCEL_TASK);
|
||||
cancelReq.putExtra(BUNDLE_PARAM_TAG, tag);
|
||||
cancelReq.putExtra(BUNDLE_PARAM_COMPONENT, new ComponentName(mContext, getReceiverClass()));
|
||||
return cancelReq;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Intent createBatchCancelRequest() {
|
||||
Intent cancelReq = createSchedulerIntent(SCHEDULER_ACTION_CANCEL_ALL);
|
||||
cancelReq.putExtra(BUNDLE_PARAM_COMPONENT, new ComponentName(mContext, getReceiverClass()));
|
||||
return cancelReq;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Class<GooglePlayReceiver> getReceiverClass() {
|
||||
return GooglePlayReceiver.class;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public JobValidator getValidator() {
|
||||
return mValidator;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Intent createScheduleRequest(JobParameters job) {
|
||||
Intent scheduleReq = createSchedulerIntent(SCHEDULER_ACTION_SCHEDULE_TASK);
|
||||
scheduleReq.putExtras(mWriter.writeToBundle(job, scheduleReq.getExtras()));
|
||||
return scheduleReq;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Intent createSchedulerIntent(String schedulerAction) {
|
||||
Intent scheduleReq = new Intent(ACTION_SCHEDULE);
|
||||
|
||||
scheduleReq.setPackage(BACKEND_PACKAGE);
|
||||
scheduleReq.putExtra(BUNDLE_PARAM_SCHEDULER_ACTION, schedulerAction);
|
||||
scheduleReq.putExtra(BUNDLE_PARAM_TOKEN, mToken);
|
||||
scheduleReq.putExtra(INTENT_PARAM_SOURCE, JOB_DISPATCHER_SOURCE_CODE);
|
||||
scheduleReq.putExtra(INTENT_PARAM_SOURCE_VERSION, JOB_DISPATCHER_SOURCE_VERSION_CODE);
|
||||
|
||||
return scheduleReq;
|
||||
}
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
|
||||
/**
|
||||
* Wraps the GooglePlay-specific callback class in a JobCallback-compatible interface.
|
||||
*/
|
||||
/* package */ final class GooglePlayJobCallback implements JobCallback {
|
||||
|
||||
private static final String DESCRIPTOR = "com.google.android.gms.gcm.INetworkTaskCallback";
|
||||
/** The only supported transaction ID. */
|
||||
private static final int TRANSACTION_TASK_FINISHED = IBinder.FIRST_CALL_TRANSACTION + 1;
|
||||
|
||||
private final IBinder mRemote;
|
||||
|
||||
public GooglePlayJobCallback(IBinder binder) {
|
||||
mRemote = binder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jobFinished(@JobService.JobResult int status) {
|
||||
Parcel request = Parcel.obtain();
|
||||
Parcel response = Parcel.obtain();
|
||||
try {
|
||||
request.writeInterfaceToken(DESCRIPTOR);
|
||||
request.writeInt(status);
|
||||
|
||||
mRemote.transact(TRANSACTION_TASK_FINISHED, request, response, 0);
|
||||
|
||||
response.readException();
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
request.recycle();
|
||||
response.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,198 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger;
|
||||
import com.firebase.jobdispatcher.RetryStrategy.RetryPolicy;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/* package */ final class GooglePlayJobWriter {
|
||||
|
||||
static final String REQUEST_PARAM_UPDATE_CURRENT = "update_current";
|
||||
static final String REQUEST_PARAM_EXTRAS = "extras";
|
||||
static final String REQUEST_PARAM_PERSISTED = "persisted";
|
||||
static final String REQUEST_PARAM_REQUIRED_NETWORK = "requiredNetwork";
|
||||
static final String REQUEST_PARAM_REQUIRES_CHARGING = "requiresCharging";
|
||||
static final String REQUEST_PARAM_REQUIRES_IDLE = "requiresIdle";
|
||||
static final String REQUEST_PARAM_RETRY_STRATEGY = "retryStrategy";
|
||||
static final String REQUEST_PARAM_SERVICE = "service";
|
||||
static final String REQUEST_PARAM_TAG = "tag";
|
||||
|
||||
static final String REQUEST_PARAM_RETRY_STRATEGY_INITIAL_BACKOFF_SECONDS =
|
||||
"initial_backoff_seconds";
|
||||
static final String REQUEST_PARAM_RETRY_STRATEGY_MAXIMUM_BACKOFF_SECONDS =
|
||||
"maximum_backoff_seconds";
|
||||
static final String REQUEST_PARAM_RETRY_STRATEGY_POLICY = "retry_policy";
|
||||
|
||||
static final String REQUEST_PARAM_TRIGGER_TYPE = "trigger_type";
|
||||
static final String REQUEST_PARAM_TRIGGER_WINDOW_END = "window_end";
|
||||
static final String REQUEST_PARAM_TRIGGER_WINDOW_FLEX = "period_flex";
|
||||
static final String REQUEST_PARAM_TRIGGER_WINDOW_PERIOD = "period";
|
||||
static final String REQUEST_PARAM_TRIGGER_WINDOW_START = "window_start";
|
||||
|
||||
@VisibleForTesting
|
||||
/* package */ static final int LEGACY_RETRY_POLICY_EXPONENTIAL = 0;
|
||||
@VisibleForTesting
|
||||
/* package */ static final int LEGACY_RETRY_POLICY_LINEAR = 1;
|
||||
@VisibleForTesting
|
||||
/* package */ final static int LEGACY_NETWORK_UNMETERED = 1;
|
||||
@VisibleForTesting
|
||||
/* package */ final static int LEGACY_NETWORK_CONNECTED = 0;
|
||||
@VisibleForTesting
|
||||
/* package */ final static int LEGACY_NETWORK_ANY = 2;
|
||||
|
||||
private JobCoder jobCoder = new JobCoder(BundleProtocol.PACKED_PARAM_BUNDLE_PREFIX, false);
|
||||
|
||||
private static void writeExecutionWindowTriggerToBundle(JobParameters job, Bundle b,
|
||||
JobTrigger.ExecutionWindowTrigger trigger) {
|
||||
|
||||
b.putInt(REQUEST_PARAM_TRIGGER_TYPE, BundleProtocol.TRIGGER_TYPE_EXECUTION_WINDOW);
|
||||
|
||||
if (job.isRecurring()) {
|
||||
b.putLong(REQUEST_PARAM_TRIGGER_WINDOW_PERIOD,
|
||||
trigger.getWindowEnd());
|
||||
b.putLong(REQUEST_PARAM_TRIGGER_WINDOW_FLEX,
|
||||
trigger.getWindowEnd() - trigger.getWindowStart());
|
||||
} else {
|
||||
b.putLong(REQUEST_PARAM_TRIGGER_WINDOW_START,
|
||||
trigger.getWindowStart());
|
||||
b.putLong(REQUEST_PARAM_TRIGGER_WINDOW_END,
|
||||
trigger.getWindowEnd());
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeImmediateTriggerToBundle(Bundle b) {
|
||||
b.putInt(REQUEST_PARAM_TRIGGER_TYPE, BundleProtocol.TRIGGER_TYPE_IMMEDIATE);
|
||||
b.putLong(REQUEST_PARAM_TRIGGER_WINDOW_START, 0);
|
||||
b.putLong(REQUEST_PARAM_TRIGGER_WINDOW_END, 30);
|
||||
}
|
||||
|
||||
private void writeContentUriTriggerToBundle(Bundle data, ContentUriTrigger uriTrigger) {
|
||||
data.putInt(BundleProtocol.PACKED_PARAM_TRIGGER_TYPE,
|
||||
BundleProtocol.TRIGGER_TYPE_CONTENT_URI);
|
||||
|
||||
int size = uriTrigger.getUris().size();
|
||||
int[] flagsArray = new int[size];
|
||||
Uri[] uriArray = new Uri[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
ObservedUri uri = uriTrigger.getUris().get(i);
|
||||
flagsArray[i] = uri.getFlags();
|
||||
uriArray[i] = uri.getUri();
|
||||
}
|
||||
data.putIntArray(BundleProtocol.PACKED_PARAM_CONTENT_URI_FLAGS_ARRAY, flagsArray);
|
||||
data.putParcelableArray(BundleProtocol.PACKED_PARAM_CONTENT_URI_ARRAY, uriArray);
|
||||
}
|
||||
|
||||
public Bundle writeToBundle(JobParameters job, Bundle b) {
|
||||
b.putString(REQUEST_PARAM_TAG, job.getTag());
|
||||
b.putBoolean(REQUEST_PARAM_UPDATE_CURRENT, job.shouldReplaceCurrent());
|
||||
|
||||
boolean persisted = job.getLifetime() == Lifetime.FOREVER;
|
||||
b.putBoolean(REQUEST_PARAM_PERSISTED, persisted);
|
||||
b.putString(REQUEST_PARAM_SERVICE, GooglePlayReceiver.class.getName());
|
||||
|
||||
writeTriggerToBundle(job, b);
|
||||
writeConstraintsToBundle(job, b);
|
||||
writeRetryStrategyToBundle(job, b);
|
||||
|
||||
// Embed the job spec (minus extras) into the extras (under a prefix)
|
||||
Bundle extras = job.getExtras();
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
}
|
||||
b.putBundle(REQUEST_PARAM_EXTRAS, jobCoder.encode(job, extras));
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
private void writeRetryStrategyToBundle(JobParameters job, Bundle b) {
|
||||
RetryStrategy strategy = job.getRetryStrategy();
|
||||
|
||||
Bundle rb = new Bundle();
|
||||
rb.putInt(REQUEST_PARAM_RETRY_STRATEGY_POLICY,
|
||||
convertRetryPolicyToLegacyVersion(strategy.getPolicy()));
|
||||
rb.putInt(REQUEST_PARAM_RETRY_STRATEGY_INITIAL_BACKOFF_SECONDS,
|
||||
strategy.getInitialBackoff());
|
||||
rb.putInt(REQUEST_PARAM_RETRY_STRATEGY_MAXIMUM_BACKOFF_SECONDS,
|
||||
strategy.getMaximumBackoff());
|
||||
|
||||
b.putBundle(REQUEST_PARAM_RETRY_STRATEGY, rb);
|
||||
}
|
||||
|
||||
private int convertRetryPolicyToLegacyVersion(@RetryPolicy int policy) {
|
||||
switch (policy) {
|
||||
case RetryStrategy.RETRY_POLICY_LINEAR:
|
||||
return LEGACY_RETRY_POLICY_LINEAR;
|
||||
|
||||
case RetryStrategy.RETRY_POLICY_EXPONENTIAL:
|
||||
// fallthrough
|
||||
default:
|
||||
return LEGACY_RETRY_POLICY_EXPONENTIAL;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeTriggerToBundle(JobParameters job, Bundle b) {
|
||||
final JobTrigger trigger = job.getTrigger();
|
||||
|
||||
if (trigger == Trigger.NOW) {
|
||||
writeImmediateTriggerToBundle(b);
|
||||
} else if (trigger instanceof JobTrigger.ExecutionWindowTrigger) {
|
||||
writeExecutionWindowTriggerToBundle(job, b, (JobTrigger.ExecutionWindowTrigger) trigger);
|
||||
} else if (trigger instanceof JobTrigger.ContentUriTrigger) {
|
||||
writeContentUriTriggerToBundle(b, (JobTrigger.ContentUriTrigger) trigger);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown trigger: " + trigger.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeConstraintsToBundle(JobParameters job, Bundle b) {
|
||||
int c = Constraint.compact(job.getConstraints());
|
||||
|
||||
b.putBoolean(REQUEST_PARAM_REQUIRES_CHARGING,
|
||||
(c & Constraint.DEVICE_CHARGING) == Constraint.DEVICE_CHARGING);
|
||||
b.putBoolean(REQUEST_PARAM_REQUIRES_IDLE,
|
||||
(c & Constraint.DEVICE_IDLE) == Constraint.DEVICE_IDLE);
|
||||
b.putInt(REQUEST_PARAM_REQUIRED_NETWORK, convertConstraintsToLegacyNetConstant(c));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a bitmap of Constraint values into a LegacyNetworkConstraint constant (int).
|
||||
*/
|
||||
@LegacyNetworkConstant
|
||||
private int convertConstraintsToLegacyNetConstant(int constraintMap) {
|
||||
int reqNet = LEGACY_NETWORK_ANY;
|
||||
|
||||
reqNet = (constraintMap & Constraint.ON_ANY_NETWORK) == Constraint.ON_ANY_NETWORK
|
||||
? LEGACY_NETWORK_CONNECTED
|
||||
: reqNet;
|
||||
|
||||
reqNet = (constraintMap & Constraint.ON_UNMETERED_NETWORK) == Constraint.ON_UNMETERED_NETWORK
|
||||
? LEGACY_NETWORK_UNMETERED
|
||||
: reqNet;
|
||||
|
||||
return reqNet;
|
||||
}
|
||||
|
||||
@IntDef({LEGACY_NETWORK_ANY, LEGACY_NETWORK_CONNECTED, LEGACY_NETWORK_UNMETERED})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface LegacyNetworkConstant {}
|
||||
}
|
||||
@ -1,114 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static com.firebase.jobdispatcher.GooglePlayJobWriter.REQUEST_PARAM_TAG;
|
||||
import static com.firebase.jobdispatcher.GooglePlayReceiver.TAG;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.util.Log;
|
||||
import com.firebase.jobdispatcher.JobInvocation.Builder;
|
||||
|
||||
/**
|
||||
* A messenger for communication with GCM Network Scheduler.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
class GooglePlayMessageHandler extends Handler {
|
||||
|
||||
static final int MSG_START_EXEC = 1;
|
||||
static final int MSG_STOP_EXEC = 2;
|
||||
static final int MSG_RESULT = 3;
|
||||
private static final int MSG_INIT = 4;
|
||||
private final GooglePlayReceiver googlePlayReceiver;
|
||||
|
||||
public GooglePlayMessageHandler(Looper mainLooper, GooglePlayReceiver googlePlayReceiver) {
|
||||
super(mainLooper);
|
||||
this.googlePlayReceiver = googlePlayReceiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppOpsManager appOpsManager = (AppOpsManager) googlePlayReceiver.getApplicationContext()
|
||||
.getSystemService(Context.APP_OPS_SERVICE);
|
||||
try {
|
||||
appOpsManager.checkPackage(message.sendingUid, GooglePlayDriver.BACKEND_PACKAGE);
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Message was not sent from GCM.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.what) {
|
||||
case MSG_START_EXEC:
|
||||
handleStartMessage(message);
|
||||
break;
|
||||
|
||||
case MSG_STOP_EXEC:
|
||||
handleStopMessage(message);
|
||||
break;
|
||||
|
||||
case MSG_INIT:
|
||||
// Not implemented.
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.e(TAG, "Unrecognized message received: " + message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStartMessage(Message message) {
|
||||
final Bundle data = message.getData();
|
||||
|
||||
final Messenger replyTo = message.replyTo;
|
||||
String tag = data.getString(REQUEST_PARAM_TAG);
|
||||
if (replyTo == null || tag == null) {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "Invalid start execution message.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
GooglePlayMessengerCallback messengerCallback =
|
||||
new GooglePlayMessengerCallback(replyTo, tag);
|
||||
JobInvocation jobInvocation = googlePlayReceiver.prepareJob(messengerCallback, data);
|
||||
googlePlayReceiver.getExecutionDelegator().executeJob(jobInvocation);
|
||||
}
|
||||
|
||||
private void handleStopMessage(Message message) {
|
||||
Builder builder = GooglePlayReceiver.getJobCoder().decode(message.getData());
|
||||
if (builder == null) {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "Invalid stop execution message.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
JobInvocation job = builder.build();
|
||||
googlePlayReceiver.getExecutionDelegator().stopJob(job);
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static com.firebase.jobdispatcher.GooglePlayJobWriter.REQUEST_PARAM_TAG;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.annotation.NonNull;
|
||||
import com.firebase.jobdispatcher.JobService.JobResult;
|
||||
|
||||
/**
|
||||
* Wraps the GooglePlay messenger in a JobCallback-compatible interface.
|
||||
*/
|
||||
class GooglePlayMessengerCallback implements JobCallback {
|
||||
|
||||
private final Messenger messenger;
|
||||
private final String tag;
|
||||
|
||||
GooglePlayMessengerCallback(Messenger messenger, String tag) {
|
||||
this.messenger = messenger;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jobFinished(@JobResult int status) {
|
||||
try {
|
||||
messenger.send(createResultMessage(status));
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Message createResultMessage(int result) {
|
||||
final Message msg = Message.obtain();
|
||||
msg.what = GooglePlayMessageHandler.MSG_RESULT;
|
||||
msg.arg1 = result;
|
||||
|
||||
Bundle b = new Bundle();
|
||||
b.putString(REQUEST_PARAM_TAG, tag);
|
||||
msg.setData(b);
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
@ -1,274 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Messenger;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.support.v4.util.SimpleArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import com.firebase.jobdispatcher.Job.Builder;
|
||||
import com.firebase.jobdispatcher.JobService.JobResult;
|
||||
import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger;
|
||||
|
||||
/**
|
||||
* Handles incoming execute requests from the GooglePlay driver and forwards them to your Service.
|
||||
*/
|
||||
public class GooglePlayReceiver extends Service implements ExecutionDelegator.JobFinishedCallback {
|
||||
/**
|
||||
* Logging tag.
|
||||
*/
|
||||
/* package */ static final String TAG = "FJD.GooglePlayReceiver";
|
||||
/**
|
||||
* The action sent by Google Play services that triggers job execution.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final String ACTION_EXECUTE = "com.google.android.gms.gcm.ACTION_TASK_READY";
|
||||
|
||||
/** Action sent by Google Play services when your app has been updated. */
|
||||
@VisibleForTesting
|
||||
static final String ACTION_INITIALIZE = "com.google.android.gms.gcm.SERVICE_ACTION_INITIALIZE";
|
||||
|
||||
private static final String ERROR_NULL_INTENT = "Null Intent passed, terminating";
|
||||
private static final String ERROR_UNKNOWN_ACTION = "Unknown action received, terminating";
|
||||
private static final String ERROR_NO_DATA = "No data provided, terminating";
|
||||
|
||||
private static final JobCoder prefixedCoder =
|
||||
new JobCoder(BundleProtocol.PACKED_PARAM_BUNDLE_PREFIX, true);
|
||||
|
||||
private final GooglePlayCallbackExtractor callbackExtractor = new GooglePlayCallbackExtractor();
|
||||
|
||||
/**
|
||||
* The single Messenger that's returned from valid onBind requests. Guarded by intrinsic lock.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
Messenger serviceMessenger;
|
||||
|
||||
/**
|
||||
* Driver for rescheduling jobs. Guarded by intrinsic lock.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
Driver driver;
|
||||
|
||||
/**
|
||||
* Guarded by intrinsic lock.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
ValidationEnforcer validationEnforcer;
|
||||
|
||||
/**
|
||||
* The ExecutionDelegator used to communicate with client JobServices.
|
||||
* Guarded by intrinsic lock.
|
||||
*/
|
||||
private ExecutionDelegator executionDelegator;
|
||||
|
||||
/**
|
||||
* The most recent startId passed to onStartCommand.
|
||||
* Guarded by intrinsic lock.
|
||||
*/
|
||||
private int latestStartId;
|
||||
|
||||
/**
|
||||
* Endpoint (String) -> Tag (String) -> JobCallback
|
||||
*/
|
||||
private SimpleArrayMap<String, SimpleArrayMap<String, JobCallback>> callbacks =
|
||||
new SimpleArrayMap<>(1);
|
||||
|
||||
private static void sendResultSafely(JobCallback callback, int result) {
|
||||
try {
|
||||
callback.jobFinished(result);
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Encountered error running callback", e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int onStartCommand(Intent intent, int flags, int startId) {
|
||||
try {
|
||||
super.onStartCommand(intent, flags, startId);
|
||||
|
||||
if (intent == null) {
|
||||
Log.w(TAG, ERROR_NULL_INTENT);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
String action = intent.getAction();
|
||||
if (ACTION_EXECUTE.equals(action)) {
|
||||
getExecutionDelegator().executeJob(prepareJob(intent));
|
||||
return START_NOT_STICKY;
|
||||
} else if (ACTION_INITIALIZE.equals(action)) {
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
Log.e(TAG, ERROR_UNKNOWN_ACTION);
|
||||
return START_NOT_STICKY;
|
||||
} finally {
|
||||
synchronized (this) {
|
||||
latestStartId = startId;
|
||||
if (callbacks.isEmpty()) {
|
||||
stopSelf(latestStartId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
// Only Lollipop+ supports UID checking messages, so we can't trust this system on older
|
||||
// platforms.
|
||||
if (intent == null
|
||||
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
|
||||
|| !ACTION_EXECUTE.equals(intent.getAction())) {
|
||||
return null;
|
||||
}
|
||||
return getServiceMessenger().getBinder();
|
||||
}
|
||||
|
||||
private synchronized Messenger getServiceMessenger() {
|
||||
if (serviceMessenger == null) {
|
||||
serviceMessenger =
|
||||
new Messenger(new GooglePlayMessageHandler(Looper.getMainLooper(), this));
|
||||
}
|
||||
return serviceMessenger;
|
||||
}
|
||||
|
||||
/* package */ synchronized ExecutionDelegator getExecutionDelegator() {
|
||||
if (executionDelegator == null) {
|
||||
executionDelegator = new ExecutionDelegator(this, this);
|
||||
}
|
||||
return executionDelegator;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private synchronized Driver getGooglePlayDriver() {
|
||||
if (driver == null) {
|
||||
driver = new GooglePlayDriver(getApplicationContext());
|
||||
}
|
||||
return driver;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private synchronized ValidationEnforcer getValidationEnforcer() {
|
||||
if (validationEnforcer == null) {
|
||||
validationEnforcer = new ValidationEnforcer(getGooglePlayDriver().getValidator());
|
||||
}
|
||||
return validationEnforcer;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@VisibleForTesting
|
||||
JobInvocation prepareJob(Intent intent) {
|
||||
Bundle intentExtras = intent.getExtras();
|
||||
if (intentExtras == null) {
|
||||
Log.e(TAG, ERROR_NO_DATA);
|
||||
return null;
|
||||
}
|
||||
|
||||
// get the callback first. If we don't have this we can't talk back to the backend.
|
||||
Pair<JobCallback, Bundle> extraction = callbackExtractor.extractCallback(intentExtras);
|
||||
if (extraction == null) {
|
||||
Log.i(TAG, "no callback found");
|
||||
return null;
|
||||
}
|
||||
return prepareJob(extraction.first, extraction.second);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
synchronized JobInvocation prepareJob(JobCallback callback, Bundle bundle) {
|
||||
JobInvocation job = prefixedCoder.decodeIntentBundle(bundle);
|
||||
if (job == null) {
|
||||
Log.e(TAG, "unable to decode job");
|
||||
sendResultSafely(callback, JobService.RESULT_FAIL_NORETRY);
|
||||
return null;
|
||||
}
|
||||
SimpleArrayMap<String, JobCallback> map = callbacks.get(job.getService());
|
||||
if (map == null) {
|
||||
map = new SimpleArrayMap<>(1);
|
||||
callbacks.put(job.getService(), map);
|
||||
}
|
||||
|
||||
map.put(job.getTag(), callback);
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onJobFinished(@NonNull JobInvocation js, @JobResult int result) {
|
||||
try {
|
||||
SimpleArrayMap<String, JobCallback> map = callbacks.get(js.getService());
|
||||
if (map == null) {
|
||||
return;
|
||||
}
|
||||
JobCallback callback = map.remove(js.getTag());
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
if (map.isEmpty()) {
|
||||
callbacks.remove(js.getService());
|
||||
}
|
||||
|
||||
if (needsToBeRescheduled(js, result)) {
|
||||
reschedule(js);
|
||||
} else {
|
||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||
Log.v(TAG, "sending jobFinished for " + js.getTag() + " = " + result);
|
||||
}
|
||||
sendResultSafely(callback, result);
|
||||
}
|
||||
} finally {
|
||||
if (callbacks.isEmpty()) {
|
||||
// Safe to call stopSelf, even if we're being bound to
|
||||
stopSelf(latestStartId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reschedule(JobInvocation jobInvocation) {
|
||||
Job job = new Builder(getValidationEnforcer(), jobInvocation)
|
||||
.setReplaceCurrent(true)
|
||||
.build();
|
||||
|
||||
getGooglePlayDriver().schedule(job);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurring content URI triggered jobs need to be rescheduled when execution is finished.
|
||||
*
|
||||
* <p>GooglePlay does not support recurring content URI triggered jobs.
|
||||
*
|
||||
* <p>{@link JobService#RESULT_FAIL_RETRY} needs to be sent or current triggered URIs will be
|
||||
* lost.
|
||||
*/
|
||||
private static boolean needsToBeRescheduled(JobParameters job, int result) {
|
||||
return job.isRecurring()
|
||||
&& job.getTrigger() instanceof ContentUriTrigger
|
||||
&& result != JobService.RESULT_FAIL_RETRY;
|
||||
}
|
||||
|
||||
static JobCoder getJobCoder() {
|
||||
return prefixedCoder;
|
||||
}
|
||||
}
|
||||
@ -1,378 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.firebase.jobdispatcher.Constraint.JobConstraint;
|
||||
|
||||
/**
|
||||
* Job is the embodiment of a unit of work and an associated set of triggers, settings, and runtime
|
||||
* constraints.
|
||||
*/
|
||||
public final class Job implements JobParameters {
|
||||
private final String mService;
|
||||
private final String mTag;
|
||||
private final JobTrigger mTrigger;
|
||||
private final RetryStrategy mRetryStrategy;
|
||||
private final int mLifetime;
|
||||
private final boolean mRecurring;
|
||||
private final int[] mConstraints;
|
||||
private final boolean mReplaceCurrent;
|
||||
private Bundle mExtras;
|
||||
|
||||
private Job(Builder builder) {
|
||||
mService = builder.mServiceClassName;
|
||||
mExtras = builder.mExtras;
|
||||
mTag = builder.mTag;
|
||||
mTrigger = builder.mTrigger;
|
||||
mRetryStrategy = builder.mRetryStrategy;
|
||||
mLifetime = builder.mLifetime;
|
||||
mRecurring = builder.mRecurring;
|
||||
mConstraints = builder.mConstraints != null ? builder.mConstraints : new int[0];
|
||||
mReplaceCurrent = builder.mReplaceCurrent;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public int[] getConstraints() {
|
||||
return mConstraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public RetryStrategy getRetryStrategy() {
|
||||
return mRetryStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldReplaceCurrent() {
|
||||
return mReplaceCurrent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TriggerReason getTriggerReason() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public String getTag() {
|
||||
return mTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public JobTrigger getTrigger() {
|
||||
return mTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getLifetime() {
|
||||
return mLifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isRecurring() {
|
||||
return mRecurring;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public String getService() {
|
||||
return mService;
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that understands how to build a {@link Job}. Retrieved by calling
|
||||
* {@link FirebaseJobDispatcher#newJobBuilder()}.
|
||||
*/
|
||||
public final static class Builder implements JobParameters {
|
||||
private final ValidationEnforcer mValidator;
|
||||
|
||||
private String mServiceClassName;
|
||||
private Bundle mExtras;
|
||||
private String mTag;
|
||||
private JobTrigger mTrigger = Trigger.NOW;
|
||||
private int mLifetime = Lifetime.UNTIL_NEXT_BOOT;
|
||||
private int[] mConstraints;
|
||||
|
||||
private RetryStrategy mRetryStrategy = RetryStrategy.DEFAULT_EXPONENTIAL;
|
||||
private boolean mReplaceCurrent = false;
|
||||
private boolean mRecurring = false;
|
||||
|
||||
Builder(ValidationEnforcer validator) {
|
||||
mValidator = validator;
|
||||
}
|
||||
|
||||
Builder(ValidationEnforcer validator, JobParameters job) {
|
||||
mValidator = validator;
|
||||
|
||||
mTag = job.getTag();
|
||||
mServiceClassName = job.getService();
|
||||
mTrigger = job.getTrigger();
|
||||
mRecurring = job.isRecurring();
|
||||
mLifetime = job.getLifetime();
|
||||
mConstraints = job.getConstraints();
|
||||
mExtras = job.getExtras();
|
||||
mRetryStrategy = job.getRetryStrategy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided constraint to the current list of runtime constraints.
|
||||
*/
|
||||
public Builder addConstraint(@JobConstraint int constraint) {
|
||||
// Create a new, longer constraints array
|
||||
int[] newConstraints = new int[mConstraints == null ? 1 : mConstraints.length + 1];
|
||||
|
||||
if (mConstraints != null && mConstraints.length != 0) {
|
||||
// Copy all the old values over
|
||||
System.arraycopy(mConstraints, 0, newConstraints, 0, mConstraints.length);
|
||||
}
|
||||
|
||||
// add the new value
|
||||
newConstraints[newConstraints.length - 1] = constraint;
|
||||
// update the pointer
|
||||
mConstraints = newConstraints;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this Job should replace pre-existing Jobs with the same tag.
|
||||
*/
|
||||
public Builder setReplaceCurrent(boolean replaceCurrent) {
|
||||
mReplaceCurrent = replaceCurrent;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the Job, using the settings provided so far.
|
||||
*
|
||||
* @throws ValidationEnforcer.ValidationException
|
||||
*/
|
||||
public Job build() {
|
||||
mValidator.ensureValid(this);
|
||||
|
||||
return new Job(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public String getService() {
|
||||
return mServiceClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the backing JobService class for the Job. See {@link #getService()}.
|
||||
*/
|
||||
public Builder setService(Class<? extends JobService> serviceClass) {
|
||||
mServiceClassName = serviceClass == null ? null : serviceClass.getName();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the backing JobService class name for the Job. See {@link #getService()}.
|
||||
*
|
||||
* <p>Should not be exposed, for internal use only.
|
||||
*/
|
||||
Builder setServiceName(String serviceClassName) {
|
||||
mServiceClassName = serviceClassName;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public String getTag() {
|
||||
return mTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the unique String tag used to identify the Job. See {@link #getTag()}.
|
||||
*/
|
||||
public Builder setTag(String tag) {
|
||||
mTag = tag;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public JobTrigger getTrigger() {
|
||||
return mTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Trigger used for the Job. See {@link #getTrigger()}.
|
||||
*/
|
||||
public Builder setTrigger(JobTrigger trigger) {
|
||||
mTrigger = trigger;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@Lifetime.LifetimeConstant
|
||||
public int getLifetime() {
|
||||
return mLifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Job's lifetime, or how long it should persist. See {@link #getLifetime()}.
|
||||
*/
|
||||
public Builder setLifetime(@Lifetime.LifetimeConstant int lifetime) {
|
||||
mLifetime = lifetime;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isRecurring() {
|
||||
return mRecurring;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the job should recur. The default is false.
|
||||
*/
|
||||
public Builder setRecurring(boolean recurring) {
|
||||
mRecurring = recurring;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@JobConstraint
|
||||
public int[] getConstraints() {
|
||||
return mConstraints == null ? new int[]{} : mConstraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Job's runtime constraints. See {@link #getConstraints()}.
|
||||
*/
|
||||
public Builder setConstraints(@JobConstraint int... constraints) {
|
||||
mConstraints = constraints;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user-defined extras associated with the Job. See {@link #getExtras()}.
|
||||
*/
|
||||
public Builder setExtras(Bundle extras) {
|
||||
mExtras = extras;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public RetryStrategy getRetryStrategy() {
|
||||
return mRetryStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the RetryStrategy used for the Job. See {@link #getRetryStrategy()}.
|
||||
*/
|
||||
public Builder setRetryStrategy(RetryStrategy retryStrategy) {
|
||||
mRetryStrategy = retryStrategy;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldReplaceCurrent() {
|
||||
return mReplaceCurrent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TriggerReason getTriggerReason() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
/**
|
||||
* JobCallback describes an object that knows how to send a JobResult back to the underlying
|
||||
* execution driver.
|
||||
*/
|
||||
public interface JobCallback {
|
||||
/**
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
void jobFinished(@JobService.JobResult int status);
|
||||
}
|
||||
@ -1,253 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static com.firebase.jobdispatcher.Constraint.compact;
|
||||
import static com.firebase.jobdispatcher.Constraint.uncompact;
|
||||
import static com.firebase.jobdispatcher.ExecutionDelegator.TAG;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* JobCoder is a tool to encode and decode JobSpecs from Bundles.
|
||||
*/
|
||||
/* package */ final class JobCoder {
|
||||
private final boolean includeExtras;
|
||||
private final String prefix;
|
||||
|
||||
private static final String JSON_URI_FLAGS = "uri_flags";
|
||||
private static final String JSON_URIS = "uris";
|
||||
|
||||
JobCoder(String prefix, boolean includeExtras) {
|
||||
this.includeExtras = includeExtras;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Bundle encode(@NonNull JobParameters jobParameters, @NonNull Bundle data) {
|
||||
if (data == null) {
|
||||
throw new IllegalArgumentException("Unexpected null Bundle provided");
|
||||
}
|
||||
|
||||
data.putInt(prefix + BundleProtocol.PACKED_PARAM_LIFETIME,
|
||||
jobParameters.getLifetime());
|
||||
data.putBoolean(prefix + BundleProtocol.PACKED_PARAM_RECURRING,
|
||||
jobParameters.isRecurring());
|
||||
data.putBoolean(prefix + BundleProtocol.PACKED_PARAM_REPLACE_CURRENT,
|
||||
jobParameters.shouldReplaceCurrent());
|
||||
data.putString(prefix + BundleProtocol.PACKED_PARAM_TAG,
|
||||
jobParameters.getTag());
|
||||
data.putString(prefix + BundleProtocol.PACKED_PARAM_SERVICE,
|
||||
jobParameters.getService());
|
||||
data.putInt(prefix + BundleProtocol.PACKED_PARAM_CONSTRAINTS,
|
||||
compact(jobParameters.getConstraints()));
|
||||
|
||||
if (includeExtras) {
|
||||
data.putBundle(prefix + BundleProtocol.PACKED_PARAM_EXTRAS,
|
||||
jobParameters.getExtras());
|
||||
}
|
||||
|
||||
encodeTrigger(jobParameters.getTrigger(), data);
|
||||
encodeRetryStrategy(jobParameters.getRetryStrategy(), data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
JobInvocation decodeIntentBundle(@NonNull Bundle bundle) {
|
||||
if (bundle == null) {
|
||||
Log.e(TAG, "Unexpected null Bundle provided");
|
||||
return null;
|
||||
}
|
||||
|
||||
Bundle taskExtras = bundle.getBundle(GooglePlayJobWriter.REQUEST_PARAM_EXTRAS);
|
||||
if (taskExtras == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JobInvocation.Builder builder = decode(taskExtras);
|
||||
|
||||
List<Uri> triggeredContentUris =
|
||||
bundle.getParcelableArrayList(BundleProtocol.PACKED_PARAM_TRIGGERED_URIS);
|
||||
if (triggeredContentUris != null) {
|
||||
builder.setTriggerReason(new TriggerReason(triggeredContentUris));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JobInvocation.Builder decode(@NonNull Bundle data) {
|
||||
if (data == null) {
|
||||
throw new IllegalArgumentException("Unexpected null Bundle provided");
|
||||
}
|
||||
|
||||
boolean recur = data.getBoolean(prefix + BundleProtocol.PACKED_PARAM_RECURRING);
|
||||
boolean replaceCur = data.getBoolean(prefix + BundleProtocol.PACKED_PARAM_REPLACE_CURRENT);
|
||||
int lifetime = data.getInt(prefix + BundleProtocol.PACKED_PARAM_LIFETIME);
|
||||
int[] constraints = uncompact(data.getInt(prefix + BundleProtocol.PACKED_PARAM_CONSTRAINTS));
|
||||
|
||||
JobTrigger trigger = decodeTrigger(data);
|
||||
RetryStrategy retryStrategy = decodeRetryStrategy(data);
|
||||
|
||||
String tag = data.getString(prefix + BundleProtocol.PACKED_PARAM_TAG);
|
||||
String service = data.getString(prefix + BundleProtocol.PACKED_PARAM_SERVICE);
|
||||
|
||||
if (tag == null || service == null || trigger == null || retryStrategy == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JobInvocation.Builder builder = new JobInvocation.Builder();
|
||||
builder.setTag(tag);
|
||||
builder.setService(service);
|
||||
builder.setTrigger(trigger);
|
||||
builder.setRetryStrategy(retryStrategy);
|
||||
builder.setRecurring(recur);
|
||||
//noinspection WrongConstant
|
||||
builder.setLifetime(lifetime);
|
||||
//noinspection WrongConstant
|
||||
builder.setConstraints(constraints);
|
||||
builder.setReplaceCurrent(replaceCur);
|
||||
|
||||
// repack the taskExtras
|
||||
builder.addExtras(data);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private JobTrigger decodeTrigger(Bundle data) {
|
||||
switch (data.getInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_TYPE)) {
|
||||
case BundleProtocol.TRIGGER_TYPE_IMMEDIATE:
|
||||
return Trigger.NOW;
|
||||
|
||||
case BundleProtocol.TRIGGER_TYPE_EXECUTION_WINDOW:
|
||||
return Trigger.executionWindow(
|
||||
data.getInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_WINDOW_START),
|
||||
data.getInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_WINDOW_END));
|
||||
|
||||
case BundleProtocol.TRIGGER_TYPE_CONTENT_URI:
|
||||
String uris = data.getString(prefix + BundleProtocol.PACKED_PARAM_OBSERVED_URI);
|
||||
List<ObservedUri> observedUris = convertJsonToObservedUris(uris);
|
||||
return Trigger.contentUriTrigger(Collections.unmodifiableList(observedUris));
|
||||
|
||||
default:
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "Unsupported trigger.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void encodeTrigger(JobTrigger trigger, Bundle data) {
|
||||
if (trigger == Trigger.NOW) {
|
||||
data.putInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_TYPE,
|
||||
BundleProtocol.TRIGGER_TYPE_IMMEDIATE);
|
||||
} else if (trigger instanceof JobTrigger.ExecutionWindowTrigger) {
|
||||
JobTrigger.ExecutionWindowTrigger t = (JobTrigger.ExecutionWindowTrigger) trigger;
|
||||
|
||||
data.putInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_TYPE,
|
||||
BundleProtocol.TRIGGER_TYPE_EXECUTION_WINDOW);
|
||||
data.putInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_WINDOW_START,
|
||||
t.getWindowStart());
|
||||
data.putInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_WINDOW_END,
|
||||
t.getWindowEnd());
|
||||
} else if (trigger instanceof JobTrigger.ContentUriTrigger) {
|
||||
data.putInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_TYPE,
|
||||
BundleProtocol.TRIGGER_TYPE_CONTENT_URI);
|
||||
ContentUriTrigger uriTrigger = (ContentUriTrigger) trigger;
|
||||
String jsonTrigger = convertObservedUrisToJsonString(uriTrigger.getUris());
|
||||
data.putString(prefix + BundleProtocol.PACKED_PARAM_OBSERVED_URI, jsonTrigger);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported trigger.");
|
||||
}
|
||||
}
|
||||
|
||||
private RetryStrategy decodeRetryStrategy(Bundle data) {
|
||||
int policy = data.getInt(prefix + BundleProtocol.PACKED_PARAM_RETRY_STRATEGY_POLICY);
|
||||
if (policy != RetryStrategy.RETRY_POLICY_EXPONENTIAL
|
||||
&& policy != RetryStrategy.RETRY_POLICY_LINEAR) {
|
||||
|
||||
return RetryStrategy.DEFAULT_EXPONENTIAL;
|
||||
}
|
||||
|
||||
//noinspection WrongConstant
|
||||
return new RetryStrategy(
|
||||
policy,
|
||||
data.getInt(prefix + BundleProtocol.PACKED_PARAM_RETRY_STRATEGY_INITIAL_BACKOFF_SECONDS),
|
||||
data.getInt(prefix + BundleProtocol.PACKED_PARAM_RETRY_STRATEGY_MAXIMUM_BACKOFF_SECONDS));
|
||||
}
|
||||
|
||||
private void encodeRetryStrategy(RetryStrategy retryStrategy, Bundle data) {
|
||||
if (retryStrategy == null) {
|
||||
retryStrategy = RetryStrategy.DEFAULT_EXPONENTIAL;
|
||||
}
|
||||
|
||||
data.putInt(prefix + BundleProtocol.PACKED_PARAM_RETRY_STRATEGY_POLICY,
|
||||
retryStrategy.getPolicy());
|
||||
data.putInt(prefix + BundleProtocol.PACKED_PARAM_RETRY_STRATEGY_INITIAL_BACKOFF_SECONDS,
|
||||
retryStrategy.getInitialBackoff());
|
||||
data.putInt(prefix + BundleProtocol.PACKED_PARAM_RETRY_STRATEGY_MAXIMUM_BACKOFF_SECONDS,
|
||||
retryStrategy.getMaximumBackoff());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String convertObservedUrisToJsonString(@NonNull List<ObservedUri> uris) {
|
||||
JSONObject contentUris = new JSONObject();
|
||||
JSONArray jsonFlags = new JSONArray();
|
||||
JSONArray jsonUris = new JSONArray();
|
||||
for (ObservedUri uri : uris) {
|
||||
jsonFlags.put(uri.getFlags());
|
||||
jsonUris.put(uri.getUri());
|
||||
}
|
||||
try {
|
||||
contentUris.put(JSON_URI_FLAGS, jsonFlags);
|
||||
contentUris.put(JSON_URIS, jsonUris);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return contentUris.toString();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<ObservedUri> convertJsonToObservedUris(@NonNull String contentUrisJson) {
|
||||
List<ObservedUri> uris = new ArrayList<>();
|
||||
try {
|
||||
JSONObject json = new JSONObject(contentUrisJson);
|
||||
JSONArray jsonFlags = json.getJSONArray(JSON_URI_FLAGS);
|
||||
JSONArray jsonUris = json.getJSONArray(JSON_URIS);
|
||||
int length = jsonFlags.length();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int flags = jsonFlags.getInt(i);
|
||||
String uri = jsonUris.getString(i);
|
||||
uris.add(new ObservedUri(Uri.parse(uri), flags));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return uris;
|
||||
}
|
||||
}
|
||||
@ -1,236 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import com.firebase.jobdispatcher.Constraint.JobConstraint;
|
||||
|
||||
/**
|
||||
* An internal non-Job implementation of JobParameters. Passed to JobService invocations.
|
||||
*/
|
||||
/* package */ final class JobInvocation implements JobParameters {
|
||||
|
||||
@NonNull
|
||||
private final String mTag;
|
||||
|
||||
@NonNull
|
||||
private final String mService;
|
||||
|
||||
@NonNull
|
||||
private final JobTrigger mTrigger;
|
||||
|
||||
private final boolean mRecurring;
|
||||
|
||||
private final int mLifetime;
|
||||
|
||||
@NonNull
|
||||
@JobConstraint
|
||||
private final int[] mConstraints;
|
||||
|
||||
@NonNull
|
||||
private final Bundle mExtras;
|
||||
|
||||
private final RetryStrategy mRetryStrategy;
|
||||
|
||||
private final boolean mReplaceCurrent;
|
||||
|
||||
private final TriggerReason mTriggerReason;
|
||||
|
||||
private JobInvocation(Builder builder) {
|
||||
mTag = builder.mTag;
|
||||
mService = builder.mService;
|
||||
mTrigger = builder.mTrigger;
|
||||
mRetryStrategy = builder.mRetryStrategy;
|
||||
mRecurring = builder.mRecurring;
|
||||
mLifetime = builder.mLifetime;
|
||||
mConstraints = builder.mConstraints;
|
||||
mExtras = builder.mExtras;
|
||||
mReplaceCurrent = builder.mReplaceCurrent;
|
||||
mTriggerReason = builder.mTriggerReason;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getService() {
|
||||
return mService;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getTag() {
|
||||
return mTag;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public JobTrigger getTrigger() {
|
||||
return mTrigger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLifetime() {
|
||||
return mLifetime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecurring() {
|
||||
return mRecurring;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public int[] getConstraints() {
|
||||
return mConstraints;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RetryStrategy getRetryStrategy() {
|
||||
return mRetryStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldReplaceCurrent() {
|
||||
return mReplaceCurrent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TriggerReason getTriggerReason() {
|
||||
return mTriggerReason;
|
||||
}
|
||||
|
||||
static final class Builder {
|
||||
|
||||
@NonNull
|
||||
private String mTag;
|
||||
|
||||
@NonNull
|
||||
private String mService;
|
||||
|
||||
@NonNull
|
||||
private JobTrigger mTrigger;
|
||||
|
||||
private boolean mRecurring;
|
||||
|
||||
private int mLifetime;
|
||||
|
||||
@NonNull
|
||||
@JobConstraint
|
||||
private int[] mConstraints;
|
||||
|
||||
@NonNull
|
||||
private final Bundle mExtras = new Bundle();
|
||||
|
||||
private RetryStrategy mRetryStrategy;
|
||||
|
||||
private boolean mReplaceCurrent;
|
||||
|
||||
private TriggerReason mTriggerReason;
|
||||
|
||||
JobInvocation build() {
|
||||
if (mTag == null || mService == null || mTrigger == null) {
|
||||
throw new IllegalArgumentException("Required fields were not populated.");
|
||||
}
|
||||
return new JobInvocation(this);
|
||||
}
|
||||
|
||||
public Builder setTag(@NonNull String mTag) {
|
||||
this.mTag = mTag;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setService(@NonNull String mService) {
|
||||
this.mService = mService;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTrigger(@NonNull JobTrigger mTrigger) {
|
||||
this.mTrigger = mTrigger;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRecurring(boolean mRecurring) {
|
||||
this.mRecurring = mRecurring;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLifetime(@Lifetime.LifetimeConstant int mLifetime) {
|
||||
this.mLifetime = mLifetime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setConstraints(@JobConstraint @NonNull int[] mConstraints) {
|
||||
this.mConstraints = mConstraints;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addExtras(@NonNull Bundle bundle) {
|
||||
if (bundle != null) {
|
||||
mExtras.putAll(bundle);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRetryStrategy(RetryStrategy mRetryStrategy) {
|
||||
this.mRetryStrategy = mRetryStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setReplaceCurrent(boolean mReplaceCurrent) {
|
||||
this.mReplaceCurrent = mReplaceCurrent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTriggerReason(TriggerReason triggerReason) {
|
||||
this.mTriggerReason = triggerReason;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the tag and the service of provided {@link JobInvocation} have the same
|
||||
* values.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || !getClass().equals(o.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JobInvocation jobInvocation = (JobInvocation) o;
|
||||
|
||||
return mTag.equals(jobInvocation.mTag)
|
||||
&& mService.equals(jobInvocation.mService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = mTag.hashCode();
|
||||
result = 31 * result + mService.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -1,86 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.firebase.jobdispatcher.Constraint.JobConstraint;
|
||||
|
||||
/**
|
||||
* JobParameters represents anything that can describe itself in terms of Job components.
|
||||
*/
|
||||
public interface JobParameters {
|
||||
|
||||
/**
|
||||
* Returns the name of the backing JobService class.
|
||||
*/
|
||||
@NonNull
|
||||
String getService();
|
||||
|
||||
/**
|
||||
* Returns a string identifier for the Job. Used when cancelling Jobs and displaying debug
|
||||
* messages.
|
||||
*/
|
||||
@NonNull
|
||||
String getTag();
|
||||
|
||||
/**
|
||||
* The Job's Trigger, which decides when the Job is ready to run.
|
||||
*/
|
||||
@NonNull
|
||||
JobTrigger getTrigger();
|
||||
|
||||
/**
|
||||
* The Job's lifetime; how long it should persist for.
|
||||
*/
|
||||
@Lifetime.LifetimeConstant
|
||||
int getLifetime();
|
||||
|
||||
/**
|
||||
* Whether the Job should repeat.
|
||||
*/
|
||||
boolean isRecurring();
|
||||
|
||||
/**
|
||||
* The runtime constraints applied to this Job. A Job is not run until the trigger is activated
|
||||
* and all the runtime constraints are satisfied.
|
||||
*/
|
||||
@JobConstraint
|
||||
int[] getConstraints();
|
||||
|
||||
/**
|
||||
* The optional set of user-supplied extras associated with this Job.
|
||||
*/
|
||||
@Nullable
|
||||
Bundle getExtras();
|
||||
|
||||
/**
|
||||
* The RetryStrategy for the Job. Used to determine how to handle failures.
|
||||
*/
|
||||
@NonNull
|
||||
RetryStrategy getRetryStrategy();
|
||||
|
||||
/**
|
||||
* Whether the Job should replace a pre-existing Job with the same tag.
|
||||
*/
|
||||
boolean shouldReplaceCurrent();
|
||||
|
||||
/** @return A {@link TriggerReason} that - if non null - describes why the job was triggered. */
|
||||
@Nullable
|
||||
TriggerReason getTriggerReason();
|
||||
}
|
||||
@ -1,259 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.MainThread;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.support.v4.util.SimpleArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* JobService is the fundamental unit of work used in the JobDispatcher.
|
||||
* <p>
|
||||
* Users will need to override {@link #onStartJob(JobParameters)}, which is where any asynchronous
|
||||
* execution should start. This method, like most lifecycle methods, runs on the main thread; you
|
||||
* <b>must</b> offload execution to another thread (or {@link android.os.AsyncTask}, or
|
||||
* {@link android.os.Handler}, or your favorite flavor of concurrency).
|
||||
* <p>
|
||||
* Once any asynchronous work is complete {@link #jobFinished(JobParameters, boolean)} should be
|
||||
* called to inform the backing driver of the result.
|
||||
* <p>
|
||||
* Implementations should also override {@link #onStopJob(JobParameters)}, which will be called if
|
||||
* the scheduling engine wishes to interrupt your work (most likely because the runtime constraints
|
||||
* that are associated with the job in question are no longer met).
|
||||
*/
|
||||
public abstract class JobService extends Service {
|
||||
/**
|
||||
* Returned to indicate the job was executed successfully. If the job is not recurring (i.e. a
|
||||
* one-off) it will be dequeued and forgotten. If it is recurring the trigger will be reset and
|
||||
* the job will be requeued.
|
||||
*/
|
||||
public static final int RESULT_SUCCESS = 0;
|
||||
|
||||
/**
|
||||
* Returned to indicate the job encountered an error during execution and should be retried after
|
||||
* a backoff period.
|
||||
*/
|
||||
public static final int RESULT_FAIL_RETRY = 1;
|
||||
|
||||
/**
|
||||
* Returned to indicate the job encountered an error during execution but should not be retried.
|
||||
* If the job is not recurring (i.e. a one-off) it will be dequeued and forgotten. If it is
|
||||
* recurring the trigger will be reset and the job will be requeued.
|
||||
*/
|
||||
public static final int RESULT_FAIL_NORETRY = 2;
|
||||
|
||||
static final String TAG = "FJD.JobService";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String ACTION_EXECUTE = "com.firebase.jobdispatcher.ACTION_EXECUTE";
|
||||
|
||||
/**
|
||||
* Correlates job tags (unique strings) with Messages, which are used to signal the completion
|
||||
* of a job.
|
||||
*/
|
||||
private final SimpleArrayMap<String, JobCallback> runningJobs = new SimpleArrayMap<>(1);
|
||||
private LocalBinder binder = new LocalBinder();
|
||||
|
||||
/**
|
||||
* The entry point to your Job. Implementations should offload work to another thread of
|
||||
* execution as soon as possible because this runs on the main thread. If work was offloaded,
|
||||
* call {@link JobService#jobFinished(JobParameters, boolean)} to notify the scheduling service
|
||||
* that the work is completed.
|
||||
* <p>
|
||||
* In order to reschedule use {@link JobService#jobFinished(JobParameters, boolean)}.
|
||||
*
|
||||
* @return {@code true} if there is more work remaining in the worker thread, {@code false} if the job was completed.
|
||||
*/
|
||||
@MainThread
|
||||
public abstract boolean onStartJob(JobParameters job);
|
||||
|
||||
/**
|
||||
* Called when the scheduling engine has decided to interrupt the execution of a running job,
|
||||
* most likely because the runtime constraints associated with the job are no longer satisfied.
|
||||
* The job must stop execution.
|
||||
*
|
||||
* @return true if the job should be retried
|
||||
* @see com.firebase.jobdispatcher.JobInvocation.Builder#setRetryStrategy(RetryStrategy)
|
||||
* @see RetryStrategy
|
||||
*/
|
||||
@MainThread
|
||||
public abstract boolean onStopJob(JobParameters job);
|
||||
|
||||
@MainThread
|
||||
void start(JobParameters job, Message msg) {
|
||||
synchronized (runningJobs) {
|
||||
if (runningJobs.containsKey(job.getTag())) {
|
||||
Log.w(TAG, String
|
||||
.format(Locale.US, "Job with tag = %s was already running.", job.getTag()));
|
||||
return;
|
||||
}
|
||||
runningJobs.put(job.getTag(), new JobCallback(msg));
|
||||
|
||||
boolean moreWork = onStartJob(job);
|
||||
if (!moreWork) {
|
||||
JobCallback callback = runningJobs.remove(job.getTag());
|
||||
if (callback != null) {
|
||||
callback.sendResult(RESULT_SUCCESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
void stop(JobInvocation job) {
|
||||
synchronized (runningJobs) {
|
||||
JobCallback jobCallback = runningJobs.remove(job.getTag());
|
||||
|
||||
if (jobCallback == null) {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "Provided job has already been executed.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
boolean shouldRetry = onStopJob(job);
|
||||
jobCallback.sendResult(shouldRetry ? RESULT_FAIL_RETRY : RESULT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to inform the scheduling driver that you've finished executing. Can be called from
|
||||
* any thread. When the system receives this message, it will release the wakelock being held.
|
||||
*
|
||||
* @param job
|
||||
* @param needsReschedule
|
||||
* whether the job should be rescheduled
|
||||
* @see com.firebase.jobdispatcher.JobInvocation.Builder#setRetryStrategy(RetryStrategy)
|
||||
*/
|
||||
public final void jobFinished(@NonNull JobParameters job, boolean needsReschedule) {
|
||||
if (job == null) {
|
||||
Log.e(TAG, "jobFinished called with a null JobParameters");
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (runningJobs) {
|
||||
JobCallback jobCallback = runningJobs.remove(job.getTag());
|
||||
|
||||
if (jobCallback != null) {
|
||||
jobCallback.sendResult(needsReschedule ? RESULT_FAIL_RETRY : RESULT_SUCCESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int onStartCommand(Intent intent, int flags, int startId) {
|
||||
stopSelf(startId);
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public final IBinder onBind(Intent intent) {
|
||||
return binder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onUnbind(Intent intent) {
|
||||
synchronized (runningJobs) {
|
||||
for (int i = runningJobs.size() - 1; i >= 0; i--) {
|
||||
JobCallback callback = runningJobs.get(runningJobs.keyAt(i));
|
||||
if (callback != null && callback.message != null) {
|
||||
if (callback.message.obj instanceof JobParameters) {
|
||||
callback.sendResult(onStopJob((JobParameters) callback.message.obj)
|
||||
// returned true, would like to be rescheduled
|
||||
? RESULT_FAIL_RETRY
|
||||
// returned false, but was interrupted so consider it a fail
|
||||
: RESULT_FAIL_NORETRY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.onUnbind(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onRebind(Intent intent) {
|
||||
super.onRebind(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onStart(Intent intent, int startId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
||||
super.dump(fd, writer, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onTaskRemoved(Intent rootIntent) {
|
||||
super.onTaskRemoved(rootIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* The result returned from a job execution.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({RESULT_SUCCESS, RESULT_FAIL_RETRY, RESULT_FAIL_NORETRY})
|
||||
public @interface JobResult {
|
||||
}
|
||||
|
||||
private final static class JobCallback {
|
||||
public final Message message;
|
||||
|
||||
private JobCallback(Message message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
void sendResult(@JobResult int result) {
|
||||
try {
|
||||
if (message != null) {
|
||||
message.arg1 = result;
|
||||
message.sendToTarget();
|
||||
}
|
||||
} catch (Exception ignored) {}//catch this freaking crash!!!!
|
||||
}
|
||||
}
|
||||
|
||||
class LocalBinder extends Binder {
|
||||
JobService getService() {
|
||||
return JobService.this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static com.firebase.jobdispatcher.ExecutionDelegator.TAG;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* ServiceConnection for job execution.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
class JobServiceConnection implements ServiceConnection {
|
||||
|
||||
private final JobInvocation jobInvocation;
|
||||
// Should be sent only once. Can't be reused.
|
||||
private final Message jobFinishedMessage;
|
||||
private boolean wasMessageUsed = false;
|
||||
|
||||
//Guarded by "this". Can be updated from main and binder threads.
|
||||
private JobService.LocalBinder binder;
|
||||
|
||||
JobServiceConnection(JobInvocation jobInvocation, Message jobFinishedMessage) {
|
||||
this.jobFinishedMessage = jobFinishedMessage;
|
||||
this.jobInvocation = jobInvocation;
|
||||
this.jobFinishedMessage.obj = this.jobInvocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onServiceConnected(ComponentName name, IBinder service) {
|
||||
if (!(service instanceof JobService.LocalBinder)) {
|
||||
Log.w(TAG, "Unknown service connected");
|
||||
return;
|
||||
}
|
||||
if (wasMessageUsed) {
|
||||
Log.w(TAG, "onServiceConnected Duplicate calls. Ignored.");
|
||||
return;
|
||||
} else {
|
||||
wasMessageUsed = true;
|
||||
}
|
||||
|
||||
binder = (JobService.LocalBinder) service;
|
||||
|
||||
JobService jobService = binder.getService();
|
||||
|
||||
jobService.start(jobInvocation, jobFinishedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onServiceDisconnected(ComponentName name) {
|
||||
binder = null;
|
||||
}
|
||||
|
||||
synchronized boolean isBound() {
|
||||
return binder != null;
|
||||
}
|
||||
|
||||
synchronized void onStop() {
|
||||
if (isBound()) {
|
||||
binder.getService().stop(jobInvocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Contains all supported triggers.
|
||||
*/
|
||||
public class JobTrigger {
|
||||
|
||||
/**
|
||||
* ImmediateTrigger is a Trigger that's immediately available. The Job will be run as soon as
|
||||
* the runtime constraints are satisfied.
|
||||
*/
|
||||
public static final class ImmediateTrigger extends JobTrigger {
|
||||
/* package */ ImmediateTrigger() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* ExecutionWindow represents a Job trigger that becomes eligible once
|
||||
* the current elapsed time exceeds the scheduled time + the {@code windowStart}
|
||||
* value. The scheduler backend is encouraged to use the windowEnd value as a
|
||||
* signal that the job should be run, but this is not an enforced behavior.
|
||||
*/
|
||||
public static final class ExecutionWindowTrigger extends JobTrigger {
|
||||
private final int mWindowStart;
|
||||
private final int mWindowEnd;
|
||||
|
||||
/* package */ ExecutionWindowTrigger(int windowStart, int windowEnd) {
|
||||
this.mWindowStart = windowStart;
|
||||
this.mWindowEnd = windowEnd;
|
||||
}
|
||||
|
||||
public int getWindowStart() {
|
||||
return mWindowStart;
|
||||
}
|
||||
|
||||
public int getWindowEnd() {
|
||||
return mWindowEnd;
|
||||
}
|
||||
}
|
||||
|
||||
/** A trigger that will be triggered on content update for any of provided uris. */
|
||||
public static final class ContentUriTrigger extends JobTrigger {
|
||||
private final List<ObservedUri> uris;
|
||||
|
||||
/* package */ ContentUriTrigger(List<ObservedUri> uris) {
|
||||
this.uris = uris;
|
||||
}
|
||||
|
||||
public List<ObservedUri> getUris() {
|
||||
return uris;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A JobValidator is an object that knows how to validate Jobs and some of their composite
|
||||
* components.
|
||||
*/
|
||||
public interface JobValidator {
|
||||
/**
|
||||
* Returns a List of error messages, or null if the JobParameters is
|
||||
* valid.
|
||||
*/
|
||||
@Nullable
|
||||
List<String> validate(JobParameters job);
|
||||
|
||||
/**
|
||||
* Returns a List of error messages, or null if the Trigger is
|
||||
* valid.
|
||||
* @param trigger
|
||||
*/
|
||||
@Nullable
|
||||
List<String> validate(JobTrigger trigger);
|
||||
|
||||
/**
|
||||
* Returns a List of error messages, or null if the RetryStrategy
|
||||
* is valid.
|
||||
*/
|
||||
@Nullable
|
||||
List<String> validate(RetryStrategy retryStrategy);
|
||||
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Lifetime represents how long a Job should last.
|
||||
*/
|
||||
public final class Lifetime {
|
||||
/**
|
||||
* The Job should be preserved until the next boot. This is the default.
|
||||
*/
|
||||
public final static int UNTIL_NEXT_BOOT = 1;
|
||||
|
||||
/**
|
||||
* The Job should be preserved "forever."
|
||||
*/
|
||||
public final static int FOREVER = 2;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({FOREVER, UNTIL_NEXT_BOOT})
|
||||
@interface LifetimeConstant {}
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.NonNull;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/** Represents a single observed URI and any associated flags. */
|
||||
public final class ObservedUri {
|
||||
|
||||
private final Uri uri;
|
||||
|
||||
private final int flags;
|
||||
|
||||
/** Flag enforcement. */
|
||||
@IntDef(flag = true, value = Flags.FLAG_NOTIFY_FOR_DESCENDANTS)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Flags {
|
||||
|
||||
/**
|
||||
* Triggers if any descendants of the given URI change. Corresponds to the {@code
|
||||
* notifyForDescendants} of {@link android.content.ContentResolver#registerContentObserver}.
|
||||
*/
|
||||
int FLAG_NOTIFY_FOR_DESCENDANTS = 1 << 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ObservedUri.
|
||||
*
|
||||
* @param uri The URI to observe.
|
||||
* @param flags Any {@link Flags} associated with the URI.
|
||||
*/
|
||||
public ObservedUri(@NonNull Uri uri, @Flags int flags) {
|
||||
if (uri == null) {
|
||||
throw new IllegalArgumentException("URI must not be null.");
|
||||
}
|
||||
this.uri = uri;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof ObservedUri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ObservedUri otherUri = (ObservedUri) o;
|
||||
return flags == otherUri.flags && uri.equals(otherUri.uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return uri.hashCode() ^ flags;
|
||||
}
|
||||
}
|
||||
@ -1,106 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* RetryStrategy represents an approach to handling job execution failures. Jobs will have a
|
||||
* time-based backoff enforced, based on the chosen policy (one of {@code RETRY_POLICY_EXPONENTIAL}
|
||||
* or {@code RETRY_POLICY_LINEAR}.
|
||||
*/
|
||||
public final class RetryStrategy {
|
||||
/**
|
||||
* Increase the backoff time exponentially.
|
||||
* <p>
|
||||
* Calculated using {@code initial_backoff * 2 ^ (num_failures - 1)}.
|
||||
*/
|
||||
public final static int RETRY_POLICY_EXPONENTIAL = 1;
|
||||
|
||||
/**
|
||||
* Increase the backoff time linearly.
|
||||
* <p>
|
||||
* Calculated using {@code initial_backoff * num_failures}.
|
||||
*/
|
||||
public final static int RETRY_POLICY_LINEAR = 2;
|
||||
|
||||
/**
|
||||
* Expected schedule is: [30s, 60s, 120s, 240s, ..., 3600s]
|
||||
*/
|
||||
public final static RetryStrategy DEFAULT_EXPONENTIAL =
|
||||
new RetryStrategy(RETRY_POLICY_EXPONENTIAL, 30, 3600);
|
||||
|
||||
/**
|
||||
* Expected schedule is: [30s, 60s, 90s, 120s, ..., 3600s]
|
||||
*/
|
||||
public final static RetryStrategy DEFAULT_LINEAR =
|
||||
new RetryStrategy(RETRY_POLICY_LINEAR, 30, 3600);
|
||||
|
||||
@RetryPolicy
|
||||
private final int mPolicy;
|
||||
private final int mInitialBackoff;
|
||||
private final int mMaximumBackoff;
|
||||
|
||||
/* package */ RetryStrategy(@RetryPolicy int policy, int initialBackoff, int maximumBackoff) {
|
||||
mPolicy = policy;
|
||||
mInitialBackoff = initialBackoff;
|
||||
mMaximumBackoff = maximumBackoff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the backoff policy in place.
|
||||
*/
|
||||
@RetryPolicy
|
||||
public int getPolicy() {
|
||||
return mPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initial backoff (i.e. when # of failures == 1), in seconds.
|
||||
*/
|
||||
public int getInitialBackoff() {
|
||||
return mInitialBackoff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum backoff duration in seconds.
|
||||
*/
|
||||
public int getMaximumBackoff() {
|
||||
return mMaximumBackoff;
|
||||
}
|
||||
|
||||
@IntDef({RETRY_POLICY_LINEAR, RETRY_POLICY_EXPONENTIAL})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface RetryPolicy {
|
||||
}
|
||||
|
||||
/* package */ final static class Builder {
|
||||
private final ValidationEnforcer mValidator;
|
||||
|
||||
Builder(ValidationEnforcer validator) {
|
||||
mValidator = validator;
|
||||
}
|
||||
|
||||
public RetryStrategy build(@RetryPolicy int policy, int initialBackoff, int maxBackoff) {
|
||||
RetryStrategy rs = new RetryStrategy(policy, initialBackoff, maxBackoff);
|
||||
mValidator.ensureValid(rs);
|
||||
return rs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.v4.util.SimpleArrayMap;
|
||||
|
||||
/**
|
||||
* SimpleJobService provides a simple way of doing background work in a JobService.
|
||||
*
|
||||
* Users should override onRunJob and return one of the {@link JobResult} ints.
|
||||
*/
|
||||
public abstract class SimpleJobService extends JobService {
|
||||
private final SimpleArrayMap<JobParameters, AsyncJobTask> runningJobs =
|
||||
new SimpleArrayMap<>();
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters job) {
|
||||
AsyncJobTask async = new AsyncJobTask(this, job);
|
||||
|
||||
synchronized (runningJobs) {
|
||||
runningJobs.put(job, async);
|
||||
}
|
||||
|
||||
async.execute();
|
||||
|
||||
return true; // more work to do
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters job) {
|
||||
synchronized (runningJobs) {
|
||||
AsyncJobTask async = runningJobs.remove(job);
|
||||
if (async != null) {
|
||||
async.cancel(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onJobFinished(JobParameters jobParameters, boolean b) {
|
||||
synchronized (runningJobs) {
|
||||
runningJobs.remove(jobParameters);
|
||||
}
|
||||
|
||||
jobFinished(jobParameters, b);
|
||||
}
|
||||
|
||||
@JobResult
|
||||
public abstract int onRunJob(JobParameters job);
|
||||
|
||||
private static class AsyncJobTask extends AsyncTask<Void, Void, Integer> {
|
||||
private final SimpleJobService jobService;
|
||||
private final JobParameters jobParameters;
|
||||
|
||||
private AsyncJobTask(SimpleJobService jobService, JobParameters jobParameters) {
|
||||
this.jobService = jobService;
|
||||
this.jobParameters = jobParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... params) {
|
||||
return jobService.onRunJob(jobParameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer integer) {
|
||||
jobService.onJobFinished(jobParameters, integer == JobService.RESULT_FAIL_RETRY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Generally, a Trigger is an object that can answer the question, "is this job ready to run?"
|
||||
* <p>
|
||||
* More specifically, a Trigger is an opaque, abstract class used to root the type hierarchy.
|
||||
*/
|
||||
public final class Trigger {
|
||||
|
||||
/**
|
||||
* Immediate is a Trigger that's immediately available. The Job will be run as soon as the
|
||||
* runtime constraints are satisfied.
|
||||
* <p>
|
||||
* It is invalid to schedule an Immediate with a recurring Job.
|
||||
*/
|
||||
public final static JobTrigger.ImmediateTrigger NOW = new JobTrigger.ImmediateTrigger();
|
||||
|
||||
/**
|
||||
* Creates a new ExecutionWindow based on the provided time interval.
|
||||
*
|
||||
* @param windowStart The earliest time (in seconds) the job should be
|
||||
* considered eligible to run. Calculated from when the
|
||||
* job was scheduled (for new jobs) or last run (for
|
||||
* recurring jobs).
|
||||
* @param windowEnd The latest time (in seconds) the job should be run in
|
||||
* an ideal world. Calculated in the same way as
|
||||
* {@code windowStart}.
|
||||
* @throws IllegalArgumentException if the provided parameters are too
|
||||
* restrictive.
|
||||
*/
|
||||
public static JobTrigger.ExecutionWindowTrigger executionWindow(int windowStart, int windowEnd) {
|
||||
if (windowStart < 0) {
|
||||
throw new IllegalArgumentException("Window start can't be less than 0");
|
||||
} else if (windowEnd < windowStart) {
|
||||
throw new IllegalArgumentException("Window end can't be less than window start");
|
||||
}
|
||||
|
||||
return new JobTrigger.ExecutionWindowTrigger(windowStart, windowEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ContentUriTrigger based on the provided list of {@link ObservedUri}.
|
||||
*
|
||||
* @param uris The list of URIs to observe. The trigger will be available if a piece of content,
|
||||
* corresponding to any of provided URIs, is updated.
|
||||
* @throws IllegalArgumentException if provided list of URIs is null or empty.
|
||||
*/
|
||||
public static JobTrigger.ContentUriTrigger contentUriTrigger(@NonNull List<ObservedUri> uris) {
|
||||
if (uris == null || uris.isEmpty()) {
|
||||
throw new IllegalArgumentException("Uris must not be null or empty.");
|
||||
}
|
||||
return new JobTrigger.ContentUriTrigger(uris);
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.net.Uri;
|
||||
import java.util.List;
|
||||
|
||||
/** The class contains a summary of the events which caused the job to be executed. */
|
||||
public class TriggerReason {
|
||||
private final List<Uri> mTriggeredContentUris;
|
||||
|
||||
TriggerReason(List<Uri> mTriggeredContentUris) {
|
||||
this.mTriggeredContentUris = mTriggeredContentUris;
|
||||
}
|
||||
|
||||
public List<Uri> getTriggeredContentUris() {
|
||||
return mTriggeredContentUris;
|
||||
}
|
||||
}
|
||||
@ -1,132 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Wraps a JobValidator and provides helpful validation utilities.
|
||||
*/
|
||||
public class ValidationEnforcer implements JobValidator {
|
||||
private final JobValidator mValidator;
|
||||
|
||||
public ValidationEnforcer(JobValidator validator) {
|
||||
mValidator = validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> validate(JobParameters job) {
|
||||
return mValidator.validate(job);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @param trigger
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> validate(JobTrigger trigger) {
|
||||
return mValidator.validate(trigger);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> validate(RetryStrategy retryStrategy) {
|
||||
return mValidator.validate(retryStrategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the provided JobParameters is valid.
|
||||
*/
|
||||
public final boolean isValid(JobParameters job) {
|
||||
return validate(job) == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the provided JobTrigger is valid.
|
||||
*/
|
||||
public final boolean isValid(JobTrigger trigger) {
|
||||
return validate(trigger) == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the provided RetryStrategy is valid.
|
||||
*/
|
||||
public final boolean isValid(RetryStrategy retryStrategy) {
|
||||
return validate(retryStrategy) == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a RuntimeException if the provided JobParameters is invalid.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public final void ensureValid(JobParameters job) {
|
||||
ensureNoErrors(validate(job));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a RuntimeException if the provided JobTrigger is invalid.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public final void ensureValid(JobTrigger trigger) {
|
||||
ensureNoErrors(validate(trigger));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a RuntimeException if the provided RetryStrategy is
|
||||
* invalid.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public final void ensureValid(RetryStrategy retryStrategy) {
|
||||
ensureNoErrors(validate(retryStrategy));
|
||||
}
|
||||
|
||||
private void ensureNoErrors(List<String> errors) {
|
||||
if (errors != null) {
|
||||
throw new ValidationException("JobParameters is invalid", errors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An Exception thrown when a validation error is encountered.
|
||||
*/
|
||||
public final static class ValidationException extends RuntimeException {
|
||||
private final List<String> mErrors;
|
||||
|
||||
public ValidationException(String msg, @NonNull List<String> errors) {
|
||||
super(msg + ": " + TextUtils.join("\n - ", errors));
|
||||
mErrors = errors;
|
||||
}
|
||||
|
||||
public List<String> getErrors() {
|
||||
return mErrors;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
package android.net.http;
|
||||
|
||||
/**
|
||||
* Robolectric requires this class be available in the classpath, otherwise {@link
|
||||
* org.robolectric.Shadows#shadowOf(android.os.Looper)} fails. We don't use AndroidHttpClient, so
|
||||
* include a stub to make Robolectric happy.
|
||||
*
|
||||
* @see https://github.com/robolectric/robolectric/issues/1862
|
||||
*/
|
||||
public class AndroidHttpClient {}
|
||||
@ -1,66 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23)
|
||||
public class ConstraintTest {
|
||||
|
||||
/**
|
||||
* Just to get 100% coverage.
|
||||
*/
|
||||
@Test
|
||||
public void testPrivateConstructor() throws Exception {
|
||||
TestUtil.assertHasSinglePrivateConstructor(Constraint.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompactAndUnCompact() {
|
||||
for (List<Integer> combo : TestUtil.getAllConstraintCombinations()) {
|
||||
int[] input = TestUtil.toIntArray(combo);
|
||||
Arrays.sort(input);
|
||||
|
||||
int[] output = Constraint.uncompact(Constraint.compact(input));
|
||||
Arrays.sort(output);
|
||||
|
||||
for (int i = 0; i < combo.size(); i++) {
|
||||
assertEquals("Combination = " + TextUtils.join(", ", combo),
|
||||
input[i],
|
||||
output[i]);
|
||||
}
|
||||
|
||||
assertEquals("Expected length of two arrays to be the same",
|
||||
input.length,
|
||||
output.length);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compactNull() {
|
||||
assertEquals(0, Constraint.compact(null));
|
||||
}
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import android.provider.ContactsContract;
|
||||
import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
/** Test for {@link ContentUriTrigger}. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 21)
|
||||
public class ContentUriTriggerTest {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constrains_null() throws Exception {
|
||||
Trigger.contentUriTrigger(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constrains_emptyList() throws Exception {
|
||||
Trigger.contentUriTrigger(Collections.<ObservedUri>emptyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constrains_valid() throws Exception {
|
||||
List<ObservedUri> uris = Arrays.asList(new ObservedUri(ContactsContract.AUTHORITY_URI, 0));
|
||||
ContentUriTrigger uriTrigger = Trigger.contentUriTrigger(uris);
|
||||
assertEquals(uris, uriTrigger.getUris());
|
||||
}
|
||||
}
|
||||
@ -1,119 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.provider.ContactsContract;
|
||||
import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger;
|
||||
import com.firebase.jobdispatcher.ObservedUri.Flags;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23)
|
||||
public class DefaultJobValidatorTest {
|
||||
|
||||
@Mock
|
||||
private Context mMockContext;
|
||||
|
||||
private DefaultJobValidator mValidator;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mValidator = new DefaultJobValidator(mMockContext);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WrongConstant")
|
||||
@Test
|
||||
public void testValidate_retryStrategy() throws Exception {
|
||||
Map<RetryStrategy, List<String>> testCases = new HashMap<>();
|
||||
testCases.put(
|
||||
new RetryStrategy(0 /* bad policy */, 30, 3600),
|
||||
singletonList("Unknown retry policy provided"));
|
||||
testCases.put(
|
||||
new RetryStrategy(RetryStrategy.RETRY_POLICY_LINEAR, 15, 3600),
|
||||
singletonList("Initial backoff must be at least 30s"));
|
||||
testCases.put(
|
||||
new RetryStrategy(RetryStrategy.RETRY_POLICY_EXPONENTIAL, 15, 3600),
|
||||
singletonList("Initial backoff must be at least 30s"));
|
||||
testCases.put(
|
||||
new RetryStrategy(RetryStrategy.RETRY_POLICY_LINEAR, 30, 60),
|
||||
singletonList("Maximum backoff must be greater than 300s (5 minutes)"));
|
||||
testCases.put(
|
||||
new RetryStrategy(RetryStrategy.RETRY_POLICY_EXPONENTIAL, 30, 60),
|
||||
singletonList("Maximum backoff must be greater than 300s (5 minutes)"));
|
||||
testCases.put(
|
||||
new RetryStrategy(RetryStrategy.RETRY_POLICY_LINEAR, 301, 300),
|
||||
singletonList("Maximum backoff must be greater than or equal to initial backoff"));
|
||||
testCases.put(
|
||||
new RetryStrategy(RetryStrategy.RETRY_POLICY_EXPONENTIAL, 301, 300),
|
||||
singletonList("Maximum backoff must be greater than or equal to initial backoff"));
|
||||
|
||||
for (Entry<RetryStrategy, List<String>> testCase : testCases.entrySet()) {
|
||||
List<String> validationErrors = mValidator.validate(testCase.getKey());
|
||||
assertNotNull("Expected validation errors, but got null", validationErrors);
|
||||
|
||||
for (String expected : testCase.getValue()) {
|
||||
assertTrue(
|
||||
"Expected validation errors to contain \"" + expected + "\"",
|
||||
validationErrors.contains(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidate_trigger() throws Exception {
|
||||
Map<JobTrigger, String> testCases = new HashMap<>();
|
||||
|
||||
testCases.put(Trigger.NOW, null);
|
||||
testCases.put(Trigger.executionWindow(0, 100), null);
|
||||
ContentUriTrigger contentUriTrigger =
|
||||
Trigger.contentUriTrigger(
|
||||
Arrays.asList(
|
||||
new ObservedUri(
|
||||
ContactsContract.AUTHORITY_URI, Flags.FLAG_NOTIFY_FOR_DESCENDANTS)));
|
||||
testCases.put(contentUriTrigger, null);
|
||||
|
||||
for (Entry<JobTrigger, String> testCase : testCases.entrySet()) {
|
||||
List<String> validationErrors = mValidator.validate(testCase.getKey());
|
||||
if (testCase.getValue() == null) {
|
||||
assertNull("Expected no validation errors for trigger", validationErrors);
|
||||
} else {
|
||||
assertTrue(
|
||||
"Expected validation errors to contain \"" + testCase.getValue() + "\"",
|
||||
validationErrors.contains(testCase.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,224 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static com.firebase.jobdispatcher.TestUtil.getContentUriTrigger;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.support.annotation.NonNull;
|
||||
import com.firebase.jobdispatcher.JobService.JobResult;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@SuppressWarnings("WrongConstant")
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 21)
|
||||
public class ExecutionDelegatorTest {
|
||||
|
||||
private Context mMockContext;
|
||||
private TestJobReceiver mReceiver;
|
||||
private ExecutionDelegator mExecutionDelegator;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mMockContext = spy(RuntimeEnvironment.application);
|
||||
doReturn("com.example.foo").when(mMockContext).getPackageName();
|
||||
|
||||
mReceiver = new TestJobReceiver();
|
||||
mExecutionDelegator = new ExecutionDelegator(mMockContext, mReceiver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteJob_sendsBroadcastWithJobAndMessage() throws Exception {
|
||||
for (JobInvocation input : TestUtil.getJobInvocationCombinations()) {
|
||||
verifyExecuteJob(input);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyExecuteJob(JobInvocation input) throws Exception {
|
||||
reset(mMockContext);
|
||||
mReceiver.lastResult = -1;
|
||||
|
||||
mReceiver.setLatch(new CountDownLatch(1));
|
||||
|
||||
mExecutionDelegator.executeJob(input);
|
||||
|
||||
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
final ArgumentCaptor<ServiceConnection> connCaptor =
|
||||
ArgumentCaptor.forClass(ServiceConnection.class);
|
||||
verify(mMockContext).bindService(intentCaptor.capture(), connCaptor.capture(), anyInt());
|
||||
|
||||
final Intent result = intentCaptor.getValue();
|
||||
// verify the intent was sent to the right place
|
||||
assertEquals(input.getService(), result.getComponent().getClassName());
|
||||
assertEquals(JobService.ACTION_EXECUTE, result.getAction());
|
||||
|
||||
final ServiceConnection connection = connCaptor.getValue();
|
||||
|
||||
ComponentName cname = mock(ComponentName.class);
|
||||
JobService.LocalBinder mockLocalBinder = mock(JobService.LocalBinder.class);
|
||||
final JobParameters[] out = new JobParameters[1];
|
||||
|
||||
JobService mockJobService = new JobService() {
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters job) {
|
||||
out[0] = job;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters job) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
when(mockLocalBinder.getService()).thenReturn(mockJobService);
|
||||
|
||||
connection.onServiceConnected(cname, mockLocalBinder);
|
||||
|
||||
TestUtil.assertJobsEqual(input, out[0]);
|
||||
|
||||
// make sure the countdownlatch was decremented
|
||||
assertTrue(mReceiver.mLatch.await(1, TimeUnit.SECONDS));
|
||||
|
||||
// verify the lastResult was set correctly
|
||||
assertEquals(JobService.RESULT_SUCCESS, mReceiver.lastResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteJob_handlesNull() {
|
||||
assertFalse("Expected calling triggerExecution on null to fail and return false",
|
||||
mExecutionDelegator.executeJob(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMessage_doesntCrashOnBadJobData() {
|
||||
JobInvocation j = new JobInvocation.Builder()
|
||||
.setService(TestJobService.class.getName())
|
||||
.setTag("tag")
|
||||
.setTrigger(Trigger.NOW)
|
||||
.build();
|
||||
|
||||
mExecutionDelegator.executeJob(j);
|
||||
|
||||
ArgumentCaptor<Intent> intentCaptor =
|
||||
ArgumentCaptor.forClass(Intent.class);
|
||||
ArgumentCaptor<ServiceConnection> connCaptor =
|
||||
ArgumentCaptor.forClass(ServiceConnection.class);
|
||||
|
||||
//noinspection WrongConstant
|
||||
verify(mMockContext).bindService(intentCaptor.capture(), connCaptor.capture(), anyInt());
|
||||
|
||||
Intent executeReq = intentCaptor.getValue();
|
||||
assertEquals(JobService.ACTION_EXECUTE, executeReq.getAction());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStop_mock() throws InterruptedException {
|
||||
JobInvocation job = new JobInvocation.Builder()
|
||||
.setTag("TAG")
|
||||
.setTrigger(getContentUriTrigger())
|
||||
.setService(TestJobService.class.getName())
|
||||
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
|
||||
.build();
|
||||
|
||||
reset(mMockContext);
|
||||
mReceiver.lastResult = -1;
|
||||
|
||||
mExecutionDelegator.executeJob(job);
|
||||
|
||||
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
final ArgumentCaptor<ServiceConnection> connCaptor =
|
||||
ArgumentCaptor.forClass(ServiceConnection.class);
|
||||
verify(mMockContext).bindService(intentCaptor.capture(), connCaptor.capture(), anyInt());
|
||||
|
||||
final Intent result = intentCaptor.getValue();
|
||||
// verify the intent was sent to the right place
|
||||
assertEquals(job.getService(), result.getComponent().getClassName());
|
||||
assertEquals(JobService.ACTION_EXECUTE, result.getAction());
|
||||
|
||||
final JobParameters[] out = new JobParameters[2];
|
||||
|
||||
JobService mockJobService = new JobService() {
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters job) {
|
||||
out[0] = job;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters job) {
|
||||
out[1] = job;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
JobService.LocalBinder mockLocalBinder = mock(JobService.LocalBinder.class);
|
||||
when(mockLocalBinder.getService()).thenReturn(mockJobService);
|
||||
|
||||
ComponentName componentName = mock(ComponentName.class);
|
||||
final ServiceConnection connection = connCaptor.getValue();
|
||||
connection.onServiceConnected(componentName, mockLocalBinder);
|
||||
|
||||
mExecutionDelegator.stopJob(job);
|
||||
|
||||
TestUtil.assertJobsEqual(job, out[0]);
|
||||
TestUtil.assertJobsEqual(job, out[1]);
|
||||
}
|
||||
|
||||
private final static class TestJobReceiver implements ExecutionDelegator.JobFinishedCallback {
|
||||
int lastResult;
|
||||
|
||||
private CountDownLatch mLatch;
|
||||
|
||||
@Override
|
||||
public void onJobFinished(@NonNull JobInvocation js, @JobResult int result) {
|
||||
lastResult = result;
|
||||
|
||||
if (mLatch != null) {
|
||||
mLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for tests.
|
||||
*/
|
||||
public void setLatch(CountDownLatch latch) {
|
||||
mLatch = latch;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23)
|
||||
public class ExecutionWindowTriggerTest {
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void testNewInstance_withValidWindow() throws Exception {
|
||||
JobTrigger.ExecutionWindowTrigger trigger = Trigger.executionWindow(0, 60);
|
||||
|
||||
assertEquals(0, trigger.getWindowStart());
|
||||
assertEquals(60, trigger.getWindowEnd());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewInstance_withNegativeStart() throws Exception {
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
|
||||
Trigger.executionWindow(-10, 60);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewInstance_withNegativeEnd() throws Exception {
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
|
||||
Trigger.executionWindow(0, -1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewInstance_withReversedValues() throws Exception {
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
|
||||
Trigger.executionWindow(60, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewInstance_withTooSmallWindow_now() throws Exception {
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
|
||||
Trigger.executionWindow(60, 59);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewInstance_withTooSmallWindow_inFuture() throws Exception {
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
|
||||
Trigger.executionWindow(200, 100);
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
import org.robolectric.annotation.RealObject;
|
||||
import org.robolectric.shadows.ShadowParcel;
|
||||
|
||||
/**
|
||||
* ShadowParcel doesn't correctly handle {@link Parcel#writeStrongBinder(IBinder)} or {@link
|
||||
* Parcel#readStrongBinder()}, so we shim a simple implementation that uses an in-memory map to read
|
||||
* and write Binder objects.
|
||||
*/
|
||||
@Implements(Parcel.class)
|
||||
public class ExtendedShadowParcel extends ShadowParcel {
|
||||
@RealObject private Parcel realObject;
|
||||
|
||||
// Map each IBinder to an integer, and use the super's int-writing capability to fake Binder
|
||||
// read/writes.
|
||||
private final AtomicInteger nextBinderId = new AtomicInteger(1);
|
||||
private final Map<Integer, IBinder> binderMap =
|
||||
Collections.synchronizedMap(new HashMap<Integer, IBinder>());
|
||||
|
||||
@Implementation
|
||||
public void writeStrongBinder(IBinder binder) {
|
||||
int id = nextBinderId.getAndIncrement();
|
||||
binderMap.put(id, binder);
|
||||
realObject.writeInt(id);
|
||||
}
|
||||
|
||||
@Implementation
|
||||
public IBinder readStrongBinder() {
|
||||
return binderMap.get(realObject.readInt());
|
||||
}
|
||||
}
|
||||
@ -1,205 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.firebase.jobdispatcher.FirebaseJobDispatcher.ScheduleFailedException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23)
|
||||
public class FirebaseJobDispatcherTest {
|
||||
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private Driver mDriver;
|
||||
|
||||
@Mock
|
||||
private JobValidator mValidator;
|
||||
|
||||
private FirebaseJobDispatcher mDispatcher;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mDriver.getValidator()).thenReturn(mValidator);
|
||||
|
||||
mDispatcher = new FirebaseJobDispatcher(mDriver);
|
||||
setDriverAvailability(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedule_passThrough() throws Exception {
|
||||
final int[] possibleResults = {
|
||||
FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS,
|
||||
FirebaseJobDispatcher.SCHEDULE_RESULT_NO_DRIVER_AVAILABLE,
|
||||
FirebaseJobDispatcher.SCHEDULE_RESULT_BAD_SERVICE,
|
||||
FirebaseJobDispatcher.SCHEDULE_RESULT_UNKNOWN_ERROR,
|
||||
FirebaseJobDispatcher.SCHEDULE_RESULT_UNSUPPORTED_TRIGGER};
|
||||
|
||||
for (int result : possibleResults) {
|
||||
when(mDriver.schedule(null)).thenReturn(result);
|
||||
assertEquals(result, mDispatcher.schedule(null));
|
||||
}
|
||||
|
||||
verify(mDriver, times(possibleResults.length)).schedule(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedule_unavailable() throws Exception {
|
||||
setDriverAvailability(false);
|
||||
assertEquals(
|
||||
FirebaseJobDispatcher.SCHEDULE_RESULT_NO_DRIVER_AVAILABLE,
|
||||
mDispatcher.schedule(null));
|
||||
verify(mDriver, never()).schedule(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelJob() throws Exception {
|
||||
final String tag = "foo";
|
||||
|
||||
// simulate success
|
||||
when(mDriver.cancel(tag))
|
||||
.thenReturn(FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS);
|
||||
|
||||
assertEquals(
|
||||
"Expected dispatcher to pass the result of Driver#cancel(String, Class) through",
|
||||
FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS,
|
||||
mDispatcher.cancel(tag));
|
||||
|
||||
// verify the driver was indeed called
|
||||
verify(mDriver).cancel(tag);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelJob_unavailable() throws Exception {
|
||||
setDriverAvailability(false); // driver is unavailable
|
||||
|
||||
assertEquals(
|
||||
FirebaseJobDispatcher.CANCEL_RESULT_NO_DRIVER_AVAILABLE,
|
||||
mDispatcher.cancel("foo"));
|
||||
|
||||
// verify the driver was never even consulted
|
||||
verify(mDriver, never()).cancel("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelAllJobs() throws Exception {
|
||||
when(mDriver.cancelAll()).thenReturn(FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS);
|
||||
|
||||
assertEquals(FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS, mDispatcher.cancelAll());
|
||||
verify(mDriver).cancelAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelAllJobs_unavailable() throws Exception {
|
||||
setDriverAvailability(false); // driver is unavailable
|
||||
|
||||
assertEquals(
|
||||
FirebaseJobDispatcher.CANCEL_RESULT_NO_DRIVER_AVAILABLE,
|
||||
mDispatcher.cancelAll());
|
||||
|
||||
verify(mDriver, never()).cancelAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMustSchedule_success() throws Exception {
|
||||
when(mDriver.schedule(null)).thenReturn(FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS);
|
||||
|
||||
/* assert no exception is thrown */
|
||||
mDispatcher.mustSchedule(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMustSchedule_unavailable() throws Exception {
|
||||
setDriverAvailability(false); // driver is unavailable
|
||||
expectedException.expect(FirebaseJobDispatcher.ScheduleFailedException.class);
|
||||
|
||||
mDispatcher.mustSchedule(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMustSchedule_failure() throws Exception {
|
||||
final int[] possibleErrors = {
|
||||
FirebaseJobDispatcher.SCHEDULE_RESULT_NO_DRIVER_AVAILABLE,
|
||||
FirebaseJobDispatcher.SCHEDULE_RESULT_BAD_SERVICE,
|
||||
FirebaseJobDispatcher.SCHEDULE_RESULT_UNKNOWN_ERROR,
|
||||
FirebaseJobDispatcher.SCHEDULE_RESULT_UNSUPPORTED_TRIGGER};
|
||||
|
||||
for (int scheduleError : possibleErrors) {
|
||||
when(mDriver.schedule(null)).thenReturn(scheduleError);
|
||||
|
||||
try {
|
||||
mDispatcher.mustSchedule(null);
|
||||
|
||||
fail("Expected mustSchedule() with error code " + scheduleError + " to fail");
|
||||
} catch (ScheduleFailedException expected) { /* expected */ }
|
||||
}
|
||||
|
||||
verify(mDriver, times(possibleErrors.length)).schedule(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewRetryStrategyBuilder() {
|
||||
// custom validator that only approves strategies where initialbackoff == 30s
|
||||
when(mValidator.validate(any(RetryStrategy.class))).thenAnswer(new Answer<List<String>>() {
|
||||
@Override
|
||||
public List<String> answer(InvocationOnMock invocation) throws Throwable {
|
||||
RetryStrategy rs = (RetryStrategy) invocation.getArguments()[0];
|
||||
// only succeed if initialBackoff == 30s
|
||||
return rs.getInitialBackoff() == 30 ? null : Arrays.asList("foo", "bar");
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
mDispatcher.newRetryStrategy(RetryStrategy.RETRY_POLICY_EXPONENTIAL, 0, 30);
|
||||
fail("Expected initial backoff != 30s to fail using custom validator");
|
||||
} catch (Exception unused) { /* unused */ }
|
||||
|
||||
try {
|
||||
mDispatcher.newRetryStrategy(RetryStrategy.RETRY_POLICY_EXPONENTIAL, 30, 30);
|
||||
} catch (Exception unused) {
|
||||
fail("Expected initial backoff == 30s not to fail using custom validator");
|
||||
}
|
||||
}
|
||||
|
||||
public void setDriverAvailability(boolean driverAvailability) {
|
||||
when(mDriver.isAvailable()).thenReturn(driverAvailability);
|
||||
}
|
||||
}
|
||||
@ -1,153 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Pair;
|
||||
import com.firebase.jobdispatcher.TestUtil.InspectableBinder;
|
||||
import com.firebase.jobdispatcher.TestUtil.TransactionArguments;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(
|
||||
constants = BuildConfig.class,
|
||||
manifest = Config.NONE,
|
||||
sdk = 23,
|
||||
shadows = {ExtendedShadowParcel.class}
|
||||
)
|
||||
public final class GooglePlayCallbackExtractorTest {
|
||||
@Mock
|
||||
private IBinder mBinder;
|
||||
|
||||
private GooglePlayCallbackExtractor mExtractor;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mExtractor = new GooglePlayCallbackExtractor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractCallback_nullBundle() {
|
||||
assertNull(mExtractor.extractCallback(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractCallback_nullParcelable() {
|
||||
Bundle emptyBundle = new Bundle();
|
||||
assertNull(extractCallback(emptyBundle));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractCallback_badParcelable() {
|
||||
Bundle misconfiguredBundle = new Bundle();
|
||||
misconfiguredBundle.putParcelable("callback", new BadParcelable(1));
|
||||
|
||||
assertNull(extractCallback(misconfiguredBundle));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractCallback_goodParcelable() {
|
||||
InspectableBinder binder = new InspectableBinder();
|
||||
Bundle validBundle = new Bundle();
|
||||
validBundle.putParcelable("callback", binder.toPendingCallback());
|
||||
|
||||
Pair<JobCallback, Bundle> extraction = extractCallback(validBundle);
|
||||
assertNotNull(extraction);
|
||||
assertEquals("should have stripped the 'callback' entry from the extracted bundle",
|
||||
0, extraction.second.keySet().size());
|
||||
extraction.first.jobFinished(JobService.RESULT_SUCCESS);
|
||||
|
||||
// Check our homemade Binder is doing the right things:
|
||||
TransactionArguments args = binder.getArguments().get(0);
|
||||
// Should have set the transaction code:
|
||||
assertEquals("transaction code", IBinder.FIRST_CALL_TRANSACTION + 1, args.code);
|
||||
|
||||
// strong mode bit
|
||||
args.data.readInt();
|
||||
// interface token
|
||||
assertEquals("com.google.android.gms.gcm.INetworkTaskCallback", args.data.readString());
|
||||
// result
|
||||
assertEquals("result", JobService.RESULT_SUCCESS, args.data.readInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractCallback_extraMapValues() {
|
||||
Bundle validBundle = new Bundle();
|
||||
validBundle.putString("foo", "bar");
|
||||
validBundle.putInt("bar", 3);
|
||||
validBundle.putParcelable("parcelable", new Bundle());
|
||||
validBundle.putParcelable("callback", new InspectableBinder().toPendingCallback());
|
||||
|
||||
Pair<JobCallback, Bundle> extraction = extractCallback(validBundle);
|
||||
assertNotNull(extraction);
|
||||
assertEquals("should have stripped the 'callback' entry from the extracted bundle",
|
||||
3, extraction.second.keySet().size());
|
||||
}
|
||||
|
||||
private Pair<JobCallback, Bundle> extractCallback(Bundle bundle) {
|
||||
return mExtractor.extractCallback(bundle);
|
||||
}
|
||||
|
||||
private static final class BadParcelable implements Parcelable {
|
||||
public static final Parcelable.Creator<BadParcelable> CREATOR
|
||||
= new Parcelable.Creator<BadParcelable>() {
|
||||
@Override
|
||||
public BadParcelable createFromParcel(Parcel in) {
|
||||
return new BadParcelable(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BadParcelable[] newArray(int size) {
|
||||
return new BadParcelable[size];
|
||||
}
|
||||
};
|
||||
private final int mNum;
|
||||
|
||||
public BadParcelable(int i) {
|
||||
mNum = i;
|
||||
}
|
||||
|
||||
private BadParcelable(Parcel in) {
|
||||
mNum = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dst, int flags) {
|
||||
dst.writeInt(mNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,203 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import java.util.Arrays;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23)
|
||||
public class GooglePlayDriverTest {
|
||||
@Mock
|
||||
public Context mMockContext;
|
||||
|
||||
private TestJobDriver mDriver;
|
||||
private FirebaseJobDispatcher mDispatcher;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mDriver = new TestJobDriver(new GooglePlayDriver(mMockContext));
|
||||
mDispatcher = new FirebaseJobDispatcher(mDriver);
|
||||
|
||||
when(mMockContext.getPackageName()).thenReturn("foo.bar.whatever");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedule_failsWhenPlayServicesIsUnavailable() throws Exception {
|
||||
markBackendUnavailable();
|
||||
mockPackageManagerInfo();
|
||||
|
||||
Job job = null;
|
||||
try {
|
||||
job = mDispatcher.newJobBuilder()
|
||||
.setService(TestJobService.class)
|
||||
.setTag("foobar")
|
||||
.setConstraints(Constraint.DEVICE_CHARGING)
|
||||
.setTrigger(Trigger.executionWindow(0, 60))
|
||||
.build();
|
||||
} catch (ValidationEnforcer.ValidationException ve) {
|
||||
fail(TextUtils.join("\n", ve.getErrors()));
|
||||
}
|
||||
|
||||
assertEquals("Expected schedule() request to fail when backend is unavailable",
|
||||
FirebaseJobDispatcher.SCHEDULE_RESULT_NO_DRIVER_AVAILABLE,
|
||||
mDispatcher.schedule(job));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelJobs_backendUnavailable() throws Exception {
|
||||
markBackendUnavailable();
|
||||
|
||||
assertEquals("Expected cancelAll() request to fail when backend is unavailable",
|
||||
FirebaseJobDispatcher.CANCEL_RESULT_NO_DRIVER_AVAILABLE,
|
||||
mDispatcher.cancelAll());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedule_sendsAppropriateBroadcast() {
|
||||
ArgumentCaptor<Intent> pmQueryIntentCaptor = mockPackageManagerInfo();
|
||||
|
||||
Job job = mDispatcher.newJobBuilder()
|
||||
.setConstraints(Constraint.DEVICE_CHARGING)
|
||||
.setService(TestJobService.class)
|
||||
.setTrigger(Trigger.executionWindow(0, 60))
|
||||
.setRecurring(false)
|
||||
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
|
||||
.setTag("foobar")
|
||||
.build();
|
||||
|
||||
Intent pmQueryIntent = pmQueryIntentCaptor.getValue();
|
||||
assertEquals(JobService.ACTION_EXECUTE, pmQueryIntent.getAction());
|
||||
assertEquals(TestJobService.class.getName(), pmQueryIntent.getComponent().getClassName());
|
||||
|
||||
assertEquals("Expected schedule() request to succeed",
|
||||
FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS,
|
||||
mDispatcher.schedule(job));
|
||||
|
||||
final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mMockContext).sendBroadcast(captor.capture());
|
||||
|
||||
Intent broadcast = captor.getValue();
|
||||
|
||||
assertNotNull(broadcast);
|
||||
assertEquals("com.google.android.gms.gcm.ACTION_SCHEDULE", broadcast.getAction());
|
||||
assertEquals("SCHEDULE_TASK", broadcast.getStringExtra("scheduler_action"));
|
||||
assertEquals("com.google.android.gms", broadcast.getPackage());
|
||||
assertEquals(8, broadcast.getIntExtra("source", -1));
|
||||
assertEquals(1, broadcast.getIntExtra("source_version", -1));
|
||||
|
||||
final Parcelable parcelablePendingIntent = broadcast.getParcelableExtra("app");
|
||||
assertTrue("Expected 'app' value to be a PendingIntent",
|
||||
parcelablePendingIntent instanceof PendingIntent);
|
||||
}
|
||||
|
||||
private ArgumentCaptor<Intent> mockPackageManagerInfo() {
|
||||
PackageManager packageManager = mock(PackageManager.class);
|
||||
when(mMockContext.getPackageManager()).thenReturn(packageManager);
|
||||
ArgumentCaptor<Intent> intentArgCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
|
||||
ResolveInfo info = new ResolveInfo();
|
||||
info.serviceInfo = new ServiceInfo();
|
||||
info.serviceInfo.enabled = true;
|
||||
|
||||
//noinspection WrongConstant
|
||||
when(packageManager.queryIntentServices(intentArgCaptor.capture(), eq(0)))
|
||||
.thenReturn(Arrays.asList(info));
|
||||
|
||||
return intentArgCaptor;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancel_sendsAppropriateBroadcast() {
|
||||
mDispatcher.cancel("foobar");
|
||||
|
||||
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mMockContext).sendBroadcast(captor.capture());
|
||||
|
||||
Intent broadcast = captor.getValue();
|
||||
|
||||
assertNotNull(broadcast);
|
||||
assertEquals("foobar", broadcast.getStringExtra("tag"));
|
||||
}
|
||||
|
||||
private void markBackendUnavailable() {
|
||||
mDriver.available = false;
|
||||
}
|
||||
|
||||
public final static class TestJobDriver implements Driver {
|
||||
public boolean available = true;
|
||||
|
||||
private final Driver wrappedDriver;
|
||||
|
||||
public TestJobDriver(Driver wrappedDriver) {
|
||||
this.wrappedDriver = wrappedDriver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int schedule(@NonNull Job job) {
|
||||
return this.wrappedDriver.schedule(job);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int cancel(@NonNull String tag) {
|
||||
return this.wrappedDriver.cancel(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int cancelAll() {
|
||||
return this.wrappedDriver.cancelAll();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public JobValidator getValidator() {
|
||||
return this.wrappedDriver.getValidator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return available;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,268 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import com.firebase.jobdispatcher.Job.Builder;
|
||||
import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger;
|
||||
import com.firebase.jobdispatcher.ObservedUri.Flags;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23)
|
||||
public class GooglePlayJobWriterTest {
|
||||
|
||||
private static final boolean[] ALL_BOOLEANS = {true, false};
|
||||
|
||||
private GooglePlayJobWriter mWriter;
|
||||
|
||||
private static Builder initializeDefaultBuilder() {
|
||||
return TestUtil.getBuilderWithNoopValidator()
|
||||
.setConstraints(Constraint.DEVICE_CHARGING)
|
||||
.setExtras(null)
|
||||
.setLifetime(Lifetime.FOREVER)
|
||||
.setRecurring(false)
|
||||
.setReplaceCurrent(false)
|
||||
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
|
||||
.setService(TestJobService.class)
|
||||
.setTag("tag")
|
||||
.setTrigger(Trigger.NOW);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mWriter = new GooglePlayJobWriter();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToBundle_tags() {
|
||||
for (String tag : Arrays.asList("foo", "bar", "foobar", "this is a tag")) {
|
||||
Bundle b = mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setTag(tag).build(),
|
||||
new Bundle());
|
||||
|
||||
assertEquals("tag", tag, b.getString("tag"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToBundle_updateCurrent() {
|
||||
for (boolean replaceCurrent : ALL_BOOLEANS) {
|
||||
Bundle b = mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setReplaceCurrent(replaceCurrent).build(),
|
||||
new Bundle());
|
||||
|
||||
assertEquals("update_current", replaceCurrent, b.getBoolean("update_current"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToBundle_persisted() {
|
||||
Bundle b = mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setLifetime(Lifetime.FOREVER).build(),
|
||||
new Bundle());
|
||||
|
||||
assertTrue("persisted", b.getBoolean("persisted"));
|
||||
|
||||
for (int lifetime : new int[]{Lifetime.UNTIL_NEXT_BOOT}) {
|
||||
b = mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setLifetime(lifetime).build(),
|
||||
new Bundle());
|
||||
|
||||
assertFalse("persisted", b.getBoolean("persisted"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToBundle_service() {
|
||||
Bundle b = mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setService(TestJobService.class).build(),
|
||||
new Bundle());
|
||||
|
||||
assertEquals("service", GooglePlayReceiver.class.getName(), b.getString("service"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToBundle_requiredNetwork() {
|
||||
Map<Integer, Integer> mapping = new HashMap<>();
|
||||
mapping.put(Constraint.ON_ANY_NETWORK, GooglePlayJobWriter.LEGACY_NETWORK_CONNECTED);
|
||||
mapping.put(Constraint.ON_UNMETERED_NETWORK, GooglePlayJobWriter.LEGACY_NETWORK_UNMETERED);
|
||||
mapping.put(0, GooglePlayJobWriter.LEGACY_NETWORK_ANY);
|
||||
|
||||
for (Entry<Integer, Integer> testCase : mapping.entrySet()) {
|
||||
@SuppressWarnings("WrongConstant")
|
||||
Bundle b = mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setConstraints(testCase.getKey()).build(),
|
||||
new Bundle());
|
||||
|
||||
assertEquals("requiredNetwork", (int) testCase.getValue(), b.getInt("requiredNetwork"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToBundle_unmeteredConstraintShouldTakePrecendence() {
|
||||
Bundle b = mWriter.writeToBundle(
|
||||
initializeDefaultBuilder()
|
||||
.setConstraints(Constraint.ON_ANY_NETWORK, Constraint.ON_UNMETERED_NETWORK)
|
||||
.build(),
|
||||
new Bundle());
|
||||
|
||||
assertEquals("expected ON_UNMETERED_NETWORK to take precendence over ON_ANY_NETWORK",
|
||||
GooglePlayJobWriter.LEGACY_NETWORK_UNMETERED, b.getInt("requiredNetwork"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToBundle_requiresCharging() {
|
||||
assertTrue("requiresCharging", mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setConstraints(Constraint.DEVICE_CHARGING).build(),
|
||||
new Bundle()).getBoolean("requiresCharging"));
|
||||
|
||||
for (Integer constraint : Arrays.asList(
|
||||
Constraint.ON_ANY_NETWORK,
|
||||
Constraint.ON_UNMETERED_NETWORK)) {
|
||||
|
||||
assertFalse("requiresCharging", mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setConstraints(constraint).build(),
|
||||
new Bundle()).getBoolean("requiresCharging"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToBundle_requiresIdle() {
|
||||
assertTrue("requiresIdle", mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setConstraints(Constraint.DEVICE_IDLE).build(),
|
||||
new Bundle()).getBoolean("requiresIdle"));
|
||||
|
||||
for (Integer constraint : Arrays.asList(
|
||||
Constraint.ON_ANY_NETWORK,
|
||||
Constraint.ON_UNMETERED_NETWORK)) {
|
||||
|
||||
assertFalse("requiresIdle", mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setConstraints(constraint).build(),
|
||||
new Bundle()).getBoolean("requiresIdle"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToBundle_retryPolicy() {
|
||||
assertEquals("retry_policy",
|
||||
GooglePlayJobWriter.LEGACY_RETRY_POLICY_EXPONENTIAL,
|
||||
mWriter.writeToBundle(
|
||||
initializeDefaultBuilder()
|
||||
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
|
||||
.build(),
|
||||
new Bundle()).getBundle("retryStrategy").getInt("retry_policy"));
|
||||
|
||||
assertEquals("retry_policy",
|
||||
GooglePlayJobWriter.LEGACY_RETRY_POLICY_LINEAR,
|
||||
mWriter.writeToBundle(
|
||||
initializeDefaultBuilder()
|
||||
.setRetryStrategy(RetryStrategy.DEFAULT_LINEAR)
|
||||
.build(),
|
||||
new Bundle()).getBundle("retryStrategy").getInt("retry_policy"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToBundle_backoffSeconds() {
|
||||
for (RetryStrategy retryStrategy : Arrays
|
||||
.asList(RetryStrategy.DEFAULT_EXPONENTIAL, RetryStrategy.DEFAULT_LINEAR)) {
|
||||
|
||||
Bundle b = mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setRetryStrategy(retryStrategy).build(),
|
||||
new Bundle()).getBundle("retryStrategy");
|
||||
|
||||
assertEquals("initial_backoff_seconds",
|
||||
retryStrategy.getInitialBackoff(),
|
||||
b.getInt("initial_backoff_seconds"));
|
||||
|
||||
assertEquals("maximum_backoff_seconds",
|
||||
retryStrategy.getMaximumBackoff(),
|
||||
b.getInt("maximum_backoff_seconds"));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToBundle_triggers() {
|
||||
// immediate
|
||||
Bundle b = mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setTrigger(Trigger.NOW).build(),
|
||||
new Bundle());
|
||||
|
||||
assertEquals("window_start", 0, b.getLong("window_start"));
|
||||
assertEquals("window_end", 30, b.getLong("window_end"));
|
||||
|
||||
// execution window (oneoff)
|
||||
JobTrigger.ExecutionWindowTrigger t = Trigger.executionWindow(631, 978);
|
||||
b = mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setTrigger(t).build(),
|
||||
new Bundle());
|
||||
|
||||
assertEquals("window_start", t.getWindowStart(), b.getLong("window_start"));
|
||||
assertEquals("window_end", t.getWindowEnd(), b.getLong("window_end"));
|
||||
|
||||
// execution window (periodic)
|
||||
b = mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setRecurring(true).setTrigger(t).build(),
|
||||
new Bundle());
|
||||
|
||||
assertEquals("period", t.getWindowEnd(), b.getLong("period"));
|
||||
assertEquals("period_flex", t.getWindowEnd() - t.getWindowStart(), b.getLong("period_flex"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToBundle_contentUriTrigger() {
|
||||
ObservedUri observedUri = new ObservedUri(ContactsContract.AUTHORITY_URI,
|
||||
Flags.FLAG_NOTIFY_FOR_DESCENDANTS);
|
||||
ContentUriTrigger contentUriTrigger = Trigger.contentUriTrigger(Arrays.asList(observedUri));
|
||||
Bundle bundle = mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setTrigger(contentUriTrigger).build(), new Bundle());
|
||||
Uri[] uris =
|
||||
(Uri[]) bundle.getParcelableArray(BundleProtocol.PACKED_PARAM_CONTENT_URI_ARRAY);
|
||||
int[] flags = bundle.getIntArray(BundleProtocol.PACKED_PARAM_CONTENT_URI_FLAGS_ARRAY);
|
||||
assertTrue("Array size", uris.length == flags.length && flags.length == 1);
|
||||
assertEquals(BundleProtocol.PACKED_PARAM_CONTENT_URI_ARRAY,
|
||||
ContactsContract.AUTHORITY_URI, uris[0]);
|
||||
assertEquals(BundleProtocol.PACKED_PARAM_CONTENT_URI_FLAGS_ARRAY,
|
||||
Flags.FLAG_NOTIFY_FOR_DESCENDANTS, flags[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToBundle_extras() {
|
||||
Bundle extras = new Bundle();
|
||||
|
||||
Bundle result = mWriter.writeToBundle(
|
||||
initializeDefaultBuilder().setExtras(extras).build(),
|
||||
new Bundle());
|
||||
|
||||
assertEquals("extras", extras, result.getBundle("extras"));
|
||||
}
|
||||
}
|
||||
@ -1,153 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static com.firebase.jobdispatcher.GooglePlayJobWriter.REQUEST_PARAM_TAG;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import com.firebase.jobdispatcher.JobInvocation.Builder;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
/**
|
||||
* Tests {@link GooglePlayMessageHandler}.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 21)
|
||||
public class GooglePlayMessageHandlerTest {
|
||||
|
||||
@Mock
|
||||
Looper looper;
|
||||
@Mock
|
||||
GooglePlayReceiver receiverMock;
|
||||
@Mock
|
||||
Context context;
|
||||
@Mock
|
||||
AppOpsManager appOpsManager;
|
||||
@Mock
|
||||
Messenger messengerMock;
|
||||
@Mock
|
||||
ExecutionDelegator executionDelegatorMock;
|
||||
|
||||
GooglePlayMessageHandler handler;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
handler = new GooglePlayMessageHandler(looper, receiverMock);
|
||||
when(receiverMock.getExecutionDelegator()).thenReturn(executionDelegatorMock);
|
||||
when(receiverMock.getApplicationContext()).thenReturn(context);
|
||||
when(context.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(appOpsManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleMessage_nullNoException() throws Exception {
|
||||
handler.handleMessage(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleMessage_ignoreIfSenderIsNotGcm() throws Exception {
|
||||
Message message = Message.obtain();
|
||||
message.what = GooglePlayMessageHandler.MSG_START_EXEC;
|
||||
Bundle data = new Bundle();
|
||||
data.putString(REQUEST_PARAM_TAG, "TAG");
|
||||
message.setData(data);
|
||||
message.replyTo = messengerMock;
|
||||
doThrow(new SecurityException()).when(appOpsManager)
|
||||
.checkPackage(message.sendingUid, GooglePlayDriver.BACKEND_PACKAGE);
|
||||
handler.handleMessage(message);
|
||||
verify(receiverMock, never()).prepareJob(any(GooglePlayMessengerCallback.class), eq(data));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleMessage_startExecution_noData() throws Exception {
|
||||
Message message = Message.obtain();
|
||||
message.what = GooglePlayMessageHandler.MSG_START_EXEC;
|
||||
message.replyTo = messengerMock;
|
||||
|
||||
handler.handleMessage(message);
|
||||
verify(receiverMock, never())
|
||||
.prepareJob(any(GooglePlayMessengerCallback.class), any(Bundle.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleMessage_startExecution() throws Exception {
|
||||
Message message = Message.obtain();
|
||||
message.what = GooglePlayMessageHandler.MSG_START_EXEC;
|
||||
Bundle data = new Bundle();
|
||||
data.putString(REQUEST_PARAM_TAG, "TAG");
|
||||
message.setData(data);
|
||||
message.replyTo = messengerMock;
|
||||
JobInvocation jobInvocation = new Builder()
|
||||
.setTag("tag")
|
||||
.setService(TestJobService.class.getName())
|
||||
.setTrigger(Trigger.NOW).build();
|
||||
when(receiverMock.prepareJob(any(GooglePlayMessengerCallback.class), eq(data)))
|
||||
.thenReturn(jobInvocation);
|
||||
|
||||
handler.handleMessage(message);
|
||||
|
||||
verify(executionDelegatorMock).executeJob(jobInvocation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleMessage_stopExecution() throws Exception {
|
||||
Message message = Message.obtain();
|
||||
message.what = GooglePlayMessageHandler.MSG_STOP_EXEC;
|
||||
JobCoder jobCoder = GooglePlayReceiver.getJobCoder();
|
||||
Bundle data = TestUtil.encodeContentUriJob(TestUtil.getContentUriTrigger(), jobCoder);
|
||||
JobInvocation jobInvocation = jobCoder.decode(data).build();
|
||||
message.setData(data);
|
||||
message.replyTo = messengerMock;
|
||||
|
||||
handler.handleMessage(message);
|
||||
|
||||
final ArgumentCaptor<JobInvocation> captor = ArgumentCaptor.forClass(JobInvocation.class);
|
||||
|
||||
verify(executionDelegatorMock).stopJob(captor.capture());
|
||||
|
||||
TestUtil.assertJobsEqual(jobInvocation, captor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleMessage_stopExecution_invalidNoCrash() throws Exception {
|
||||
Message message = Message.obtain();
|
||||
message.what = GooglePlayMessageHandler.MSG_STOP_EXEC;
|
||||
message.replyTo = messengerMock;
|
||||
|
||||
handler.handleMessage(message);
|
||||
|
||||
verify(executionDelegatorMock, never()).stopJob(any(JobInvocation.class));
|
||||
}
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static com.firebase.jobdispatcher.GooglePlayJobWriter.REQUEST_PARAM_TAG;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
/**
|
||||
* Tests {@link GooglePlayMessengerCallback}.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 21)
|
||||
public class GooglePlayMessengerCallbackTest {
|
||||
|
||||
@Mock
|
||||
Messenger messengerMock;
|
||||
GooglePlayMessengerCallback callback;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
callback = new GooglePlayMessengerCallback(messengerMock, "tag");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jobFinished() throws Exception {
|
||||
final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||
|
||||
callback.jobFinished(JobService.RESULT_SUCCESS);
|
||||
|
||||
Mockito.verify(messengerMock).send(messageCaptor.capture());
|
||||
Message message = messageCaptor.getValue();
|
||||
assertEquals(message.what, GooglePlayMessageHandler.MSG_RESULT);
|
||||
assertEquals(message.arg1, JobService.RESULT_SUCCESS);
|
||||
assertEquals(message.getData().getString(REQUEST_PARAM_TAG), "tag");
|
||||
}
|
||||
}
|
||||
@ -1,327 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static com.firebase.jobdispatcher.TestUtil.encodeContentUriJob;
|
||||
import static com.firebase.jobdispatcher.TestUtil.encodeRecurringContentUriJob;
|
||||
import static com.firebase.jobdispatcher.TestUtil.getContentUriTrigger;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNull;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Messenger;
|
||||
import android.os.Parcel;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.Contacts;
|
||||
import android.provider.MediaStore.Images.Media;
|
||||
import android.support.annotation.NonNull;
|
||||
import com.firebase.jobdispatcher.GooglePlayReceiverTest.ShadowMessenger;
|
||||
import com.firebase.jobdispatcher.JobInvocation.Builder;
|
||||
import com.firebase.jobdispatcher.TestUtil.InspectableBinder;
|
||||
import com.google.android.gms.gcm.PendingCallback;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.annotation.Implements;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(
|
||||
constants = BuildConfig.class,
|
||||
manifest = Config.NONE,
|
||||
sdk = 21,
|
||||
shadows = {ShadowMessenger.class}
|
||||
)
|
||||
public class GooglePlayReceiverTest {
|
||||
|
||||
/**
|
||||
* The default ShadowMessenger implementation causes NPEs when using the
|
||||
* {@link Messenger#Messenger(Handler)} constructor. We create our own empty Shadow so we can
|
||||
* just use the standard Android implementation, which is totally fine.
|
||||
*
|
||||
* @see <a href="https://github.com/robolectric/robolectric/issues/2246">Robolectric issue</a>
|
||||
*
|
||||
*/
|
||||
@Implements(Messenger.class)
|
||||
public static class ShadowMessenger {}
|
||||
|
||||
GooglePlayReceiver receiver;
|
||||
|
||||
JobCoder jobCoder = new JobCoder(BundleProtocol.PACKED_PARAM_BUNDLE_PREFIX, true);
|
||||
|
||||
@Mock
|
||||
Messenger messengerMock;
|
||||
@Mock
|
||||
IBinder binderMock;
|
||||
@Mock
|
||||
JobCallback callbackMock;
|
||||
@Mock
|
||||
ExecutionDelegator executionDelegatorMock;
|
||||
@Mock
|
||||
Driver driverMock;
|
||||
@Captor
|
||||
ArgumentCaptor<Job> jobArgumentCaptor;
|
||||
|
||||
ArrayList<Uri> triggeredUris = new ArrayList<>();
|
||||
|
||||
{
|
||||
triggeredUris.add(ContactsContract.AUTHORITY_URI);
|
||||
triggeredUris.add(Media.EXTERNAL_CONTENT_URI);
|
||||
}
|
||||
|
||||
Builder jobInvocationBuilder = new Builder()
|
||||
.setTag("tag")
|
||||
.setService(TestJobService.class.getName())
|
||||
.setTrigger(Trigger.NOW);
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
receiver = spy(new GooglePlayReceiver());
|
||||
when(receiver.getExecutionDelegator()).thenReturn(executionDelegatorMock);
|
||||
receiver.driver = driverMock;
|
||||
receiver.validationEnforcer = new ValidationEnforcer(new NoopJobValidator());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onJobFinished_unknownJobCallbackIsNotPresent_ignoreNoException() {
|
||||
receiver.onJobFinished(jobInvocationBuilder.build(), JobService.RESULT_SUCCESS);
|
||||
|
||||
verifyZeroInteractions(driverMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onJobFinished_notRecurringContentJob_sendResult() {
|
||||
jobInvocationBuilder.setTrigger(
|
||||
Trigger.contentUriTrigger(Arrays.asList(new ObservedUri(Contacts.CONTENT_URI, 0))));
|
||||
|
||||
JobInvocation jobInvocation = receiver
|
||||
.prepareJob(callbackMock, getBundleForContentJobExecution());
|
||||
|
||||
receiver.onJobFinished(jobInvocation, JobService.RESULT_SUCCESS);
|
||||
verify(callbackMock).jobFinished(JobService.RESULT_SUCCESS);
|
||||
verifyZeroInteractions(driverMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onJobFinished_successRecurringContentJob_reschedule() {
|
||||
JobInvocation jobInvocation = receiver
|
||||
.prepareJob(callbackMock, getBundleForContentJobExecutionRecurring());
|
||||
|
||||
receiver.onJobFinished(jobInvocation, JobService.RESULT_SUCCESS);
|
||||
|
||||
verify(driverMock).schedule(jobArgumentCaptor.capture());
|
||||
|
||||
// No need to callback when job finished.
|
||||
// Reschedule request is treated as two events: completion of old job and scheduling of new
|
||||
// job with the same parameters.
|
||||
verifyZeroInteractions(callbackMock);
|
||||
|
||||
Job rescheduledJob = jobArgumentCaptor.getValue();
|
||||
TestUtil.assertJobsEqual(jobInvocation, rescheduledJob);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onJobFinished_failWithRetryRecurringContentJob_sendResult() {
|
||||
JobInvocation jobInvocation = receiver
|
||||
.prepareJob(callbackMock, getBundleForContentJobExecutionRecurring());
|
||||
|
||||
receiver.onJobFinished(jobInvocation, JobService.RESULT_FAIL_RETRY);
|
||||
|
||||
// If a job finishes with RESULT_FAIL_RETRY we don't need to send a reschedule request.
|
||||
// Rescheduling will erase previously triggered URIs.
|
||||
verify(callbackMock).jobFinished(JobService.RESULT_FAIL_RETRY);
|
||||
|
||||
verifyZeroInteractions(driverMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareJob() {
|
||||
Intent intent = new Intent();
|
||||
|
||||
Bundle encode = encodeContentUriJob(getContentUriTrigger(), jobCoder);
|
||||
intent.putExtra(GooglePlayJobWriter.REQUEST_PARAM_EXTRAS, encode);
|
||||
|
||||
Parcel container = Parcel.obtain();
|
||||
container.writeStrongBinder(new Binder());
|
||||
PendingCallback pcb = new PendingCallback(container);
|
||||
intent.putExtra("callback", pcb);
|
||||
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
uris.add(ContactsContract.AUTHORITY_URI);
|
||||
uris.add(Media.EXTERNAL_CONTENT_URI);
|
||||
intent.putParcelableArrayListExtra(BundleProtocol.PACKED_PARAM_TRIGGERED_URIS, uris);
|
||||
|
||||
JobInvocation jobInvocation = receiver.prepareJob(intent);
|
||||
assertEquals(jobInvocation.getTriggerReason().getTriggeredContentUris(), uris);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareJob_messenger() {
|
||||
JobInvocation jobInvocation = receiver.prepareJob(callbackMock, new Bundle());
|
||||
assertNull(jobInvocation);
|
||||
verify(callbackMock).jobFinished(JobService.RESULT_FAIL_NORETRY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareJob_messenger_noExtras() {
|
||||
Bundle bundle = getBundleForContentJobExecution();
|
||||
|
||||
JobInvocation jobInvocation = receiver.prepareJob(callbackMock, bundle);
|
||||
assertEquals(jobInvocation.getTriggerReason().getTriggeredContentUris(), triggeredUris);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Bundle getBundleForContentJobExecution() {
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
Bundle encode = encodeContentUriJob(getContentUriTrigger(), jobCoder);
|
||||
bundle.putBundle(GooglePlayJobWriter.REQUEST_PARAM_EXTRAS, encode);
|
||||
|
||||
bundle.putParcelableArrayList(BundleProtocol.PACKED_PARAM_TRIGGERED_URIS, triggeredUris);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Bundle getBundleForContentJobExecutionRecurring() {
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
Bundle encode = encodeRecurringContentUriJob(getContentUriTrigger(), jobCoder);
|
||||
bundle.putBundle(GooglePlayJobWriter.REQUEST_PARAM_EXTRAS, encode);
|
||||
|
||||
bundle.putParcelableArrayList(BundleProtocol.PACKED_PARAM_TRIGGERED_URIS, triggeredUris);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBind() {
|
||||
Intent intent = new Intent(GooglePlayReceiver.ACTION_EXECUTE);
|
||||
IBinder binderA = receiver.onBind(intent);
|
||||
IBinder binderB = receiver.onBind(intent);
|
||||
|
||||
assertEquals(binderA, binderB);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBind_nullIntent() {
|
||||
IBinder binder = receiver.onBind(null);
|
||||
assertNull(binder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBind_wrongAction() {
|
||||
Intent intent = new Intent("test");
|
||||
IBinder binder = receiver.onBind(intent);
|
||||
assertNull(binder);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = VERSION_CODES.KITKAT)
|
||||
public void onBind_wrongBuild() {
|
||||
Intent intent = new Intent(GooglePlayReceiver.ACTION_EXECUTE);
|
||||
IBinder binder = receiver.onBind(intent);
|
||||
assertNull(binder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStartCommand_nullIntent() {
|
||||
assertResultWasStartNotSticky(receiver.onStartCommand(null, 0, 101));
|
||||
verify(receiver).stopSelf(101);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStartCommand_initAction() {
|
||||
Intent initIntent = new Intent("com.google.android.gms.gcm.SERVICE_ACTION_INITIALIZE");
|
||||
assertResultWasStartNotSticky(receiver.onStartCommand(initIntent, 0, 101));
|
||||
verify(receiver).stopSelf(101);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStartCommand_unknownAction() {
|
||||
Intent unknownIntent = new Intent("com.example.foo.bar");
|
||||
assertResultWasStartNotSticky(receiver.onStartCommand(unknownIntent, 0, 101));
|
||||
assertResultWasStartNotSticky(receiver.onStartCommand(unknownIntent, 0, 102));
|
||||
assertResultWasStartNotSticky(receiver.onStartCommand(unknownIntent, 0, 103));
|
||||
|
||||
InOrder inOrder = inOrder(receiver);
|
||||
inOrder.verify(receiver).stopSelf(101);
|
||||
inOrder.verify(receiver).stopSelf(102);
|
||||
inOrder.verify(receiver).stopSelf(103);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStartCommand_executeActionWithEmptyExtras() {
|
||||
Intent execIntent = new Intent("com.google.android.gms.gcm.ACTION_TASK_READY");
|
||||
assertResultWasStartNotSticky(receiver.onStartCommand(execIntent, 0, 101));
|
||||
verify(receiver).stopSelf(101);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStartCommand_executeAction() {
|
||||
JobInvocation job = new JobInvocation.Builder()
|
||||
.setTag("tag")
|
||||
.setService("com.example.foo.FooService")
|
||||
.setTrigger(Trigger.NOW)
|
||||
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
|
||||
.setLifetime(Lifetime.UNTIL_NEXT_BOOT)
|
||||
.setConstraints(new int[]{Constraint.DEVICE_IDLE})
|
||||
.build();
|
||||
|
||||
Intent execIntent = new Intent("com.google.android.gms.gcm.ACTION_TASK_READY")
|
||||
.putExtra("extras", new JobCoder(BundleProtocol.PACKED_PARAM_BUNDLE_PREFIX, true)
|
||||
.encode(job, new Bundle()))
|
||||
.putExtra("callback", new InspectableBinder().toPendingCallback());
|
||||
|
||||
when(executionDelegatorMock.executeJob(any(JobInvocation.class))).thenReturn(true);
|
||||
|
||||
assertResultWasStartNotSticky(receiver.onStartCommand(execIntent, 0, 101));
|
||||
|
||||
verify(receiver, never()).stopSelf(anyInt());
|
||||
verify(executionDelegatorMock).executeJob(any(JobInvocation.class));
|
||||
|
||||
receiver.onJobFinished(job, JobService.RESULT_SUCCESS);
|
||||
|
||||
verify(receiver).stopSelf(101);
|
||||
}
|
||||
|
||||
private void assertResultWasStartNotSticky(int result) {
|
||||
assertEquals(
|
||||
"Result for onStartCommand wasn't START_NOT_STICKY", Service.START_NOT_STICKY, result);
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23)
|
||||
public class ImmediateTriggerTest {
|
||||
/**
|
||||
* Code coverage.
|
||||
*/
|
||||
@Test
|
||||
public void testPrivateConstructor() throws Exception {
|
||||
TestUtil.assertHasSinglePrivateConstructor(JobTrigger.ImmediateTrigger.class);
|
||||
}
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23)
|
||||
public class JobBuilderTest {
|
||||
private static final int[] ALL_LIFETIMES = {Lifetime.UNTIL_NEXT_BOOT, Lifetime.FOREVER};
|
||||
|
||||
private Job.Builder mBuilder;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mBuilder = TestUtil.getBuilderWithNoopValidator();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddConstraints() {
|
||||
mBuilder.setConstraints()
|
||||
.addConstraint(Constraint.DEVICE_CHARGING)
|
||||
.addConstraint(Constraint.ON_UNMETERED_NETWORK);
|
||||
|
||||
int[] expected = {Constraint.DEVICE_CHARGING, Constraint.ON_UNMETERED_NETWORK};
|
||||
|
||||
assertEquals(Constraint.compact(expected), Constraint.compact(mBuilder.getConstraints()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetLifetime() {
|
||||
for (int lifetime : ALL_LIFETIMES) {
|
||||
mBuilder.setLifetime(lifetime);
|
||||
assertEquals(lifetime, mBuilder.getLifetime());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetShouldReplaceCurrent() {
|
||||
for (boolean replace : new boolean[]{true, false}) {
|
||||
mBuilder.setReplaceCurrent(replace);
|
||||
assertEquals(replace, mBuilder.shouldReplaceCurrent());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,158 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static com.firebase.jobdispatcher.TestUtil.encodeContentUriJob;
|
||||
import static com.firebase.jobdispatcher.TestUtil.getContentUriTrigger;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.MediaStore.Images.Media;
|
||||
import com.firebase.jobdispatcher.Job.Builder;
|
||||
import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger;
|
||||
import java.util.ArrayList;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23)
|
||||
public class JobCoderTest {
|
||||
private final JobCoder mCoder = new JobCoder(PREFIX, true);
|
||||
private static final String PREFIX = "prefix";
|
||||
private Builder mBuilder;
|
||||
|
||||
private static Builder setValidBuilderDefaults(Builder mBuilder) {
|
||||
return mBuilder
|
||||
.setTag("tag")
|
||||
.setTrigger(Trigger.NOW)
|
||||
.setService(TestJobService.class)
|
||||
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mBuilder = TestUtil.getBuilderWithNoopValidator();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCodingIsLossless() {
|
||||
for (JobParameters input : TestUtil.getJobCombinations(mBuilder)) {
|
||||
|
||||
JobParameters output = mCoder.decode(mCoder.encode(input, input.getExtras())).build();
|
||||
|
||||
TestUtil.assertJobsEqual(input, output);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testEncode_throwsOnNullBundle() {
|
||||
mCoder.encode(mBuilder.build(), null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testDecode_throwsOnNullBundle() {
|
||||
mCoder.decode(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecode_failsWhenMissingFields() {
|
||||
assertNull("Expected null tag to cause decoding to fail",
|
||||
mCoder.decode(mCoder.encode(
|
||||
setValidBuilderDefaults(mBuilder).setTag(null).build(),
|
||||
new Bundle())));
|
||||
|
||||
assertNull("Expected null service to cause decoding to fail",
|
||||
mCoder.decode(mCoder.encode(
|
||||
setValidBuilderDefaults(mBuilder).setService(null).build(),
|
||||
new Bundle())));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testDecode_failsUnsupportedTrigger() {
|
||||
mCoder.decode(mCoder.encode(setValidBuilderDefaults(mBuilder).setTrigger(null).build(),
|
||||
new Bundle()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecode_ignoresMissingRetryStrategy() {
|
||||
assertNotNull("Expected null retry strategy to cause decode to use a default",
|
||||
mCoder.decode(mCoder.encode(
|
||||
setValidBuilderDefaults(mBuilder).setRetryStrategy(null).build(),
|
||||
new Bundle())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encode_contentUriTrigger() {
|
||||
Bundle encode = TestUtil.encodeContentUriJob(TestUtil.getContentUriTrigger(), mCoder);
|
||||
int triggerType = encode.getInt(PREFIX + BundleProtocol.PACKED_PARAM_TRIGGER_TYPE);
|
||||
assertEquals("Trigger type", BundleProtocol.TRIGGER_TYPE_CONTENT_URI, triggerType);
|
||||
|
||||
String json = encode.getString(PREFIX + BundleProtocol.PACKED_PARAM_OBSERVED_URI);
|
||||
String expectedJson = "{\"uri_flags\":[1,0],\"uris\":[\"content:\\/\\/com.android.contacts"
|
||||
+ "\",\"content:\\/\\/media\\/external\\/images\\/media\"]}";
|
||||
assertEquals("Json trigger", expectedJson, json);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decode_contentUriTrigger() {
|
||||
ContentUriTrigger contentUriTrigger = TestUtil.getContentUriTrigger();
|
||||
Bundle bundle = TestUtil.encodeContentUriJob(contentUriTrigger, mCoder);
|
||||
JobInvocation decode = mCoder.decode(bundle).build();
|
||||
ContentUriTrigger trigger = (ContentUriTrigger) decode.getTrigger();
|
||||
assertEquals(contentUriTrigger.getUris(), trigger.getUris());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decode_addBundleAsExtras() {
|
||||
ContentUriTrigger contentUriTrigger = TestUtil.getContentUriTrigger();
|
||||
Bundle bundle = TestUtil.encodeContentUriJob(contentUriTrigger, mCoder);
|
||||
bundle.putString("test_key", "test_value");
|
||||
JobInvocation decode = mCoder.decode(bundle).build();
|
||||
assertEquals("test_value", decode.getExtras().getString("test_key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeIntentBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
ContentUriTrigger uriTrigger = getContentUriTrigger();
|
||||
Bundle encode = encodeContentUriJob(uriTrigger, mCoder);
|
||||
bundle.putBundle(GooglePlayJobWriter.REQUEST_PARAM_EXTRAS, encode);
|
||||
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
uris.add(ContactsContract.AUTHORITY_URI);
|
||||
uris.add(Media.EXTERNAL_CONTENT_URI);
|
||||
bundle.putParcelableArrayList(BundleProtocol.PACKED_PARAM_TRIGGERED_URIS, uris);
|
||||
|
||||
JobInvocation jobInvocation = mCoder.decodeIntentBundle(bundle);
|
||||
|
||||
assertEquals(uris, jobInvocation.getTriggerReason().getTriggeredContentUris());
|
||||
assertEquals("TAG", jobInvocation.getTag());
|
||||
assertEquals(uriTrigger.getUris(), ((ContentUriTrigger) jobInvocation.getTrigger())
|
||||
.getUris());
|
||||
assertEquals(TestJobService.class.getName(), jobInvocation.getService());
|
||||
assertEquals(RetryStrategy.DEFAULT_EXPONENTIAL.getPolicy(),
|
||||
jobInvocation.getRetryStrategy().getPolicy());
|
||||
}
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.os.Bundle;
|
||||
import com.firebase.jobdispatcher.JobInvocation.Builder;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23)
|
||||
public class JobInvocationTest {
|
||||
private Builder builder;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
builder = new Builder()
|
||||
.setTag("tag")
|
||||
.setService(TestJobService.class.getName())
|
||||
.setTrigger(Trigger.NOW);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
public void testShouldReplaceCurrent() throws Exception {
|
||||
assertTrue("Expected shouldReplaceCurrent() to return value passed in constructor",
|
||||
builder.setReplaceCurrent(true).build().shouldReplaceCurrent());
|
||||
assertFalse("Expected shouldReplaceCurrent() to return value passed in constructor",
|
||||
builder.setReplaceCurrent(false).build().shouldReplaceCurrent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extras() throws Exception {
|
||||
assertNotNull(builder.build().getExtras());
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putLong("test", 1L);
|
||||
Bundle extras = builder.addExtras(bundle).build().getExtras();
|
||||
assertEquals(1, extras.size());
|
||||
assertEquals(1L, extras.getLong("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contract_hashCode_equals() {
|
||||
JobInvocation jobInvocation = builder.build();
|
||||
assertEquals(jobInvocation, builder.build());
|
||||
assertEquals(jobInvocation.hashCode(), builder.build().hashCode());
|
||||
JobInvocation jobInvocationNew = builder.setTag("new").build();
|
||||
assertNotEquals(jobInvocation, jobInvocationNew);
|
||||
assertNotEquals(jobInvocation.hashCode(), jobInvocationNew.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contract_hashCode_equals_triggerShouldBeIgnored() {
|
||||
JobInvocation jobInvocation = builder.build();
|
||||
JobInvocation periodic = builder.setTrigger(Trigger.executionWindow(0, 1)).build();
|
||||
assertEquals(jobInvocation, periodic);
|
||||
assertEquals(jobInvocation.hashCode(), periodic.hashCode());
|
||||
}
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import com.firebase.jobdispatcher.JobInvocation.Builder;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
/**
|
||||
* Test for {@link JobServiceConnection}.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23)
|
||||
public class JobServiceConnectionTest {
|
||||
|
||||
JobInvocation job = new Builder()
|
||||
.setTag("tag")
|
||||
.setService(TestJobService.class.getName())
|
||||
.setTrigger(Trigger.NOW)
|
||||
.build();
|
||||
|
||||
@Mock
|
||||
Message messageMock;
|
||||
@Mock
|
||||
JobService.LocalBinder binderMock;
|
||||
@Mock
|
||||
JobService jobServiceMock;
|
||||
|
||||
JobServiceConnection connection;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(binderMock.getService()).thenReturn(jobServiceMock);
|
||||
connection = new JobServiceConnection(job, messageMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fullConnectionCycle() {
|
||||
assertFalse(connection.isBound());
|
||||
|
||||
connection.onServiceConnected(null, binderMock);
|
||||
verify(jobServiceMock).start(job, messageMock);
|
||||
assertTrue(connection.isBound());
|
||||
|
||||
connection.onStop();
|
||||
verify(jobServiceMock).stop(job);
|
||||
assertTrue(connection.isBound());
|
||||
|
||||
connection.onServiceDisconnected(null);
|
||||
assertFalse(connection.isBound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onServiceConnected_shouldNotSendExecutionRequestTwice() {
|
||||
assertFalse(connection.isBound());
|
||||
|
||||
connection.onServiceConnected(null, binderMock);
|
||||
verify(jobServiceMock).start(job, messageMock);
|
||||
assertTrue(connection.isBound());
|
||||
reset(jobServiceMock);
|
||||
|
||||
connection.onServiceConnected(null, binderMock);
|
||||
verify(jobServiceMock, never()).start(job, messageMock); // start should not be called again
|
||||
|
||||
connection.onStop();
|
||||
verify(jobServiceMock).stop(job);
|
||||
assertTrue(connection.isBound());
|
||||
|
||||
connection.onServiceDisconnected(null);
|
||||
assertFalse(connection.isBound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stopOnUnboundConnection() {
|
||||
assertFalse(connection.isBound());
|
||||
connection.onStop();
|
||||
verify(jobServiceMock, never()).onStopJob(job);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onServiceConnectedWrongBinder() {
|
||||
IBinder binder = mock(IBinder.class);
|
||||
connection.onServiceConnected(null, binder);
|
||||
assertFalse(connection.isBound());
|
||||
}
|
||||
}
|
||||
@ -1,346 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Parcel;
|
||||
import com.firebase.jobdispatcher.JobInvocation.Builder;
|
||||
import com.google.android.gms.gcm.PendingCallback;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23)
|
||||
public class JobServiceTest {
|
||||
private static CountDownLatch countDownLatch;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
countDownLatch = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartCommand_handlesNullIntent() throws Exception {
|
||||
JobService service = spy(new ExampleJobService());
|
||||
int startId = 7;
|
||||
|
||||
try {
|
||||
service.onStartCommand(null, 0, startId);
|
||||
|
||||
verify(service).stopSelf(startId);
|
||||
} catch (NullPointerException npe) {
|
||||
fail("Unexpected NullPointerException after calling onStartCommand with a null Intent.");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartCommand_handlesNullAction() throws Exception {
|
||||
JobService service = spy(new ExampleJobService());
|
||||
int startId = 7;
|
||||
|
||||
Intent nullActionIntent = new Intent();
|
||||
service.onStartCommand(nullActionIntent, 0, startId);
|
||||
|
||||
verify(service).stopSelf(startId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartCommand_handlesEmptyAction() throws Exception {
|
||||
JobService service = spy(new ExampleJobService());
|
||||
int startId = 7;
|
||||
|
||||
Intent emptyActionIntent = new Intent("");
|
||||
service.onStartCommand(emptyActionIntent, 0, startId);
|
||||
|
||||
verify(service).stopSelf(startId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartCommand_handlesUnknownAction() throws Exception {
|
||||
JobService service = spy(new ExampleJobService());
|
||||
int startId = 7;
|
||||
|
||||
Intent emptyActionIntent = new Intent("foo.bar.baz");
|
||||
service.onStartCommand(emptyActionIntent, 0, startId);
|
||||
|
||||
verify(service).stopSelf(startId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartCommand_handlesStartJob_nullData() {
|
||||
JobService service = spy(new ExampleJobService());
|
||||
int startId = 7;
|
||||
|
||||
Intent executeJobIntent = new Intent(JobService.ACTION_EXECUTE);
|
||||
service.onStartCommand(executeJobIntent, 0, startId);
|
||||
|
||||
verify(service).stopSelf(startId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartCommand_handlesStartJob_noTag() {
|
||||
JobService service = spy(new ExampleJobService());
|
||||
int startId = 7;
|
||||
|
||||
Intent executeJobIntent = new Intent(JobService.ACTION_EXECUTE);
|
||||
Parcel p = Parcel.obtain();
|
||||
p.writeStrongBinder(mock(IBinder.class));
|
||||
executeJobIntent.putExtra("callback", new PendingCallback(p));
|
||||
|
||||
service.onStartCommand(executeJobIntent, 0, startId);
|
||||
|
||||
verify(service).stopSelf(startId);
|
||||
|
||||
p.recycle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartCommand_handlesStartJob_noCallback() {
|
||||
JobService service = spy(new ExampleJobService());
|
||||
int startId = 7;
|
||||
|
||||
Intent executeJobIntent = new Intent(JobService.ACTION_EXECUTE);
|
||||
executeJobIntent.putExtra("tag", "foobar");
|
||||
|
||||
service.onStartCommand(executeJobIntent, 0, startId);
|
||||
|
||||
verify(service).stopSelf(startId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartCommand_handlesStartJob_validRequest() throws InterruptedException {
|
||||
JobService service = spy(new ExampleJobService());
|
||||
|
||||
HandlerThread ht = new HandlerThread("handler");
|
||||
ht.start();
|
||||
Handler h = new Handler(ht.getLooper());
|
||||
|
||||
Intent executeJobIntent = new Intent(JobService.ACTION_EXECUTE);
|
||||
|
||||
Job jobSpec = TestUtil.getBuilderWithNoopValidator()
|
||||
.setTag("tag")
|
||||
.setService(ExampleJobService.class)
|
||||
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
|
||||
.setTrigger(Trigger.NOW)
|
||||
.setLifetime(Lifetime.FOREVER)
|
||||
.build();
|
||||
|
||||
countDownLatch = new CountDownLatch(1);
|
||||
|
||||
((JobService.LocalBinder) service.onBind(executeJobIntent))
|
||||
.getService()
|
||||
.start(jobSpec, h.obtainMessage(ExecutionDelegator.JOB_FINISHED, jobSpec));
|
||||
|
||||
assertTrue("Expected job to run to completion", countDownLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartCommand_handlesStartJob_doNotStartRunningJobAgain() {
|
||||
StoppableJobService service = new StoppableJobService(false);
|
||||
|
||||
Job jobSpec = TestUtil.getBuilderWithNoopValidator()
|
||||
.setTag("tag")
|
||||
.setService(StoppableJobService.class)
|
||||
.setTrigger(Trigger.NOW)
|
||||
.build();
|
||||
|
||||
((JobService.LocalBinder) service.onBind(null)).getService().start(jobSpec, null);
|
||||
((JobService.LocalBinder) service.onBind(null)).getService().start(jobSpec, null);
|
||||
|
||||
assertEquals(1, service.getNumberOfExecutionRequestsReceived());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stop_noCallback_finished() {
|
||||
JobService service = spy(new StoppableJobService(false));
|
||||
JobInvocation job = new Builder()
|
||||
.setTag("Tag")
|
||||
.setTrigger(Trigger.NOW)
|
||||
.setService(StoppableJobService.class.getName())
|
||||
.build();
|
||||
service.stop(job);
|
||||
verify(service, never()).onStopJob(job);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stop_withCallback_retry() {
|
||||
JobService service = spy(new StoppableJobService(false));
|
||||
|
||||
JobInvocation job = new Builder()
|
||||
.setTag("Tag")
|
||||
.setTrigger(Trigger.NOW)
|
||||
.setService(StoppableJobService.class.getName())
|
||||
.build();
|
||||
|
||||
Handler handlerMock = mock(Handler.class);
|
||||
Message message = Message.obtain(handlerMock);
|
||||
service.start(job, message);
|
||||
|
||||
service.stop(job);
|
||||
verify(service).onStopJob(job);
|
||||
verify(handlerMock).sendMessage(message);
|
||||
assertEquals(message.arg1, JobService.RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stop_withCallback_done() {
|
||||
JobService service = spy(new StoppableJobService(true));
|
||||
|
||||
JobInvocation job = new Builder()
|
||||
.setTag("Tag")
|
||||
.setTrigger(Trigger.NOW)
|
||||
.setService(StoppableJobService.class.getName())
|
||||
.build();
|
||||
|
||||
Handler handlerMock = mock(Handler.class);
|
||||
Message message = Message.obtain(handlerMock);
|
||||
service.start(job, message);
|
||||
|
||||
service.stop(job);
|
||||
verify(service).onStopJob(job);
|
||||
verify(handlerMock).sendMessage(message);
|
||||
assertEquals(message.arg1, JobService.RESULT_FAIL_RETRY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStartJob_jobFinishedReschedule() {
|
||||
// Verify that a retry request from within onStartJob will cause the retry result to be sent
|
||||
// to the bouncer service's handler, regardless of what value is ultimately returned from
|
||||
// onStartJob.
|
||||
JobService reschedulingService = new JobService() {
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters job) {
|
||||
// Reschedules job.
|
||||
jobFinished(job, true /* retry this job */);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters job) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Job jobSpec = TestUtil.getBuilderWithNoopValidator()
|
||||
.setTag("tag")
|
||||
.setService(reschedulingService.getClass())
|
||||
.setTrigger(Trigger.NOW)
|
||||
.build();
|
||||
Handler mock = mock(Handler.class);
|
||||
Message message = new Message();
|
||||
message.setTarget(mock);
|
||||
reschedulingService.start(jobSpec, message);
|
||||
|
||||
verify(mock).sendMessage(message);
|
||||
assertEquals(message.arg1, JobService.RESULT_FAIL_RETRY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStartJob_jobFinishedNotReschedule() {
|
||||
// Verify that a termination request from within onStartJob will cause the result to be sent
|
||||
// to the bouncer service's handler, regardless of what value is ultimately returned from
|
||||
// onStartJob.
|
||||
JobService reschedulingService = new JobService() {
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters job) {
|
||||
jobFinished(job, false /* don't retry this job */);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters job) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Job jobSpec = TestUtil.getBuilderWithNoopValidator()
|
||||
.setTag("tag")
|
||||
.setService(reschedulingService.getClass())
|
||||
.setTrigger(Trigger.NOW)
|
||||
.build();
|
||||
Handler mock = mock(Handler.class);
|
||||
Message message = new Message();
|
||||
message.setTarget(mock);
|
||||
reschedulingService.start(jobSpec, message);
|
||||
|
||||
verify(mock).sendMessage(message);
|
||||
assertEquals(message.arg1, JobService.RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
public static class ExampleJobService extends JobService {
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters job) {
|
||||
countDownLatch.countDown();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters job) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class StoppableJobService extends JobService {
|
||||
|
||||
private final boolean shouldReschedule;
|
||||
|
||||
public int getNumberOfExecutionRequestsReceived() {
|
||||
return amountOfExecutionRequestReceived.get();
|
||||
}
|
||||
|
||||
private final AtomicInteger amountOfExecutionRequestReceived = new AtomicInteger();
|
||||
|
||||
public StoppableJobService(boolean shouldReschedule) {
|
||||
this.shouldReschedule = shouldReschedule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters job) {
|
||||
amountOfExecutionRequestReceived.incrementAndGet();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters job) {
|
||||
return shouldReschedule;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,187 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23)
|
||||
public class ValidationEnforcerTest {
|
||||
private static final List<String> ERROR_LIST = Collections.singletonList("error: foo");
|
||||
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private JobValidator mValidator;
|
||||
|
||||
@Mock
|
||||
private JobParameters mMockJobParameters;
|
||||
|
||||
@Mock
|
||||
private JobTrigger mMockTrigger;
|
||||
|
||||
private ValidationEnforcer mEnforcer;
|
||||
private RetryStrategy mRetryStrategy = RetryStrategy.DEFAULT_EXPONENTIAL;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mEnforcer = new ValidationEnforcer(mValidator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidate_retryStrategy() throws Exception {
|
||||
mEnforcer.validate(mRetryStrategy);
|
||||
verify(mValidator).validate(mRetryStrategy);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidate_jobSpec() throws Exception {
|
||||
mEnforcer.validate(mMockJobParameters);
|
||||
verify(mValidator).validate(mMockJobParameters);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidate_trigger() throws Exception {
|
||||
mEnforcer.validate(mMockTrigger);
|
||||
verify(mValidator).validate(mMockTrigger);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValid_retryStrategy_invalid() throws Exception {
|
||||
when(mValidator.validate(mRetryStrategy))
|
||||
.thenReturn(Collections.singletonList("error: foo"));
|
||||
|
||||
assertFalse("isValid", mEnforcer.isValid(mRetryStrategy));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValid_retryStrategy_valid() throws Exception {
|
||||
when(mValidator.validate(mRetryStrategy)).thenReturn(null);
|
||||
|
||||
assertTrue("isValid", mEnforcer.isValid(mRetryStrategy));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValid_trigger_invalid() throws Exception {
|
||||
when(mValidator.validate(mMockTrigger))
|
||||
.thenReturn(Collections.singletonList("error: foo"));
|
||||
|
||||
assertFalse("isValid", mEnforcer.isValid(mMockTrigger));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValid_trigger_valid() throws Exception {
|
||||
when(mValidator.validate(mMockTrigger)).thenReturn(null);
|
||||
|
||||
assertTrue("isValid", mEnforcer.isValid(mMockTrigger));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValid_jobSpec_invalid() throws Exception {
|
||||
when(mValidator.validate(mMockJobParameters)).thenReturn(ERROR_LIST);
|
||||
|
||||
assertFalse("isValid", mEnforcer.isValid(mMockJobParameters));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValid_jobSpec_valid() throws Exception {
|
||||
when(mValidator.validate(mMockJobParameters)).thenReturn(null);
|
||||
|
||||
assertTrue("isValid", mEnforcer.isValid(mMockJobParameters));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnsureValid_retryStrategy_valid() throws Exception {
|
||||
when(mValidator.validate(mRetryStrategy)).thenReturn(null);
|
||||
|
||||
mEnforcer.ensureValid(mRetryStrategy);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnsureValid_trigger_valid() throws Exception {
|
||||
when(mValidator.validate(mMockTrigger)).thenReturn(null);
|
||||
|
||||
mEnforcer.ensureValid(mMockTrigger);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnsureValid_jobSpec_valid() throws Exception {
|
||||
when(mValidator.validate(mMockJobParameters)).thenReturn(null);
|
||||
|
||||
mEnforcer.ensureValid(mMockJobParameters);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnsureValid_retryStrategy_invalid() throws Exception {
|
||||
expectedException.expect(ValidationEnforcer.ValidationException.class);
|
||||
|
||||
when(mValidator.validate(mRetryStrategy)).thenReturn(ERROR_LIST);
|
||||
mEnforcer.ensureValid(mRetryStrategy);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnsureValid_trigger_invalid() throws Exception {
|
||||
expectedException.expect(ValidationEnforcer.ValidationException.class);
|
||||
|
||||
when(mValidator.validate(mMockTrigger)).thenReturn(ERROR_LIST);
|
||||
mEnforcer.ensureValid(mMockTrigger);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnsureValid_jobSpec_invalid() throws Exception {
|
||||
expectedException.expect(ValidationEnforcer.ValidationException.class);
|
||||
|
||||
when(mValidator.validate(mMockJobParameters)).thenReturn(ERROR_LIST);
|
||||
mEnforcer.ensureValid(mMockJobParameters);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidationMessages() throws Exception {
|
||||
when(mValidator.validate(mMockJobParameters)).thenReturn(ERROR_LIST);
|
||||
|
||||
try {
|
||||
mEnforcer.ensureValid(mMockJobParameters);
|
||||
|
||||
fail("Expected ensureValid to fail");
|
||||
} catch (ValidationEnforcer.ValidationException ve) {
|
||||
assertEquals("Expected ValidationException to have 1 error message",
|
||||
1,
|
||||
ve.getErrors().size());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A very simple Validator that thinks that everything is ok. Used for testing.
|
||||
*/
|
||||
class NoopJobValidator implements JobValidator {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> validate(JobParameters job) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> validate(JobTrigger trigger) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> validate(RetryStrategy retryStrategy) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
/** A very simple JobService that can be configured for individual tests. */
|
||||
public class TestJobService extends JobService {
|
||||
|
||||
public interface JobServiceProxy {
|
||||
boolean onStartJob(JobParameters job);
|
||||
|
||||
boolean onStopJob(JobParameters job);
|
||||
}
|
||||
|
||||
public static final JobServiceProxy NOOP_PROXY =
|
||||
new JobServiceProxy() {
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters job) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters job) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Object lock = new Object();
|
||||
|
||||
// GuardedBy("lock")
|
||||
private static JobServiceProxy currentProxy = NOOP_PROXY;
|
||||
|
||||
public static void setProxy(JobServiceProxy proxy) {
|
||||
synchronized (lock) {
|
||||
currentProxy = proxy;
|
||||
}
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
synchronized (lock) {
|
||||
currentProxy = NOOP_PROXY;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters job) {
|
||||
synchronized (lock) {
|
||||
return currentProxy.onStartJob(job);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters job) {
|
||||
synchronized (lock) {
|
||||
return currentProxy.onStopJob(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,375 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.firebase.jobdispatcher;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.MediaStore.Images.Media;
|
||||
import android.support.annotation.NonNull;
|
||||
import com.firebase.jobdispatcher.Job.Builder;
|
||||
import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger;
|
||||
import com.firebase.jobdispatcher.ObservedUri.Flags;
|
||||
import com.google.android.gms.gcm.PendingCallback;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides common utilities helpful for testing.
|
||||
*/
|
||||
public class TestUtil {
|
||||
|
||||
private static final String TAG = "TAG";
|
||||
private static final String[] TAG_COMBINATIONS = {"tag", "foobar", "fooooooo", "bz", "100"};
|
||||
|
||||
private static final int[] LIFETIME_COMBINATIONS = {
|
||||
Lifetime.UNTIL_NEXT_BOOT,
|
||||
Lifetime.FOREVER};
|
||||
|
||||
private static final JobTrigger[] TRIGGER_COMBINATIONS = {
|
||||
Trigger.executionWindow(0, 30),
|
||||
Trigger.executionWindow(300, 600),
|
||||
Trigger.executionWindow(86400, 86400 * 2),
|
||||
Trigger.NOW,
|
||||
Trigger.contentUriTrigger(
|
||||
Arrays.asList(new ObservedUri(ContactsContract.AUTHORITY_URI, 0))),
|
||||
Trigger.contentUriTrigger(Arrays.asList(new ObservedUri(ContactsContract.AUTHORITY_URI, 0),
|
||||
new ObservedUri(ContactsContract.AUTHORITY_URI, Flags.FLAG_NOTIFY_FOR_DESCENDANTS)))
|
||||
};
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final List<Class<TestJobService>> SERVICE_COMBINATIONS =
|
||||
Arrays.asList(TestJobService.class);
|
||||
|
||||
private static final RetryStrategy[] RETRY_STRATEGY_COMBINATIONS = {
|
||||
RetryStrategy.DEFAULT_LINEAR,
|
||||
new RetryStrategy(RetryStrategy.RETRY_POLICY_LINEAR, 60, 300),
|
||||
RetryStrategy.DEFAULT_EXPONENTIAL,
|
||||
new RetryStrategy(RetryStrategy.RETRY_POLICY_EXPONENTIAL, 300, 600),
|
||||
};
|
||||
|
||||
public static void assertHasSinglePrivateConstructor(Class<?> cls) throws Exception {
|
||||
Constructor<?>[] constructors = cls.getDeclaredConstructors();
|
||||
assertEquals("expected number of constructors to be == 1", 1, constructors.length);
|
||||
|
||||
Constructor<?> constructor = constructors[0];
|
||||
assertFalse("expected constructor to be inaccessible", constructor.isAccessible());
|
||||
|
||||
constructor.setAccessible(true);
|
||||
constructor.newInstance();
|
||||
}
|
||||
|
||||
static List<List<Integer>> getAllConstraintCombinations() {
|
||||
List<List<Integer>> combos = new LinkedList<>();
|
||||
|
||||
combos.add(Collections.<Integer>emptyList());
|
||||
for (Integer cur : Constraint.ALL_CONSTRAINTS) {
|
||||
for (int l = combos.size() - 1; l >= 0; l--) {
|
||||
List<Integer> oldCombo = combos.get(l);
|
||||
List<Integer> newCombo = Arrays.asList(new Integer[oldCombo.size() + 1]);
|
||||
|
||||
Collections.copy(newCombo, oldCombo);
|
||||
newCombo.set(oldCombo.size(), cur);
|
||||
combos.add(newCombo);
|
||||
}
|
||||
combos.add(Collections.singletonList(cur));
|
||||
}
|
||||
|
||||
return combos;
|
||||
}
|
||||
|
||||
static int[] toIntArray(List<Integer> list) {
|
||||
int[] input = new int[list.size()];
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
input[i] = list.get(i);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
static List<Job> getJobCombinations(Builder builder) {
|
||||
return getCombination(new JobBuilder(builder));
|
||||
}
|
||||
|
||||
static List<JobInvocation> getJobInvocationCombinations() {
|
||||
return getCombination(new JobInvocationBuilder());
|
||||
}
|
||||
|
||||
private static <T extends JobParameters> List<T> getCombination(
|
||||
JobParameterBuilder<T> buildJobParam) {
|
||||
|
||||
List<T> result = new ArrayList<>();
|
||||
for (String tag : TAG_COMBINATIONS) {
|
||||
for (List<Integer> constraintList : getAllConstraintCombinations()) {
|
||||
for (boolean recurring : new boolean[]{true, false}) {
|
||||
for (boolean replaceCurrent : new boolean[]{true, false}) {
|
||||
for (int lifetime : LIFETIME_COMBINATIONS) {
|
||||
for (JobTrigger trigger : TRIGGER_COMBINATIONS) {
|
||||
for (Class<TestJobService> service : SERVICE_COMBINATIONS) {
|
||||
for (Bundle extras : getBundleCombinations()) {
|
||||
for (RetryStrategy rs : RETRY_STRATEGY_COMBINATIONS) {
|
||||
result.add(buildJobParam.build(
|
||||
tag,
|
||||
replaceCurrent,
|
||||
constraintList,
|
||||
recurring,
|
||||
lifetime,
|
||||
trigger,
|
||||
service,
|
||||
extras,
|
||||
rs));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Bundle[] getBundleCombinations() {
|
||||
List<Bundle> bundles = new LinkedList<>();
|
||||
bundles.add(new Bundle());
|
||||
|
||||
Bundle b = new Bundle();
|
||||
b.putString("foo", "bar");
|
||||
b.putInt("bar", 1);
|
||||
b.putLong("baz", 3L);
|
||||
bundles.add(b);
|
||||
|
||||
return bundles.toArray(new Bundle[bundles.size()]);
|
||||
}
|
||||
|
||||
static void assertJobsEqual(JobParameters input, JobParameters output) {
|
||||
assertNotNull("input", input);
|
||||
assertNotNull("output", output);
|
||||
|
||||
assertEquals("isRecurring()", input.isRecurring(), output.isRecurring());
|
||||
assertEquals("shouldReplaceCurrent()",
|
||||
input.shouldReplaceCurrent(),
|
||||
output.shouldReplaceCurrent());
|
||||
assertEquals("getLifetime()", input.getLifetime(), output.getLifetime());
|
||||
assertEquals("getTag()", input.getTag(), output.getTag());
|
||||
assertEquals("getService()", input.getService(), output.getService());
|
||||
assertEquals("getConstraints()",
|
||||
Constraint.compact(input.getConstraints()),
|
||||
Constraint.compact(output.getConstraints()));
|
||||
|
||||
assertTriggersEqual(input.getTrigger(), output.getTrigger());
|
||||
assertBundlesEqual(input.getExtras(), output.getExtras());
|
||||
assertRetryStrategiesEqual(input.getRetryStrategy(), output.getRetryStrategy());
|
||||
}
|
||||
|
||||
static void assertRetryStrategiesEqual(RetryStrategy in, RetryStrategy out) {
|
||||
String prefix = "getRetryStrategy().";
|
||||
|
||||
assertEquals(prefix + "getPolicy()",
|
||||
in.getPolicy(), out.getPolicy());
|
||||
assertEquals(prefix + "getInitialBackoff()",
|
||||
in.getInitialBackoff(), out.getInitialBackoff());
|
||||
assertEquals(prefix + "getMaximumBackoff()",
|
||||
in.getMaximumBackoff(), out.getMaximumBackoff());
|
||||
}
|
||||
|
||||
static void assertBundlesEqual(Bundle inExtras, Bundle outExtras) {
|
||||
if (inExtras == null || outExtras == null) {
|
||||
assertNull(inExtras);
|
||||
assertNull(outExtras);
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals("getExtras().size()", inExtras.size(), outExtras.size());
|
||||
final Set<String> inKeys = inExtras.keySet();
|
||||
for (String key : inKeys) {
|
||||
assertTrue("getExtras().containsKey(\"" + key + "\")", outExtras.containsKey(key));
|
||||
assertEquals("getExtras().get(\"" + key + "\")", inExtras.get(key), outExtras.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
static void assertTriggersEqual(JobTrigger inTrigger, JobTrigger outTrigger) {
|
||||
assertEquals("", inTrigger.getClass(), outTrigger.getClass());
|
||||
|
||||
if (inTrigger instanceof JobTrigger.ExecutionWindowTrigger) {
|
||||
assertEquals("getTrigger().getWindowStart()",
|
||||
((JobTrigger.ExecutionWindowTrigger) inTrigger).getWindowStart(),
|
||||
((JobTrigger.ExecutionWindowTrigger) outTrigger).getWindowStart());
|
||||
assertEquals("getTrigger().getWindowEnd()",
|
||||
((JobTrigger.ExecutionWindowTrigger) inTrigger).getWindowEnd(),
|
||||
((JobTrigger.ExecutionWindowTrigger) outTrigger).getWindowEnd());
|
||||
} else if (inTrigger == Trigger.NOW) {
|
||||
assertEquals(inTrigger, outTrigger);
|
||||
} else if (inTrigger instanceof JobTrigger.ContentUriTrigger) {
|
||||
assertEquals("Collection of URIs",
|
||||
((ContentUriTrigger) inTrigger).getUris(),
|
||||
((ContentUriTrigger) outTrigger).getUris());
|
||||
} else {
|
||||
fail("Unknown Trigger class: " + inTrigger.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Builder getBuilderWithNoopValidator() {
|
||||
return new Builder(new ValidationEnforcer(new NoopJobValidator()));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static Bundle encodeContentUriJob(ContentUriTrigger trigger, JobCoder coder) {
|
||||
Job job = getBuilderWithNoopValidator()
|
||||
.setTag(TAG)
|
||||
.setTrigger(trigger)
|
||||
.setService(TestJobService.class)
|
||||
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
|
||||
.build();
|
||||
return coder.encode(job, new Bundle());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static Bundle encodeRecurringContentUriJob(ContentUriTrigger trigger, JobCoder coder) {
|
||||
Job job = getBuilderWithNoopValidator()
|
||||
.setTag(TAG)
|
||||
.setTrigger(trigger)
|
||||
.setService(TestJobService.class)
|
||||
.setReplaceCurrent(true)
|
||||
.setRecurring(true)
|
||||
.build();
|
||||
return coder.encode(job, new Bundle());
|
||||
}
|
||||
|
||||
static ContentUriTrigger getContentUriTrigger() {
|
||||
ObservedUri contactUri = new ObservedUri(
|
||||
ContactsContract.AUTHORITY_URI, Flags.FLAG_NOTIFY_FOR_DESCENDANTS);
|
||||
ObservedUri imageUri = new ObservedUri(Media.EXTERNAL_CONTENT_URI, 0);
|
||||
return Trigger.contentUriTrigger(Arrays.asList(contactUri, imageUri));
|
||||
}
|
||||
|
||||
public static class TransactionArguments {
|
||||
public final int code;
|
||||
public final Parcel data;
|
||||
public final int flags;
|
||||
|
||||
public TransactionArguments(int code, Parcel data, int flags) {
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
this.flags = flags;
|
||||
}
|
||||
}
|
||||
|
||||
public static class InspectableBinder extends Binder {
|
||||
private final List<TransactionArguments> transactionArguments = new LinkedList<>();
|
||||
|
||||
public InspectableBinder() {}
|
||||
|
||||
@Override
|
||||
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
|
||||
transactionArguments.add(new TransactionArguments(code, copyParcel(data), flags));
|
||||
return true;
|
||||
}
|
||||
|
||||
public PendingCallback toPendingCallback() {
|
||||
Parcel container = Parcel.obtain();
|
||||
try {
|
||||
container.writeStrongBinder(this);
|
||||
container.setDataPosition(0);
|
||||
return new PendingCallback(container);
|
||||
} finally {
|
||||
container.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private Parcel copyParcel(Parcel data) {
|
||||
Parcel clone = Parcel.obtain();
|
||||
clone.appendFrom(data, 0, data.dataSize());
|
||||
clone.setDataPosition(0);
|
||||
return clone;
|
||||
}
|
||||
|
||||
public List<TransactionArguments> getArguments() {
|
||||
return Collections.unmodifiableList(transactionArguments);
|
||||
}
|
||||
}
|
||||
|
||||
private static class JobInvocationBuilder implements
|
||||
JobParameterBuilder<JobInvocation> {
|
||||
|
||||
@Override
|
||||
public JobInvocation build(String tag, boolean replaceCurrent, List<Integer> constraintList,
|
||||
boolean recurring, int lifetime, JobTrigger trigger, Class<TestJobService> service,
|
||||
Bundle extras, RetryStrategy rs) {
|
||||
//noinspection WrongConstant
|
||||
return new JobInvocation.Builder()
|
||||
.setTag(tag)
|
||||
.setReplaceCurrent(replaceCurrent)
|
||||
.setRecurring(recurring)
|
||||
.setConstraints(toIntArray(constraintList))
|
||||
.setLifetime(lifetime)
|
||||
.setTrigger(trigger)
|
||||
.setService(service.getName())
|
||||
.addExtras(extras)
|
||||
.setRetryStrategy(rs)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static class JobBuilder implements JobParameterBuilder<Job> {
|
||||
|
||||
private final Builder builder;
|
||||
|
||||
public JobBuilder(Builder builder){
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Job build(String tag, boolean replaceCurrent, List<Integer> constraintList,
|
||||
boolean recurring, int lifetime, JobTrigger trigger, Class<TestJobService> service,
|
||||
Bundle extras, RetryStrategy rs) {
|
||||
//noinspection WrongConstant
|
||||
return builder
|
||||
.setTag(tag)
|
||||
.setReplaceCurrent(replaceCurrent)
|
||||
.setRecurring(recurring)
|
||||
.setConstraints(toIntArray(constraintList))
|
||||
.setLifetime(lifetime)
|
||||
.setTrigger(trigger)
|
||||
.setService(service)
|
||||
.setExtras(extras)
|
||||
.setRetryStrategy(rs)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private interface JobParameterBuilder<T extends JobParameters> {
|
||||
|
||||
T build(String tag, boolean replaceCurrent, List<Integer> constraintList, boolean recurring,
|
||||
int lifetime, JobTrigger trigger, Class<TestJobService> service, Bundle extras,
|
||||
RetryStrategy rs);
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
// Copyright 2016 Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package com.google.android.gms.gcm;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Keep;
|
||||
|
||||
/**
|
||||
* Parcelable class to wrap the binder we send to the client over IPC. Only included for the benefit
|
||||
* of tests.
|
||||
*/
|
||||
@Keep
|
||||
public final class PendingCallback implements Parcelable {
|
||||
public static final Creator<PendingCallback> CREATOR =
|
||||
new Creator<PendingCallback>() {
|
||||
@Override
|
||||
public PendingCallback createFromParcel(Parcel parcel) {
|
||||
return new PendingCallback(parcel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PendingCallback[] newArray(int i) {
|
||||
return new PendingCallback[i];
|
||||
}
|
||||
};
|
||||
private final IBinder mBinder;
|
||||
|
||||
public PendingCallback(Parcel in) {
|
||||
mBinder = in.readStrongBinder();
|
||||
}
|
||||
|
||||
public IBinder getIBinder() {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeStrongBinder(mBinder);
|
||||
}
|
||||
}
|
||||
@ -1 +1 @@
|
||||
include ':app', ':jobdispatcher'
|
||||
include ':app'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user