/* * Copyright (C) 2014 SCVNGR, Inc. d/b/a LevelUp * * 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.scvngr.levelup.core.service; import android.app.IntentService; import android.content.Context; import android.content.Intent; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.LocalBroadcastManager; import com.scvngr.levelup.core.annotation.LevelUpApi; import com.scvngr.levelup.core.annotation.LevelUpApi.Contract; import com.scvngr.levelup.core.annotation.VisibleForTesting; import com.scvngr.levelup.core.annotation.VisibleForTesting.Visibility; import com.scvngr.levelup.core.net.AbstractRequest; import com.scvngr.levelup.core.net.LevelUpConnection; import com.scvngr.levelup.core.net.LevelUpResponse; import com.scvngr.levelup.core.util.LogManager; import java.util.UUID; /** * Abstract base class to be used for services that do network requests in the background and may or * may not want to send results back to the clients that triggered the request. Subclasses should * make sure that any requests to start this service include a unique token passed in * {@link #EXTRA_STRING_TOKEN}. Subclasses may override {@code getRequest(Intent)} to not require a * request to be passed in the start Intent. */ @LevelUpApi(contract = Contract.INTERNAL) public abstract class AbstractNetworkRequestService extends IntentService { /** * Intent action broadcast via {@link LocalBroadcastManager} when the service completes its * work. * <p> * The Intent will contain {@link #EXTRA_PARCELABLE_RESPONSE}, {@link #EXTRA_STRING_TOKEN}, and * {@link #EXTRA_BOOLEAN_IS_REQUEST_SUCCESSFUL}. */ public static final String ACTION_REQUEST_FINISHED = AbstractNetworkRequestService.class .getName() + ".intent.action.request_finished"; /** * Type: {@code AbstractRequest}. * <p> * Key mapping to a AbstractRequest that the service will perform. This extra is sent in the * Intent to start the service. */ /* package */static final String EXTRA_PARCELABLE_REQUEST = AbstractNetworkRequestService.class .getName() + ".extra.PARCELABLE_REQUEST"; /** * Type: {@code Response}. * <p> * Key mapping to a response from the request the service performed. */ public static final String EXTRA_PARCELABLE_RESPONSE = AbstractNetworkRequestService.class .getName() + ".extra.PARCELABLE_RESPONSE"; /** * Type: {@code String}. * <p> * Key mapping to a token uniquely identifying the original request to the service. */ public static final String EXTRA_STRING_TOKEN = AbstractNetworkRequestService.class.getName() + ".extra.STRING_TOKEN"; /** * Type: {@code boolean}. * <p> * Key mapping to a boolean indicating whether the request was successful (e.g. the response * doesn't contain an error). */ public static final String EXTRA_BOOLEAN_IS_REQUEST_SUCCESSFUL = AbstractNetworkRequestService.class.getName() + ".extra.BOOLEAN_IS_REQUEST_SUCCESSFUL"; /** * Constructs a new instance of the service. */ public AbstractNetworkRequestService() { super(AbstractNetworkRequestService.class.getSimpleName()); setIntentRedelivery(false); } /** * Handle the response from the server. * * @param context the Application context. * @param response the {@link LevelUpResponse} received from the server. * @return true if the response indicated a successful request. */ protected abstract boolean handleResponse(@NonNull final Context context, @NonNull final LevelUpResponse response); @Override public void onHandleIntent(final Intent intent) { performRequest(getApplicationContext(), intent); } /** * Performs the request to the server, handles the response, and sends a broadcast when the * process is done. * * @param context the Application context. * @param intent the intent that was used to start the service. */ @VisibleForTesting(visibility = Visibility.PRIVATE) /* package */void performRequest(@NonNull final Context context, @NonNull final Intent intent) { final AbstractRequest request = getRequest(intent); if (null != request) { LogManager.v("Sending request in the background: %s", request); final LevelUpResponse response = LevelUpConnection.newInstance(context).send(request); LogManager.v("Response from background request %s", response.getStatus()); final boolean success = handleResponse(context, response); onRequestFinished(context, intent, response, success); } else { LogManager.w("No request passed"); } } /** * Get the request to send with this service. Default implementation gets the request from the * intent (stored in key {@link #EXTRA_PARCELABLE_REQUEST}). Subclasses can override this to * build a request differently. * * @param intent the intent used to start the service. * @return {@link AbstractRequest} to send with the service. */ @Nullable protected AbstractRequest getRequest(@NonNull final Intent intent) { return intent.getParcelableExtra(EXTRA_PARCELABLE_REQUEST); } /** * Gets a unique token that identifies a request. Subclasses should make sure to add a token to * the Intent sent to any instances of this class because receivers use the tokens to determine * if a result is intended for them. * * @return the token to use to identify a request. */ @NonNull protected static String getToken() { return UUID.randomUUID().toString(); } /** * Called when the request finished. Takes the response, success flag and request token and * broadcasts them to the {@link android.content.BroadcastReceiver}s listening for the action * {@link #ACTION_REQUEST_FINISHED}. * * @param context the Application context. * @param intent the intent to get the request token from. * @param response the {@link LevelUpResponse} received during the request. * @param success true if the request was successful, false otherwise. */ @VisibleForTesting(visibility = Visibility.PRIVATE) /* package */static void onRequestFinished(@NonNull final Context context, @NonNull final Intent intent, @NonNull final LevelUpResponse response, final boolean success) { final Intent resultIntent = new Intent(ACTION_REQUEST_FINISHED); resultIntent.putExtra(EXTRA_STRING_TOKEN, intent.getStringExtra(EXTRA_STRING_TOKEN)); resultIntent.putExtra(EXTRA_BOOLEAN_IS_REQUEST_SUCCESSFUL, success); resultIntent.putExtra(EXTRA_PARCELABLE_RESPONSE, response); LocalBroadcastManager.getInstance(context).sendBroadcast(resultIntent); } }