/*
* Copyright 2013 Robotoworks Limited
* 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.robotoworks.mechanoid.ops;
import java.util.LinkedList;
import java.util.Queue;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.util.SparseArray;
@Deprecated
public abstract class OperationManagerBase {
private static final String TAG = "OperationManager";
OperationManagerCallbacks mCallbacks;
private String mStateKey;
private boolean mEnableLogging;
private SparseArray<OpInfo> mOperations = new SparseArray<OpInfo>();
private Queue<Runnable> mPendingOperations = new LinkedList<Runnable>();
private boolean mStarted = false;
static class OpInfo implements Parcelable {
int mUserCode = 0;
int mId = 0;
boolean mCallbackInvoked = false;
OperationResult mResult = null;
public Intent mIntent;
public static final Parcelable.Creator<OpInfo> CREATOR
= new Parcelable.Creator<OpInfo>() {
public OpInfo createFromParcel(Parcel in) {
return new OpInfo(in);
}
public OpInfo[] newArray(int size) {
return new OpInfo[size];
}
};
OpInfo() {
}
OpInfo(Parcel in) {
mUserCode = in.readInt();
mId = in.readInt();
mCallbackInvoked = in.readInt() > 0;
mResult = in.readParcelable(OperationResult.class.getClassLoader());
mIntent = in.readParcelable(Intent.class.getClassLoader());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mUserCode);
dest.writeInt(mId);
dest.writeInt(mCallbackInvoked ? 1 : 0);
dest.writeParcelable(mResult, 0);
dest.writeParcelable(mIntent, 0);
}
}
private OperationServiceListener mServiceListener = new OperationServiceListener() {
@Override
public void onOperationComplete(int id, OperationResult result) {
OpInfo op = findOperationInfoByRequestId(id);
// This is not our op
if(op == null) {
return;
}
op.mResult = result;
if(invokeOnOperationComplete(op.mUserCode, result, false)) {
op.mCallbackInvoked = true;
if(mEnableLogging) {
Log.d(TAG, String.format("[Operation Complete] request id:%s, user code:%s", id, op.mUserCode));
}
}
}
};
protected OperationManagerBase(OperationManagerCallbacks callbacks, boolean enableLogging) {
mCallbacks = callbacks;
mEnableLogging = enableLogging;
mStateKey = "com.robotoworks.mechanoid.ops.OperationManager.State";
}
public void removeCallbacks() {
mCallbacks = null;
}
public void setCallbacks(OperationManagerCallbacks callbacks) {
mCallbacks = callbacks;
}
private OpInfo findOperationInfoByRequestId(int requestId) {
for (int i = 0; i < mOperations.size(); i++){
OpInfo op = mOperations.valueAt(i);
if (op.mId == requestId) {
return op;
}
}
return null;
}
void restoreState(Bundle savedInstanceState) {
if(savedInstanceState != null) {
Bundle state = savedInstanceState.getBundle(mStateKey);
if(state != null) {
mOperations = savedInstanceState.getSparseParcelableArray("operations");
if(mEnableLogging) {
Log.d(TAG, String.format("[Restoring State]"));
}
}
}
}
void saveState(Bundle outState) {
if(mEnableLogging) {
Log.d(TAG, String.format("[Saving State]"));
}
Bundle state = new Bundle();
outState.putSparseParcelableArray("operations", mOperations);
outState.putBundle(mStateKey, state);
}
void start() {
if(mStarted) {
return;
}
if(mEnableLogging) {
Log.d(TAG, String.format("[Starting]"));
}
Ops.bindListener(mServiceListener);
mStarted = true;
ensureCallbacks();
executePendingOperations();
}
private void executePendingOperations() {
while(mPendingOperations.peek() != null) {
mPendingOperations.poll().run();
}
}
private void ensureCallbacks() {
for(int i=0; i < mOperations.size(); i++) {
OpInfo op = mOperations.valueAt(i);
if(Ops.isOperationPending(op.mId)) {
if(mEnableLogging) {
Log.d(TAG, String.format("[Operation Pending] request id: %s, user code:%s", op.mId, op.mUserCode));
}
invokeOnOperationPending(op.mUserCode);
continue;
}
OperationResult result = Ops.getLog().get(op.mId);
if (result != null) {
op.mResult = result;
if(!op.mCallbackInvoked) {
if(invokeOnOperationComplete(op.mUserCode, op.mResult, false)) {
op.mCallbackInvoked = true;
if(mEnableLogging) {
Log.d(TAG, String.format("[Operation Complete] request id: %s, user code:%s", op.mId, op.mUserCode));
}
}
}
continue;
}
}
}
void stop() {
if(!mStarted) {
return;
}
mStarted = false;
if(mEnableLogging) {
Log.d(TAG, String.format("[Stopping]"));
}
Ops.unbindListener(mServiceListener);
}
/**
* <p>Execute an operation described by the given intent with a user defined code</p>
*
* <p>When the operation completes, {@link OperationManagerCallbacks#onOperationComplete(int, OperationResult, boolean)}
* will be invoked.</p>
* <p><b>execute</b> can be invoked many times for the same operation intent, however it will only run the operation once if
* the <b>force</b> flag is set to false,
* subsequent calls will be ignored unless the <b>force</b> argument is set to true. In all cases the
* {@link OperationManagerCallbacks#onOperationComplete(int, OperationResult, boolean)} will
* be invoked for each call to runOperation but subsequent calls for the same operation will have the <b>fromCache</b> argument set to true.</p>
*
* <p>Setting the force flag to true, will force the operation to run, if an operation is currently running then it will continue to run but
* its result will be ignored and no callbacks will be received.</p>
*
* @param code user-defined code representing the operation to run
* @param force whether to force the operation to run
*/
public void execute(Intent operationIntent, int code, boolean force) {
if (operationIntent == null) {
Log.d(TAG, String.format("[Operation Null] operationintent argument was null, code:%s", code));
return;
}
if(!mStarted) {
if(mEnableLogging) {
Log.d(TAG, String.format("[Queue Operation] manager not started, queueing, user code:%s", code));
}
queuePendingOperation(operationIntent, code, force);
return;
}
OpInfo op = mOperations.get(code);
if (force || op == null) {
mOperations.delete(code);
if(mEnableLogging) {
Log.d(TAG, String.format("[Operation Pending] user code:%s", code));
}
invokeOnOperationPending(code);
if(mEnableLogging) {
Log.d(TAG, String.format("[Execute Operation] user code:%s", code));
}
OpInfo newOp = new OpInfo();
newOp.mUserCode = code;
newOp.mIntent = operationIntent;
mOperations.put(code, newOp);
newOp.mId = Ops.execute(operationIntent);
return;
}
if(op.mResult != null) {
if(mEnableLogging) {
Log.d(TAG, String.format("[Operation Complete] request id: %s, user code:%s, from cache:%s", op.mId, op.mUserCode, op.mCallbackInvoked));
}
if(invokeOnOperationComplete(op.mUserCode, op.mResult, op.mCallbackInvoked)) {
op.mCallbackInvoked = true;
}
}
}
private void queuePendingOperation(final Intent operationIntent, final int pendingOperationCode, final boolean pendingForce) {
mPendingOperations.add(new Runnable() {
@Override
public void run() {
execute(operationIntent, pendingOperationCode, pendingForce);
}
});
return;
}
protected boolean invokeOnOperationPending(int code) {
if(mCallbacks == null) {
return false;
}
mCallbacks.onOperationPending(code);
return true;
}
protected boolean invokeOnOperationComplete(int code, OperationResult result, boolean fromCache) {
if(mCallbacks == null) {
return false;
}
mCallbacks.onOperationComplete(code, result, fromCache);
return true;
}
}