// 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.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; /** * JobCoder is a tool to encode and decode JobSpecs from Bundles. */ /* package */ final class JobCoder { private final boolean includeExtras; private final String prefix; 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); 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)); 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 { 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()); } }