16ecf27f41115fb2f9dc708cbe6bf39972829021
[pub/Android/ownCloud.git] / src / com / owncloud / android / operations / RemoteOperation.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012-2013 ownCloud Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17 package com.owncloud.android.operations;
18
19 import java.io.IOException;
20
21 import org.apache.commons.httpclient.Credentials;
22
23 import com.owncloud.android.Log_OC;
24 import com.owncloud.android.authentication.AccountAuthenticator;
25 import com.owncloud.android.network.BearerCredentials;
26 import com.owncloud.android.network.OwnCloudClientUtils;
27 import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
28
29 import android.accounts.Account;
30 import android.accounts.AccountManager;
31 import android.accounts.AccountsException;
32 import android.app.Activity;
33 import android.content.Context;
34 import android.os.Handler;
35
36 import eu.alefzero.webdav.WebdavClient;
37
38 /**
39 * Operation which execution involves one or several interactions with an ownCloud server.
40 *
41 * Provides methods to execute the operation both synchronously or asynchronously.
42 *
43 * @author David A. Velasco
44 */
45 public abstract class RemoteOperation implements Runnable {
46
47 private static final String TAG = RemoteOperation.class.getSimpleName();
48
49 /** ownCloud account in the remote ownCloud server to operate */
50 private Account mAccount = null;
51
52 /** Android Application context */
53 private Context mContext = null;
54
55 /** Object to interact with the remote server */
56 private WebdavClient mClient = null;
57
58 /** Callback object to notify about the execution of the remote operation */
59 private OnRemoteOperationListener mListener = null;
60
61 /** Handler to the thread where mListener methods will be called */
62 private Handler mListenerHandler = null;
63
64 /** Activity */
65 private Activity mCallerActivity;
66
67
68 /**
69 * Abstract method to implement the operation in derived classes.
70 */
71 protected abstract RemoteOperationResult run(WebdavClient client);
72
73
74 /**
75 * Synchronously executes the remote operation on the received ownCloud account.
76 *
77 * Do not call this method from the main thread.
78 *
79 * This method should be used whenever an ownCloud account is available, instead of {@link #execute(WebdavClient)}.
80 *
81 * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation.
82 * @param context Android context for the component calling the method.
83 * @return Result of the operation.
84 */
85 public final RemoteOperationResult execute(Account account, Context context) {
86 if (account == null)
87 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account");
88 if (context == null)
89 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context");
90 mAccount = account;
91 mContext = context.getApplicationContext();
92 try {
93 mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext);
94 } catch (Exception e) {
95 Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e);
96 return new RemoteOperationResult(e);
97 }
98 return run(mClient);
99 }
100
101
102 /**
103 * Synchronously executes the remote operation
104 *
105 * Do not call this method from the main thread.
106 *
107 * @param client Client object to reach an ownCloud server during the execution of the operation.
108 * @return Result of the operation.
109 */
110 public final RemoteOperationResult execute(WebdavClient client) {
111 if (client == null)
112 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient");
113 mClient = client;
114 return run(client);
115 }
116
117
118 /**
119 * Asynchronously executes the remote operation
120 *
121 * This method should be used whenever an ownCloud account is available, instead of {@link #execute(WebdavClient)}.
122 *
123 * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation.
124 * @param context Android context for the component calling the method.
125 * @param listener Listener to be notified about the execution of the operation.
126 * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
127 * @return Thread were the remote operation is executed.
128 */
129 public final Thread execute(Account account, Context context, OnRemoteOperationListener listener, Handler listenerHandler, Activity callerActivity) {
130 if (account == null)
131 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account");
132 if (context == null)
133 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context");
134 mAccount = account;
135 mContext = context.getApplicationContext();
136 mCallerActivity = callerActivity;
137 mClient = null; // the client instance will be created from mAccount and mContext in the runnerThread to create below
138
139 if (listener == null) {
140 throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a listener to notiy the result");
141 }
142 mListener = listener;
143
144 if (listenerHandler == null) {
145 throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread");
146 }
147 mListenerHandler = listenerHandler;
148
149 Thread runnerThread = new Thread(this);
150 runnerThread.start();
151 return runnerThread;
152 }
153
154
155 /**
156 * Asynchronously executes the remote operation
157 *
158 * @param client Client object to reach an ownCloud server during the execution of the operation.
159 * @param listener Listener to be notified about the execution of the operation.
160 * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
161 * @return Thread were the remote operation is executed.
162 */
163 public final Thread execute(WebdavClient client, OnRemoteOperationListener listener, Handler listenerHandler) {
164 if (client == null) {
165 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient");
166 }
167 mClient = client;
168
169 if (listener == null) {
170 throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a listener to notiy the result");
171 }
172 mListener = listener;
173
174 if (listenerHandler == null) {
175 throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread");
176 }
177 mListenerHandler = listenerHandler;
178
179 Thread runnerThread = new Thread(this);
180 runnerThread.start();
181 return runnerThread;
182 }
183
184 /**
185 * Synchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient)}
186 *
187 * @param listener Listener to be notified about the execution of the operation.
188 * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
189 * @return Thread were the remote operation is executed.
190 */
191 public final RemoteOperationResult retry() {
192 return execute(mClient);
193 }
194
195 /**
196 * Asynchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)}
197 *
198 * @param listener Listener to be notified about the execution of the operation.
199 * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
200 * @return Thread were the remote operation is executed.
201 */
202 public final Thread retry(OnRemoteOperationListener listener, Handler listenerHandler) {
203 return execute(mClient, listener, listenerHandler);
204 }
205
206
207 /**
208 * Asynchronous execution of the operation
209 * started by {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)},
210 * and result posting.
211 *
212 * TODO refactor && clean the code; now it's a mess
213 */
214 @Override
215 public final void run() {
216 RemoteOperationResult result = null;
217 boolean repeat = false;
218 do {
219 try{
220 if (mClient == null) {
221 if (mAccount != null && mContext != null) {
222 if (mCallerActivity != null) {
223 mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext, mCallerActivity);
224 } else {
225 mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext);
226 }
227 } else {
228 throw new IllegalStateException("Trying to run a remote operation asynchronously with no client instance or account");
229 }
230 }
231
232 } catch (IOException e) {
233 Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, new AccountsException("I/O exception while trying to authorize the account", e));
234 result = new RemoteOperationResult(e);
235
236 } catch (AccountsException e) {
237 Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e);
238 result = new RemoteOperationResult(e);
239 }
240
241 if (result == null)
242 result = run(mClient);
243
244 repeat = false;
245 if (mCallerActivity != null && mAccount != null && mContext != null && !result.isSuccess() &&
246 (result.getCode() == ResultCode.UNAUTHORIZED || result.isTemporalRedirection())) {
247 /// possible fail due to lack of authorization in an operation performed in foreground
248 Credentials cred = mClient.getCredentials();
249 String ssoSessionCookie = mClient.getSsoSessionCookie();
250 if (cred != null || ssoSessionCookie != null) {
251 /// confirmed : unauthorized operation
252 AccountManager am = AccountManager.get(mContext);
253 boolean bearerAuthorization = (cred != null && cred instanceof BearerCredentials);
254 boolean samlBasedSsoAuthorization = (cred == null && ssoSessionCookie != null);
255 if (bearerAuthorization) {
256 am.invalidateAuthToken(AccountAuthenticator.ACCOUNT_TYPE, ((BearerCredentials)cred).getAccessToken());
257 } else if (samlBasedSsoAuthorization ) {
258 am.invalidateAuthToken(AccountAuthenticator.ACCOUNT_TYPE, ssoSessionCookie);
259 } else {
260 am.clearPassword(mAccount);
261 }
262 mClient = null;
263 repeat = true; // when repeated, the creation of a new OwnCloudClient after erasing the saved credentials will trigger the login activity
264 result = null;
265 }
266 }
267 } while (repeat);
268
269 final RemoteOperationResult resultToSend = result;
270 if (mListenerHandler != null && mListener != null) {
271 mListenerHandler.post(new Runnable() {
272 @Override
273 public void run() {
274 mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend);
275 }
276 });
277 }
278 }
279
280
281 /**
282 * Returns the current client instance to access the remote server.
283 *
284 * @return Current client instance to access the remote server.
285 */
286 public final WebdavClient getClient() {
287 return mClient;
288 }
289
290
291 }