7efb588e357a86a78977945df3d6fd223611995c
[pub/Android/ownCloud.git] / oc_framework / src / com / owncloud / android / oc_framework / operations / RemoteOperation.java
1 /* ownCloud Android Library is available under MIT license
2 * Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 * THE SOFTWARE.
22 *
23 */
24
25 package com.owncloud.android.oc_framework.operations;
26
27 import java.io.IOException;
28
29 import org.apache.commons.httpclient.Credentials;
30
31 import com.owncloud.android.oc_framework.network.BearerCredentials;
32 import com.owncloud.android.oc_framework.network.webdav.WebdavClient;
33 import com.owncloud.android.oc_framework.network.webdav.OwnCloudClientFactory;
34 import com.owncloud.android.oc_framework.operations.RemoteOperationResult.ResultCode;
35
36
37
38 import android.accounts.Account;
39 import android.accounts.AccountManager;
40 import android.accounts.AccountsException;
41 import android.app.Activity;
42 import android.content.Context;
43 import android.os.Handler;
44 import android.util.Log;
45
46
47 /**
48 * Operation which execution involves one or several interactions with an ownCloud server.
49 *
50 * Provides methods to execute the operation both synchronously or asynchronously.
51 *
52 * @author David A. Velasco
53 */
54 public abstract class RemoteOperation implements Runnable {
55
56 private static final String TAG = RemoteOperation.class.getSimpleName();
57
58 /** ownCloud account in the remote ownCloud server to operate */
59 private Account mAccount = null;
60
61 /** Android Application context */
62 private Context mContext = null;
63
64 /** Object to interact with the remote server */
65 private WebdavClient mClient = null;
66
67 /** Callback object to notify about the execution of the remote operation */
68 private OnRemoteOperationListener mListener = null;
69
70 /** Handler to the thread where mListener methods will be called */
71 private Handler mListenerHandler = null;
72
73 /** Activity */
74 private Activity mCallerActivity;
75
76
77 /**
78 * Abstract method to implement the operation in derived classes.
79 */
80 protected abstract RemoteOperationResult run(WebdavClient client);
81
82
83 /**
84 * Synchronously executes the remote operation on the received ownCloud account.
85 *
86 * Do not call this method from the main thread.
87 *
88 * This method should be used whenever an ownCloud account is available, instead of {@link #execute(WebdavClient)}.
89 *
90 * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation.
91 * @param context Android context for the component calling the method.
92 * @return Result of the operation.
93 */
94 public final RemoteOperationResult execute(Account account, Context context) {
95 if (account == null)
96 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account");
97 if (context == null)
98 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context");
99 mAccount = account;
100 mContext = context.getApplicationContext();
101 try {
102 mClient = OwnCloudClientFactory.createOwnCloudClient(mAccount, mContext);
103 } catch (Exception e) {
104 Log.e(TAG, "Error while trying to access to " + mAccount.name, e);
105 return new RemoteOperationResult(e);
106 }
107 return run(mClient);
108 }
109
110
111 /**
112 * Synchronously executes the remote operation
113 *
114 * Do not call this method from the main thread.
115 *
116 * @param client Client object to reach an ownCloud server during the execution of the operation.
117 * @return Result of the operation.
118 */
119 public final RemoteOperationResult execute(WebdavClient client) {
120 if (client == null)
121 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient");
122 mClient = client;
123 return run(client);
124 }
125
126
127 /**
128 * Asynchronously executes the remote operation
129 *
130 * This method should be used whenever an ownCloud account is available, instead of {@link #execute(WebdavClient)}.
131 *
132 * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation.
133 * @param context Android context for the component calling the method.
134 * @param listener Listener to be notified about the execution of the operation.
135 * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
136 * @return Thread were the remote operation is executed.
137 */
138 public final Thread execute(Account account, Context context, OnRemoteOperationListener listener, Handler listenerHandler, Activity callerActivity) {
139 if (account == null)
140 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account");
141 if (context == null)
142 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context");
143 mAccount = account;
144 mContext = context.getApplicationContext();
145 mCallerActivity = callerActivity;
146 mClient = null; // the client instance will be created from mAccount and mContext in the runnerThread to create below
147
148 mListener = listener;
149
150 mListenerHandler = listenerHandler;
151
152 Thread runnerThread = new Thread(this);
153 runnerThread.start();
154 return runnerThread;
155 }
156
157
158 /**
159 * Asynchronously executes the remote operation
160 *
161 * @param client Client object to reach an ownCloud server during the execution of the operation.
162 * @param listener Listener to be notified about the execution of the operation.
163 * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
164 * @return Thread were the remote operation is executed.
165 */
166 public final Thread execute(WebdavClient client, OnRemoteOperationListener listener, Handler listenerHandler) {
167 if (client == null) {
168 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient");
169 }
170 mClient = client;
171
172 if (listener == null) {
173 throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a listener to notiy the result");
174 }
175 mListener = listener;
176
177 if (listenerHandler == null) {
178 throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread");
179 }
180 mListenerHandler = listenerHandler;
181
182 Thread runnerThread = new Thread(this);
183 runnerThread.start();
184 return runnerThread;
185 }
186
187 /**
188 * Synchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient)}
189 *
190 * @param listener Listener to be notified about the execution of the operation.
191 * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
192 * @return Thread were the remote operation is executed.
193 */
194 public final RemoteOperationResult retry() {
195 return execute(mClient);
196 }
197
198 /**
199 * Asynchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)}
200 *
201 * @param listener Listener to be notified about the execution of the operation.
202 * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
203 * @return Thread were the remote operation is executed.
204 */
205 public final Thread retry(OnRemoteOperationListener listener, Handler listenerHandler) {
206 return execute(mClient, listener, listenerHandler);
207 }
208
209
210 /**
211 * Asynchronous execution of the operation
212 * started by {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)},
213 * and result posting.
214 *
215 * TODO refactor && clean the code; now it's a mess
216 */
217 @Override
218 public final void run() {
219 RemoteOperationResult result = null;
220 boolean repeat = false;
221 do {
222 try{
223 if (mClient == null) {
224 if (mAccount != null && mContext != null) {
225 if (mCallerActivity != null) {
226 mClient = OwnCloudClientFactory.createOwnCloudClient(mAccount, mContext, mCallerActivity);
227 } else {
228 mClient = OwnCloudClientFactory.createOwnCloudClient(mAccount, mContext);
229 }
230 } else {
231 throw new IllegalStateException("Trying to run a remote operation asynchronously with no client instance or account");
232 }
233 }
234
235 } catch (IOException e) {
236 Log.e(TAG, "Error while trying to access to " + mAccount.name, new AccountsException("I/O exception while trying to authorize the account", e));
237 result = new RemoteOperationResult(e);
238
239 } catch (AccountsException e) {
240 Log.e(TAG, "Error while trying to access to " + mAccount.name, e);
241 result = new RemoteOperationResult(e);
242 }
243
244 if (result == null)
245 result = run(mClient);
246
247 repeat = false;
248 if (mCallerActivity != null && mAccount != null && mContext != null && !result.isSuccess() &&
249 // (result.getCode() == ResultCode.UNAUTHORIZED || (result.isTemporalRedirection() && result.isIdPRedirection()))) {
250 (result.getCode() == ResultCode.UNAUTHORIZED || result.isIdPRedirection())) {
251 /// possible fail due to lack of authorization in an operation performed in foreground
252 Credentials cred = mClient.getCredentials();
253 String ssoSessionCookie = mClient.getSsoSessionCookie();
254 if (cred != null || ssoSessionCookie != null) {
255 /// confirmed : unauthorized operation
256 AccountManager am = AccountManager.get(mContext);
257 boolean bearerAuthorization = (cred != null && cred instanceof BearerCredentials);
258 boolean samlBasedSsoAuthorization = (cred == null && ssoSessionCookie != null);
259 if (bearerAuthorization) {
260 am.invalidateAuthToken(mAccount.type, ((BearerCredentials)cred).getAccessToken());
261 } else if (samlBasedSsoAuthorization ) {
262 am.invalidateAuthToken(mAccount.type, ssoSessionCookie);
263 } else {
264 am.clearPassword(mAccount);
265 }
266 mClient = null;
267 repeat = true; // when repeated, the creation of a new OwnCloudClient after erasing the saved credentials will trigger the login activity
268 result = null;
269 }
270 }
271 } while (repeat);
272
273 final RemoteOperationResult resultToSend = result;
274 if (mListenerHandler != null && mListener != null) {
275 mListenerHandler.post(new Runnable() {
276 @Override
277 public void run() {
278 mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend);
279 }
280 });
281 }
282 }
283
284
285 /**
286 * Returns the current client instance to access the remote server.
287 *
288 * @return Current client instance to access the remote server.
289 */
290 public final WebdavClient getClient() {
291 return mClient;
292 }
293
294
295 }