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