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