// 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.IntDef; import android.support.annotation.VisibleForTesting; 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_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); } 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 { 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.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 {} }