/* ownCloud Android client application
- * Copyright (C) 2012-2013 ownCloud Inc.
+ * Copyright (C) 2012-2014 ownCloud Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
package com.owncloud.android.services;
import java.io.IOException;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
-
-import com.owncloud.android.lib.network.OwnCloudClientFactory;
-import com.owncloud.android.lib.network.OwnCloudClient;
-import com.owncloud.android.operations.GetSharesOperation;
+import com.owncloud.android.lib.common.OwnCloudClientFactory;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.resources.shares.ShareType;
import com.owncloud.android.operations.common.SyncOperation;
-import com.owncloud.android.lib.operations.common.RemoteOperation;
-import com.owncloud.android.lib.operations.common.RemoteOperationResult;
+import com.owncloud.android.operations.CreateShareOperation;
+import com.owncloud.android.operations.GetServerInfoOperation;
+import com.owncloud.android.operations.OAuth2GetAccessToken;
+import com.owncloud.android.operations.UnshareLinkOperation;
import com.owncloud.android.utils.Log_OC;
import android.accounts.Account;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
-//import android.support.v4.content.LocalBroadcastManager;
import android.util.Pair;
public class OperationsService extends Service {
public static final String EXTRA_ACCOUNT = "ACCOUNT";
public static final String EXTRA_SERVER_URL = "SERVER_URL";
- public static final String EXTRA_RESULT = "RESULT";
+ public static final String EXTRA_AUTH_TOKEN_TYPE = "AUTH_TOKEN_TYPE";
+ public static final String EXTRA_OAUTH2_QUERY_PARAMETERS = "OAUTH2_QUERY_PARAMETERS";
+ public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
+ public static final String EXTRA_SEND_INTENT = "SEND_INTENT";
+ public static final String EXTRA_RESULT = "RESULT";
+
+ public static final String ACTION_CREATE_SHARE = "CREATE_SHARE";
+ public static final String ACTION_UNSHARE = "UNSHARE";
+ public static final String ACTION_GET_SERVER_INFO = "GET_SERVER_INFO";
+ public static final String ACTION_OAUTH2_GET_ACCESS_TOKEN = "OAUTH2_GET_ACCESS_TOKEN";
public static final String ACTION_OPERATION_ADDED = OperationsService.class.getName() + ".OPERATION_ADDED";
public static final String ACTION_OPERATION_FINISHED = OperationsService.class.getName() + ".OPERATION_FINISHED";
- private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations = new ConcurrentLinkedQueue<Pair<Target, RemoteOperation>>();
+ private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations =
+ new ConcurrentLinkedQueue<Pair<Target, RemoteOperation>>();
+
+ private ConcurrentMap<Integer, RemoteOperationResult> mOperationResults =
+ new ConcurrentHashMap<Integer, RemoteOperationResult>();
private static class Target {
public Uri mServerUrl = null;
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
- private IBinder mBinder;
+ private OperationsServiceBinder mBinder;
private OwnCloudClient mOwnCloudClient = null;
private Target mLastTarget = null;
private FileDataStorageManager mStorageManager;
mBinder = new OperationsServiceBinder();
}
+
/**
* Entry point to add a new operation to the queue of operations.
*
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- if (!intent.hasExtra(EXTRA_ACCOUNT) && !intent.hasExtra(EXTRA_SERVER_URL)) {
- Log_OC.e(TAG, "Not enough information provided in intent");
- return START_NOT_STICKY;
- }
- try {
- Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
- String serverUrl = intent.getStringExtra(EXTRA_SERVER_URL);
- Target target = new Target(account, (serverUrl == null) ? null : Uri.parse(serverUrl));
- GetSharesOperation operation = new GetSharesOperation();
- mPendingOperations.add(new Pair<Target , RemoteOperation>(target, operation));
- sendBroadcastNewOperation(target, operation);
-
- Message msg = mServiceHandler.obtainMessage();
- msg.arg1 = startId;
- mServiceHandler.sendMessage(msg);
-
- } catch (IllegalArgumentException e) {
- Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
- return START_NOT_STICKY;
- }
-
+ //Log.wtf(TAG, "onStartCommand init" );
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = startId;
+ mServiceHandler.sendMessage(msg);
+ //Log.wtf(TAG, "onStartCommand end" );
return START_NOT_STICKY;
}
*/
@Override
public IBinder onBind(Intent intent) {
+ //Log.wtf(TAG, "onBind" );
return mBinder;
}
*/
@Override
public boolean onUnbind(Intent intent) {
- //((OperationsServiceBinder)mBinder).clearListeners();
+ ((OperationsServiceBinder)mBinder).clearListeners();
return false; // not accepting rebinding (default behaviour)
}
*
* It provides by itself the available operations.
*/
- public class OperationsServiceBinder extends Binder {
- // TODO
+ public class OperationsServiceBinder extends Binder /* implements OnRemoteOperationListener */ {
+
+ /**
+ * Map of listeners that will be reported about the end of operations from a {@link OperationsServiceBinder} instance
+ */
+ private ConcurrentMap<OnRemoteOperationListener, Handler> mBoundListeners =
+ new ConcurrentHashMap<OnRemoteOperationListener, Handler>();
+
+ /**
+ * Cancels an operation
+ *
+ * TODO
+ */
+ public void cancel() {
+ // TODO
+ }
+
+
+ public void clearListeners() {
+
+ mBoundListeners.clear();
+ }
+
+
+ /**
+ * Adds a listener interested in being reported about the end of operations.
+ *
+ * @param listener Object to notify about the end of operations.
+ * @param callbackHandler {@link Handler} to access the listener without breaking Android threading protection.
+ */
+ public void addOperationListener (OnRemoteOperationListener listener, Handler callbackHandler) {
+ synchronized (mBoundListeners) {
+ mBoundListeners.put(listener, callbackHandler);
+ }
+ }
+
+
+ /**
+ * Removes a listener from the list of objects interested in the being reported about the end of operations.
+ *
+ * @param listener Object to notify about progress of transfer.
+ */
+ public void removeOperationListener (OnRemoteOperationListener listener) {
+ synchronized (mBoundListeners) {
+ mBoundListeners.remove(listener);
+ }
+ }
+
+
+ /**
+ * TODO - IMPORTANT: update implementation when more operations are moved into the service
+ *
+ * @return 'True' when an operation that enforces the user to wait for completion is in process.
+ */
+ public boolean isPerformingBlockingOperation() {
+ return (!mPendingOperations.isEmpty());
+ }
+
+
+ /**
+ * Creates and adds to the queue a new operation, as described by operationIntent
+ *
+ * @param operationIntent Intent describing a new operation to queue and execute.
+ * @return Identifier of the operation created, or -1 if failed.
+ */
+ public int newOperation(Intent operationIntent) {
+ RemoteOperation operation = null;
+ Target target = null;
+ try {
+ if (!operationIntent.hasExtra(EXTRA_ACCOUNT) &&
+ !operationIntent.hasExtra(EXTRA_SERVER_URL)) {
+ Log_OC.e(TAG, "Not enough information provided in intent");
+
+ } else {
+ Account account = operationIntent.getParcelableExtra(EXTRA_ACCOUNT);
+ String serverUrl = operationIntent.getStringExtra(EXTRA_SERVER_URL);
+ target = new Target(
+ account,
+ (serverUrl == null) ? null : Uri.parse(serverUrl)
+ );
+
+ String action = operationIntent.getAction();
+ if (action.equals(ACTION_CREATE_SHARE)) { // Create Share
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ Intent sendIntent = operationIntent.getParcelableExtra(EXTRA_SEND_INTENT);
+ if (remotePath.length() > 0) {
+ operation = new CreateShareOperation(remotePath, ShareType.PUBLIC_LINK,
+ "", false, "", 1, sendIntent);
+ }
+ } else if (action.equals(ACTION_UNSHARE)) { // Unshare file
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ if (remotePath.length() > 0) {
+ operation = new UnshareLinkOperation(
+ remotePath,
+ OperationsService.this);
+ }
+ } else if (action.equals(ACTION_GET_SERVER_INFO)) {
+ // check OC server and get basic information from it
+ String authTokenType =
+ operationIntent.getStringExtra(EXTRA_AUTH_TOKEN_TYPE);
+ operation = new GetServerInfoOperation(
+ serverUrl, authTokenType, OperationsService.this);
+
+ } else if (action.equals(ACTION_OAUTH2_GET_ACCESS_TOKEN)) {
+ /// GET ACCESS TOKEN to the OAuth server
+ String oauth2QueryParameters =
+ operationIntent.getStringExtra(EXTRA_OAUTH2_QUERY_PARAMETERS);
+ operation = new OAuth2GetAccessToken(
+ getString(R.string.oauth2_client_id),
+ getString(R.string.oauth2_redirect_uri),
+ getString(R.string.oauth2_grant_type),
+ oauth2QueryParameters);
+ }
+ }
+
+ } catch (IllegalArgumentException e) {
+ Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
+ operation = null;
+ }
+
+ if (operation != null) {
+ mPendingOperations.add(new Pair<Target , RemoteOperation>(target, operation));
+ startService(new Intent(OperationsService.this, OperationsService.class));
+ return operation.hashCode();
+
+ } else {
+ return -1;
+ }
+ }
+
+ public RemoteOperationResult getOperationResultIfFinished(int mDetectAuthOpId) {
+ //Log_OC.wtf(TAG, "Searching result for operation with id " + mDetectAuthOpId);
+ return mOperationResults.remove(mDetectAuthOpId);
+ }
+
}
*/
private void nextOperation() {
+ //Log.wtf(TAG, "nextOperation init" );
+
Pair<Target, RemoteOperation> next = null;
synchronized(mPendingOperations) {
next = mPendingOperations.peek();
} finally {
synchronized(mPendingOperations) {
mPendingOperations.poll();
+ mOperationResults.put(mCurrentOperation.hashCode(), result);
}
}
- sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result);
+ //sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result);
+ callbackOperationListeners(mLastTarget, mCurrentOperation, result);
}
}
/**
- * Sends a LOCAL broadcast when a new operation is added to the queue.
+ * Sends a broadcast when a new operation is added to the queue.
*
- * Local broadcasts are only delivered to activities in the same process.
+ * Local broadcasts are only delivered to activities in the same process, but can't be done sticky :\
*
* @param target Account or URL pointing to an OC server.
* @param operation Added operation.
//lbm.sendBroadcast(intent);
sendStickyBroadcast(intent);
}
+
+ /**
+ * Notifies the currently subscribed listeners about the end of an operation.
+ *
+ * @param target Account or URL pointing to an OC server.
+ * @param operation Finished operation.
+ * @param result Result of the operation.
+ */
+ private void callbackOperationListeners(
+ Target target, final RemoteOperation operation, final RemoteOperationResult result) {
+ int count = 0;
+ Iterator<OnRemoteOperationListener> listeners = mBinder.mBoundListeners.keySet().iterator();
+ while (listeners.hasNext()) {
+ final OnRemoteOperationListener listener = listeners.next();
+ final Handler handler = mBinder.mBoundListeners.get(listener);
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onRemoteOperationFinish(operation, result);
+ }
+ });
+ count += 1;
+ }
+ }
+ Log_OC.d(TAG, "Called " + count + " listeners");
+ }
+
}