All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.java.com.cloudant.sync.replication.ReplicationService Maven / Gradle / Ivy

There is a newer version: 2.4.1
Show newest version
/*
 * Copyright © 2016 IBM Corp. All rights reserved.
 *
 * 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.cloudant.sync.replication;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.support.v4.content.WakefulBroadcastReceiver;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * This abstract class forms the basis for creating replication policies on Android.
 * The replications run in a {@link Service} so that they can properly manage the
 * lifecycle and handle being killed or restarted by the operating system
 */
public abstract class ReplicationService extends Service
        implements PolicyReplicationsCompletedListener {

    public static final String EXTRA_INTENT = "intent";

    /**
     * The key used to identify the command being sent to the service.
     */
    public static final String EXTRA_COMMAND = "command";

    public static final int COMMAND_NONE = -1;

    /**
     * To start replications, this value should be passed to this service in an Intent using an
     * Extra with the {@link #EXTRA_COMMAND} key.
     *
     * @see
     * 
     * Replication Policy User Guide
     */
    public static final int COMMAND_START_REPLICATION = 0;

    /**
     * To stop replications, this value should be passed to this service in an Intent using an
     * Extra with the {@link #EXTRA_COMMAND} key.
     *
     * @see
     * 
     * Replication Policy User Guide
     */
    public static final int COMMAND_STOP_REPLICATION = 1;

    private static final int INTERNALLY_RESERVED_COMMAND_MAX_USED = 5;
    private static final int INTERNALLY_RESERVED_COMMAND_MAX = 99;

    private Handler mServiceHandler;
    private ReplicationPolicyManager mReplicationPolicyManager;
    private boolean mReplicatorsInitialised;
    private List mCommandQueue = new ArrayList();
    private Intent mStartReplicationsIntent;
    private int mStartReplicationsId;

    /**
     * Stores the set of {@link PolicyReplicationsCompletedListener}s
     * listening for replication complete
     * events. Note that all modifications or iterations over mListeners should be protected by
     * synchronization on the mListeners object.
     */
    private final Set mListeners = new
            HashSet();

    // It's safest to assume we could be transferring a large amount of data in a
    // replication, so we want a high performance WiFi connection even though it
    // requires more power.
    private WifiManager.WifiLock mWifiLock;

    private OperationStartedListener mOperationStartedListener;

    private final IBinder mBinder = new LocalBinder();

    interface OperationStartedListener {
        /**
         * Callback to indicate that an operation has started.
         */
        void operationStarted(int operationId);
    }

    // A binder to allow components running in the same process to bind to this Service.
    public class LocalBinder extends Binder {
        public ReplicationService getService() {
            return ReplicationService.this;
        }
    }

    // Handler that receives messages from the thread.
    protected class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            try {
                if (msg.arg2 > INTERNALLY_RESERVED_COMMAND_MAX_USED && msg.arg2 <=
                        INTERNALLY_RESERVED_COMMAND_MAX) {
                    throw new RuntimeException("ReplicationService received an EXTRA_COMMAND " +
                            "using an id in the range reserved for internal use. Custom commands " +
                            "must use an id above " + INTERNALLY_RESERVED_COMMAND_MAX);
                }

                Intent intent = msg.getData().getParcelable(EXTRA_INTENT);

                // Process the commands passed in msg.arg2.
                switch (msg.arg2) {
                    case COMMAND_START_REPLICATION:
                        mStartReplicationsIntent = intent;
                        mStartReplicationsId = msg.arg1;
                        startReplications();
                        break;
                    case COMMAND_STOP_REPLICATION:
                        stopReplications();
                        releaseWakeLock(intent);
                        stopSelf(msg.arg1);
                        break;
                    default:
                        // Do nothing
                        break;
                }
            } finally {
                notifyOperationStarted(msg.arg2);
            }
        }
    }

    protected Handler getHandler(Looper looper) {
        return new ServiceHandler(looper);
    }

    @Override
    public void onCreate() {
        super.onCreate();

        WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        if (wifiManager != null) {
            mWifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                "ReplicationService");
        }

        // Create a background priority thread to so we don't block the process's main thread.
        HandlerThread thread = new HandlerThread("ServiceStartArguments",
                android.os.Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();

        // Get the HandlerThread's Looper and use it for our Handler.
        Looper serviceLooper = thread.getLooper();
        mServiceHandler = getHandler(serviceLooper);
    }

    /**
     * Set the {@link Replicator} objects configured to perform the required replications.
     * The {@code ReplicationService} will not begin replications until this method has
     * been called. This operation should only be called once.
     *
     * @param replicators An array of the configured {@code Replicator} objects.
     * @throws IllegalArgumentException if {@code replicators} is null or empty.
     * @throws IllegalStateException    if called again after a previous call to this method
     *                                  with a valid array of {@code Replicator} objects.
     */
    public void setReplicators(Replicator[] replicators) {
        if (mReplicatorsInitialised) {
            throw new IllegalStateException("Replicators already set");
        }
        if (replicators != null && replicators.length > 0) {
            if (mReplicationPolicyManager == null) {
                mReplicationPolicyManager = new ReplicationPolicyManager();
            }
            mReplicationPolicyManager.addReplicators(replicators);
            mReplicationPolicyManager.setReplicationsCompletedListener(this);
            synchronized (mCommandQueue) {
                mReplicatorsInitialised = true;
                for (Message msg : mCommandQueue) {
                    mServiceHandler.sendMessage(msg);
                }
                mCommandQueue.clear();
            }
        } else {
            throw new IllegalArgumentException(
                    "No replications setup. Please pass Replicators to setReplicators" +
                            "(Replicator[])");
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // Extract the command from the given Intent and pass it to our handler to process the
        // command on a separate thread.
        if (intent != null && intent.hasExtra(EXTRA_COMMAND)) {
            Message msg = mServiceHandler.obtainMessage();
            msg.arg1 = startId;
            msg.arg2 = intent.getIntExtra(EXTRA_COMMAND, COMMAND_STOP_REPLICATION);
            Bundle bundle = new Bundle();
            bundle.putParcelable(EXTRA_INTENT, intent);
            msg.setData(bundle);
            synchronized (mCommandQueue) {
                if (mReplicatorsInitialised) {
                    mServiceHandler.sendMessage(msg);
                } else {
                    // Add the message to the command queue if it's different to the one at the
                    // tail of the queue. These messages will then be
                    // processed once setReplicators(Replicator[]) has been called.
                    if (mCommandQueue.size() == 0 ||
                            mCommandQueue.get(mCommandQueue.size() - 1).arg2 != msg.arg2) {
                        mCommandQueue.add(msg);
                    }
                }
            }
        }

        return START_REDELIVER_INTENT;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        // Ensure onRebind is called when new clients bind to the service.
        return true;
    }

    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
    }

    /**
     * Set the {@link ReplicationPolicyManager} to be used by this ReplicationService.
     *
     * @param replicationPolicyManager the {@link ReplicationPolicyManager}
     */
    public void setReplicationPolicyManager(ReplicationPolicyManager replicationPolicyManager) {
        mReplicationPolicyManager = replicationPolicyManager;
    }

    /**
     * Start the set of replications specified in the set of {@link Replicator}s passed to
     * {@link #setReplicators(Replicator[])}.
     */
    protected void startReplications() {
        if (mReplicationPolicyManager != null) {
            // Make sure we've got a WiFi lock so that the wifi isn't switched off while we're
            // trying to replicate.
            if (mWifiLock != null) {
                synchronized (mWifiLock) {
                    if (!mWifiLock.isHeld()) {
                        mWifiLock.acquire();
                    }
                }
            }
            mReplicationPolicyManager.startReplications();
        }
    }

    private void releaseWifiLockIfHeld() {
        if (mWifiLock != null) {
            synchronized (mWifiLock) {
                if (mWifiLock.isHeld()) {
                    mWifiLock.release();
                }
            }
        }
    }

    protected void releaseWakeLock(Intent intent) {
        if (intent != null) {
            // Release the WakeLock if there is one. Calling completeWakefulIntent is safe even if
            // there is no wakelock held.
            WakefulBroadcastReceiver.completeWakefulIntent(intent);
        }
    }

    /**
     * Stop replications currently in progress and terminate this Service.
     */
    protected void stopReplications() {
        if (mReplicationPolicyManager != null) {
            mReplicationPolicyManager.stopReplications();
            releaseWifiLockIfHeld();
        }
    }

    @Override
    public void allReplicationsCompleted() {
        synchronized (mListeners) {
            for (PolicyReplicationsCompletedListener listener : mListeners) {
                listener.allReplicationsCompleted();
            }
        }
        releaseWifiLockIfHeld();
        releaseWakeLock(mStartReplicationsIntent);
        stopSelf(mStartReplicationsId);
    }

    @Override
    public void replicationCompleted(int id) {
        synchronized (mListeners) {
            for (PolicyReplicationsCompletedListener listener : mListeners) {
                listener.replicationCompleted(id);
            }
        }
    }

    @Override
    public void replicationErrored(int id) {
        synchronized (mListeners) {
            for (PolicyReplicationsCompletedListener listener : mListeners) {
                listener.replicationErrored(id);
            }
        }
    }

    /**
     * Add a listener to the set of
     * {@link PolicyReplicationsCompletedListener}s that are notified when
     * replications complete.
     *
     * @param listener The listener to add.
     */
    public void addListener(PolicyReplicationsCompletedListener listener) {
        synchronized (mListeners) {
            mListeners.add(listener);
        }
    }

    /**
     * Remove a listener from the set of
     * {@link PolicyReplicationsCompletedListener}s that are notified when
     * replications complete.
     *
     * @param listener The listener to remove.
     */
    public void removeListener(PolicyReplicationsCompletedListener listener) {
        synchronized (mListeners) {
            mListeners.remove(listener);
        }
    }

    /**
     * Set a listener to be notified when an operation has started.
     *
     * @param listener The listener to add
     */
    public void setOperationStartedListener(OperationStartedListener listener) {
        mOperationStartedListener = listener;
    }

    /**
     * Invoke the callback used to notify listeners that the operation has started.
     */
    void notifyOperationStarted(int operationId) {
        if (mOperationStartedListener != null) {
            mOperationStartedListener.operationStarted(operationId);
        }
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy