/*
* Copyright 2012 Evernote Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.evernote.client.android;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.app.FragmentActivity;
import com.evernote.client.android.asyncclient.EvernoteClientFactory;
import com.evernote.client.android.helper.Cat;
import com.evernote.client.android.helper.EvernotePreconditions;
import com.evernote.client.android.login.EvernoteLoginActivity;
import com.evernote.client.android.login.EvernoteLoginFragment;
import java.util.Locale;
/**
* Represents a session with the Evernote web service API. Used to authenticate
* to the service via OAuth and obtain NoteStore.Client objects, which are used
* to make authenticated API calls.
*
* To use EvernoteSession, first initialize the EvernoteSession singleton with the
* {@link EvernoteSession.Builder} class and call {@link EvernoteSession#asSingleton()}. After that
* initiate authentication at an appropriate time:
* <pre>
* EvernoteSession evernoteSession = new EvernoteSession.Builder(this)
* .setEvernoteService(EvernoteSession.EvernoteService.PRODUCTION)
* .setSupportAppLinkedNotebooks(SUPPORT_APP_LINKED_NOTEBOOKS)
* .build(consumerKey, consumerSecret)
* .asSingleton();
*
* if (!session.isLoggedIn()) {
* session.authenticate(...);
* }
* </pre>
*
* Later, you can make any Evernote API calls that you need by obtaining a
* NoteStore.Client from the session and using the session's auth token:
* <pre>
* NoteStore.client noteStore = session.createNoteStoreClient();
* Notebook notebook = noteStore.getDefaultNotebook(session.getAuthToken());
* </pre>
*
* @author tsmith
* @author rwondratschek
*/
@SuppressWarnings("UnusedDeclaration")
public final class EvernoteSession {
// Standard hostnames for bootstrap detection
public static final String HOST_SANDBOX = "https://sandbox.evernote.com";
public static final String HOST_PRODUCTION = "https://www.evernote.com";
public static final String HOST_CHINA = "https://app.yinxiang.com";
public static final String SCREEN_NAME_YXBIJI = "印象笔记";
public static final String SCREEN_NAME_INTERNATIONAL = "Evernote International";
/**
* The used request code when you launch authentication process from a {@link Activity}. Override
* {@link Activity#onActivityResult(int, int, Intent)} to receive the result.
*/
public static final int REQUEST_CODE_LOGIN = 14390;
private static final Cat CAT = new Cat("EvernoteSession");
private static EvernoteSession sInstance = null;
public static EvernoteSession getInstance() {
return sInstance;
}
private Context mApplicationContext;
private String mConsumerKey;
private String mConsumerSecret;
private EvernoteService mEvernoteService;
private AuthenticationResult mAuthenticationResult;
private boolean mSupportAppLinkedNotebooks;
private boolean mForceAuthenticationInThirdPartyApp;
private Locale mLocale;
private EvernoteClientFactory.Builder mEvernoteClientFactoryBuilder;
private ThreadLocal<EvernoteClientFactory> mFactoryThreadLocal;
private EvernoteSession() {
// do nothing, builder sets up everything
}
/**
* @return the Bootstrap object to check for server host urls
*/
protected EvernoteService getEvernoteService() {
return mEvernoteService;
}
/**
* Returns a factory to create various clients and helper objects to get access to the Evernote API.
*
* <br>
* <br>
*
* The returned factory is <b>not thread safe</b> itself, however, the cached factory is a thread local
* object. That means a new factory is created for each thread calling this method. The recommended
* approach is to reuse worker threads to keep the number of created factories small.
*
* <br>
* <br>
*
* With {@link #setEvernoteClientFactoryBuilder(EvernoteClientFactory.Builder)} you can exchange
* the builder.
*
* @return A factory for this thread.
*/
public synchronized EvernoteClientFactory getEvernoteClientFactory() {
if (mFactoryThreadLocal == null) {
mFactoryThreadLocal = new ThreadLocal<>();
}
if (mEvernoteClientFactoryBuilder == null) {
mEvernoteClientFactoryBuilder = new EvernoteClientFactory.Builder(this);
}
EvernoteClientFactory factory = mFactoryThreadLocal.get();
if (factory == null) {
factory = mEvernoteClientFactoryBuilder.build();
mFactoryThreadLocal.set(factory);
}
return factory;
}
/**
* @param builder The new builder returning {@link EvernoteClientFactory}s in {@link #getEvernoteClientFactory()}.
*/
public synchronized void setEvernoteClientFactoryBuilder(EvernoteClientFactory.Builder builder) {
mEvernoteClientFactoryBuilder = EvernotePreconditions.checkNotNull(builder);
mFactoryThreadLocal = null; // invalidate
}
/**
* @return The application context for the running app.
*/
public Context getApplicationContext() {
return mApplicationContext;
}
/**
* Get the authentication token that is used to make API calls
* though a NoteStore.Client.
*
* @return the authentication token, or null if {@link #isLoggedIn()}
* is false.
*/
public String getAuthToken() {
if (mAuthenticationResult != null) {
return mAuthenticationResult.getAuthToken();
} else {
return null;
}
}
/**
* Get the authentication information returned by a successful
* OAuth authentication to the Evernote web service.
*/
public AuthenticationResult getAuthenticationResult() {
return mAuthenticationResult;
}
/**
* Recommended approach to authenticate the user. If the main Evernote app is installed and up to date,
* the app is launched and authenticates the user. Otherwise the old OAuth process is launched and
* the user needs to enter his credentials.
*
* <p/>
*
* Your {@link FragmentActivity} should implement {@link EvernoteLoginFragment.ResultCallback} to receive
* the authentication result. Alternatively you can extend {@link EvernoteLoginFragment} and override
* {@link EvernoteLoginFragment#onLoginFinished(boolean)}.
*
* @param activity The {@link FragmentActivity} holding the progress dialog.
*/
public void authenticate(FragmentActivity activity) {
authenticate(activity, EvernoteLoginFragment.create(mConsumerKey, mConsumerSecret, mSupportAppLinkedNotebooks, mLocale));
}
/**
* @see EvernoteSession#authenticate(FragmentActivity)
*/
public void authenticate(FragmentActivity activity, EvernoteLoginFragment fragment) {
fragment.show(activity.getSupportFragmentManager(), EvernoteLoginFragment.TAG);
}
/**
* Similar to {@link EvernoteSession#authenticate(FragmentActivity)}, but instead of opening a dialog
* this method launches a separate {@link Activity}.
*
* <p/>
*
* The calling {@code activity} should override {@link Activity#onActivityResult(int, int, android.content.Intent)}. The {@code requestCode}
* is {@link EvernoteSession#REQUEST_CODE_LOGIN}. The {@code resultCode} is either {@link Activity#RESULT_OK} or
* {@link Activity#RESULT_CANCELED}.
*
* @param activity The {@link Activity} launching the {@link EvernoteLoginActivity}.
*/
public void authenticate(Activity activity) {
activity.startActivityForResult(EvernoteLoginActivity.createIntent(activity, mConsumerKey, mConsumerSecret, mSupportAppLinkedNotebooks, mLocale), REQUEST_CODE_LOGIN);
}
/**
* Sets this session instance as singleton. After that you can use {@link #getInstance()} to get
* this session.
*
* @return The same instance.
*/
public EvernoteSession asSingleton() {
sInstance = this;
return this;
}
protected synchronized void setAuthenticationResult(AuthenticationResult authenticationResult) {
mAuthenticationResult = authenticationResult;
}
/**
* Check whether the session has valid authentication information
* that will allow successful API calls to be made.
*/
public synchronized boolean isLoggedIn() {
return mAuthenticationResult != null;
}
/**
* Clears all stored session information. If the user is not logged in, then this is a no-op.
*
* @return {@code true} if the user successfully logged out, {@code false} if the user wasn't
* logged in.
* @see #isLoggedIn()
*/
public synchronized boolean logOut() {
if (!isLoggedIn()) {
return false;
}
mAuthenticationResult.clear();
mAuthenticationResult = null;
EvernoteUtil.removeAllCookies(getApplicationContext());
return true;
}
/*package*/ boolean isForceAuthenticationInThirdPartyApp() {
return mForceAuthenticationInThirdPartyApp;
}
/**
* Evernote Service to use with the bootstrap profile detection.
* Sandbox will return profiles referencing sandbox.evernote.com
* Production will return evernote.com and app.yinxiang.com
*/
public enum EvernoteService implements Parcelable {
/**
* References sandbox.evernote.com.
*/
SANDBOX,
/**
* References evernote.com and app.yinxiang.com.
*/
PRODUCTION;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeInt(ordinal());
}
public static final Creator<EvernoteService> CREATOR = new Creator<EvernoteService>() {
@Override
public EvernoteService createFromParcel(final Parcel source) {
return EvernoteService.values()[source.readInt()];
}
@Override
public EvernoteService[] newArray(final int size) {
return new EvernoteService[size];
}
};
}
/**
* Builder class to construct an {@link EvernoteSession}.
*/
public static class Builder {
private final Context mContext;
private EvernoteService mEvernoteService;
private boolean mSupportAppLinkedNotebooks;
private Locale mLocale;
private boolean mForceAuthenticationInThirdPartyApp;
/**
* @param context Any context. The session caches the application context.
*/
public Builder(Context context) {
EvernotePreconditions.checkNotNull(context);
mContext = context.getApplicationContext();
mSupportAppLinkedNotebooks = true;
mEvernoteService = EvernoteService.SANDBOX;
mLocale = Locale.getDefault();
}
/**
* Default is {@link EvernoteService#SANDBOX}. You need to exchange the value for your
* production app.
*
* @param evernoteService The desired service.
* @return This Builder object to allow for chaining of calls to set methods.
*/
public Builder setEvernoteService(EvernoteService evernoteService) {
mEvernoteService = EvernotePreconditions.checkNotNull(evernoteService);
return this;
}
/**
* Default is {@code true}.
*
* @param supportAppLinkedNotebooks {@code true} if app linked notebooks are supported.
* @return This Builder object to allow for chaining of calls to set methods.
*/
public Builder setSupportAppLinkedNotebooks(boolean supportAppLinkedNotebooks) {
mSupportAppLinkedNotebooks = supportAppLinkedNotebooks;
return this;
}
/**
* Default is {@code false}.
*
* @param forceAuthenticationInThirdPartyApp {@code true} if the authentication should be
* launched in the third party app and not in the
* main Evernote app.
* @return This Builder object to allow for chaining of calls to set methods.
*/
public Builder setForceAuthenticationInThirdPartyApp(boolean forceAuthenticationInThirdPartyApp) {
mForceAuthenticationInThirdPartyApp = forceAuthenticationInThirdPartyApp;
return this;
}
/**
* The parameter is used to find the appropriate Evernote server, which can be with
* {@link EvernoteService#PRODUCTION} either {@link #HOST_PRODUCTION} or {@link #HOST_CHINA}.
* China is only used if the locale is Chinese, e.g. {@link Locale#SIMPLIFIED_CHINESE}.
*
* <br>
* <br>
*
* Usually you don't want change this value. But for testing purposes it makes sense to switch
* to a Chinese locale and to test that your app works for Chinese users.
*
* <br>
* <br>
*
* The default value is {@link Locale#getDefault()}.
*
* @param locale The new locale used the fetch the bootstrap profiles. {@code null} is not allowed.
* @return This Builder object to allow for chaining of calls to set methods.
*/
public Builder setLocale(Locale locale) {
mLocale = EvernotePreconditions.checkNotNull(locale);
return this;
}
/**
* Creates a new instance with this consumer key and secret pair.
*
* @param consumerKey Your consumer key.
* @param consumerSecret Your consumer secret.
* @return The new created session. Call {@link #asSingleton()} to make reuse the session in the SDK.
*/
public EvernoteSession build(String consumerKey, String consumerSecret) {
EvernoteSession evernoteSession = new EvernoteSession();
evernoteSession.mConsumerKey = EvernotePreconditions.checkNotEmpty(consumerKey);
evernoteSession.mConsumerSecret = EvernotePreconditions.checkNotEmpty(consumerSecret);
evernoteSession.mAuthenticationResult = AuthenticationResult.fromPreferences(mContext);
return build(evernoteSession);
}
/**
* Creates a session only for your personal account. Use this with the production environment.
*
* @param developerToken Your personal developer token.
* @param noteStoreUrl The note store url of your Evernote account.
* @return The new created session. Call {@link #asSingleton()} to make reuse the session in the SDK.
*/
public EvernoteSession buildForSingleUser(String developerToken, String noteStoreUrl) {
EvernoteSession evernoteSession = new EvernoteSession();
evernoteSession.mAuthenticationResult = new AuthenticationResult(EvernotePreconditions.checkNotEmpty(developerToken),
EvernotePreconditions.checkNotEmpty(noteStoreUrl), mSupportAppLinkedNotebooks);
return build(evernoteSession);
}
private EvernoteSession build(EvernoteSession session) {
session.mApplicationContext = mContext;
session.mLocale = mLocale;
session.mSupportAppLinkedNotebooks = mSupportAppLinkedNotebooks;
session.mEvernoteService = mEvernoteService;
session.mForceAuthenticationInThirdPartyApp = mForceAuthenticationInThirdPartyApp;
return session;
}
}
}