mirror of
https://github.com/moparisthebest/keepass2android
synced 2025-03-03 10:21:44 -05:00
* directly integrated LiveSDK files to allow modification and simplify build
* added initializeSynchronous method to LiveSDK * added UserInteractionRequiredException, now thrown by GDrive+SkyDrive
This commit is contained in:
parent
3831dbb345
commit
7fa3dd191f
2
src/java/.gitignore
vendored
Normal file
2
src/java/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/InputStickAPI - Kopie
|
||||||
|
/DemoBT
|
@ -14,4 +14,3 @@ proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.
|
|||||||
target=android-17
|
target=android-17
|
||||||
android.library=true
|
android.library=true
|
||||||
android.library.reference.1=..\\..\\..\\..\\..\\..\\..\\AppData\\Local\\Android\\android-sdk\\extras\\google\\google_play_services\\libproject\\google-play-services_lib
|
android.library.reference.1=..\\..\\..\\..\\..\\..\\..\\AppData\\Local\\Android\\android-sdk\\extras\\google\\google_play_services\\libproject\\google-play-services_lib
|
||||||
android.library.reference.2=../../../../LiveSDK-for-Android/src
|
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.microsoft.live.OAuth.GrantType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AccessTokenRequest represents a request for an Access Token.
|
||||||
|
* It subclasses the abstract class TokenRequest, which does most of the work.
|
||||||
|
* This class adds the proper parameters for the access token request via the
|
||||||
|
* constructBody() hook.
|
||||||
|
*/
|
||||||
|
class AccessTokenRequest extends TokenRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REQUIRED. The authorization code received from the
|
||||||
|
* authorization server.
|
||||||
|
*/
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
/** REQUIRED. Value MUST be set to "authorization_code". */
|
||||||
|
private final GrantType grantType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REQUIRED, if the "redirect_uri" parameter was included in the
|
||||||
|
* authorization request as described in Section 4.1.1, and their
|
||||||
|
* values MUST be identical.
|
||||||
|
*/
|
||||||
|
private final String redirectUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new AccessTokenRequest, and initializes its member variables
|
||||||
|
*
|
||||||
|
* @param client the HttpClient to make HTTP requests on
|
||||||
|
* @param clientId the client_id of the calling application
|
||||||
|
* @param redirectUri the redirect_uri to be called back
|
||||||
|
* @param code the authorization code received from the AuthorizationRequest
|
||||||
|
*/
|
||||||
|
public AccessTokenRequest(HttpClient client,
|
||||||
|
String clientId,
|
||||||
|
String redirectUri,
|
||||||
|
String code) {
|
||||||
|
super(client, clientId);
|
||||||
|
|
||||||
|
assert !TextUtils.isEmpty(redirectUri);
|
||||||
|
assert !TextUtils.isEmpty(code);
|
||||||
|
|
||||||
|
this.redirectUri = redirectUri;
|
||||||
|
this.code = code;
|
||||||
|
this.grantType = GrantType.AUTHORIZATION_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the "code", "redirect_uri", and "grant_type" parameters to the body.
|
||||||
|
*
|
||||||
|
* @param body the list of NameValuePairs to be placed in the body of the HTTP request
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void constructBody(List<NameValuePair> body) {
|
||||||
|
body.add(new BasicNameValuePair(OAuth.CODE, this.code));
|
||||||
|
body.add(new BasicNameValuePair(OAuth.REDIRECT_URI, this.redirectUri));
|
||||||
|
body.add(new BasicNameValuePair(OAuth.GRANT_TYPE,
|
||||||
|
this.grantType.toString().toLowerCase()));
|
||||||
|
}
|
||||||
|
}
|
252
src/java/JavaFileStorage/src/com/microsoft/live/ApiRequest.java
Normal file
252
src/java/JavaFileStorage/src/com/microsoft/live/ApiRequest.java
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.http.Header;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.auth.AUTH;
|
||||||
|
import org.apache.http.client.ClientProtocolException;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.ResponseHandler;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.apache.http.message.BasicHeader;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ApiRequest is an abstract base class that represents an Http Request made by the API.
|
||||||
|
* It does most of the Http Request work inside of the execute method, and provides a
|
||||||
|
* an abstract factory method for subclasses to choose the type of Http Request to be
|
||||||
|
* created.
|
||||||
|
*/
|
||||||
|
abstract class ApiRequest<ResponseType> {
|
||||||
|
|
||||||
|
public interface Observer {
|
||||||
|
public void onComplete(HttpResponse response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Redirects {
|
||||||
|
SUPPRESS {
|
||||||
|
@Override
|
||||||
|
protected void setQueryParameterOn(UriBuilder builder) {
|
||||||
|
Redirects.setQueryParameterOn(builder, Boolean.TRUE);
|
||||||
|
}
|
||||||
|
}, UNSUPPRESSED {
|
||||||
|
@Override
|
||||||
|
protected void setQueryParameterOn(UriBuilder builder) {
|
||||||
|
Redirects.setQueryParameterOn(builder, Boolean.FALSE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the suppress_redirects query parameter by removing all existing ones
|
||||||
|
* and then appending it on the given UriBuilder.
|
||||||
|
*/
|
||||||
|
protected abstract void setQueryParameterOn(UriBuilder builder);
|
||||||
|
|
||||||
|
private static void setQueryParameterOn(UriBuilder builder, Boolean value) {
|
||||||
|
// The Live SDK is designed to use our value of suppress_redirects.
|
||||||
|
// If it uses any other value it could cause issues. Remove any previously
|
||||||
|
// existing suppress_redirects and use ours.
|
||||||
|
builder.removeQueryParametersWithKey(QueryParameters.SUPPRESS_REDIRECTS);
|
||||||
|
builder.appendQueryParameter(QueryParameters.SUPPRESS_REDIRECTS, value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ResponseCodes {
|
||||||
|
SUPPRESS {
|
||||||
|
@Override
|
||||||
|
protected void setQueryParameterOn(UriBuilder builder) {
|
||||||
|
ResponseCodes.setQueryParameterOn(builder, Boolean.TRUE);
|
||||||
|
}
|
||||||
|
}, UNSUPPRESSED {
|
||||||
|
@Override
|
||||||
|
protected void setQueryParameterOn(UriBuilder builder) {
|
||||||
|
ResponseCodes.setQueryParameterOn(builder, Boolean.FALSE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the suppress_response_codes query parameter by removing all existing ones
|
||||||
|
* and then appending it on the given UriBuilder.
|
||||||
|
*/
|
||||||
|
protected abstract void setQueryParameterOn(UriBuilder builder);
|
||||||
|
|
||||||
|
private static void setQueryParameterOn(UriBuilder builder, Boolean value) {
|
||||||
|
// The Live SDK is designed to use our value of suppress_response_codes.
|
||||||
|
// If it uses any other value it could cause issues. Remove any previously
|
||||||
|
// existing suppress_response_codes and use ours.
|
||||||
|
builder.removeQueryParametersWithKey(QueryParameters.SUPPRESS_RESPONSE_CODES);
|
||||||
|
builder.appendQueryParameter(QueryParameters.SUPPRESS_RESPONSE_CODES, value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Header LIVE_LIBRARY_HEADER =
|
||||||
|
new BasicHeader("X-HTTP-Live-Library", "android/" + Build.VERSION.RELEASE + "_" +
|
||||||
|
Config.INSTANCE.getApiVersion());
|
||||||
|
private static final int SESSION_REFRESH_BUFFER_SECS = 30;
|
||||||
|
private static final int SESSION_TOKEN_SEND_BUFFER_SECS = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new instance of a Header that contains the
|
||||||
|
* @param accessToken to construct inside the Authorization header
|
||||||
|
* @return a new instance of a Header that contains the Authorization access_token
|
||||||
|
*/
|
||||||
|
private static Header createAuthroizationHeader(LiveConnectSession session) {
|
||||||
|
assert session != null;
|
||||||
|
|
||||||
|
String accessToken = session.getAccessToken();
|
||||||
|
assert !TextUtils.isEmpty(accessToken);
|
||||||
|
|
||||||
|
String tokenType = OAuth.TokenType.BEARER.toString().toLowerCase();
|
||||||
|
String value = TextUtils.join(" ", new String[]{tokenType, accessToken});
|
||||||
|
return new BasicHeader(AUTH.WWW_AUTH_RESP, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final HttpClient client;
|
||||||
|
private final List<Observer> observers;
|
||||||
|
private final String path;
|
||||||
|
private final ResponseHandler<ResponseType> responseHandler;
|
||||||
|
private final LiveConnectSession session;
|
||||||
|
|
||||||
|
protected final UriBuilder requestUri;
|
||||||
|
|
||||||
|
/** The original path string parsed into a Uri object. */
|
||||||
|
protected final Uri pathUri;
|
||||||
|
|
||||||
|
public ApiRequest(LiveConnectSession session,
|
||||||
|
HttpClient client,
|
||||||
|
ResponseHandler<ResponseType> responseHandler,
|
||||||
|
String path) {
|
||||||
|
this(session, client, responseHandler, path, ResponseCodes.SUPPRESS, Redirects.SUPPRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new instance of an ApiRequest and initializes its member variables
|
||||||
|
*
|
||||||
|
* @param session that contains the access_token
|
||||||
|
* @param client to make Http Requests on
|
||||||
|
* @param responseHandler to handle the response
|
||||||
|
* @param path of the request. it can be relative or absolute.
|
||||||
|
*/
|
||||||
|
public ApiRequest(LiveConnectSession session,
|
||||||
|
HttpClient client,
|
||||||
|
ResponseHandler<ResponseType> responseHandler,
|
||||||
|
String path,
|
||||||
|
ResponseCodes responseCodes,
|
||||||
|
Redirects redirects) {
|
||||||
|
assert session != null;
|
||||||
|
assert client != null;
|
||||||
|
assert responseHandler != null;
|
||||||
|
assert !TextUtils.isEmpty(path);
|
||||||
|
|
||||||
|
this.session = session;
|
||||||
|
this.client = client;
|
||||||
|
this.observers = new ArrayList<Observer>();
|
||||||
|
this.responseHandler = responseHandler;
|
||||||
|
this.path = path;
|
||||||
|
|
||||||
|
UriBuilder builder;
|
||||||
|
this.pathUri = Uri.parse(path);
|
||||||
|
|
||||||
|
if (this.pathUri.isAbsolute()) {
|
||||||
|
// if the path is absolute we will just use that entire path
|
||||||
|
builder = UriBuilder.newInstance(this.pathUri);
|
||||||
|
} else {
|
||||||
|
// if it is a relative path then we should use the config's API URI,
|
||||||
|
// which is usually something like https://apis.live.net/v5.0
|
||||||
|
builder = UriBuilder.newInstance(Config.INSTANCE.getApiUri())
|
||||||
|
.appendToPath(this.pathUri.getEncodedPath())
|
||||||
|
.query(this.pathUri.getQuery());
|
||||||
|
}
|
||||||
|
|
||||||
|
responseCodes.setQueryParameterOn(builder);
|
||||||
|
redirects.setQueryParameterOn(builder);
|
||||||
|
|
||||||
|
this.requestUri = builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addObserver(Observer observer) {
|
||||||
|
this.observers.add(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the Http Request and returns the response from the server
|
||||||
|
*
|
||||||
|
* @return an instance of ResponseType from the server
|
||||||
|
* @throws LiveOperationException if there was an error executing the HttpRequest
|
||||||
|
*/
|
||||||
|
public ResponseType execute() throws LiveOperationException {
|
||||||
|
// Let subclass decide which type of request to instantiate
|
||||||
|
HttpUriRequest request = this.createHttpRequest();
|
||||||
|
|
||||||
|
request.addHeader(LIVE_LIBRARY_HEADER);
|
||||||
|
|
||||||
|
if (this.session.willExpireInSecs(SESSION_REFRESH_BUFFER_SECS)) {
|
||||||
|
this.session.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the session will soon expire, try to send the request without a token.
|
||||||
|
// the request *may* not need the token, let's give it a try rather than
|
||||||
|
// risk a request with an invalid token.
|
||||||
|
if (!this.session.willExpireInSecs(SESSION_TOKEN_SEND_BUFFER_SECS)) {
|
||||||
|
request.addHeader(createAuthroizationHeader(this.session));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
HttpResponse response = this.client.execute(request);
|
||||||
|
|
||||||
|
for (Observer observer : this.observers) {
|
||||||
|
observer.onComplete(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.responseHandler.handleResponse(response);
|
||||||
|
} catch (ClientProtocolException e) {
|
||||||
|
throw new LiveOperationException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// The IOException could contain a JSON object body
|
||||||
|
// (see InputStreamResponseHandler.java). If it does,
|
||||||
|
// we want to throw an exception with its message. If it does not, we want to wrap
|
||||||
|
// the IOException.
|
||||||
|
try {
|
||||||
|
new JSONObject(e.getMessage());
|
||||||
|
throw new LiveOperationException(e.getMessage());
|
||||||
|
} catch (JSONException jsonException) {
|
||||||
|
throw new LiveOperationException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the HTTP method being performed by the request */
|
||||||
|
public abstract String getMethod();
|
||||||
|
|
||||||
|
/** @return the path of the request */
|
||||||
|
public String getPath() {
|
||||||
|
return this.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeObserver(Observer observer) {
|
||||||
|
this.observers.remove(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method that allows subclasses to choose which type of request will
|
||||||
|
* be performed.
|
||||||
|
*
|
||||||
|
* @return the HttpRequest to perform
|
||||||
|
* @throws LiveOperationException if there is an error creating the HttpRequest
|
||||||
|
*/
|
||||||
|
protected abstract HttpUriRequest createHttpRequest() throws LiveOperationException;
|
||||||
|
}
|
@ -0,0 +1,175 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import com.microsoft.live.EntityEnclosingApiRequest.UploadProgressListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ApiRequestAsync performs an async ApiRequest by subclassing AsyncTask
|
||||||
|
* and executing the request inside of doInBackground and giving the
|
||||||
|
* response to the appropriate listener on the main/UI thread.
|
||||||
|
*/
|
||||||
|
class ApiRequestAsync<ResponseType> extends AsyncTask<Void, Long, Runnable>
|
||||||
|
implements UploadProgressListener {
|
||||||
|
|
||||||
|
public interface Observer<ResponseType> {
|
||||||
|
public void onComplete(ResponseType result);
|
||||||
|
|
||||||
|
public void onError(LiveOperationException e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ProgressObserver {
|
||||||
|
public void onProgress(Long... values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OnCompleteRunnable implements Runnable {
|
||||||
|
|
||||||
|
private final ResponseType response;
|
||||||
|
|
||||||
|
public OnCompleteRunnable(ResponseType response) {
|
||||||
|
assert response != null;
|
||||||
|
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (Observer<ResponseType> observer : observers) {
|
||||||
|
observer.onComplete(this.response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OnErrorRunnable implements Runnable {
|
||||||
|
|
||||||
|
private final LiveOperationException exception;
|
||||||
|
|
||||||
|
public OnErrorRunnable(LiveOperationException exception) {
|
||||||
|
assert exception != null;
|
||||||
|
|
||||||
|
this.exception = exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (Observer<ResponseType> observer : observers) {
|
||||||
|
observer.onError(this.exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static constructor. Prefer to use this over the normal constructor, because
|
||||||
|
* this will infer the generic types, and be less verbose.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @return a new ApiRequestAsync
|
||||||
|
*/
|
||||||
|
public static <T> ApiRequestAsync<T> newInstance(ApiRequest<T> request) {
|
||||||
|
return new ApiRequestAsync<T>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static constructor. Prefer to use this over the normal constructor, because
|
||||||
|
* this will infer the generic types, and be less verbose.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @return a new ApiRequestAsync
|
||||||
|
*/
|
||||||
|
public static <T> ApiRequestAsync<T> newInstance(EntityEnclosingApiRequest<T> request) {
|
||||||
|
return new ApiRequestAsync<T>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ArrayList<Observer<ResponseType>> observers;
|
||||||
|
private final ArrayList<ProgressObserver> progressListeners;
|
||||||
|
private final ApiRequest<ResponseType> request;
|
||||||
|
|
||||||
|
{
|
||||||
|
this.observers = new ArrayList<Observer<ResponseType>>();
|
||||||
|
this.progressListeners = new ArrayList<ProgressObserver>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new ApiRequestAsync object and initializes its member variables.
|
||||||
|
*
|
||||||
|
* This method attaches a progress observer to the EntityEnclosingApiRequest, and call
|
||||||
|
* publicProgress when ever there is an on progress event.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
*/
|
||||||
|
public ApiRequestAsync(EntityEnclosingApiRequest<ResponseType> request) {
|
||||||
|
assert request != null;
|
||||||
|
|
||||||
|
// Whenever the request has upload progress we need to publish the progress, so
|
||||||
|
// listen to progress events.
|
||||||
|
request.addListener(this);
|
||||||
|
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new ApiRequestAsync object and initializes its member variables.
|
||||||
|
*
|
||||||
|
* @param operation to launch in an asynchronous manner
|
||||||
|
*/
|
||||||
|
public ApiRequestAsync(ApiRequest<ResponseType> request) {
|
||||||
|
assert request != null;
|
||||||
|
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addObserver(Observer<ResponseType> observer) {
|
||||||
|
return this.observers.add(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addProgressObserver(ProgressObserver observer) {
|
||||||
|
return this.progressListeners.add(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgress(long totalBytes, long numBytesWritten) {
|
||||||
|
publishProgress(Long.valueOf(totalBytes), Long.valueOf(numBytesWritten));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeObserver(Observer<ResponseType> observer) {
|
||||||
|
return this.observers.remove(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeProgressObserver(ProgressObserver observer) {
|
||||||
|
return this.progressListeners.remove(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Runnable doInBackground(Void... args) {
|
||||||
|
ResponseType response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = this.request.execute();
|
||||||
|
} catch (LiveOperationException e) {
|
||||||
|
return new OnErrorRunnable(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OnCompleteRunnable(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Runnable result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
result.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onProgressUpdate(Long... values) {
|
||||||
|
for (ProgressObserver listener : this.progressListeners) {
|
||||||
|
listener.onProgress(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,507 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.DialogInterface.OnCancelListener;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.SharedPreferences.Editor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.net.http.SslError;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup.LayoutParams;
|
||||||
|
import android.webkit.CookieManager;
|
||||||
|
import android.webkit.CookieSyncManager;
|
||||||
|
import android.webkit.SslErrorHandler;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AuthorizationRequest performs an Authorization Request by launching a WebView Dialog that
|
||||||
|
* displays the login and consent page and then, on a successful login and consent, performs an
|
||||||
|
* async AccessToken request.
|
||||||
|
*/
|
||||||
|
class AuthorizationRequest implements ObservableOAuthRequest, OAuthRequestObserver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuthDialog is a Dialog that contains a WebView. The WebView loads the passed in Uri, and
|
||||||
|
* loads the passed in WebViewClient that allows the WebView to be observed (i.e., when a page
|
||||||
|
* loads the WebViewClient will be notified).
|
||||||
|
*/
|
||||||
|
private class OAuthDialog extends Dialog implements OnCancelListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AuthorizationWebViewClient is a static (i.e., does not have access to the instance that
|
||||||
|
* created it) class that checks for when the end_uri is loaded in to the WebView and calls
|
||||||
|
* the AuthorizationRequest's onEndUri method.
|
||||||
|
*/
|
||||||
|
private class AuthorizationWebViewClient extends WebViewClient {
|
||||||
|
|
||||||
|
private final CookieManager cookieManager;
|
||||||
|
private final Set<String> cookieKeys;
|
||||||
|
|
||||||
|
public AuthorizationWebViewClient() {
|
||||||
|
// I believe I need to create a syncManager before I can use a cookie manager.
|
||||||
|
CookieSyncManager.createInstance(getContext());
|
||||||
|
this.cookieManager = CookieManager.getInstance();
|
||||||
|
this.cookieKeys = new HashSet<String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call back used when a page is being started.
|
||||||
|
*
|
||||||
|
* This will check to see if the given URL is one of the end_uris/redirect_uris and
|
||||||
|
* based on the query parameters the method will either return an error, or proceed with
|
||||||
|
* an AccessTokenRequest.
|
||||||
|
*
|
||||||
|
* @param view {@link WebView} that this is attached to.
|
||||||
|
* @param url of the page being started
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(WebView view, String url) {
|
||||||
|
Uri uri = Uri.parse(url);
|
||||||
|
|
||||||
|
// only clear cookies that are on the logout domain.
|
||||||
|
if (uri.getHost().equals(Config.INSTANCE.getOAuthLogoutUri().getHost())) {
|
||||||
|
this.saveCookiesInMemory(this.cookieManager.getCookie(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri endUri = Config.INSTANCE.getOAuthDesktopUri();
|
||||||
|
boolean isEndUri = UriComparator.INSTANCE.compare(uri, endUri) == 0;
|
||||||
|
if (!isEndUri) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveCookiesToPreferences();
|
||||||
|
|
||||||
|
AuthorizationRequest.this.onEndUri(uri);
|
||||||
|
OAuthDialog.this.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when the WebView received an Error.
|
||||||
|
*
|
||||||
|
* This method will notify the listener about the error and dismiss the WebView dialog.
|
||||||
|
*
|
||||||
|
* @param view the WebView that received the error
|
||||||
|
* @param errorCode the error code corresponding to a WebViewClient.ERROR_* value
|
||||||
|
* @param description the String containing the description of the error
|
||||||
|
* @param failingUrl the url that encountered an error
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onReceivedError(WebView view,
|
||||||
|
int errorCode,
|
||||||
|
String description,
|
||||||
|
String failingUrl) {
|
||||||
|
AuthorizationRequest.this.onError("", description, failingUrl);
|
||||||
|
OAuthDialog.this.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
||||||
|
// TODO: Android does not like the SSL certificate we use, because it has '*' in
|
||||||
|
// it. Proceed with the errors.
|
||||||
|
handler.proceed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveCookiesInMemory(String cookie) {
|
||||||
|
// Not all URLs will have cookies
|
||||||
|
if (TextUtils.isEmpty(cookie)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] pairs = TextUtils.split(cookie, "; ");
|
||||||
|
for (String pair : pairs) {
|
||||||
|
int index = pair.indexOf(EQUALS);
|
||||||
|
String key = pair.substring(0, index);
|
||||||
|
this.cookieKeys.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveCookiesToPreferences() {
|
||||||
|
SharedPreferences preferences =
|
||||||
|
getContext().getSharedPreferences(PreferencesConstants.FILE_NAME,
|
||||||
|
Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
// If the application tries to login twice, before calling logout, there could
|
||||||
|
// be a cookie that was sent on the first login, that was not sent in the second
|
||||||
|
// login. So, read the cookies in that was saved before, and perform a union
|
||||||
|
// with the new cookies.
|
||||||
|
String value = preferences.getString(PreferencesConstants.COOKIES_KEY, "");
|
||||||
|
String[] valueSplit = TextUtils.split(value, PreferencesConstants.COOKIE_DELIMITER);
|
||||||
|
|
||||||
|
this.cookieKeys.addAll(Arrays.asList(valueSplit));
|
||||||
|
|
||||||
|
Editor editor = preferences.edit();
|
||||||
|
value = TextUtils.join(PreferencesConstants.COOKIE_DELIMITER, this.cookieKeys);
|
||||||
|
editor.putString(PreferencesConstants.COOKIES_KEY, value);
|
||||||
|
editor.commit();
|
||||||
|
|
||||||
|
// we do not need to hold on to the cookieKeys in memory anymore.
|
||||||
|
// It could be garbage collected when this object does, but let's clear it now,
|
||||||
|
// since it will not be used again in the future.
|
||||||
|
this.cookieKeys.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Uri to load */
|
||||||
|
private final Uri requestUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new OAuthDialog.
|
||||||
|
*
|
||||||
|
* @param context to construct the Dialog in
|
||||||
|
* @param requestUri to load in the WebView
|
||||||
|
* @param webViewClient to be placed in the WebView
|
||||||
|
*/
|
||||||
|
public OAuthDialog(Uri requestUri) {
|
||||||
|
super(AuthorizationRequest.this.activity, android.R.style.Theme_Translucent_NoTitleBar);
|
||||||
|
this.setOwnerActivity(AuthorizationRequest.this.activity);
|
||||||
|
|
||||||
|
assert requestUri != null;
|
||||||
|
this.requestUri = requestUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when the user hits the back button on the dialog. */
|
||||||
|
@Override
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
LiveAuthException exception = new LiveAuthException(ErrorMessages.SIGNIN_CANCEL);
|
||||||
|
AuthorizationRequest.this.onException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
this.setOnCancelListener(this);
|
||||||
|
|
||||||
|
FrameLayout content = new FrameLayout(this.getContext());
|
||||||
|
LinearLayout webViewContainer = new LinearLayout(this.getContext());
|
||||||
|
WebView webView = new WebView(this.getContext());
|
||||||
|
|
||||||
|
webView.setWebViewClient(new AuthorizationWebViewClient());
|
||||||
|
|
||||||
|
WebSettings webSettings = webView.getSettings();
|
||||||
|
webSettings.setJavaScriptEnabled(true);
|
||||||
|
|
||||||
|
webView.loadUrl(this.requestUri.toString());
|
||||||
|
webView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
|
||||||
|
LayoutParams.FILL_PARENT));
|
||||||
|
webView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
webViewContainer.addView(webView);
|
||||||
|
webViewContainer.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
content.addView(webViewContainer);
|
||||||
|
content.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
content.forceLayout();
|
||||||
|
webViewContainer.forceLayout();
|
||||||
|
|
||||||
|
this.addContentView(content,
|
||||||
|
new LayoutParams(LayoutParams.FILL_PARENT,
|
||||||
|
LayoutParams.FILL_PARENT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares just the scheme, authority, and path. It does not compare the query parameters or
|
||||||
|
* the fragment.
|
||||||
|
*/
|
||||||
|
private enum UriComparator implements Comparator<Uri> {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Uri lhs, Uri rhs) {
|
||||||
|
String[] lhsParts = { lhs.getScheme(), lhs.getAuthority(), lhs.getPath() };
|
||||||
|
String[] rhsParts = { rhs.getScheme(), rhs.getAuthority(), rhs.getPath() };
|
||||||
|
|
||||||
|
assert lhsParts.length == rhsParts.length;
|
||||||
|
for (int i = 0; i < lhsParts.length; i++) {
|
||||||
|
int compare = lhsParts[i].compareTo(rhsParts[i]);
|
||||||
|
if (compare != 0) {
|
||||||
|
return compare;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String AMPERSAND = "&";
|
||||||
|
private static final String EQUALS = "=";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns the fragment parameters of the uri into a map.
|
||||||
|
*
|
||||||
|
* @param uri to get fragment parameters from
|
||||||
|
* @return a map containing the fragment parameters
|
||||||
|
*/
|
||||||
|
private static Map<String, String> getFragmentParametersMap(Uri uri) {
|
||||||
|
String fragment = uri.getFragment();
|
||||||
|
String[] keyValuePairs = TextUtils.split(fragment, AMPERSAND);
|
||||||
|
Map<String, String> fragementParameters = new HashMap<String, String>();
|
||||||
|
|
||||||
|
for (String keyValuePair : keyValuePairs) {
|
||||||
|
int index = keyValuePair.indexOf(EQUALS);
|
||||||
|
String key = keyValuePair.substring(0, index);
|
||||||
|
String value = keyValuePair.substring(index + 1);
|
||||||
|
fragementParameters.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fragementParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Activity activity;
|
||||||
|
private final HttpClient client;
|
||||||
|
private final String clientId;
|
||||||
|
private final DefaultObservableOAuthRequest observable;
|
||||||
|
private final String redirectUri;
|
||||||
|
private final String scope;
|
||||||
|
|
||||||
|
public AuthorizationRequest(Activity activity,
|
||||||
|
HttpClient client,
|
||||||
|
String clientId,
|
||||||
|
String redirectUri,
|
||||||
|
String scope) {
|
||||||
|
assert activity != null;
|
||||||
|
assert client != null;
|
||||||
|
assert !TextUtils.isEmpty(clientId);
|
||||||
|
assert !TextUtils.isEmpty(redirectUri);
|
||||||
|
assert !TextUtils.isEmpty(scope);
|
||||||
|
|
||||||
|
this.activity = activity;
|
||||||
|
this.client = client;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.redirectUri = redirectUri;
|
||||||
|
this.observable = new DefaultObservableOAuthRequest();
|
||||||
|
this.scope = scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addObserver(OAuthRequestObserver observer) {
|
||||||
|
this.observable.addObserver(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launches the login/consent page inside of a Dialog that contains a WebView and then performs
|
||||||
|
* a AccessTokenRequest on successful login and consent. This method is async and will call the
|
||||||
|
* passed in listener when it is completed.
|
||||||
|
*/
|
||||||
|
public void execute() {
|
||||||
|
String displayType = this.getDisplayParameter();
|
||||||
|
String responseType = OAuth.ResponseType.CODE.toString().toLowerCase();
|
||||||
|
String locale = Locale.getDefault().toString();
|
||||||
|
Uri requestUri = Config.INSTANCE.getOAuthAuthorizeUri()
|
||||||
|
.buildUpon()
|
||||||
|
.appendQueryParameter(OAuth.CLIENT_ID, this.clientId)
|
||||||
|
.appendQueryParameter(OAuth.SCOPE, this.scope)
|
||||||
|
.appendQueryParameter(OAuth.DISPLAY, displayType)
|
||||||
|
.appendQueryParameter(OAuth.RESPONSE_TYPE, responseType)
|
||||||
|
.appendQueryParameter(OAuth.LOCALE, locale)
|
||||||
|
.appendQueryParameter(OAuth.REDIRECT_URI, this.redirectUri)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
OAuthDialog oAuthDialog = new OAuthDialog(requestUri);
|
||||||
|
oAuthDialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(LiveAuthException exception) {
|
||||||
|
this.observable.notifyObservers(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(OAuthResponse response) {
|
||||||
|
this.observable.notifyObservers(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeObserver(OAuthRequestObserver observer) {
|
||||||
|
return this.observable.removeObserver(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the display parameter by looking at the screen size of the activity.
|
||||||
|
* @return "android_phone" for phones and "android_tablet" for tablets.
|
||||||
|
*/
|
||||||
|
private String getDisplayParameter() {
|
||||||
|
ScreenSize screenSize = ScreenSize.determineScreenSize(this.activity);
|
||||||
|
DeviceType deviceType = screenSize.getDeviceType();
|
||||||
|
|
||||||
|
return deviceType.getDisplayParameter().toString().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the response uri contains an access_token in the fragment.
|
||||||
|
*
|
||||||
|
* This method reads the response and calls back the LiveOAuthListener on the UI/main thread,
|
||||||
|
* and then dismisses the dialog window.
|
||||||
|
*
|
||||||
|
* See <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-1.3.1">Section
|
||||||
|
* 1.3.1</a> of the OAuth 2.0 spec.
|
||||||
|
*
|
||||||
|
* @param fragmentParameters in the uri
|
||||||
|
*/
|
||||||
|
private void onAccessTokenResponse(Map<String, String> fragmentParameters) {
|
||||||
|
assert fragmentParameters != null;
|
||||||
|
|
||||||
|
OAuthSuccessfulResponse response;
|
||||||
|
try {
|
||||||
|
response = OAuthSuccessfulResponse.createFromFragment(fragmentParameters);
|
||||||
|
} catch (LiveAuthException e) {
|
||||||
|
this.onException(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the response uri contains an authorization code.
|
||||||
|
*
|
||||||
|
* This method launches an async AccessTokenRequest and dismisses the dialog window.
|
||||||
|
*
|
||||||
|
* See <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-4.1.2">Section
|
||||||
|
* 4.1.2</a> of the OAuth 2.0 spec for more information.
|
||||||
|
*
|
||||||
|
* @param code is the authorization code from the uri
|
||||||
|
*/
|
||||||
|
private void onAuthorizationResponse(String code) {
|
||||||
|
assert !TextUtils.isEmpty(code);
|
||||||
|
|
||||||
|
// Since we DO have an authorization code, launch an AccessTokenRequest.
|
||||||
|
// We do this asynchronously to prevent the HTTP IO from occupying the
|
||||||
|
// UI/main thread (which we are on right now).
|
||||||
|
AccessTokenRequest request = new AccessTokenRequest(this.client,
|
||||||
|
this.clientId,
|
||||||
|
this.redirectUri,
|
||||||
|
code);
|
||||||
|
|
||||||
|
TokenRequestAsync requestAsync = new TokenRequestAsync(request);
|
||||||
|
// We want to know when this request finishes, because we need to notify our
|
||||||
|
// observers.
|
||||||
|
requestAsync.addObserver(this);
|
||||||
|
requestAsync.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the end uri is loaded.
|
||||||
|
*
|
||||||
|
* This method will read the uri's query parameters and fragment, and respond with the
|
||||||
|
* appropriate action.
|
||||||
|
*
|
||||||
|
* @param endUri that was loaded
|
||||||
|
*/
|
||||||
|
private void onEndUri(Uri endUri) {
|
||||||
|
// If we are on an end uri, the response could either be in
|
||||||
|
// the fragment or the query parameters. The response could
|
||||||
|
// either be successful or it could contain an error.
|
||||||
|
// Check all situations and call the listener's appropriate callback.
|
||||||
|
// Callback the listener on the UI/main thread. We could call it right away since
|
||||||
|
// we are on the UI/main thread, but it is probably better that we finish up with
|
||||||
|
// the WebView code before we callback on the listener.
|
||||||
|
boolean hasFragment = endUri.getFragment() != null;
|
||||||
|
boolean hasQueryParameters = endUri.getQuery() != null;
|
||||||
|
boolean invalidUri = !hasFragment && !hasQueryParameters;
|
||||||
|
|
||||||
|
// check for an invalid uri, and leave early
|
||||||
|
if (invalidUri) {
|
||||||
|
this.onInvalidUri();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFragment) {
|
||||||
|
Map<String, String> fragmentParameters =
|
||||||
|
AuthorizationRequest.getFragmentParametersMap(endUri);
|
||||||
|
|
||||||
|
boolean isSuccessfulResponse =
|
||||||
|
fragmentParameters.containsKey(OAuth.ACCESS_TOKEN) &&
|
||||||
|
fragmentParameters.containsKey(OAuth.TOKEN_TYPE);
|
||||||
|
if (isSuccessfulResponse) {
|
||||||
|
this.onAccessTokenResponse(fragmentParameters);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String error = fragmentParameters.get(OAuth.ERROR);
|
||||||
|
if (error != null) {
|
||||||
|
String errorDescription = fragmentParameters.get(OAuth.ERROR_DESCRIPTION);
|
||||||
|
String errorUri = fragmentParameters.get(OAuth.ERROR_URI);
|
||||||
|
this.onError(error, errorDescription, errorUri);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasQueryParameters) {
|
||||||
|
String code = endUri.getQueryParameter(OAuth.CODE);
|
||||||
|
if (code != null) {
|
||||||
|
this.onAuthorizationResponse(code);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String error = endUri.getQueryParameter(OAuth.ERROR);
|
||||||
|
if (error != null) {
|
||||||
|
String errorDescription = endUri.getQueryParameter(OAuth.ERROR_DESCRIPTION);
|
||||||
|
String errorUri = endUri.getQueryParameter(OAuth.ERROR_URI);
|
||||||
|
this.onError(error, errorDescription, errorUri);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the code reaches this point, the uri was invalid
|
||||||
|
// because it did not contain either a successful response
|
||||||
|
// or an error in either the queryParameter or the fragment
|
||||||
|
this.onInvalidUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when end uri had an error in either the fragment or the query parameter.
|
||||||
|
*
|
||||||
|
* This method constructs the proper exception, calls the listener's appropriate callback method
|
||||||
|
* on the main/UI thread, and then dismisses the dialog window.
|
||||||
|
*
|
||||||
|
* @param error containing an error code
|
||||||
|
* @param errorDescription optional text with additional information
|
||||||
|
* @param errorUri optional uri that is associated with the error.
|
||||||
|
*/
|
||||||
|
private void onError(String error, String errorDescription, String errorUri) {
|
||||||
|
LiveAuthException exception = new LiveAuthException(error,
|
||||||
|
errorDescription,
|
||||||
|
errorUri);
|
||||||
|
this.onException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an invalid uri (i.e., a uri that does not contain an error or a successful
|
||||||
|
* response).
|
||||||
|
*
|
||||||
|
* This method constructs an exception, calls the listener's appropriate callback on the main/UI
|
||||||
|
* thread, and then dismisses the dialog window.
|
||||||
|
*/
|
||||||
|
private void onInvalidUri() {
|
||||||
|
LiveAuthException exception = new LiveAuthException(ErrorMessages.SERVER_ERROR);
|
||||||
|
this.onException(exception);
|
||||||
|
}
|
||||||
|
}
|
89
src/java/JavaFileStorage/src/com/microsoft/live/Config.java
Normal file
89
src/java/JavaFileStorage/src/com/microsoft/live/Config.java
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config is a singleton class that contains the values used throughout the SDK.
|
||||||
|
*/
|
||||||
|
enum Config {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
private Uri apiUri;
|
||||||
|
private String apiVersion;
|
||||||
|
private Uri oAuthAuthorizeUri;
|
||||||
|
private Uri oAuthDesktopUri;
|
||||||
|
private Uri oAuthLogoutUri;
|
||||||
|
private Uri oAuthTokenUri;
|
||||||
|
|
||||||
|
Config() {
|
||||||
|
// initialize default values for constants
|
||||||
|
apiUri = Uri.parse("https://apis.live.net/v5.0");
|
||||||
|
apiVersion = "5.0";
|
||||||
|
oAuthAuthorizeUri = Uri.parse("https://login.live.com/oauth20_authorize.srf");
|
||||||
|
oAuthDesktopUri = Uri.parse("https://login.live.com/oauth20_desktop.srf");
|
||||||
|
oAuthLogoutUri = Uri.parse("https://login.live.com/oauth20_logout.srf");
|
||||||
|
oAuthTokenUri = Uri.parse("https://login.live.com/oauth20_token.srf");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getApiUri() {
|
||||||
|
return apiUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getApiVersion() {
|
||||||
|
return apiVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getOAuthAuthorizeUri() {
|
||||||
|
return oAuthAuthorizeUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getOAuthDesktopUri() {
|
||||||
|
return oAuthDesktopUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getOAuthLogoutUri() {
|
||||||
|
return oAuthLogoutUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getOAuthTokenUri() {
|
||||||
|
return oAuthTokenUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApiUri(Uri apiUri) {
|
||||||
|
assert apiUri != null;
|
||||||
|
this.apiUri = apiUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApiVersion(String apiVersion) {
|
||||||
|
assert !TextUtils.isEmpty(apiVersion);
|
||||||
|
this.apiVersion = apiVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOAuthAuthorizeUri(Uri oAuthAuthorizeUri) {
|
||||||
|
assert oAuthAuthorizeUri != null;
|
||||||
|
this.oAuthAuthorizeUri = oAuthAuthorizeUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOAuthDesktopUri(Uri oAuthDesktopUri) {
|
||||||
|
assert oAuthDesktopUri != null;
|
||||||
|
this.oAuthDesktopUri = oAuthDesktopUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOAuthLogoutUri(Uri oAuthLogoutUri) {
|
||||||
|
assert oAuthLogoutUri != null;
|
||||||
|
|
||||||
|
this.oAuthLogoutUri = oAuthLogoutUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOAuthTokenUri(Uri oAuthTokenUri) {
|
||||||
|
assert oAuthTokenUri != null;
|
||||||
|
this.oAuthTokenUri = oAuthTokenUri;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CopyRequest is a subclass of a BodyEnclosingApiRequest and performs a Copy request.
|
||||||
|
*/
|
||||||
|
class CopyRequest extends EntityEnclosingApiRequest<JSONObject> {
|
||||||
|
|
||||||
|
public static final String METHOD = HttpCopy.METHOD_NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new CopyRequest and initializes its member variables.
|
||||||
|
*
|
||||||
|
* @param session with the access_token
|
||||||
|
* @param client to make Http requests on
|
||||||
|
* @param path of the request
|
||||||
|
* @param entity body of the request
|
||||||
|
*/
|
||||||
|
public CopyRequest(LiveConnectSession session,
|
||||||
|
HttpClient client,
|
||||||
|
String path,
|
||||||
|
HttpEntity entity) {
|
||||||
|
super(session, client, JsonResponseHandler.INSTANCE, path, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the string "COPY" */
|
||||||
|
@Override
|
||||||
|
public String getMethod() {
|
||||||
|
return METHOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method override that constructs a HttpCopy and adds a body to it.
|
||||||
|
*
|
||||||
|
* @return a HttpCopy with the properly body added to it.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected HttpUriRequest createHttpRequest() throws LiveOperationException {
|
||||||
|
final HttpCopy request = new HttpCopy(this.requestUri.toString());
|
||||||
|
|
||||||
|
request.setEntity(this.entity);
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of an ObserverableOAuthRequest.
|
||||||
|
* Other classes that need to be observed can compose themselves out of this class.
|
||||||
|
*/
|
||||||
|
class DefaultObservableOAuthRequest implements ObservableOAuthRequest {
|
||||||
|
|
||||||
|
private final List<OAuthRequestObserver> observers;
|
||||||
|
|
||||||
|
public DefaultObservableOAuthRequest() {
|
||||||
|
this.observers = new ArrayList<OAuthRequestObserver>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addObserver(OAuthRequestObserver observer) {
|
||||||
|
this.observers.add(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls all the Observerable's observer's onException.
|
||||||
|
*
|
||||||
|
* @param exception to give to the observers
|
||||||
|
*/
|
||||||
|
public void notifyObservers(LiveAuthException exception) {
|
||||||
|
for (final OAuthRequestObserver observer : this.observers) {
|
||||||
|
observer.onException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls all this Observable's observer's onResponse.
|
||||||
|
*
|
||||||
|
* @param response to give to the observers
|
||||||
|
*/
|
||||||
|
public void notifyObservers(OAuthResponse response) {
|
||||||
|
for (final OAuthRequestObserver observer : this.observers) {
|
||||||
|
observer.onResponse(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeObserver(OAuthRequestObserver observer) {
|
||||||
|
return this.observers.remove(observer);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpDelete;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeleteRequest is a subclass of an ApiRequest and performs a delete request.
|
||||||
|
*/
|
||||||
|
class DeleteRequest extends ApiRequest<JSONObject> {
|
||||||
|
|
||||||
|
public static final String METHOD = HttpDelete.METHOD_NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new DeleteRequest and initializes its member variables.
|
||||||
|
*
|
||||||
|
* @param session with the access_token
|
||||||
|
* @param client to perform Http requests on
|
||||||
|
* @param path of the request
|
||||||
|
*/
|
||||||
|
public DeleteRequest(LiveConnectSession session, HttpClient client, String path) {
|
||||||
|
super(session, client, JsonResponseHandler.INSTANCE, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the string "DELETE" */
|
||||||
|
@Override
|
||||||
|
public String getMethod() {
|
||||||
|
return METHOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method override that constructs a HttpDelete request
|
||||||
|
*
|
||||||
|
* @return a HttpDelete request
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected HttpUriRequest createHttpRequest() {
|
||||||
|
return new HttpDelete(this.requestUri.toString());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import com.microsoft.live.OAuth.DisplayType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the device is used to determine the display query parameter for login.live.com.
|
||||||
|
* Phones have a display parameter of android_phone.
|
||||||
|
* Tablets have a display parameter of android_tablet.
|
||||||
|
*/
|
||||||
|
enum DeviceType {
|
||||||
|
PHONE {
|
||||||
|
@Override
|
||||||
|
public DisplayType getDisplayParameter() {
|
||||||
|
return DisplayType.ANDROID_PHONE;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TABLET {
|
||||||
|
@Override
|
||||||
|
public DisplayType getDisplayParameter() {
|
||||||
|
return DisplayType.ANDROID_TABLET;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
abstract public DisplayType getDisplayParameter();
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
|
||||||
|
class DownloadRequest extends ApiRequest<InputStream> {
|
||||||
|
|
||||||
|
public static final String METHOD = HttpGet.METHOD_NAME;
|
||||||
|
|
||||||
|
public DownloadRequest(LiveConnectSession session, HttpClient client, String path) {
|
||||||
|
super(session,
|
||||||
|
client,
|
||||||
|
InputStreamResponseHandler.INSTANCE,
|
||||||
|
path,
|
||||||
|
ResponseCodes.UNSUPPRESSED,
|
||||||
|
Redirects.UNSUPPRESSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMethod() {
|
||||||
|
return METHOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HttpUriRequest createHttpRequest() throws LiveOperationException {
|
||||||
|
return new HttpGet(this.requestUri.toString());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,184 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.io.FilterOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.ResponseHandler;
|
||||||
|
import org.apache.http.entity.HttpEntityWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EntityEnclosingApiRequest is an ApiRequest with a body.
|
||||||
|
* Upload progress can be monitored by adding an UploadProgressListener to this class.
|
||||||
|
*/
|
||||||
|
abstract class EntityEnclosingApiRequest<ResponseType> extends ApiRequest<ResponseType> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UploadProgressListener is a listener that is called during upload progress.
|
||||||
|
*/
|
||||||
|
public interface UploadProgressListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param totalBytes of the upload request
|
||||||
|
* @param numBytesWritten during the upload request
|
||||||
|
*/
|
||||||
|
public void onProgress(long totalBytes, long numBytesWritten);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps the given entity, and intercepts writeTo calls to check the upload progress.
|
||||||
|
*/
|
||||||
|
private static class ProgressableEntity extends HttpEntityWrapper {
|
||||||
|
|
||||||
|
final List<UploadProgressListener> listeners;
|
||||||
|
|
||||||
|
ProgressableEntity(HttpEntity wrapped, List<UploadProgressListener> listeners) {
|
||||||
|
super(wrapped);
|
||||||
|
|
||||||
|
assert listeners != null;
|
||||||
|
this.listeners = listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(OutputStream outstream) throws IOException {
|
||||||
|
this.wrappedEntity.writeTo(new ProgressableOutputStream(outstream,
|
||||||
|
this.getContentLength(),
|
||||||
|
this.listeners));
|
||||||
|
// If we don't consume the content, the content will be leaked (i.e., the InputStream
|
||||||
|
// in the HttpEntity is not closed).
|
||||||
|
// You'd think the library would call this.
|
||||||
|
this.wrappedEntity.consumeContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps the given output stream and notifies the given listeners, when the
|
||||||
|
* stream is written to.
|
||||||
|
*/
|
||||||
|
private static class ProgressableOutputStream extends FilterOutputStream {
|
||||||
|
|
||||||
|
final List<UploadProgressListener> listeners;
|
||||||
|
long numBytesWritten;
|
||||||
|
long totalBytes;
|
||||||
|
|
||||||
|
public ProgressableOutputStream(OutputStream outstream,
|
||||||
|
long totalBytes,
|
||||||
|
List<UploadProgressListener> listeners) {
|
||||||
|
super(outstream);
|
||||||
|
|
||||||
|
assert totalBytes >= 0L;
|
||||||
|
assert listeners != null;
|
||||||
|
|
||||||
|
this.listeners = listeners;
|
||||||
|
this.numBytesWritten = 0L;
|
||||||
|
this.totalBytes = totalBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buffer) throws IOException {
|
||||||
|
this.out.write(buffer);
|
||||||
|
|
||||||
|
this.numBytesWritten += buffer.length;
|
||||||
|
this.notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buffer, int offset, int count) throws IOException {
|
||||||
|
this.out.write(buffer, offset, count);
|
||||||
|
|
||||||
|
this.numBytesWritten += count;
|
||||||
|
this.notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int oneByte) throws IOException {
|
||||||
|
this.out.write(oneByte);
|
||||||
|
|
||||||
|
this.numBytesWritten += 1;
|
||||||
|
this.notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyListeners() {
|
||||||
|
assert this.numBytesWritten <= this.totalBytes;
|
||||||
|
|
||||||
|
for (final UploadProgressListener listener : this.listeners) {
|
||||||
|
listener.onProgress(this.totalBytes, this.numBytesWritten);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final HttpEntity entity;
|
||||||
|
|
||||||
|
private final List<UploadProgressListener> listeners;
|
||||||
|
|
||||||
|
public EntityEnclosingApiRequest(LiveConnectSession session,
|
||||||
|
HttpClient client,
|
||||||
|
ResponseHandler<ResponseType> responseHandler,
|
||||||
|
String path,
|
||||||
|
HttpEntity entity) {
|
||||||
|
this(session,
|
||||||
|
client,
|
||||||
|
responseHandler,
|
||||||
|
path,
|
||||||
|
entity,
|
||||||
|
ResponseCodes.SUPPRESS,
|
||||||
|
Redirects.SUPPRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new EntiyEnclosingApiRequest and initializes its member variables.
|
||||||
|
*
|
||||||
|
* @param session that contains the access token
|
||||||
|
* @param client to make Http Requests on
|
||||||
|
* @param path of the request
|
||||||
|
* @param entity of the request
|
||||||
|
*/
|
||||||
|
public EntityEnclosingApiRequest(LiveConnectSession session,
|
||||||
|
HttpClient client,
|
||||||
|
ResponseHandler<ResponseType> responseHandler,
|
||||||
|
String path,
|
||||||
|
HttpEntity entity,
|
||||||
|
ResponseCodes responseCodes,
|
||||||
|
Redirects redirects) {
|
||||||
|
super(session, client, responseHandler, path, responseCodes, redirects);
|
||||||
|
|
||||||
|
assert entity != null;
|
||||||
|
|
||||||
|
this.listeners = new ArrayList<UploadProgressListener>();
|
||||||
|
this.entity = new ProgressableEntity(entity, this.listeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an UploadProgressListener to be called when there is upload progress.
|
||||||
|
*
|
||||||
|
* @param listener to add
|
||||||
|
* @return always true
|
||||||
|
*/
|
||||||
|
public boolean addListener(UploadProgressListener listener) {
|
||||||
|
assert listener != null;
|
||||||
|
|
||||||
|
return this.listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an UploadProgressListener.
|
||||||
|
*
|
||||||
|
* @param listener to be removed
|
||||||
|
* @return true if the the listener was removed
|
||||||
|
*/
|
||||||
|
public boolean removeListener(UploadProgressListener listener) {
|
||||||
|
assert listener != null;
|
||||||
|
|
||||||
|
return this.listeners.remove(listener);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ErrorMessages is a non-instantiable class that contains all the String constants
|
||||||
|
* used in for errors and exceptions.
|
||||||
|
*/
|
||||||
|
final class ErrorMessages {
|
||||||
|
public static final String ABSOLUTE_PARAMETER =
|
||||||
|
"Input parameter '%1$s' is invalid. '%1$s' cannot be absolute.";
|
||||||
|
public static final String CLIENT_ERROR =
|
||||||
|
"An error occured on the client during the operation.";
|
||||||
|
public static final String EMPTY_PARAMETER =
|
||||||
|
"Input parameter '%1$s' is invalid. '%1$s' cannot be empty.";
|
||||||
|
public static final String INVALID_URI =
|
||||||
|
"Input parameter '%1$s' is invalid. '%1$s' must be a valid URI.";
|
||||||
|
public static final String LOGGED_OUT = "The user has is logged out.";
|
||||||
|
public static final String LOGIN_IN_PROGRESS =
|
||||||
|
"Another login operation is already in progress.";
|
||||||
|
public static final String MISSING_UPLOAD_LOCATION =
|
||||||
|
"The provided path does not contain an upload_location.";
|
||||||
|
public static final String NON_INSTANTIABLE_CLASS = "Non-instantiable class";
|
||||||
|
public static final String NULL_PARAMETER =
|
||||||
|
"Input parameter '%1$s' is invalid. '%1$s' cannot be null.";
|
||||||
|
public static final String SERVER_ERROR =
|
||||||
|
"An error occured while communicating with the server during the operation. " +
|
||||||
|
"Please try again later.";
|
||||||
|
public static final String SIGNIN_CANCEL = "The user cancelled the login operation.";
|
||||||
|
|
||||||
|
private ErrorMessages() { throw new AssertionError(NON_INSTANTIABLE_CLASS); }
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetRequest is a subclass of an ApiRequest and performs a GET request.
|
||||||
|
*/
|
||||||
|
class GetRequest extends ApiRequest<JSONObject> {
|
||||||
|
|
||||||
|
public static final String METHOD = HttpGet.METHOD_NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new GetRequest and initializes its member variables.
|
||||||
|
*
|
||||||
|
* @param session with the access_token
|
||||||
|
* @param client to perform Http requests on
|
||||||
|
* @param path of the request
|
||||||
|
*/
|
||||||
|
public GetRequest(LiveConnectSession session, HttpClient client, String path) {
|
||||||
|
super(session, client, JsonResponseHandler.INSTANCE, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the string "GET" */
|
||||||
|
@Override
|
||||||
|
public String getMethod() {
|
||||||
|
return METHOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method override that constructs a HttpGet request
|
||||||
|
*
|
||||||
|
* @return a HttpGet request
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected HttpUriRequest createHttpRequest() {
|
||||||
|
return new HttpGet(this.requestUri.toString());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpCopy represents an HTTP COPY operation.
|
||||||
|
* HTTP COPY is not a standard HTTP method and this adds it
|
||||||
|
* to the HTTP library.
|
||||||
|
*/
|
||||||
|
class HttpCopy extends HttpEntityEnclosingRequestBase {
|
||||||
|
|
||||||
|
public static final String METHOD_NAME = "COPY";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new HttpCopy with the given uri and initializes its member variables.
|
||||||
|
*
|
||||||
|
* @param uri of the request
|
||||||
|
*/
|
||||||
|
public HttpCopy(String uri) {
|
||||||
|
try {
|
||||||
|
this.setURI(new URI(uri));
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
final String message = String.format(ErrorMessages.INVALID_URI, "uri");
|
||||||
|
throw new IllegalArgumentException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the string "COPY" */
|
||||||
|
@Override
|
||||||
|
public String getMethod() {
|
||||||
|
return METHOD_NAME;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpMove represents an HTTP MOVE operation.
|
||||||
|
* HTTP MOVE is not a standard HTTP method and this adds it
|
||||||
|
* to the HTTP library.
|
||||||
|
*/
|
||||||
|
class HttpMove extends HttpEntityEnclosingRequestBase {
|
||||||
|
|
||||||
|
public static final String METHOD_NAME = "MOVE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new HttpMove with the given uri and initializes its member variables.
|
||||||
|
*
|
||||||
|
* @param uri of the request
|
||||||
|
*/
|
||||||
|
public HttpMove(String uri) {
|
||||||
|
try {
|
||||||
|
this.setURI(new URI(uri));
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
final String message = String.format(ErrorMessages.INVALID_URI, "uri");
|
||||||
|
throw new IllegalArgumentException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the string "MOVE" */
|
||||||
|
@Override
|
||||||
|
public String getMethod() {
|
||||||
|
return METHOD_NAME;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.StatusLine;
|
||||||
|
import org.apache.http.client.ClientProtocolException;
|
||||||
|
import org.apache.http.client.ResponseHandler;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InputStreamResponseHandler returns an InputStream from an HttpResponse.
|
||||||
|
* Singleton--use INSTANCE.
|
||||||
|
*/
|
||||||
|
enum InputStreamResponseHandler implements ResponseHandler<InputStream> {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream handleResponse(HttpResponse response) throws ClientProtocolException,
|
||||||
|
IOException {
|
||||||
|
HttpEntity entity = response.getEntity();
|
||||||
|
StatusLine statusLine = response.getStatusLine();
|
||||||
|
boolean successfulResponse = (statusLine.getStatusCode() / 100) == 2;
|
||||||
|
if (!successfulResponse) {
|
||||||
|
// If it was not a successful response, the response body contains a
|
||||||
|
// JSON error message body. Unfortunately, I have to adhere to the interface
|
||||||
|
// and I am throwing an IOException in this case.
|
||||||
|
String responseBody = EntityUtils.toString(entity);
|
||||||
|
throw new IOException(responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity.getContent();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
|
import org.apache.http.entity.StringEntity;
|
||||||
|
import org.apache.http.protocol.HTTP;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JsonEntity is an Entity that contains a Json body
|
||||||
|
*/
|
||||||
|
class JsonEntity extends StringEntity {
|
||||||
|
|
||||||
|
public static final String CONTENT_TYPE = "application/json;charset=" + HTTP.UTF_8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new JsonEntity.
|
||||||
|
*
|
||||||
|
* @param body
|
||||||
|
* @throws UnsupportedEncodingException
|
||||||
|
*/
|
||||||
|
JsonEntity(JSONObject body) throws UnsupportedEncodingException {
|
||||||
|
super(body.toString(), HTTP.UTF_8);
|
||||||
|
|
||||||
|
this.setContentType(CONTENT_TYPE);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.ClientProtocolException;
|
||||||
|
import org.apache.http.client.ResponseHandler;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JsonResponseHandler returns a JSONObject from an HttpResponse.
|
||||||
|
* Singleton--use INSTANCE.
|
||||||
|
*/
|
||||||
|
enum JsonResponseHandler implements ResponseHandler<JSONObject> {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject handleResponse(HttpResponse response)
|
||||||
|
throws ClientProtocolException, IOException {
|
||||||
|
final HttpEntity entity = response.getEntity();
|
||||||
|
final String stringResponse;
|
||||||
|
if (entity != null) {
|
||||||
|
stringResponse = EntityUtils.toString(entity);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(stringResponse)) {
|
||||||
|
return new JSONObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new JSONObject(stringResponse);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new IOException(e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,640 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.SharedPreferences.Editor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.CookieManager;
|
||||||
|
import android.webkit.CookieSyncManager;
|
||||||
|
|
||||||
|
import com.microsoft.live.OAuth.ErrorType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code LiveAuthClient} is a class responsible for retrieving a {@link LiveConnectSession}, which
|
||||||
|
* can be given to a {@link LiveConnectClient} in order to make requests to the Live Connect API.
|
||||||
|
*/
|
||||||
|
public class LiveAuthClient {
|
||||||
|
|
||||||
|
private static class AuthCompleteRunnable extends AuthListenerCaller implements Runnable {
|
||||||
|
|
||||||
|
private final LiveStatus status;
|
||||||
|
private final LiveConnectSession session;
|
||||||
|
|
||||||
|
public AuthCompleteRunnable(LiveAuthListener listener,
|
||||||
|
Object userState,
|
||||||
|
LiveStatus status,
|
||||||
|
LiveConnectSession session) {
|
||||||
|
super(listener, userState);
|
||||||
|
this.status = status;
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
listener.onAuthComplete(status, session, userState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AuthErrorRunnable extends AuthListenerCaller implements Runnable {
|
||||||
|
|
||||||
|
private final LiveAuthException exception;
|
||||||
|
|
||||||
|
public AuthErrorRunnable(LiveAuthListener listener,
|
||||||
|
Object userState,
|
||||||
|
LiveAuthException exception) {
|
||||||
|
super(listener, userState);
|
||||||
|
this.exception = exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
listener.onAuthError(exception, userState);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abstract class AuthListenerCaller {
|
||||||
|
protected final LiveAuthListener listener;
|
||||||
|
protected final Object userState;
|
||||||
|
|
||||||
|
public AuthListenerCaller(LiveAuthListener listener, Object userState) {
|
||||||
|
this.listener = listener;
|
||||||
|
this.userState = userState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class observes an {@link OAuthRequest} and calls the appropriate Listener method.
|
||||||
|
* On a successful response, it will call the
|
||||||
|
* {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)}.
|
||||||
|
* On an exception or an unsuccessful response, it will call
|
||||||
|
* {@link LiveAuthListener#onAuthError(LiveAuthException, Object)}.
|
||||||
|
*/
|
||||||
|
private class ListenerCallerObserver extends AuthListenerCaller
|
||||||
|
implements OAuthRequestObserver,
|
||||||
|
OAuthResponseVisitor {
|
||||||
|
|
||||||
|
public ListenerCallerObserver(LiveAuthListener listener, Object userState) {
|
||||||
|
super(listener, userState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(LiveAuthException exception) {
|
||||||
|
new AuthErrorRunnable(listener, userState, exception).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(OAuthResponse response) {
|
||||||
|
response.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(OAuthErrorResponse response) {
|
||||||
|
String error = response.getError().toString().toLowerCase();
|
||||||
|
String errorDescription = response.getErrorDescription();
|
||||||
|
String errorUri = response.getErrorUri();
|
||||||
|
LiveAuthException exception = new LiveAuthException(error,
|
||||||
|
errorDescription,
|
||||||
|
errorUri);
|
||||||
|
|
||||||
|
new AuthErrorRunnable(listener, userState, exception).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(OAuthSuccessfulResponse response) {
|
||||||
|
session.loadFromOAuthResponse(response);
|
||||||
|
|
||||||
|
new AuthCompleteRunnable(listener, userState, LiveStatus.CONNECTED, session).run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Observer that will, depending on the response, save or clear the refresh token. */
|
||||||
|
private class RefreshTokenWriter implements OAuthRequestObserver, OAuthResponseVisitor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(LiveAuthException exception) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(OAuthResponse response) {
|
||||||
|
response.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(OAuthErrorResponse response) {
|
||||||
|
if (response.getError() == ErrorType.INVALID_GRANT) {
|
||||||
|
LiveAuthClient.this.clearRefreshTokenFromPreferences();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(OAuthSuccessfulResponse response) {
|
||||||
|
String refreshToken = response.getRefreshToken();
|
||||||
|
if (!TextUtils.isEmpty(refreshToken)) {
|
||||||
|
this.saveRefreshTokenToPerferences(refreshToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean saveRefreshTokenToPerferences(String refreshToken) {
|
||||||
|
assert !TextUtils.isEmpty(refreshToken);
|
||||||
|
Log.w("MYLIVE", "saveRefreshTokenToPerferences");
|
||||||
|
|
||||||
|
SharedPreferences settings =
|
||||||
|
applicationContext.getSharedPreferences(PreferencesConstants.FILE_NAME,
|
||||||
|
Context.MODE_PRIVATE);
|
||||||
|
Editor editor = settings.edit();
|
||||||
|
editor.putString(PreferencesConstants.REFRESH_TOKEN_KEY, refreshToken);
|
||||||
|
|
||||||
|
|
||||||
|
boolean res = editor.commit();
|
||||||
|
Log.w("MYLIVE", "saveRefreshTokenToPerferences done for token "+refreshToken+" res="+res);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link OAuthResponseVisitor} that checks the {@link OAuthResponse} and if it is a
|
||||||
|
* successful response, it loads the response into the given session.
|
||||||
|
*/
|
||||||
|
private static class SessionRefresher implements OAuthResponseVisitor {
|
||||||
|
|
||||||
|
private final LiveConnectSession session;
|
||||||
|
private boolean visitedSuccessfulResponse;
|
||||||
|
|
||||||
|
public SessionRefresher(LiveConnectSession session) {
|
||||||
|
assert session != null;
|
||||||
|
|
||||||
|
this.session = session;
|
||||||
|
this.visitedSuccessfulResponse = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(OAuthErrorResponse response) {
|
||||||
|
this.visitedSuccessfulResponse = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(OAuthSuccessfulResponse response) {
|
||||||
|
this.session.loadFromOAuthResponse(response);
|
||||||
|
this.visitedSuccessfulResponse = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean visitedSuccessfulResponse() {
|
||||||
|
return this.visitedSuccessfulResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A LiveAuthListener that does nothing on each of the call backs.
|
||||||
|
* This is used so when a null listener is passed in, this can be used, instead of null,
|
||||||
|
* to avoid if (listener == null) checks.
|
||||||
|
*/
|
||||||
|
private static final LiveAuthListener NULL_LISTENER = new LiveAuthListener() {
|
||||||
|
@Override
|
||||||
|
public void onAuthComplete(LiveStatus status, LiveConnectSession session, Object sender) { }
|
||||||
|
@Override
|
||||||
|
public void onAuthError(LiveAuthException exception, Object sender) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Context applicationContext;
|
||||||
|
private final String clientId;
|
||||||
|
private boolean hasPendingLoginRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for all network (i.e., HTTP) calls.
|
||||||
|
* Tests will want to change this to mock the network and HTTP responses.
|
||||||
|
* @see #setHttpClient(HttpClient)
|
||||||
|
*/
|
||||||
|
private HttpClient httpClient;
|
||||||
|
|
||||||
|
/** saved from initialize and used in the login call if login's scopes are null. */
|
||||||
|
private Set<String> scopesFromInitialize;
|
||||||
|
|
||||||
|
/** One-to-one relationship between LiveAuthClient and LiveConnectSession. */
|
||||||
|
private final LiveConnectSession session;
|
||||||
|
|
||||||
|
{
|
||||||
|
this.httpClient = new DefaultHttpClient();
|
||||||
|
this.hasPendingLoginRequest = false;
|
||||||
|
this.session = new LiveConnectSession(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new {@code LiveAuthClient} instance and initializes its member variables.
|
||||||
|
*
|
||||||
|
* @param context Context of the Application used to save any refresh_token.
|
||||||
|
* @param clientId The client_id of the Live Connect Application to login to.
|
||||||
|
*/
|
||||||
|
public LiveAuthClient(Context context, String clientId) {
|
||||||
|
LiveConnectUtils.assertNotNull(context, "context");
|
||||||
|
LiveConnectUtils.assertNotNullOrEmpty(clientId, "clientId");
|
||||||
|
|
||||||
|
this.applicationContext = context.getApplicationContext();
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the client_id of the Live Connect application. */
|
||||||
|
public String getClientId() {
|
||||||
|
return this.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@link LiveConnectSession} with the given scopes.
|
||||||
|
*
|
||||||
|
* The {@link LiveConnectSession} will be returned by calling
|
||||||
|
* {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)}.
|
||||||
|
* Otherwise, the {@link LiveAuthListener#onAuthError(LiveAuthException, Object)} will be
|
||||||
|
* called. These methods will be called on the main/UI thread.
|
||||||
|
*
|
||||||
|
* If the wl.offline_access scope is used, a refresh_token is stored in the given
|
||||||
|
* {@link Activity}'s {@link SharedPerfences}.
|
||||||
|
*
|
||||||
|
* @param scopes to initialize the {@link LiveConnectSession} with.
|
||||||
|
* See <a href="http://msdn.microsoft.com/en-us/library/hh243646.aspx">MSDN Live Connect
|
||||||
|
* Reference's Scopes and permissions</a> for a list of scopes and explanations.
|
||||||
|
* @param listener called on either completion or error during the initialize process.
|
||||||
|
*/
|
||||||
|
public void initialize(Iterable<String> scopes, LiveAuthListener listener) {
|
||||||
|
this.initialize(scopes, listener, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@link LiveConnectSession} with the given scopes.
|
||||||
|
*
|
||||||
|
* The {@link LiveConnectSession} will be returned by calling
|
||||||
|
* {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)}.
|
||||||
|
* Otherwise, the {@link LiveAuthListener#onAuthError(LiveAuthException, Object)} will be
|
||||||
|
* called. These methods will be called on the main/UI thread.
|
||||||
|
*
|
||||||
|
* If the wl.offline_access scope is used, a refresh_token is stored in the given
|
||||||
|
* {@link Activity}'s {@link SharedPerfences}.
|
||||||
|
*
|
||||||
|
* @param scopes to initialize the {@link LiveConnectSession} with.
|
||||||
|
* See <a href="http://msdn.microsoft.com/en-us/library/hh243646.aspx">MSDN Live Connect
|
||||||
|
* Reference's Scopes and permissions</a> for a list of scopes and explanations.
|
||||||
|
* @param listener called on either completion or error during the initialize process
|
||||||
|
* @param userState arbitrary object that is used to determine the caller of the method.
|
||||||
|
*/
|
||||||
|
public void initialize(Iterable<String> scopes, LiveAuthListener listener, Object userState) {
|
||||||
|
TokenRequestAsync asyncRequest = getInitializeRequest(scopes, listener,
|
||||||
|
userState);
|
||||||
|
if (asyncRequest == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncRequest.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeSynchronous(Iterable<String> scopes, LiveAuthListener listener, Object userState) {
|
||||||
|
TokenRequestAsync asyncRequest = getInitializeRequest(scopes, listener,
|
||||||
|
userState);
|
||||||
|
if (asyncRequest == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncRequest.executeSynchronous();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TokenRequestAsync getInitializeRequest(Iterable<String> scopes,
|
||||||
|
LiveAuthListener listener, Object userState) {
|
||||||
|
if (listener == null) {
|
||||||
|
listener = NULL_LISTENER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scopes == null) {
|
||||||
|
scopes = Arrays.asList(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy scopes for login
|
||||||
|
this.scopesFromInitialize = new HashSet<String>();
|
||||||
|
for (String scope : scopes) {
|
||||||
|
this.scopesFromInitialize.add(scope);
|
||||||
|
}
|
||||||
|
this.scopesFromInitialize = Collections.unmodifiableSet(this.scopesFromInitialize);
|
||||||
|
|
||||||
|
String refreshToken = this.getRefreshTokenFromPreferences();
|
||||||
|
|
||||||
|
if (refreshToken == null) {
|
||||||
|
listener.onAuthComplete(LiveStatus.UNKNOWN, null, userState);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshAccessTokenRequest request =
|
||||||
|
new RefreshAccessTokenRequest(this.httpClient,
|
||||||
|
this.clientId,
|
||||||
|
refreshToken,
|
||||||
|
TextUtils.join(OAuth.SCOPE_DELIMITER, scopes));
|
||||||
|
TokenRequestAsync asyncRequest = new TokenRequestAsync(request);
|
||||||
|
|
||||||
|
asyncRequest.addObserver(new ListenerCallerObserver(listener, userState));
|
||||||
|
asyncRequest.addObserver(new RefreshTokenWriter());
|
||||||
|
return asyncRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@link LiveConnectSession} with the given scopes.
|
||||||
|
*
|
||||||
|
* The {@link LiveConnectSession} will be returned by calling
|
||||||
|
* {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)}.
|
||||||
|
* Otherwise, the {@link LiveAuthListener#onAuthError(LiveAuthException, Object)} will be
|
||||||
|
* called. These methods will be called on the main/UI thread.
|
||||||
|
*
|
||||||
|
* If the wl.offline_access scope is used, a refresh_token is stored in the given
|
||||||
|
* {@link Activity}'s {@link SharedPerfences}.
|
||||||
|
*
|
||||||
|
* This initialize will use the last successfully used scopes from either a login or initialize.
|
||||||
|
*
|
||||||
|
* @param listener called on either completion or error during the initialize process.
|
||||||
|
*/
|
||||||
|
public void initialize(LiveAuthListener listener) {
|
||||||
|
this.initialize(listener, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@link LiveConnectSession} with the given scopes.
|
||||||
|
*
|
||||||
|
* The {@link LiveConnectSession} will be returned by calling
|
||||||
|
* {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)}.
|
||||||
|
* Otherwise, the {@link LiveAuthListener#onAuthError(LiveAuthException, Object)} will be
|
||||||
|
* called. These methods will be called on the main/UI thread.
|
||||||
|
*
|
||||||
|
* If the wl.offline_access scope is used, a refresh_token is stored in the given
|
||||||
|
* {@link Activity}'s {@link SharedPerfences}.
|
||||||
|
*
|
||||||
|
* This initialize will use the last successfully used scopes from either a login or initialize.
|
||||||
|
*
|
||||||
|
* @param listener called on either completion or error during the initialize process.
|
||||||
|
* @param userState arbitrary object that is used to determine the caller of the method.
|
||||||
|
*/
|
||||||
|
public void initialize(LiveAuthListener listener, Object userState) {
|
||||||
|
this.initialize(null, listener, userState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs in an user with the given scopes.
|
||||||
|
*
|
||||||
|
* login displays a {@link Dialog} that will prompt the
|
||||||
|
* user for a username and password, and ask for consent to use the given scopes.
|
||||||
|
* A {@link LiveConnectSession} will be returned by calling
|
||||||
|
* {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)}.
|
||||||
|
* Otherwise, the {@link LiveAuthListener#onAuthError(LiveAuthException, Object)} will be
|
||||||
|
* called. These methods will be called on the main/UI thread.
|
||||||
|
*
|
||||||
|
* @param activity {@link Activity} instance to display the Login dialog on.
|
||||||
|
* @param scopes to initialize the {@link LiveConnectSession} with.
|
||||||
|
* See <a href="http://msdn.microsoft.com/en-us/library/hh243646.aspx">MSDN Live Connect
|
||||||
|
* Reference's Scopes and permissions</a> for a list of scopes and explanations.
|
||||||
|
* @param listener called on either completion or error during the login process.
|
||||||
|
* @throws IllegalStateException if there is a pending login request.
|
||||||
|
*/
|
||||||
|
public void login(Activity activity, Iterable<String> scopes, LiveAuthListener listener) {
|
||||||
|
this.login(activity, scopes, listener, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs in an user with the given scopes.
|
||||||
|
*
|
||||||
|
* login displays a {@link Dialog} that will prompt the
|
||||||
|
* user for a username and password, and ask for consent to use the given scopes.
|
||||||
|
* A {@link LiveConnectSession} will be returned by calling
|
||||||
|
* {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)}.
|
||||||
|
* Otherwise, the {@link LiveAuthListener#onAuthError(LiveAuthException, Object)} will be
|
||||||
|
* called. These methods will be called on the main/UI thread.
|
||||||
|
*
|
||||||
|
* @param activity {@link Activity} instance to display the Login dialog on
|
||||||
|
* @param scopes to initialize the {@link LiveConnectSession} with.
|
||||||
|
* See <a href="http://msdn.microsoft.com/en-us/library/hh243646.aspx">MSDN Live Connect
|
||||||
|
* Reference's Scopes and permissions</a> for a list of scopes and explanations.
|
||||||
|
* @param listener called on either completion or error during the login process.
|
||||||
|
* @param userState arbitrary object that is used to determine the caller of the method.
|
||||||
|
* @throws IllegalStateException if there is a pending login request.
|
||||||
|
*/
|
||||||
|
public void login(Activity activity,
|
||||||
|
Iterable<String> scopes,
|
||||||
|
LiveAuthListener listener,
|
||||||
|
Object userState) {
|
||||||
|
LiveConnectUtils.assertNotNull(activity, "activity");
|
||||||
|
|
||||||
|
if (listener == null) {
|
||||||
|
listener = NULL_LISTENER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasPendingLoginRequest) {
|
||||||
|
throw new IllegalStateException(ErrorMessages.LOGIN_IN_PROGRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no scopes were passed in, use the scopes from initialize or if those are empty,
|
||||||
|
// create an empty list
|
||||||
|
if (scopes == null) {
|
||||||
|
if (this.scopesFromInitialize == null) {
|
||||||
|
scopes = Arrays.asList(new String[0]);
|
||||||
|
} else {
|
||||||
|
scopes = this.scopesFromInitialize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the session is valid and contains all the scopes, do not display the login ui.
|
||||||
|
boolean showDialog = this.session.isExpired() ||
|
||||||
|
!this.session.contains(scopes);
|
||||||
|
if (!showDialog) {
|
||||||
|
listener.onAuthComplete(LiveStatus.CONNECTED, this.session, userState);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String scope = TextUtils.join(OAuth.SCOPE_DELIMITER, scopes);
|
||||||
|
String redirectUri = Config.INSTANCE.getOAuthDesktopUri().toString();
|
||||||
|
AuthorizationRequest request = new AuthorizationRequest(activity,
|
||||||
|
this.httpClient,
|
||||||
|
this.clientId,
|
||||||
|
redirectUri,
|
||||||
|
scope);
|
||||||
|
|
||||||
|
request.addObserver(new ListenerCallerObserver(listener, userState));
|
||||||
|
request.addObserver(new RefreshTokenWriter());
|
||||||
|
request.addObserver(new OAuthRequestObserver() {
|
||||||
|
@Override
|
||||||
|
public void onException(LiveAuthException exception) {
|
||||||
|
LiveAuthClient.this.hasPendingLoginRequest = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(OAuthResponse response) {
|
||||||
|
LiveAuthClient.this.hasPendingLoginRequest = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.hasPendingLoginRequest = true;
|
||||||
|
|
||||||
|
request.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs out the given user.
|
||||||
|
*
|
||||||
|
* Also, this method clears the previously created {@link LiveConnectSession}.
|
||||||
|
* {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)} will be
|
||||||
|
* called on completion. Otherwise,
|
||||||
|
* {@link LiveAuthListener#onAuthError(LiveAuthException, Object)} will be called.
|
||||||
|
*
|
||||||
|
* @param listener called on either completion or error during the logout process.
|
||||||
|
*/
|
||||||
|
public void logout(LiveAuthListener listener) {
|
||||||
|
this.logout(listener, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs out the given user.
|
||||||
|
*
|
||||||
|
* Also, this method clears the previously created {@link LiveConnectSession}.
|
||||||
|
* {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)} will be
|
||||||
|
* called on completion. Otherwise,
|
||||||
|
* {@link LiveAuthListener#onAuthError(LiveAuthException, Object)} will be called.
|
||||||
|
*
|
||||||
|
* @param listener called on either completion or error during the logout process.
|
||||||
|
* @param userState arbitrary object that is used to determine the caller of the method.
|
||||||
|
*/
|
||||||
|
public void logout(LiveAuthListener listener, Object userState) {
|
||||||
|
if (listener == null) {
|
||||||
|
listener = NULL_LISTENER;
|
||||||
|
}
|
||||||
|
|
||||||
|
session.setAccessToken(null);
|
||||||
|
session.setAuthenticationToken(null);
|
||||||
|
session.setRefreshToken(null);
|
||||||
|
session.setScopes(null);
|
||||||
|
session.setTokenType(null);
|
||||||
|
|
||||||
|
clearRefreshTokenFromPreferences();
|
||||||
|
|
||||||
|
CookieSyncManager cookieSyncManager =
|
||||||
|
CookieSyncManager.createInstance(this.applicationContext);
|
||||||
|
CookieManager manager = CookieManager.getInstance();
|
||||||
|
Uri logoutUri = Config.INSTANCE.getOAuthLogoutUri();
|
||||||
|
String url = logoutUri.toString();
|
||||||
|
String domain = logoutUri.getHost();
|
||||||
|
|
||||||
|
List<String> cookieKeys = this.getCookieKeysFromPreferences();
|
||||||
|
for (String cookieKey : cookieKeys) {
|
||||||
|
String value = TextUtils.join("", new String[] {
|
||||||
|
cookieKey,
|
||||||
|
"=; expires=Thu, 30-Oct-1980 16:00:00 GMT;domain=",
|
||||||
|
domain,
|
||||||
|
";path=/;version=1"
|
||||||
|
});
|
||||||
|
|
||||||
|
manager.setCookie(url, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
cookieSyncManager.sync();
|
||||||
|
listener.onAuthComplete(LiveStatus.UNKNOWN, null, userState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return The {@link HttpClient} instance used by this {@code LiveAuthClient}. */
|
||||||
|
HttpClient getHttpClient() {
|
||||||
|
return this.httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return The {@link LiveConnectSession} instance that this {@code LiveAuthClient} created. */
|
||||||
|
LiveConnectSession getSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the previously created session.
|
||||||
|
*
|
||||||
|
* @return true if the session was successfully refreshed.
|
||||||
|
*/
|
||||||
|
boolean refresh() {
|
||||||
|
String scope = TextUtils.join(OAuth.SCOPE_DELIMITER, this.session.getScopes());
|
||||||
|
String refreshToken = this.session.getRefreshToken();
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(refreshToken)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshAccessTokenRequest request =
|
||||||
|
new RefreshAccessTokenRequest(this.httpClient, this.clientId, refreshToken, scope);
|
||||||
|
|
||||||
|
OAuthResponse response;
|
||||||
|
try {
|
||||||
|
response = request.execute();
|
||||||
|
} catch (LiveAuthException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionRefresher refresher = new SessionRefresher(this.session);
|
||||||
|
response.accept(refresher);
|
||||||
|
response.accept(new RefreshTokenWriter());
|
||||||
|
|
||||||
|
return refresher.visitedSuccessfulResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link HttpClient} that is used for HTTP requests by this {@code LiveAuthClient}.
|
||||||
|
* Tests will want to change this to mock the network/HTTP responses.
|
||||||
|
* @param client The new HttpClient to be set.
|
||||||
|
*/
|
||||||
|
void setHttpClient(HttpClient client) {
|
||||||
|
assert client != null;
|
||||||
|
this.httpClient = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the refresh token from this {@code LiveAuthClient}'s
|
||||||
|
* {@link Activity#getPreferences(int)}.
|
||||||
|
*
|
||||||
|
* @return true if the refresh token was successfully cleared.
|
||||||
|
*/
|
||||||
|
private boolean clearRefreshTokenFromPreferences() {
|
||||||
|
SharedPreferences settings = getSharedPreferences();
|
||||||
|
Editor editor = settings.edit();
|
||||||
|
editor.remove(PreferencesConstants.REFRESH_TOKEN_KEY);
|
||||||
|
|
||||||
|
return editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SharedPreferences getSharedPreferences() {
|
||||||
|
return applicationContext.getSharedPreferences(PreferencesConstants.FILE_NAME,
|
||||||
|
Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getCookieKeysFromPreferences() {
|
||||||
|
SharedPreferences settings = getSharedPreferences();
|
||||||
|
String cookieKeys = settings.getString(PreferencesConstants.COOKIES_KEY, "");
|
||||||
|
|
||||||
|
return Arrays.asList(TextUtils.split(cookieKeys, PreferencesConstants.COOKIE_DELIMITER));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the refresh token from this {@code LiveAuthClient}'s
|
||||||
|
* {@link Activity#getPreferences(int)}.
|
||||||
|
*
|
||||||
|
* @return the refresh token from persistent storage.
|
||||||
|
*/
|
||||||
|
private String getRefreshTokenFromPreferences() {
|
||||||
|
SharedPreferences settings = getSharedPreferences();
|
||||||
|
return settings.getString(PreferencesConstants.REFRESH_TOKEN_KEY, null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that an exception occurred during the Auth process.
|
||||||
|
*/
|
||||||
|
public class LiveAuthException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 3368677530670470856L;
|
||||||
|
|
||||||
|
private final String error;
|
||||||
|
private final String errorUri;
|
||||||
|
|
||||||
|
|
||||||
|
LiveAuthException(String errorMessage) {
|
||||||
|
super(errorMessage);
|
||||||
|
this.error = "";
|
||||||
|
this.errorUri = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveAuthException(String errorMessage, Throwable throwable) {
|
||||||
|
super(errorMessage, throwable);
|
||||||
|
this.error = "";
|
||||||
|
this.errorUri = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveAuthException(String error, String errorDescription, String errorUri) {
|
||||||
|
super(errorDescription);
|
||||||
|
|
||||||
|
assert error != null;
|
||||||
|
|
||||||
|
this.error = error;
|
||||||
|
this.errorUri = errorUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveAuthException(String error, String errorDescription, String errorUri, Throwable cause) {
|
||||||
|
super(errorDescription, cause);
|
||||||
|
|
||||||
|
assert error != null;
|
||||||
|
|
||||||
|
this.error = error;
|
||||||
|
this.errorUri = errorUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the authentication error.
|
||||||
|
*/
|
||||||
|
public String getError() {
|
||||||
|
return this.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the error URI.
|
||||||
|
*/
|
||||||
|
public String getErrorUri() {
|
||||||
|
return this.errorUri;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles callback methods for LiveAuthClient init, login, and logout methods.
|
||||||
|
* Returns the * status of the operation when onAuthComplete is called. If there was an error
|
||||||
|
* during the operation, onAuthError is called with the exception that was thrown.
|
||||||
|
*/
|
||||||
|
public interface LiveAuthListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when the operation completes successfully.
|
||||||
|
*
|
||||||
|
* @param status The {@link LiveStatus} for an operation. If successful, the status is
|
||||||
|
* CONNECTED. If unsuccessful, NOT_CONNECTED or UNKNOWN are returned.
|
||||||
|
* @param session The {@link LiveConnectSession} from the {@link LiveAuthClient}.
|
||||||
|
* @param userState An arbitrary object that is used to determine the caller of the method.
|
||||||
|
*/
|
||||||
|
public void onAuthComplete(LiveStatus status, LiveConnectSession session, Object userState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when the method call fails.
|
||||||
|
*
|
||||||
|
* @param exception The {@link LiveAuthException} error.
|
||||||
|
* @param userState An arbitrary object that is used to determine the caller of the method.
|
||||||
|
*/
|
||||||
|
public void onAuthError(LiveAuthException exception, Object userState);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,311 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.beans.PropertyChangeSupport;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a Live Connect session.
|
||||||
|
*/
|
||||||
|
public class LiveConnectSession {
|
||||||
|
|
||||||
|
private String accessToken;
|
||||||
|
private String authenticationToken;
|
||||||
|
|
||||||
|
/** Keeps track of all the listeners, and fires the property change events */
|
||||||
|
private final PropertyChangeSupport changeSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The LiveAuthClient that created this object.
|
||||||
|
* This is needed in order to perform a refresh request.
|
||||||
|
* There is a one-to-one relationship between the LiveConnectSession and LiveAuthClient.
|
||||||
|
*/
|
||||||
|
private final LiveAuthClient creator;
|
||||||
|
|
||||||
|
private Date expiresIn;
|
||||||
|
private String refreshToken;
|
||||||
|
private Set<String> scopes;
|
||||||
|
private String tokenType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructors a new LiveConnectSession, and sets its creator to the passed in
|
||||||
|
* LiveAuthClient. All other member variables are left uninitialized.
|
||||||
|
*
|
||||||
|
* @param creator
|
||||||
|
*/
|
||||||
|
LiveConnectSession(LiveAuthClient creator) {
|
||||||
|
assert creator != null;
|
||||||
|
|
||||||
|
this.creator = creator;
|
||||||
|
this.changeSupport = new PropertyChangeSupport(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@link PropertyChangeListener} to the session that receives notification when any
|
||||||
|
* property is changed.
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||||
|
if (listener == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changeSupport.addPropertyChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@link PropertyChangeListener} to the session that receives notification when a
|
||||||
|
* specific property is changed.
|
||||||
|
*
|
||||||
|
* @param propertyName
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
|
||||||
|
if (listener == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changeSupport.addPropertyChangeListener(propertyName, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The access token for the signed-in, connected user.
|
||||||
|
*/
|
||||||
|
public String getAccessToken() {
|
||||||
|
return this.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A user-specific token that provides information to an app so that it can validate
|
||||||
|
* the user.
|
||||||
|
*/
|
||||||
|
public String getAuthenticationToken() {
|
||||||
|
return this.authenticationToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The exact time when a session expires.
|
||||||
|
*/
|
||||||
|
public Date getExpiresIn() {
|
||||||
|
// Defensive copy
|
||||||
|
return new Date(this.expiresIn.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return An array of all PropertyChangeListeners for this session.
|
||||||
|
*/
|
||||||
|
public PropertyChangeListener[] getPropertyChangeListeners() {
|
||||||
|
return this.changeSupport.getPropertyChangeListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param propertyName
|
||||||
|
* @return An array of all PropertyChangeListeners for a specific property for this session.
|
||||||
|
*/
|
||||||
|
public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
|
||||||
|
return this.changeSupport.getPropertyChangeListeners(propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A user-specific refresh token that the app can use to refresh the access token.
|
||||||
|
*/
|
||||||
|
public String getRefreshToken() {
|
||||||
|
return this.refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The scopes that the user has consented to.
|
||||||
|
*/
|
||||||
|
public Iterable<String> getScopes() {
|
||||||
|
// Defensive copy is not necessary, because this.scopes is an unmodifiableSet
|
||||||
|
return this.scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The type of token.
|
||||||
|
*/
|
||||||
|
public String getTokenType() {
|
||||||
|
return this.tokenType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the session is expired.
|
||||||
|
*/
|
||||||
|
public boolean isExpired() {
|
||||||
|
if (this.expiresIn == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Date now = new Date();
|
||||||
|
|
||||||
|
return now.after(this.expiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a PropertyChangeListeners on a session.
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||||
|
if (listener == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changeSupport.removePropertyChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a PropertyChangeListener for a specific property on a session.
|
||||||
|
* @param propertyName
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
public void removePropertyChangeListener(String propertyName,
|
||||||
|
PropertyChangeListener listener) {
|
||||||
|
if (listener == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changeSupport.removePropertyChangeListener(propertyName, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("LiveConnectSession [accessToken=%s, authenticationToken=%s, expiresIn=%s, refreshToken=%s, scopes=%s, tokenType=%s]",
|
||||||
|
this.accessToken,
|
||||||
|
this.authenticationToken,
|
||||||
|
this.expiresIn,
|
||||||
|
this.refreshToken,
|
||||||
|
this.scopes,
|
||||||
|
this.tokenType);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean contains(Iterable<String> scopes) {
|
||||||
|
if (scopes == null) {
|
||||||
|
return true;
|
||||||
|
} else if (this.scopes == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String scope : scopes) {
|
||||||
|
if (!this.scopes.contains(scope)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills in the LiveConnectSession with the OAuthResponse.
|
||||||
|
* WARNING: The OAuthResponse must not contain OAuth.ERROR.
|
||||||
|
*
|
||||||
|
* @param response to load from
|
||||||
|
*/
|
||||||
|
void loadFromOAuthResponse(OAuthSuccessfulResponse response) {
|
||||||
|
this.accessToken = response.getAccessToken();
|
||||||
|
this.tokenType = response.getTokenType().toString().toLowerCase();
|
||||||
|
|
||||||
|
if (response.hasAuthenticationToken()) {
|
||||||
|
this.authenticationToken = response.getAuthenticationToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.hasExpiresIn()) {
|
||||||
|
final Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.add(Calendar.SECOND, response.getExpiresIn());
|
||||||
|
this.setExpiresIn(calendar.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.hasRefreshToken()) {
|
||||||
|
this.refreshToken = response.getRefreshToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.hasScope()) {
|
||||||
|
final String scopeString = response.getScope();
|
||||||
|
this.setScopes(Arrays.asList(scopeString.split(OAuth.SCOPE_DELIMITER)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes this LiveConnectSession
|
||||||
|
*
|
||||||
|
* @return true if it was able to refresh the refresh token.
|
||||||
|
*/
|
||||||
|
boolean refresh() {
|
||||||
|
return this.creator.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAccessToken(String accessToken) {
|
||||||
|
final String oldValue = this.accessToken;
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
|
||||||
|
this.changeSupport.firePropertyChange("accessToken", oldValue, this.accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAuthenticationToken(String authenticationToken) {
|
||||||
|
final String oldValue = this.authenticationToken;
|
||||||
|
this.authenticationToken = authenticationToken;
|
||||||
|
|
||||||
|
this.changeSupport.firePropertyChange("authenticationToken",
|
||||||
|
oldValue,
|
||||||
|
this.authenticationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setExpiresIn(Date expiresIn) {
|
||||||
|
final Date oldValue = this.expiresIn;
|
||||||
|
this.expiresIn = new Date(expiresIn.getTime());
|
||||||
|
|
||||||
|
this.changeSupport.firePropertyChange("expiresIn", oldValue, this.expiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRefreshToken(String refreshToken) {
|
||||||
|
final String oldValue = this.refreshToken;
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
|
|
||||||
|
this.changeSupport.firePropertyChange("refreshToken", oldValue, this.refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setScopes(Iterable<String> scopes) {
|
||||||
|
final Iterable<String> oldValue = this.scopes;
|
||||||
|
|
||||||
|
// Defensive copy
|
||||||
|
this.scopes = new HashSet<String>();
|
||||||
|
if (scopes != null) {
|
||||||
|
for (String scope : scopes) {
|
||||||
|
this.scopes.add(scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scopes = Collections.unmodifiableSet(this.scopes);
|
||||||
|
|
||||||
|
this.changeSupport.firePropertyChange("scopes", oldValue, this.scopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTokenType(String tokenType) {
|
||||||
|
final String oldValue = this.tokenType;
|
||||||
|
this.tokenType = tokenType;
|
||||||
|
|
||||||
|
this.changeSupport.firePropertyChange("tokenType", oldValue, this.tokenType);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean willExpireInSecs(int secs) {
|
||||||
|
final Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.add(Calendar.SECOND, secs);
|
||||||
|
|
||||||
|
final Date future = calendar.getTime();
|
||||||
|
|
||||||
|
// if add secs seconds to the current time and it is after the expired time
|
||||||
|
// then it is almost expired.
|
||||||
|
return future.after(this.expiresIn);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LiveConnectUtils is a non-instantiable utility class that contains various helper
|
||||||
|
* methods and constants.
|
||||||
|
*/
|
||||||
|
final class LiveConnectUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if the passed in Object is null, and throws a
|
||||||
|
* NullPointerException if it is.
|
||||||
|
*
|
||||||
|
* @param object to check
|
||||||
|
* @param parameterName name of the parameter that is used in the exception message
|
||||||
|
* @throws NullPointerException if the Object is null
|
||||||
|
*/
|
||||||
|
public static void assertNotNull(Object object, String parameterName) {
|
||||||
|
assert !TextUtils.isEmpty(parameterName);
|
||||||
|
|
||||||
|
if (object == null) {
|
||||||
|
final String message = String.format(ErrorMessages.NULL_PARAMETER, parameterName);
|
||||||
|
throw new NullPointerException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if the passed in is an empty string, and throws an
|
||||||
|
* IllegalArgumentException if it is.
|
||||||
|
*
|
||||||
|
* @param parameter to check
|
||||||
|
* @param parameterName name of the parameter that is used in the exception message
|
||||||
|
* @throws IllegalArgumentException if the parameter is empty
|
||||||
|
* @throws NullPointerException if the String is null
|
||||||
|
*/
|
||||||
|
public static void assertNotNullOrEmpty(String parameter, String parameterName) {
|
||||||
|
assert !TextUtils.isEmpty(parameterName);
|
||||||
|
|
||||||
|
assertNotNull(parameter, parameterName);
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(parameter)) {
|
||||||
|
final String message = String.format(ErrorMessages.EMPTY_PARAMETER, parameterName);
|
||||||
|
throw new IllegalArgumentException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private to prevent instantiation
|
||||||
|
*/
|
||||||
|
private LiveConnectUtils() { throw new AssertionError(ErrorMessages.NON_INSTANTIABLE_CLASS); }
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data returned from a download call to the Live Connect Representational State
|
||||||
|
* Transfer (REST) API.
|
||||||
|
*/
|
||||||
|
public class LiveDownloadOperation {
|
||||||
|
static class Builder {
|
||||||
|
private ApiRequestAsync<InputStream> apiRequestAsync;
|
||||||
|
private final String method;
|
||||||
|
private final String path;
|
||||||
|
private InputStream stream;
|
||||||
|
private Object userState;
|
||||||
|
|
||||||
|
public Builder(String method, String path) {
|
||||||
|
assert !TextUtils.isEmpty(method);
|
||||||
|
assert !TextUtils.isEmpty(path);
|
||||||
|
|
||||||
|
this.method = method;
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set if the operation to build is an async operation.
|
||||||
|
*
|
||||||
|
* @param apiRequestAsync
|
||||||
|
* @return this Builder
|
||||||
|
*/
|
||||||
|
public Builder apiRequestAsync(ApiRequestAsync<InputStream> apiRequestAsync) {
|
||||||
|
assert apiRequestAsync != null;
|
||||||
|
|
||||||
|
this.apiRequestAsync = apiRequestAsync;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveDownloadOperation build() {
|
||||||
|
return new LiveDownloadOperation(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder stream(InputStream stream) {
|
||||||
|
assert stream != null;
|
||||||
|
|
||||||
|
this.stream = stream;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder userState(Object userState) {
|
||||||
|
this.userState = userState;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ApiRequestAsync<InputStream> apiRequestAsync;
|
||||||
|
private int contentLength;
|
||||||
|
private final String method;
|
||||||
|
private final String path;
|
||||||
|
private InputStream stream;
|
||||||
|
private final Object userState;
|
||||||
|
|
||||||
|
LiveDownloadOperation(Builder builder) {
|
||||||
|
this.apiRequestAsync = builder.apiRequestAsync;
|
||||||
|
this.method = builder.method;
|
||||||
|
this.path = builder.path;
|
||||||
|
this.stream = builder.stream;
|
||||||
|
this.userState = builder.userState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
final boolean isCancelable = this.apiRequestAsync != null;
|
||||||
|
if (isCancelable) {
|
||||||
|
this.apiRequestAsync.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The type of HTTP method used to make the call.
|
||||||
|
*/
|
||||||
|
public String getMethod() {
|
||||||
|
return this.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The length of the stream.
|
||||||
|
*/
|
||||||
|
public int getContentLength() {
|
||||||
|
return this.contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The path for the stream object.
|
||||||
|
*/
|
||||||
|
public String getPath() {
|
||||||
|
return this.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The stream object that contains the downloaded file.
|
||||||
|
*/
|
||||||
|
public InputStream getStream() {
|
||||||
|
return this.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The user state.
|
||||||
|
*/
|
||||||
|
public Object getUserState() {
|
||||||
|
return this.userState;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setContentLength(int contentLength) {
|
||||||
|
assert contentLength >= 0;
|
||||||
|
|
||||||
|
this.contentLength = contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStream(InputStream stream) {
|
||||||
|
assert stream != null;
|
||||||
|
|
||||||
|
this.stream = stream;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents any functionality related to downloads that works with the Live Connect
|
||||||
|
* Representational State Transfer (REST) API.
|
||||||
|
*/
|
||||||
|
public interface LiveDownloadOperationListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the associated download operation call completes.
|
||||||
|
* @param operation The {@link LiveDownloadOperation} object.
|
||||||
|
*/
|
||||||
|
public void onDownloadCompleted(LiveDownloadOperation operation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the associated download operation call fails.
|
||||||
|
* @param exception The error returned by the REST operation call.
|
||||||
|
* @param operation The {@link LiveDownloadOperation} object.
|
||||||
|
*/
|
||||||
|
public void onDownloadFailed(LiveOperationException exception,
|
||||||
|
LiveDownloadOperation operation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the progression of the download.
|
||||||
|
* @param totalBytes The total bytes downloaded.
|
||||||
|
* @param bytesRemaining The bytes remaining to download.
|
||||||
|
* @param operation The {@link LiveDownloadOperation} object.
|
||||||
|
*/
|
||||||
|
public void onDownloadProgress(int totalBytes,
|
||||||
|
int bytesRemaining,
|
||||||
|
LiveDownloadOperation operation);
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data returned from the Live Connect Representational State Transfer (REST) API
|
||||||
|
* services.
|
||||||
|
*/
|
||||||
|
public class LiveOperation {
|
||||||
|
static class Builder {
|
||||||
|
private ApiRequestAsync<JSONObject> apiRequestAsync;
|
||||||
|
private final String method;
|
||||||
|
private final String path;
|
||||||
|
private JSONObject result;
|
||||||
|
private Object userState;
|
||||||
|
|
||||||
|
public Builder(String method, String path) {
|
||||||
|
assert !TextUtils.isEmpty(method);
|
||||||
|
assert !TextUtils.isEmpty(path);
|
||||||
|
|
||||||
|
this.method = method;
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set if the operation to build is an async operation.
|
||||||
|
*
|
||||||
|
* @param apiRequestAsync
|
||||||
|
* @return this Builder
|
||||||
|
*/
|
||||||
|
public Builder apiRequestAsync(ApiRequestAsync<JSONObject> apiRequestAsync) {
|
||||||
|
assert apiRequestAsync != null;
|
||||||
|
|
||||||
|
this.apiRequestAsync = apiRequestAsync;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveOperation build() {
|
||||||
|
return new LiveOperation(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder result(JSONObject result) {
|
||||||
|
assert result != null;
|
||||||
|
this.result = result;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder userState(Object userState) {
|
||||||
|
this.userState = userState;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ApiRequestAsync<JSONObject> apiRequestAsync;
|
||||||
|
private final String method;
|
||||||
|
private final String path;
|
||||||
|
private JSONObject result;
|
||||||
|
private final Object userState;
|
||||||
|
|
||||||
|
private LiveOperation(Builder builder) {
|
||||||
|
this.apiRequestAsync = builder.apiRequestAsync;
|
||||||
|
this.method = builder.method;
|
||||||
|
this.path = builder.path;
|
||||||
|
this.result = builder.result;
|
||||||
|
this.userState = builder.userState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cancels the pending request. */
|
||||||
|
public void cancel() {
|
||||||
|
final boolean isCancelable = this.apiRequestAsync != null;
|
||||||
|
if (isCancelable) {
|
||||||
|
this.apiRequestAsync.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The type of HTTP method used to make the call.
|
||||||
|
*/
|
||||||
|
public String getMethod() {
|
||||||
|
return this.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The path to which the call was made.
|
||||||
|
*/
|
||||||
|
public String getPath() {
|
||||||
|
return this.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The raw result of the operation in the requested format.
|
||||||
|
*/
|
||||||
|
public String getRawResult() {
|
||||||
|
JSONObject result = this.getResult();
|
||||||
|
if (result == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The JSON object that is the result of the requesting operation.
|
||||||
|
*/
|
||||||
|
public JSONObject getResult() {
|
||||||
|
return this.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The user state that was passed in.
|
||||||
|
*/
|
||||||
|
public Object getUserState() {
|
||||||
|
return this.userState;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setResult(JSONObject result) {
|
||||||
|
assert result != null;
|
||||||
|
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents errors that occur when making requests to the Representational State Transfer
|
||||||
|
* (REST) API.
|
||||||
|
*/
|
||||||
|
public class LiveOperationException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 4630383031651156731L;
|
||||||
|
|
||||||
|
LiveOperationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveOperationException(String message, Throwable e) {
|
||||||
|
super(message, e);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an operation finishes or has an error.
|
||||||
|
*/
|
||||||
|
public interface LiveOperationListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the associated Representational State Transfer (REST) API operation call
|
||||||
|
* completes.
|
||||||
|
* @param operation The {@link LiveOperation} object.
|
||||||
|
*/
|
||||||
|
public void onComplete(LiveOperation operation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the associated Representational State Transfer (REST) operation call fails.
|
||||||
|
* @param exception The error returned by the REST operation call.
|
||||||
|
* @param operation The {@link LiveOperation} object.
|
||||||
|
*/
|
||||||
|
public void onError(LiveOperationException exception, LiveOperation operation);
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the status of an auth operation.
|
||||||
|
*/
|
||||||
|
public enum LiveStatus {
|
||||||
|
/** The status is not known. */
|
||||||
|
UNKNOWN,
|
||||||
|
|
||||||
|
/** The session is connected. */
|
||||||
|
CONNECTED,
|
||||||
|
|
||||||
|
/** The user has not consented to the application. */
|
||||||
|
NOT_CONNECTED;
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents any functionality related to uploads that works with the Live Connect
|
||||||
|
* Representational State Transfer (REST) API.
|
||||||
|
*/
|
||||||
|
public interface LiveUploadOperationListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the associated upload operation call completes.
|
||||||
|
* @param operation The {@link LiveOperation} object.
|
||||||
|
*/
|
||||||
|
public void onUploadCompleted(LiveOperation operation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the associated upload operation call fails.
|
||||||
|
* @param exception The error returned by the REST operation call.
|
||||||
|
* @param operation The {@link LiveOperation} object.
|
||||||
|
*/
|
||||||
|
public void onUploadFailed(LiveOperationException exception, LiveOperation operation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called arbitrarily during the progress of the upload request.
|
||||||
|
* @param totalBytes The total bytes downloaded.
|
||||||
|
* @param bytesRemaining The bytes remaining to download.
|
||||||
|
* @param operation The {@link LiveOperation} object.
|
||||||
|
*/
|
||||||
|
public void onUploadProgress(int totalBytes, int bytesRemaining, LiveOperation operation);
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MoveRequest is a subclass of a BodyEnclosingApiRequest and performs a Move request.
|
||||||
|
*/
|
||||||
|
class MoveRequest extends EntityEnclosingApiRequest<JSONObject> {
|
||||||
|
|
||||||
|
public static final String METHOD = HttpMove.METHOD_NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new MoveRequest and initializes its member variables.
|
||||||
|
*
|
||||||
|
* @param session with the access_token
|
||||||
|
* @param client to make Http requests on
|
||||||
|
* @param path of the request
|
||||||
|
* @param entity body of the request
|
||||||
|
*/
|
||||||
|
public MoveRequest(LiveConnectSession session,
|
||||||
|
HttpClient client,
|
||||||
|
String path,
|
||||||
|
HttpEntity entity) {
|
||||||
|
super(session, client, JsonResponseHandler.INSTANCE, path, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the string "MOVE" */
|
||||||
|
@Override
|
||||||
|
public String getMethod() {
|
||||||
|
return METHOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method override that constructs a HttpMove and adds a body to it.
|
||||||
|
*
|
||||||
|
* @return a HttpMove with the properly body added to it.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected HttpUriRequest createHttpRequest() throws LiveOperationException {
|
||||||
|
final HttpMove request = new HttpMove(this.requestUri.toString());
|
||||||
|
|
||||||
|
request.setEntity(this.entity);
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
214
src/java/JavaFileStorage/src/com/microsoft/live/OAuth.java
Normal file
214
src/java/JavaFileStorage/src/com/microsoft/live/OAuth.java
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth is a non-instantiable utility class that contains types and constants
|
||||||
|
* for the OAuth protocol.
|
||||||
|
*
|
||||||
|
* See the <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22">OAuth 2.0 spec</a>
|
||||||
|
* for more information.
|
||||||
|
*/
|
||||||
|
final class OAuth {
|
||||||
|
|
||||||
|
public enum DisplayType {
|
||||||
|
ANDROID_PHONE,
|
||||||
|
ANDROID_TABLET
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ErrorType {
|
||||||
|
/**
|
||||||
|
* Client authentication failed (e.g. unknown client, no
|
||||||
|
* client authentication included, or unsupported
|
||||||
|
* authentication method). The authorization server MAY
|
||||||
|
* return an HTTP 401 (Unauthorized) status code to indicate
|
||||||
|
* which HTTP authentication schemes are supported. If the
|
||||||
|
* client attempted to authenticate via the "Authorization"
|
||||||
|
* request header field, the authorization server MUST
|
||||||
|
* respond with an HTTP 401 (Unauthorized) status code, and
|
||||||
|
* include the "WWW-Authenticate" response header field
|
||||||
|
* matching the authentication scheme used by the client.
|
||||||
|
*/
|
||||||
|
INVALID_CLIENT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The provided authorization grant (e.g. authorization
|
||||||
|
* code, resource owner credentials, client credentials) is
|
||||||
|
* invalid, expired, revoked, does not match the redirection
|
||||||
|
* URI used in the authorization request, or was issued to
|
||||||
|
* another client.
|
||||||
|
*/
|
||||||
|
INVALID_GRANT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request is missing a required parameter, includes an
|
||||||
|
* unsupported parameter value, repeats a parameter,
|
||||||
|
* includes multiple credentials, utilizes more than one
|
||||||
|
* mechanism for authenticating the client, or is otherwise
|
||||||
|
* malformed.
|
||||||
|
*/
|
||||||
|
INVALID_REQUEST,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The requested scope is invalid, unknown, malformed, or
|
||||||
|
* exceeds the scope granted by the resource owner.
|
||||||
|
*/
|
||||||
|
INVALID_SCOPE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authenticated client is not authorized to use this
|
||||||
|
* authorization grant type.
|
||||||
|
*/
|
||||||
|
UNAUTHORIZED_CLIENT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authorization grant type is not supported by the
|
||||||
|
* authorization server.
|
||||||
|
*/
|
||||||
|
UNSUPPORTED_GRANT_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GrantType {
|
||||||
|
AUTHORIZATION_CODE,
|
||||||
|
CLIENT_CREDENTIALS,
|
||||||
|
PASSWORD,
|
||||||
|
REFRESH_TOKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ResponseType {
|
||||||
|
CODE,
|
||||||
|
TOKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TokenType {
|
||||||
|
BEARER
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key for the access_token parameter.
|
||||||
|
*
|
||||||
|
* See <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-5.1">Section 5.1</a>
|
||||||
|
* of the OAuth 2.0 spec for more information.
|
||||||
|
*/
|
||||||
|
public static final String ACCESS_TOKEN = "access_token";
|
||||||
|
|
||||||
|
/** The app's authentication token. */
|
||||||
|
public static final String AUTHENTICATION_TOKEN = "authentication_token";
|
||||||
|
|
||||||
|
/** The app's client ID. */
|
||||||
|
public static final String CLIENT_ID = "client_id";
|
||||||
|
|
||||||
|
/** Equivalent to the profile that is described in the OAuth 2.0 protocol spec. */
|
||||||
|
public static final String CODE = "code";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The display type to be used for the authorization page. Valid values are
|
||||||
|
* "popup", "touch", "page", or "none".
|
||||||
|
*/
|
||||||
|
public static final String DISPLAY = "display";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key for the error parameter.
|
||||||
|
*
|
||||||
|
* error can have the following values:
|
||||||
|
* invalid_request, unauthorized_client, access_denied, unsupported_response_type,
|
||||||
|
* invalid_scope, server_error, or temporarily_unavailable.
|
||||||
|
*/
|
||||||
|
public static final String ERROR = "error";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key for the error_description parameter. error_description is described below.
|
||||||
|
*
|
||||||
|
* OPTIONAL. A human-readable UTF-8 encoded text providing
|
||||||
|
* additional information, used to assist the client developer in
|
||||||
|
* understanding the error that occurred.
|
||||||
|
*/
|
||||||
|
public static final String ERROR_DESCRIPTION = "error_description";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key for the error_uri parameter. error_uri is described below.
|
||||||
|
*
|
||||||
|
* OPTIONAL. A URI identifying a human-readable web page with
|
||||||
|
* information about the error, used to provide the client
|
||||||
|
* developer with additional information about the error.
|
||||||
|
*/
|
||||||
|
public static final String ERROR_URI = "error_uri";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key for the expires_in parameter. expires_in is described below.
|
||||||
|
*
|
||||||
|
* OPTIONAL. The lifetime in seconds of the access token. For
|
||||||
|
* example, the value "3600" denotes that the access token will
|
||||||
|
* expire in one hour from the time the response was generated.
|
||||||
|
*/
|
||||||
|
public static final String EXPIRES_IN = "expires_in";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key for the grant_type parameter. grant_type is described below.
|
||||||
|
*
|
||||||
|
* grant_type is used in a token request. It can take on the following
|
||||||
|
* values: authorization_code, password, client_credentials, or refresh_token.
|
||||||
|
*/
|
||||||
|
public static final String GRANT_TYPE = "grant_type";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional. A market string that determines how the consent user interface
|
||||||
|
* (UI) is localized. If the value of this parameter is missing or is not
|
||||||
|
* valid, a market value is determined by using an internal algorithm.
|
||||||
|
*/
|
||||||
|
public static final String LOCALE = "locale";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key for the redirect_uri parameter.
|
||||||
|
*
|
||||||
|
* See <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.1.2">Section 3.1.2</a>
|
||||||
|
* of the OAuth 2.0 spec for more information.
|
||||||
|
*/
|
||||||
|
public static final String REDIRECT_URI = "redirect_uri";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key used for the refresh_token parameter.
|
||||||
|
*
|
||||||
|
* See <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-5.1">Section 5.1</a>
|
||||||
|
* of the OAuth 2.0 spec for more information.
|
||||||
|
*/
|
||||||
|
public static final String REFRESH_TOKEN = "refresh_token";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of data to be returned in the response from the authorization
|
||||||
|
* server. Valid values are "code" or "token".
|
||||||
|
*/
|
||||||
|
public static final String RESPONSE_TYPE = "response_type";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to the scope parameter that is described in the OAuth 2.0
|
||||||
|
* protocol spec.
|
||||||
|
*/
|
||||||
|
public static final String SCOPE = "scope";
|
||||||
|
|
||||||
|
/** Delimiter for the scopes field response. */
|
||||||
|
public static final String SCOPE_DELIMITER = " ";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to the state parameter that is described in the OAuth 2.0
|
||||||
|
* protocol spec.
|
||||||
|
*/
|
||||||
|
public static final String STATE = "state";
|
||||||
|
|
||||||
|
public static final String THEME = "theme";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key used for the token_type parameter.
|
||||||
|
*
|
||||||
|
* See <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-5.1">Section 5.1</a>
|
||||||
|
* of the OAuth 2.0 spec for more information.
|
||||||
|
*/
|
||||||
|
public static final String TOKEN_TYPE = "token_type";
|
||||||
|
|
||||||
|
/** Private to prevent instantiation */
|
||||||
|
private OAuth() { throw new AssertionError(ErrorMessages.NON_INSTANTIABLE_CLASS); }
|
||||||
|
}
|
@ -0,0 +1,173 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import com.microsoft.live.OAuth.ErrorType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuthErrorResponse represents the an Error Response from the OAuth server.
|
||||||
|
*/
|
||||||
|
class OAuthErrorResponse implements OAuthResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder is a helper class to create a OAuthErrorResponse.
|
||||||
|
* An OAuthResponse must contain an error, but an error_description and
|
||||||
|
* error_uri are optional
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private final ErrorType error;
|
||||||
|
private String errorDescription;
|
||||||
|
private String errorUri;
|
||||||
|
|
||||||
|
public Builder(ErrorType error) {
|
||||||
|
assert error != null;
|
||||||
|
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a new instance of an OAuthErrorResponse containing
|
||||||
|
* the values called on the builder.
|
||||||
|
*/
|
||||||
|
public OAuthErrorResponse build() {
|
||||||
|
return new OAuthErrorResponse(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder errorDescription(String errorDescription) {
|
||||||
|
this.errorDescription = errorDescription;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder errorUri(String errorUri) {
|
||||||
|
this.errorUri = errorUri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static constructor that creates an OAuthErrorResponse from the given OAuth server's
|
||||||
|
* JSONObject response
|
||||||
|
* @param response from the OAuth server
|
||||||
|
* @return A new instance of an OAuthErrorResponse from the given response
|
||||||
|
* @throws LiveAuthException if there is an JSONException, or the error type cannot be found.
|
||||||
|
*/
|
||||||
|
public static OAuthErrorResponse createFromJson(JSONObject response) throws LiveAuthException {
|
||||||
|
final String errorString;
|
||||||
|
try {
|
||||||
|
errorString = response.getString(OAuth.ERROR);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ErrorType error;
|
||||||
|
try {
|
||||||
|
error = ErrorType.valueOf(errorString.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Builder builder = new Builder(error);
|
||||||
|
if (response.has(OAuth.ERROR_DESCRIPTION)) {
|
||||||
|
final String errorDescription;
|
||||||
|
try {
|
||||||
|
errorDescription = response.getString(OAuth.ERROR_DESCRIPTION);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
|
||||||
|
}
|
||||||
|
builder.errorDescription(errorDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.has(OAuth.ERROR_URI)) {
|
||||||
|
final String errorUri;
|
||||||
|
try {
|
||||||
|
errorUri = response.getString(OAuth.ERROR_URI);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
|
||||||
|
}
|
||||||
|
builder.errorUri(errorUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param response to check
|
||||||
|
* @return true if the given JSONObject is a valid OAuth response
|
||||||
|
*/
|
||||||
|
public static boolean validOAuthErrorResponse(JSONObject response) {
|
||||||
|
return response.has(OAuth.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** REQUIRED. */
|
||||||
|
private final ErrorType error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPTIONAL. A human-readable UTF-8 encoded text providing
|
||||||
|
* additional information, used to assist the client developer in
|
||||||
|
* understanding the error that occurred.
|
||||||
|
*/
|
||||||
|
private final String errorDescription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPTIONAL. A URI identifying a human-readable web page with
|
||||||
|
* information about the error, used to provide the client
|
||||||
|
* developer with additional information about the error.
|
||||||
|
*/
|
||||||
|
private final String errorUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuthErrorResponse constructor. It is private to enforce
|
||||||
|
* the use of the Builder.
|
||||||
|
*
|
||||||
|
* @param builder to use to construct the object.
|
||||||
|
*/
|
||||||
|
private OAuthErrorResponse(Builder builder) {
|
||||||
|
this.error = builder.error;
|
||||||
|
this.errorDescription = builder.errorDescription;
|
||||||
|
this.errorUri = builder.errorUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(OAuthResponseVisitor visitor) {
|
||||||
|
visitor.visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* error is a required field.
|
||||||
|
* @return the error
|
||||||
|
*/
|
||||||
|
public ErrorType getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* error_description is an optional field
|
||||||
|
* @return error_description
|
||||||
|
*/
|
||||||
|
public String getErrorDescription() {
|
||||||
|
return errorDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* error_uri is an optional field
|
||||||
|
* @return error_uri
|
||||||
|
*/
|
||||||
|
public String getErrorUri() {
|
||||||
|
return errorUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("OAuthErrorResponse [error=%s, errorDescription=%s, errorUri=%s]",
|
||||||
|
error.toString().toLowerCase(), errorDescription, errorUri);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An observer of an OAuth Request. It will be notified of an Exception or of a Response.
|
||||||
|
*/
|
||||||
|
interface OAuthRequestObserver {
|
||||||
|
/**
|
||||||
|
* Callback used on an exception.
|
||||||
|
*
|
||||||
|
* @param exception
|
||||||
|
*/
|
||||||
|
public void onException(LiveAuthException exception);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback used on a response.
|
||||||
|
*
|
||||||
|
* @param response
|
||||||
|
*/
|
||||||
|
public void onResponse(OAuthResponse response);
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuthRespresent a response from an OAuth server.
|
||||||
|
* Known implementors are OAuthSuccessfulResponse and OAuthErrorResponse.
|
||||||
|
* Different OAuthResponses can be determined by using the OAuthResponseVisitor.
|
||||||
|
*/
|
||||||
|
interface OAuthResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls visit() on the visitor.
|
||||||
|
* This method is used to determine which OAuthResponse is being returned
|
||||||
|
* without using instance of.
|
||||||
|
*
|
||||||
|
* @param visitor to visit the given OAuthResponse
|
||||||
|
*/
|
||||||
|
public void accept(OAuthResponseVisitor visitor);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuthResponseVisitor is used to visit various OAuthResponse.
|
||||||
|
*/
|
||||||
|
interface OAuthResponseVisitor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an OAuthSuccessfulResponse is visited.
|
||||||
|
*
|
||||||
|
* @param response being visited
|
||||||
|
*/
|
||||||
|
public void visit(OAuthSuccessfulResponse response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an OAuthErrorResponse is being visited.
|
||||||
|
*
|
||||||
|
* @param response being visited
|
||||||
|
*/
|
||||||
|
public void visit(OAuthErrorResponse response);
|
||||||
|
}
|
@ -0,0 +1,302 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.microsoft.live.OAuth.TokenType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuthSuccessfulResponse represents a successful response form an OAuth server.
|
||||||
|
*/
|
||||||
|
class OAuthSuccessfulResponse implements OAuthResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder is a utility class that is used to build a new OAuthSuccessfulResponse.
|
||||||
|
* It must be constructed with the required fields, and can add on the optional ones.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private final String accessToken;
|
||||||
|
private String authenticationToken;
|
||||||
|
private int expiresIn = UNINITIALIZED;
|
||||||
|
private String refreshToken;
|
||||||
|
private String scope;
|
||||||
|
private final TokenType tokenType;
|
||||||
|
|
||||||
|
public Builder(String accessToken, TokenType tokenType) {
|
||||||
|
assert accessToken != null;
|
||||||
|
assert !TextUtils.isEmpty(accessToken);
|
||||||
|
assert tokenType != null;
|
||||||
|
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.tokenType = tokenType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder authenticationToken(String authenticationToken) {
|
||||||
|
this.authenticationToken = authenticationToken;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a new instance of an OAuthSuccessfulResponse with the given
|
||||||
|
* parameters passed into the builder.
|
||||||
|
*/
|
||||||
|
public OAuthSuccessfulResponse build() {
|
||||||
|
return new OAuthSuccessfulResponse(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder expiresIn(int expiresIn) {
|
||||||
|
this.expiresIn = expiresIn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder refreshToken(String refreshToken) {
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder scope(String scope) {
|
||||||
|
this.scope = scope;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Used to declare expiresIn uninitialized */
|
||||||
|
private static final int UNINITIALIZED = -1;
|
||||||
|
|
||||||
|
public static OAuthSuccessfulResponse createFromFragment(
|
||||||
|
Map<String, String> fragmentParameters) throws LiveAuthException {
|
||||||
|
String accessToken = fragmentParameters.get(OAuth.ACCESS_TOKEN);
|
||||||
|
String tokenTypeString = fragmentParameters.get(OAuth.TOKEN_TYPE);
|
||||||
|
|
||||||
|
// must have accessToken and tokenTypeString to be a valid OAuthSuccessfulResponse
|
||||||
|
assert accessToken != null;
|
||||||
|
assert tokenTypeString != null;
|
||||||
|
|
||||||
|
TokenType tokenType;
|
||||||
|
try {
|
||||||
|
tokenType = TokenType.valueOf(tokenTypeString.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
OAuthSuccessfulResponse.Builder builder =
|
||||||
|
new OAuthSuccessfulResponse.Builder(accessToken, tokenType);
|
||||||
|
|
||||||
|
String authenticationToken = fragmentParameters.get(OAuth.AUTHENTICATION_TOKEN);
|
||||||
|
if (authenticationToken != null) {
|
||||||
|
builder.authenticationToken(authenticationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
String expiresInString = fragmentParameters.get(OAuth.EXPIRES_IN);
|
||||||
|
if (expiresInString != null) {
|
||||||
|
final int expiresIn;
|
||||||
|
try {
|
||||||
|
expiresIn = Integer.parseInt(expiresInString);
|
||||||
|
} catch (final NumberFormatException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.expiresIn(expiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
String scope = fragmentParameters.get(OAuth.SCOPE);
|
||||||
|
if (scope != null) {
|
||||||
|
builder.scope(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static constructor used to create a new OAuthSuccessfulResponse from an
|
||||||
|
* OAuth server's JSON response.
|
||||||
|
*
|
||||||
|
* @param response from an OAuth server that is used to create the object.
|
||||||
|
* @return a new instance of OAuthSuccessfulResponse that is created from the given JSONObject
|
||||||
|
* @throws LiveAuthException if there is a JSONException or the token_type is unknown.
|
||||||
|
*/
|
||||||
|
public static OAuthSuccessfulResponse createFromJson(JSONObject response)
|
||||||
|
throws LiveAuthException {
|
||||||
|
assert validOAuthSuccessfulResponse(response);
|
||||||
|
|
||||||
|
final String accessToken;
|
||||||
|
try {
|
||||||
|
accessToken = response.getString(OAuth.ACCESS_TOKEN);
|
||||||
|
} catch (final JSONException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String tokenTypeString;
|
||||||
|
try {
|
||||||
|
tokenTypeString = response.getString(OAuth.TOKEN_TYPE);
|
||||||
|
} catch (final JSONException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final TokenType tokenType;
|
||||||
|
try {
|
||||||
|
tokenType = TokenType.valueOf(tokenTypeString.toUpperCase());
|
||||||
|
} catch (final IllegalArgumentException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
} catch (final NullPointerException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Builder builder = new Builder(accessToken, tokenType);
|
||||||
|
|
||||||
|
if (response.has(OAuth.AUTHENTICATION_TOKEN)) {
|
||||||
|
final String authenticationToken;
|
||||||
|
try {
|
||||||
|
authenticationToken = response.getString(OAuth.AUTHENTICATION_TOKEN);
|
||||||
|
} catch (final JSONException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
|
||||||
|
}
|
||||||
|
builder.authenticationToken(authenticationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.has(OAuth.REFRESH_TOKEN)) {
|
||||||
|
final String refreshToken;
|
||||||
|
try {
|
||||||
|
refreshToken = response.getString(OAuth.REFRESH_TOKEN);
|
||||||
|
} catch (final JSONException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
|
||||||
|
}
|
||||||
|
builder.refreshToken(refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.has(OAuth.EXPIRES_IN)) {
|
||||||
|
final int expiresIn;
|
||||||
|
try {
|
||||||
|
expiresIn = response.getInt(OAuth.EXPIRES_IN);
|
||||||
|
} catch (final JSONException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
|
||||||
|
}
|
||||||
|
builder.expiresIn(expiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.has(OAuth.SCOPE)) {
|
||||||
|
final String scope;
|
||||||
|
try {
|
||||||
|
scope = response.getString(OAuth.SCOPE);
|
||||||
|
} catch (final JSONException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
|
||||||
|
}
|
||||||
|
builder.scope(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param response
|
||||||
|
* @return true if the given JSONObject has the required fields to construct an
|
||||||
|
* OAuthSuccessfulResponse (i.e., has access_token and token_type)
|
||||||
|
*/
|
||||||
|
public static boolean validOAuthSuccessfulResponse(JSONObject response) {
|
||||||
|
return response.has(OAuth.ACCESS_TOKEN) &&
|
||||||
|
response.has(OAuth.TOKEN_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** REQUIRED. The access token issued by the authorization server. */
|
||||||
|
private final String accessToken;
|
||||||
|
|
||||||
|
private final String authenticationToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPTIONAL. The lifetime in seconds of the access token. For
|
||||||
|
* example, the value "3600" denotes that the access token will
|
||||||
|
* expire in one hour from the time the response was generated.
|
||||||
|
*/
|
||||||
|
private final int expiresIn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPTIONAL. The refresh token which can be used to obtain new
|
||||||
|
* access tokens using the same authorization grant.
|
||||||
|
*/
|
||||||
|
private final String refreshToken;
|
||||||
|
|
||||||
|
/** OPTIONAL. */
|
||||||
|
private final String scope;
|
||||||
|
|
||||||
|
/** REQUIRED. */
|
||||||
|
private final TokenType tokenType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor to enforce the user of the builder.
|
||||||
|
* @param builder to use to construct the object from.
|
||||||
|
*/
|
||||||
|
private OAuthSuccessfulResponse(Builder builder) {
|
||||||
|
this.accessToken = builder.accessToken;
|
||||||
|
this.authenticationToken = builder.authenticationToken;
|
||||||
|
this.tokenType = builder.tokenType;
|
||||||
|
this.refreshToken = builder.refreshToken;
|
||||||
|
this.expiresIn = builder.expiresIn;
|
||||||
|
this.scope = builder.scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(OAuthResponseVisitor visitor) {
|
||||||
|
visitor.visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccessToken() {
|
||||||
|
return this.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthenticationToken() {
|
||||||
|
return this.authenticationToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getExpiresIn() {
|
||||||
|
return this.expiresIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRefreshToken() {
|
||||||
|
return this.refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScope() {
|
||||||
|
return this.scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenType getTokenType() {
|
||||||
|
return this.tokenType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAuthenticationToken() {
|
||||||
|
return this.authenticationToken != null && !TextUtils.isEmpty(this.authenticationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasExpiresIn() {
|
||||||
|
return this.expiresIn != UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasRefreshToken() {
|
||||||
|
return this.refreshToken != null && !TextUtils.isEmpty(this.refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasScope() {
|
||||||
|
return this.scope != null && !TextUtils.isEmpty(this.scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("OAuthSuccessfulResponse [accessToken=%s, authenticationToken=%s, tokenType=%s, refreshToken=%s, expiresIn=%s, scope=%s]",
|
||||||
|
this.accessToken,
|
||||||
|
this.authenticationToken,
|
||||||
|
this.tokenType,
|
||||||
|
this.refreshToken,
|
||||||
|
this.expiresIn,
|
||||||
|
this.scope);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An OAuth Request that can be observed, by adding observers that will be notified on any
|
||||||
|
* exception or response.
|
||||||
|
*/
|
||||||
|
interface ObservableOAuthRequest {
|
||||||
|
/**
|
||||||
|
* Adds an observer to observe the OAuth request
|
||||||
|
*
|
||||||
|
* @param observer to add
|
||||||
|
*/
|
||||||
|
public void addObserver(OAuthRequestObserver observer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an observer that is observing the OAuth request
|
||||||
|
*
|
||||||
|
* @param observer to remove
|
||||||
|
* @return true if the observer was removed.
|
||||||
|
*/
|
||||||
|
public boolean removeObserver(OAuthRequestObserver observer);
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum that specifies what to do during a naming conflict during an upload.
|
||||||
|
*/
|
||||||
|
public enum OverwriteOption {
|
||||||
|
|
||||||
|
/** Overwrite the existing file. */
|
||||||
|
Overwrite {
|
||||||
|
@Override
|
||||||
|
protected String overwriteQueryParamValue() {
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Do Not Overwrite the existing file and cancel the upload. */
|
||||||
|
DoNotOverwrite {
|
||||||
|
@Override
|
||||||
|
protected String overwriteQueryParamValue() {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Rename the current file to avoid a name conflict. */
|
||||||
|
Rename {
|
||||||
|
@Override
|
||||||
|
protected String overwriteQueryParamValue() {
|
||||||
|
return "choosenewname";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leaves any existing overwrite query parameter on appends this overwrite
|
||||||
|
* to the given UriBuilder.
|
||||||
|
*/
|
||||||
|
void appendQueryParameterOnTo(UriBuilder uri) {
|
||||||
|
uri.appendQueryParameter(QueryParameters.OVERWRITE, this.overwriteQueryParamValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected String overwriteQueryParamValue();
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostRequest is a subclass of a BodyEnclosingApiRequest and performs a Post request.
|
||||||
|
*/
|
||||||
|
class PostRequest extends EntityEnclosingApiRequest<JSONObject> {
|
||||||
|
|
||||||
|
public static final String METHOD = HttpPost.METHOD_NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new PostRequest and initializes its member variables.
|
||||||
|
*
|
||||||
|
* @param session with the access_token
|
||||||
|
* @param client to make Http requests on
|
||||||
|
* @param path of the request
|
||||||
|
* @param entity body of the request
|
||||||
|
*/
|
||||||
|
public PostRequest(LiveConnectSession session,
|
||||||
|
HttpClient client,
|
||||||
|
String path,
|
||||||
|
HttpEntity entity) {
|
||||||
|
super(session, client, JsonResponseHandler.INSTANCE, path, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the string "POST" */
|
||||||
|
@Override
|
||||||
|
public String getMethod() {
|
||||||
|
return METHOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method override that constructs a HttpPost and adds a body to it.
|
||||||
|
*
|
||||||
|
* @return a HttpPost with the properly body added to it.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected HttpUriRequest createHttpRequest() throws LiveOperationException {
|
||||||
|
final HttpPost request = new HttpPost(this.requestUri.toString());
|
||||||
|
|
||||||
|
request.setEntity(this.entity);
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static class that holds constants used by an application's preferences.
|
||||||
|
*/
|
||||||
|
final class PreferencesConstants {
|
||||||
|
public static final String COOKIES_KEY = "cookies";
|
||||||
|
|
||||||
|
/** Name of the preference file */
|
||||||
|
public static final String FILE_NAME = "com.microsoft.live";
|
||||||
|
|
||||||
|
public static final String REFRESH_TOKEN_KEY = "refresh_token";
|
||||||
|
public static final String COOKIE_DELIMITER = ",";
|
||||||
|
|
||||||
|
private PreferencesConstants() { throw new AssertionError(); }
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpPut;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PutRequest is a subclass of a BodyEnclosingApiRequest and performs a Put request.
|
||||||
|
*/
|
||||||
|
class PutRequest extends EntityEnclosingApiRequest<JSONObject> {
|
||||||
|
|
||||||
|
public static final String METHOD = HttpPut.METHOD_NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new PutRequest and initializes its member variables.
|
||||||
|
*
|
||||||
|
* @param session with the access_token
|
||||||
|
* @param client to make Http requests on
|
||||||
|
* @param path of the request
|
||||||
|
* @param entity body of the request
|
||||||
|
*/
|
||||||
|
public PutRequest(LiveConnectSession session,
|
||||||
|
HttpClient client,
|
||||||
|
String path,
|
||||||
|
HttpEntity entity) {
|
||||||
|
super(session, client, JsonResponseHandler.INSTANCE, path, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the string "PUT" */
|
||||||
|
@Override
|
||||||
|
public String getMethod() {
|
||||||
|
return METHOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method override that constructs a HttpPut and adds a body to it.
|
||||||
|
*
|
||||||
|
* @return a HttpPut with the properly body added to it.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected HttpUriRequest createHttpRequest() throws LiveOperationException {
|
||||||
|
final HttpPut request = new HttpPut(this.requestUri.toString());
|
||||||
|
|
||||||
|
request.setEntity(this.entity);
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QueryParameters is a non-instantiable utility class that holds query parameter constants
|
||||||
|
* used by the API service.
|
||||||
|
*/
|
||||||
|
final class QueryParameters {
|
||||||
|
|
||||||
|
public static final String PRETTY = "pretty";
|
||||||
|
public static final String CALLBACK = "callback";
|
||||||
|
public static final String SUPPRESS_REDIRECTS = "suppress_redirects";
|
||||||
|
public static final String SUPPRESS_RESPONSE_CODES = "suppress_response_codes";
|
||||||
|
public static final String METHOD = "method";
|
||||||
|
public static final String OVERWRITE = "overwrite";
|
||||||
|
public static final String RETURN_SSL_RESOURCES = "return_ssl_resources";
|
||||||
|
|
||||||
|
/** Private to present instantiation. */
|
||||||
|
private QueryParameters() {
|
||||||
|
throw new AssertionError(ErrorMessages.NON_INSTANTIABLE_CLASS);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.microsoft.live.OAuth.GrantType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RefreshAccessTokenRequest performs a refresh access token request. Most of the work
|
||||||
|
* is done by the parent class, TokenRequest. This class adds in the required body parameters via
|
||||||
|
* TokenRequest's hook method, constructBody().
|
||||||
|
*/
|
||||||
|
class RefreshAccessTokenRequest extends TokenRequest {
|
||||||
|
|
||||||
|
/** REQUIRED. Value MUST be set to "refresh_token". */
|
||||||
|
private final GrantType grantType = GrantType.REFRESH_TOKEN;
|
||||||
|
|
||||||
|
/** REQUIRED. The refresh token issued to the client. */
|
||||||
|
private final String refreshToken;
|
||||||
|
|
||||||
|
private final String scope;
|
||||||
|
|
||||||
|
public RefreshAccessTokenRequest(HttpClient client,
|
||||||
|
String clientId,
|
||||||
|
String refreshToken,
|
||||||
|
String scope) {
|
||||||
|
super(client, clientId);
|
||||||
|
|
||||||
|
assert refreshToken != null;
|
||||||
|
assert !TextUtils.isEmpty(refreshToken);
|
||||||
|
assert scope != null;
|
||||||
|
assert !TextUtils.isEmpty(scope);
|
||||||
|
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
|
this.scope = scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void constructBody(List<NameValuePair> body) {
|
||||||
|
body.add(new BasicNameValuePair(OAuth.REFRESH_TOKEN, this.refreshToken));
|
||||||
|
body.add(new BasicNameValuePair(OAuth.SCOPE, this.scope));
|
||||||
|
body.add(new BasicNameValuePair(OAuth.GRANT_TYPE, this.grantType.toString()));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ScreenSize is used to determine the DeviceType.
|
||||||
|
* Small and Normal ScreenSizes are Phones.
|
||||||
|
* Large and XLarge are Tablets.
|
||||||
|
*/
|
||||||
|
enum ScreenSize {
|
||||||
|
SMALL {
|
||||||
|
@Override
|
||||||
|
public DeviceType getDeviceType() {
|
||||||
|
return DeviceType.PHONE;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NORMAL {
|
||||||
|
@Override
|
||||||
|
public DeviceType getDeviceType() {
|
||||||
|
return DeviceType.PHONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
LARGE {
|
||||||
|
@Override
|
||||||
|
public DeviceType getDeviceType() {
|
||||||
|
return DeviceType.TABLET;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
XLARGE {
|
||||||
|
@Override
|
||||||
|
public DeviceType getDeviceType() {
|
||||||
|
return DeviceType.TABLET;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public abstract DeviceType getDeviceType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration.SCREENLAYOUT_SIZE_XLARGE was not provided in API level 9.
|
||||||
|
* However, its value of 4 does show up.
|
||||||
|
*/
|
||||||
|
private static final int SCREENLAYOUT_SIZE_XLARGE = 4;
|
||||||
|
|
||||||
|
public static ScreenSize determineScreenSize(Activity activity) {
|
||||||
|
int screenLayout = activity.getResources().getConfiguration().screenLayout;
|
||||||
|
int screenLayoutMasked = screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
|
||||||
|
switch (screenLayoutMasked) {
|
||||||
|
case Configuration.SCREENLAYOUT_SIZE_SMALL:
|
||||||
|
return SMALL;
|
||||||
|
case Configuration.SCREENLAYOUT_SIZE_NORMAL:
|
||||||
|
return NORMAL;
|
||||||
|
case Configuration.SCREENLAYOUT_SIZE_LARGE:
|
||||||
|
return LARGE;
|
||||||
|
case SCREENLAYOUT_SIZE_XLARGE:
|
||||||
|
return XLARGE;
|
||||||
|
default:
|
||||||
|
// If we cannot determine the ScreenSize, we'll guess and say it's normal.
|
||||||
|
Log.d(
|
||||||
|
"Live SDK ScreenSize",
|
||||||
|
"Unable to determine ScreenSize. A Normal ScreenSize will be returned.");
|
||||||
|
return NORMAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.ClientProtocolException;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.client.utils.URLEncodedUtils;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
import org.apache.http.protocol.HTTP;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class that represents an OAuth token request.
|
||||||
|
* Known subclasses include AccessTokenRequest and RefreshAccessTokenRequest
|
||||||
|
*/
|
||||||
|
abstract class TokenRequest {
|
||||||
|
|
||||||
|
private static final String CONTENT_TYPE =
|
||||||
|
URLEncodedUtils.CONTENT_TYPE + ";charset=" + HTTP.UTF_8;
|
||||||
|
|
||||||
|
protected final HttpClient client;
|
||||||
|
protected final String clientId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new TokenRequest instance and initializes its parameters.
|
||||||
|
*
|
||||||
|
* @param client the HttpClient to make HTTP requests on
|
||||||
|
* @param clientId the client_id of the calling application
|
||||||
|
*/
|
||||||
|
public TokenRequest(HttpClient client, String clientId) {
|
||||||
|
assert client != null;
|
||||||
|
assert clientId != null;
|
||||||
|
assert !TextUtils.isEmpty(clientId);
|
||||||
|
|
||||||
|
this.client = client;
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the Token Request and returns the OAuth server's response.
|
||||||
|
*
|
||||||
|
* @return The OAuthResponse from the server
|
||||||
|
* @throws LiveAuthException if there is any exception while executing the request
|
||||||
|
* (e.g., IOException, JSONException)
|
||||||
|
*/
|
||||||
|
public OAuthResponse execute() throws LiveAuthException {
|
||||||
|
final Uri requestUri = Config.INSTANCE.getOAuthTokenUri();
|
||||||
|
|
||||||
|
final HttpPost request = new HttpPost(requestUri.toString());
|
||||||
|
|
||||||
|
final List<NameValuePair> body = new ArrayList<NameValuePair>();
|
||||||
|
body.add(new BasicNameValuePair(OAuth.CLIENT_ID, this.clientId));
|
||||||
|
|
||||||
|
// constructBody allows subclasses to add to body
|
||||||
|
this.constructBody(body);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final UrlEncodedFormEntity entity = new UrlEncodedFormEntity(body, HTTP.UTF_8);
|
||||||
|
entity.setContentType(CONTENT_TYPE);
|
||||||
|
request.setEntity(entity);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final HttpResponse response;
|
||||||
|
try {
|
||||||
|
response = this.client.execute(request);
|
||||||
|
} catch (ClientProtocolException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final HttpEntity entity = response.getEntity();
|
||||||
|
final String stringResponse;
|
||||||
|
try {
|
||||||
|
stringResponse = EntityUtils.toString(entity);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final JSONObject jsonResponse;
|
||||||
|
try {
|
||||||
|
jsonResponse = new JSONObject(stringResponse);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OAuthErrorResponse.validOAuthErrorResponse(jsonResponse)) {
|
||||||
|
return OAuthErrorResponse.createFromJson(jsonResponse);
|
||||||
|
} else if (OAuthSuccessfulResponse.validOAuthSuccessfulResponse(jsonResponse)) {
|
||||||
|
return OAuthSuccessfulResponse.createFromJson(jsonResponse);
|
||||||
|
} else {
|
||||||
|
throw new LiveAuthException(ErrorMessages.SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method gives a hook in the execute process, and allows subclasses
|
||||||
|
* to add to the HttpRequest's body.
|
||||||
|
* NOTE: The content type has already been added
|
||||||
|
*
|
||||||
|
* @param body of NameValuePairs to add to
|
||||||
|
*/
|
||||||
|
protected abstract void constructBody(List<NameValuePair> body);
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TokenRequestAsync performs an async token request. It takes in a TokenRequest,
|
||||||
|
* executes it, checks the OAuthResponse, and then calls the given listener.
|
||||||
|
*/
|
||||||
|
class TokenRequestAsync extends AsyncTask<Void, Void, Void> implements ObservableOAuthRequest {
|
||||||
|
|
||||||
|
private final DefaultObservableOAuthRequest observerable;
|
||||||
|
|
||||||
|
/** Not null if there was an exception */
|
||||||
|
private LiveAuthException exception;
|
||||||
|
|
||||||
|
/** Not null if there was a response */
|
||||||
|
private OAuthResponse response;
|
||||||
|
|
||||||
|
private final TokenRequest request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new TokenRequestAsync and initializes its member variables
|
||||||
|
*
|
||||||
|
* @param request to perform
|
||||||
|
*/
|
||||||
|
public TokenRequestAsync(TokenRequest request) {
|
||||||
|
assert request != null;
|
||||||
|
|
||||||
|
this.observerable = new DefaultObservableOAuthRequest();
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addObserver(OAuthRequestObserver observer) {
|
||||||
|
this.observerable.addObserver(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeObserver(OAuthRequestObserver observer) {
|
||||||
|
return this.observerable.removeObserver(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
this.response = this.request.execute();
|
||||||
|
} catch (LiveAuthException e) {
|
||||||
|
this.exception = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
|
||||||
|
if (this.response != null) {
|
||||||
|
this.observerable.notifyObservers(this.response);
|
||||||
|
} else if (this.exception != null) {
|
||||||
|
this.observerable.notifyObservers(this.exception);
|
||||||
|
} else {
|
||||||
|
final LiveAuthException exception = new LiveAuthException(ErrorMessages.CLIENT_ERROR);
|
||||||
|
this.observerable.notifyObservers(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void executeSynchronous() {
|
||||||
|
Void result = doInBackground();
|
||||||
|
onPostExecute(result);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpPut;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
class UploadRequest extends EntityEnclosingApiRequest<JSONObject> {
|
||||||
|
|
||||||
|
public static final String METHOD = HttpPut.METHOD_NAME;
|
||||||
|
|
||||||
|
private static final String FILE_PATH = "file.";
|
||||||
|
private static final String ERROR_KEY = "error";
|
||||||
|
private static final String UPLOAD_LOCATION_KEY = "upload_location";
|
||||||
|
|
||||||
|
private HttpUriRequest currentRequest;
|
||||||
|
private final String filename;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* true if the given path refers to a File Object
|
||||||
|
* (i.e., the path begins with "/file").
|
||||||
|
*/
|
||||||
|
private final boolean isFileUpload;
|
||||||
|
|
||||||
|
private final OverwriteOption overwrite;
|
||||||
|
|
||||||
|
public UploadRequest(LiveConnectSession session,
|
||||||
|
HttpClient client,
|
||||||
|
String path,
|
||||||
|
HttpEntity entity,
|
||||||
|
String filename,
|
||||||
|
OverwriteOption overwrite) {
|
||||||
|
super(session,
|
||||||
|
client,
|
||||||
|
JsonResponseHandler.INSTANCE,
|
||||||
|
path,
|
||||||
|
entity,
|
||||||
|
ResponseCodes.SUPPRESS,
|
||||||
|
Redirects.UNSUPPRESSED);
|
||||||
|
|
||||||
|
assert !TextUtils.isEmpty(filename);
|
||||||
|
|
||||||
|
this.filename = filename;
|
||||||
|
this.overwrite = overwrite;
|
||||||
|
|
||||||
|
String lowerCasePath = this.pathUri.getPath().toLowerCase();
|
||||||
|
this.isFileUpload = lowerCasePath.indexOf(FILE_PATH) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMethod() {
|
||||||
|
return METHOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject execute() throws LiveOperationException {
|
||||||
|
UriBuilder uploadRequestUri;
|
||||||
|
|
||||||
|
// if the path was relative, we have to retrieve the upload location, because if we don't,
|
||||||
|
// we will proxy the upload request, which is a waste of resources.
|
||||||
|
if (this.pathUri.isRelative()) {
|
||||||
|
JSONObject response = this.getUploadLocation();
|
||||||
|
|
||||||
|
// We could of tried to get the upload location on an invalid path.
|
||||||
|
// If we did, just return that response.
|
||||||
|
// If the user passes in a path that does contain an upload location, then
|
||||||
|
// we need to throw an error.
|
||||||
|
if (response.has(ERROR_KEY)) {
|
||||||
|
return response;
|
||||||
|
} else if (!response.has(UPLOAD_LOCATION_KEY)) {
|
||||||
|
throw new LiveOperationException(ErrorMessages.MISSING_UPLOAD_LOCATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
// once we have the file object, get the upload location
|
||||||
|
String uploadLocation;
|
||||||
|
try {
|
||||||
|
uploadLocation = response.getString(UPLOAD_LOCATION_KEY);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new LiveOperationException(ErrorMessages.SERVER_ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadRequestUri = UriBuilder.newInstance(Uri.parse(uploadLocation));
|
||||||
|
|
||||||
|
// The original path might have query parameters that were sent to the
|
||||||
|
// the upload location request, and those same query parameters will need
|
||||||
|
// to be sent to the HttpPut upload request too. Also, the returned upload_location
|
||||||
|
// *could* have query parameters on it. We want to keep those intact and in front of the
|
||||||
|
// the client's query parameters.
|
||||||
|
uploadRequestUri.appendQueryString(this.pathUri.getQuery());
|
||||||
|
} else {
|
||||||
|
uploadRequestUri = this.requestUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isFileUpload) {
|
||||||
|
// if it is not a file upload it is a folder upload and we must
|
||||||
|
// add the file name to the upload location
|
||||||
|
// and don't forget to set the overwrite query parameter
|
||||||
|
uploadRequestUri.appendToPath(this.filename);
|
||||||
|
this.overwrite.appendQueryParameterOnTo(uploadRequestUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpPut uploadRequest = new HttpPut(uploadRequestUri.toString());
|
||||||
|
uploadRequest.setEntity(this.entity);
|
||||||
|
|
||||||
|
this.currentRequest = uploadRequest;
|
||||||
|
|
||||||
|
return super.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HttpUriRequest createHttpRequest() throws LiveOperationException {
|
||||||
|
return this.currentRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs an HttpGet on the folder/file object to retrieve the upload_location
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* @throws LiveOperationException if there was an error getting the getUploadLocation
|
||||||
|
*/
|
||||||
|
private JSONObject getUploadLocation() throws LiveOperationException {
|
||||||
|
this.currentRequest = new HttpGet(this.requestUri.toString());
|
||||||
|
return super.execute();
|
||||||
|
}
|
||||||
|
}
|
277
src/java/JavaFileStorage/src/com/microsoft/live/UriBuilder.java
Normal file
277
src/java/JavaFileStorage/src/com/microsoft/live/UriBuilder.java
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// Description: See the class level JavaDoc comments.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package com.microsoft.live;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for building URIs. The most useful benefit of this class is its query parameter
|
||||||
|
* management. It stores all the query parameters in a LinkedList, so parameters can
|
||||||
|
* be looked up, removed, and added easily.
|
||||||
|
*/
|
||||||
|
class UriBuilder {
|
||||||
|
|
||||||
|
public static class QueryParameter {
|
||||||
|
private final String key;
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a query parameter with no value (e.g., download).
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
public QueryParameter(String key) {
|
||||||
|
assert key != null;
|
||||||
|
|
||||||
|
this.key = key;
|
||||||
|
this.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryParameter(String key, String value) {
|
||||||
|
assert key != null;
|
||||||
|
assert value != null;
|
||||||
|
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return this.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasValue() {
|
||||||
|
return this.value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (this.hasValue()) {
|
||||||
|
return this.key + "=" + this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String EQUAL = "=";
|
||||||
|
private static final String AMPERSAND = "&";
|
||||||
|
private static final char FORWARD_SLASH = '/';
|
||||||
|
|
||||||
|
private String scheme;
|
||||||
|
private String host;
|
||||||
|
private StringBuilder path;
|
||||||
|
|
||||||
|
private final LinkedList<QueryParameter> queryParameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new UriBuilder from the given Uri.
|
||||||
|
*
|
||||||
|
* @return a new Uri Builder based off the given Uri.
|
||||||
|
*/
|
||||||
|
public static UriBuilder newInstance(Uri uri) {
|
||||||
|
return new UriBuilder().scheme(uri.getScheme())
|
||||||
|
.host(uri.getHost())
|
||||||
|
.path(uri.getPath())
|
||||||
|
.query(uri.getQuery());
|
||||||
|
}
|
||||||
|
|
||||||
|
public UriBuilder() {
|
||||||
|
this.queryParameters = new LinkedList<QueryParameter>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a new query parameter to the UriBuilder's query string.
|
||||||
|
*
|
||||||
|
* (e.g., appendQueryParameter("k1", "v1") when UriBuilder's query string is
|
||||||
|
* k2=v2&k3=v3 results in k2=v2&k3=v3&k1=v1).
|
||||||
|
*
|
||||||
|
* @param key Key of the new query parameter.
|
||||||
|
* @param value Value of the new query parameter.
|
||||||
|
* @return this UriBuilder object. Useful for chaining.
|
||||||
|
*/
|
||||||
|
public UriBuilder appendQueryParameter(String key, String value) {
|
||||||
|
assert key != null;
|
||||||
|
assert value != null;
|
||||||
|
|
||||||
|
this.queryParameters.add(new QueryParameter(key, value));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the given query string on to the existing UriBuilder's query parameters.
|
||||||
|
*
|
||||||
|
* (e.g., UriBuilder's queryString k1=v1&k2=v2 and given queryString k3=v3&k4=v4, results in
|
||||||
|
* k1=v1&k2=v2&k3=v3&k4=v4).
|
||||||
|
*
|
||||||
|
* @param queryString Key-Value pairs separated by & and = (e.g., k1=v1&k2=v2&k3=k3).
|
||||||
|
* @return this UriBuilder object. Useful for chaining.
|
||||||
|
*/
|
||||||
|
public UriBuilder appendQueryString(String queryString) {
|
||||||
|
if (queryString == null) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] pairs = TextUtils.split(queryString, UriBuilder.AMPERSAND);
|
||||||
|
for(String pair : pairs) {
|
||||||
|
String[] splitPair = TextUtils.split(pair, UriBuilder.EQUAL);
|
||||||
|
if (splitPair.length == 2) {
|
||||||
|
String key = splitPair[0];
|
||||||
|
String value = splitPair[1];
|
||||||
|
|
||||||
|
this.queryParameters.add(new QueryParameter(key, value));
|
||||||
|
} else if (splitPair.length == 1){
|
||||||
|
String key = splitPair[0];
|
||||||
|
|
||||||
|
this.queryParameters.add(new QueryParameter(key));
|
||||||
|
} else {
|
||||||
|
Log.w("com.microsoft.live.UriBuilder", "Invalid query parameter: " + pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the given path to the UriBuilder's current path.
|
||||||
|
*
|
||||||
|
* @param path The path to append onto this UriBuilder's path.
|
||||||
|
* @return this UriBuilder object. Useful for chaining.
|
||||||
|
*/
|
||||||
|
public UriBuilder appendToPath(String path) {
|
||||||
|
assert path != null;
|
||||||
|
|
||||||
|
if (this.path == null) {
|
||||||
|
this.path = new StringBuilder(path);
|
||||||
|
} else {
|
||||||
|
boolean endsWithSlash = TextUtils.isEmpty(this.path) ? false :
|
||||||
|
this.path.charAt(this.path.length() - 1) == UriBuilder.FORWARD_SLASH;
|
||||||
|
boolean pathIsEmpty = TextUtils.isEmpty(path);
|
||||||
|
boolean beginsWithSlash =
|
||||||
|
pathIsEmpty ? false : path.charAt(0) == UriBuilder.FORWARD_SLASH;
|
||||||
|
|
||||||
|
if (endsWithSlash && beginsWithSlash) {
|
||||||
|
if (path.length() > 1) {
|
||||||
|
this.path.append(path.substring(1));
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (!endsWithSlash && !beginsWithSlash) {
|
||||||
|
if (!pathIsEmpty) {
|
||||||
|
this.path.append(UriBuilder.FORWARD_SLASH).append(path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.path.append(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the Uri by converting into a android.net.Uri object.
|
||||||
|
*
|
||||||
|
* @return a new android.net.Uri defined by what was given to the builder.
|
||||||
|
*/
|
||||||
|
public Uri build() {
|
||||||
|
return new Uri.Builder().scheme(this.scheme)
|
||||||
|
.authority(this.host)
|
||||||
|
.path(this.path == null ? "" : this.path.toString())
|
||||||
|
.encodedQuery(TextUtils.join("&", this.queryParameters))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the host part of the Uri.
|
||||||
|
*
|
||||||
|
* @return this UriBuilder object. Useful for chaining.
|
||||||
|
*/
|
||||||
|
public UriBuilder host(String host) {
|
||||||
|
assert host != null;
|
||||||
|
this.host = host;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the path and removes any previously existing path.
|
||||||
|
*
|
||||||
|
* @param path The path to set on this UriBuilder.
|
||||||
|
* @return this UriBuilder object. Useful for chaining.
|
||||||
|
*/
|
||||||
|
public UriBuilder path(String path) {
|
||||||
|
assert path != null;
|
||||||
|
this.path = new StringBuilder(path);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a query string and puts it in the Uri Builder's query string removing
|
||||||
|
* any existing query parameters.
|
||||||
|
*
|
||||||
|
* @param queryString Key-Value pairs separated by & and = (e.g., k1=v1&k2=v2&k3=k3).
|
||||||
|
* @return this UriBuilder object. Useful for chaining.
|
||||||
|
*/
|
||||||
|
public UriBuilder query(String queryString) {
|
||||||
|
this.queryParameters.clear();
|
||||||
|
|
||||||
|
return this.appendQueryString(queryString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all query parameters from the UriBuilder that has the given key.
|
||||||
|
*
|
||||||
|
* (e.g., removeQueryParametersWithKey("k1") when UriBuilder's query string of k1=v1&k2=v2&k1=v3
|
||||||
|
* results in k2=v2).
|
||||||
|
*
|
||||||
|
* @param key Query parameter's key to remove
|
||||||
|
* @return this UriBuilder object. Useful for chaining.
|
||||||
|
*/
|
||||||
|
public UriBuilder removeQueryParametersWithKey(String key) {
|
||||||
|
// There could be multiple query parameters with this key and
|
||||||
|
// we want to remove all of them.
|
||||||
|
Iterator<QueryParameter> it = this.queryParameters.iterator();
|
||||||
|
|
||||||
|
while (it.hasNext()) {
|
||||||
|
QueryParameter qp = it.next();
|
||||||
|
if (qp.getKey().equals(key)) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the scheme part of the Uri.
|
||||||
|
*
|
||||||
|
* @return this UriBuilder object. Useful for chaining.
|
||||||
|
*/
|
||||||
|
public UriBuilder scheme(String scheme) {
|
||||||
|
assert scheme != null;
|
||||||
|
this.scheme = scheme;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the URI in string format (e.g., http://foo.com/bar?k1=v2).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.build().toString();
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import java.util.HashMap;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.google.android.gms.auth.UserRecoverableAuthException;
|
||||||
import com.google.api.client.extensions.android.http.AndroidHttp;
|
import com.google.api.client.extensions.android.http.AndroidHttp;
|
||||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
|
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
|
||||||
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
|
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
|
||||||
@ -723,20 +724,28 @@ public class GoogleDriveFileStorage extends JavaFileStorageBase {
|
|||||||
|
|
||||||
public void prepareFileUsage(Context appContext, String path) throws UserInteractionRequiredException, Throwable
|
public void prepareFileUsage(Context appContext, String path) throws UserInteractionRequiredException, Throwable
|
||||||
{
|
{
|
||||||
String accountName;
|
try
|
||||||
GDrivePath gdrivePath = null;
|
|
||||||
if (path.startsWith(getProtocolPrefix()))
|
|
||||||
{
|
{
|
||||||
gdrivePath = new GDrivePath();
|
String accountName;
|
||||||
//don't verify yet, we're not yet initialized:
|
GDrivePath gdrivePath = null;
|
||||||
gdrivePath.setPathWithoutVerify(path);
|
if (path.startsWith(getProtocolPrefix()))
|
||||||
|
{
|
||||||
|
gdrivePath = new GDrivePath();
|
||||||
|
//don't verify yet, we're not yet initialized:
|
||||||
|
gdrivePath.setPathWithoutVerify(path);
|
||||||
|
|
||||||
|
accountName = gdrivePath.getAccount();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
accountName = path;
|
||||||
|
|
||||||
accountName = gdrivePath.getAccount();
|
initializeAccount(appContext, accountName);
|
||||||
|
}
|
||||||
|
catch (UserRecoverableAuthIOException e)
|
||||||
|
{
|
||||||
|
throw new UserInteractionRequiredException(e);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
accountName = path;
|
|
||||||
|
|
||||||
initializeAccount(appContext, accountName);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,13 +467,18 @@ public class SkyDriveFileStorage extends JavaFileStorageBase {
|
|||||||
@Override
|
@Override
|
||||||
public void prepareFileUsage(Context appContext, String path) throws Exception
|
public void prepareFileUsage(Context appContext, String path) throws Exception
|
||||||
{
|
{
|
||||||
|
|
||||||
PrepareFileUsageListener listener = new PrepareFileUsageListener();
|
PrepareFileUsageListener listener = new PrepareFileUsageListener();
|
||||||
mAuthClient.initialize(Arrays.asList(SCOPES), listener);
|
|
||||||
|
mAuthClient.initializeSynchronous(Arrays.asList(SCOPES), listener, null);
|
||||||
|
|
||||||
|
|
||||||
if (listener.exception != null)
|
if (listener.exception != null)
|
||||||
throw listener.exception;
|
throw listener.exception;
|
||||||
|
|
||||||
if (listener.status == LiveStatus.CONNECTED) {
|
if (listener.status == LiveStatus.CONNECTED) {
|
||||||
|
|
||||||
|
mConnectClient = new LiveConnectClient(listener.session);
|
||||||
if (mFolderCache.isEmpty())
|
if (mFolderCache.isEmpty())
|
||||||
{
|
{
|
||||||
initializeFoldersCache();
|
initializeFoldersCache();
|
||||||
|
@ -1,7 +1,17 @@
|
|||||||
package keepass2android.javafilestorage;
|
package keepass2android.javafilestorage;
|
||||||
|
|
||||||
|
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
|
||||||
|
|
||||||
public class UserInteractionRequiredException extends Exception {
|
public class UserInteractionRequiredException extends Exception {
|
||||||
|
|
||||||
|
public UserInteractionRequiredException(UserRecoverableAuthIOException e) {
|
||||||
|
super(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserInteractionRequiredException() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@ -10,17 +10,28 @@ public class PrepareFileUsageListener implements LiveAuthListener {
|
|||||||
public Exception exception;
|
public Exception exception;
|
||||||
public LiveStatus status;
|
public LiveStatus status;
|
||||||
|
|
||||||
|
volatile boolean done;
|
||||||
|
public LiveConnectSession session;
|
||||||
|
|
||||||
|
public boolean isDone()
|
||||||
|
{
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthError(LiveAuthException _exception,
|
public void onAuthError(LiveAuthException _exception,
|
||||||
Object userState) {
|
Object userState) {
|
||||||
exception = _exception;
|
exception = _exception;
|
||||||
|
done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthComplete(LiveStatus _status,
|
public void onAuthComplete(LiveStatus _status,
|
||||||
LiveConnectSession session, Object userState)
|
LiveConnectSession _session, Object userState)
|
||||||
{
|
{
|
||||||
status = _status;
|
status = _status;
|
||||||
|
session = _session;
|
||||||
|
done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user