Update app icon, use AppAuth library for Oauth (instead of Play Services), fix vibrate/silent feature on pre-6.0

merge-requests/3/head 1.4.1
Trevor Slocum 6 years ago
parent 5b73d71e86
commit 840a5fd039

@ -1,3 +1,9 @@
1.4.1:
- Updated app icon (thanks Symbolisch)
- Changed MediNET Oauth method from Play Services to open-source library AppAuth
- Fix set to vibrate/silent feature on pre-Android 6.0 devices
- App now requires Android 4.1+ (was 4.0+)
1.4.0:
- Fix alarm icon appearing in the status bar when Daily Reminder is enabled
- Increase fidelity of the default gong sound

@ -1,31 +1,37 @@
buildscript {
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.1'
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
apply plugin: 'com.android.application'
repositories {
mavenCentral()
maven {
url "https://maven.google.com"
}
jcenter()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
}
android {
compileSdkVersion 24
buildToolsVersion '24.0.2'
compileSdkVersion 26
buildToolsVersion '26.0.2'
defaultConfig {
minSdkVersion 14
targetSdkVersion 24
minSdkVersion 16
targetSdkVersion 26
versionCode 141
versionName "1.4.1"
jackOptions {
enabled true
}
versionCode 140
versionName "1.4.0"
applicationId "sh.ftp.rocketninelabs.meditationassistant"
manifestPlaceholders = [
'appAuthRedirectScheme': applicationId
]
}
lintOptions {
@ -33,6 +39,12 @@ android {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
output.outputFile = new File(
@ -58,41 +70,56 @@ android {
productFlavors {
free {
applicationId "sh.ftp.rocketninelabs.meditationassistant"
buildConfigField "String", "GOOGLEOAUTHKEY", "\"163346957857.apps.googleusercontent.com\""
}
full {
buildConfigField "String", "GOOGLEOAUTHKEY", "\"153054147563.apps.googleusercontent.com\""
applicationId "sh.ftp.rocketninelabs.meditationassistant.full"
manifestPlaceholders = [
'appAuthRedirectScheme': applicationId
]
}
opensource {
applicationId "sh.ftp.rocketninelabs.meditationassistant.opensource"
buildConfigField "String", "GOOGLEOAUTHKEY", "\"821225341172-64l3l6kdk9ull9lc7fjgeuuu7nee7pb9.apps.googleusercontent.com\""
delete('google-services.json')
applicationId "sh.ftp.rocketninelabs.meditationassistant.opensource"
manifestPlaceholders = [
'appAuthRedirectScheme': applicationId
]
}
}
}
dependencies {
compile 'com.android.support:support-v4:24.2.1'
freeCompile 'com.google.android.gms:play-services-base:9.6.1'
fullCompile 'com.google.android.gms:play-services-base:9.6.1'
freeCompile 'com.google.android.gms:play-services-identity:9.6.1'
fullCompile 'com.google.android.gms:play-services-identity:9.6.1'
freeCompile 'com.google.android.gms:play-services-analytics:9.6.1'
fullCompile 'com.google.android.gms:play-services-analytics:9.6.1'
freeCompile 'com.google.android.gms:play-services-wearable:9.6.1'
fullCompile 'com.google.android.gms:play-services-wearable:9.6.1'
freeCompile 'com.google.android.gms:play-services-appinvite:9.6.1'
fullCompile 'com.google.android.gms:play-services-appinvite:9.6.1'
freeCompile 'com.google.android.gms:play-services-fitness:9.6.1'
fullCompile 'com.google.android.gms:play-services-fitness:9.6.1'
freeCompile 'com.google.android.gms:play-services-ads:9.6.1'
compile "com.android.support:support-compat:26.1.0"
compile "com.android.support:support-core-utils:26.1.0"
compile "com.android.support:support-core-ui:26.1.0"
compile "com.android.support:support-media-compat:26.1.0"
compile "com.android.support:support-fragment:26.1.0"
compile "com.android.support:recyclerview-v7:26.1.0"
compile "com.android.support:design:26.1.0"
compile "net.openid:appauth:0.7.0"
compile "com.squareup.okio:okio:1.13.0"
freeCompile 'com.google.android.gms:play-services-base:11.4.2'
fullCompile 'com.google.android.gms:play-services-base:11.4.2'
freeCompile 'com.google.android.gms:play-services-analytics:11.4.2'
fullCompile 'com.google.android.gms:play-services-analytics:11.4.2'
//freeCompile 'com.google.android.gms:play-services-wearable:11.4.2'
//fullCompile 'com.google.android.gms:play-services-wearable:11.4.2'
freeCompile 'com.google.android.gms:play-services-appinvite:11.4.2'
fullCompile 'com.google.android.gms:play-services-appinvite:11.4.2'
freeCompile 'com.google.android.gms:play-services-fitness:11.4.2'
fullCompile 'com.google.android.gms:play-services-fitness:11.4.2'
freeCompile 'com.google.android.gms:play-services-ads:11.4.2'
//noinspection GradleDynamicVersion
compile 'ch.acra:acra:4.9.0'
compile 'com.github.amlcurran.showcaseview:library:5.4.3'
compile 'com.nononsenseapps:filepicker:3.0.0'
compile 'com.nononsenseapps:filepicker:4.1.0'
//wearApp project(':MeditationAssistantWear')
// TODO: Uncomment when Wear app is ready
compile 'com.android.support:recyclerview-v7:24.2.1'
}
}

@ -183,10 +183,6 @@
-keep,allowshrinking,allowoptimization class * { <methods>; }
-keepclasseswithmembernames,allowshrinking class * {
native <methods>;
}
-keepclasseswithmembers,allowshrinking class * {
public <init>(android.content.Context,android.util.AttributeSet);
public <init>(android.content.Context,android.util.AttributeSet,int);

@ -11,8 +11,6 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
@ -69,6 +67,15 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="sh.ftp.rocketninelabs.meditationassistant.MainActivity"/>
</activity>
<activity
android:name="sh.ftp.rocketninelabs.meditationassistant.AuthResultActivity"
android:configChanges="keyboard|keyboardHidden|screenSize|orientation"
android:label="@string/signInToMediNET"
android:launchMode="singleTop">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="sh.ftp.rocketninelabs.meditationassistant.MainActivity"/>
</activity>
<activity
android:name="sh.ftp.rocketninelabs.meditationassistant.MediNETActivity"
android:configChanges="keyboard|keyboardHidden|screenSize|orientation"

@ -141,13 +141,13 @@ public class AboutActivity extends Activity {
public void openHowToMeditate(View view) {
startActivity(new Intent(
Intent.ACTION_VIEW,
Uri.parse("https://medinet.ftp.sh/howtomeditate")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
Uri.parse("https://medinet.rocketnine.space/howtomeditate")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
public void openTranslate(View view) {
startActivity(new Intent(
Intent.ACTION_VIEW,
Uri.parse("https://medinet.ftp.sh/translate")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
Uri.parse("https://medinet.rocketnine.space/translate")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
public void openDonate(View view) {
@ -192,7 +192,7 @@ public class AboutActivity extends Activity {
} else {
startActivity(new Intent(
Intent.ACTION_VIEW,
Uri.parse("https://medinet.ftp.sh/donate")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
Uri.parse("https://rocketnine.space/donate")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
}

@ -0,0 +1,173 @@
package sh.ftp.rocketninelabs.meditationassistant;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import net.openid.appauth.connectivity.ConnectionBuilder;
import net.openid.appauth.connectivity.DefaultConnectionBuilder;
import org.json.JSONObject;
import java.lang.ref.WeakReference;
/**
* Reads and validates the demo app configuration from `res/raw/auth_config.json`. Configuration
* changes are detected by comparing the hash of the last known configuration to the read
* configuration. When a configuration change is detected, the app state is reset.
*/
public final class AuthConfiguration {
private static final String TAG = "Configuration";
private static final String PREFS_NAME = "config";
private static final String KEY_LAST_HASH = "lastHash";
private static WeakReference<AuthConfiguration> sInstance = new WeakReference<>(null);
private final Context mContext;
private final SharedPreferences mPrefs;
private final Resources mResources;
private JSONObject mConfigJson;
private String mConfigError;
private String mClientId;
private String mScope;
private Uri mRedirectUri;
private Uri mDiscoveryUri;
private Uri mAuthEndpointUri;
private Uri mTokenEndpointUri;
private Uri mRegistrationEndpointUri;
public static AuthConfiguration getInstance(Context context) {
AuthConfiguration config = sInstance.get();
if (config == null) {
config = new AuthConfiguration(context);
sInstance = new WeakReference<AuthConfiguration>(config);
}
return config;
}
public AuthConfiguration(Context context) {
mContext = context;
mPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
mResources = context.getResources();
try {
readConfiguration();
} catch (InvalidConfigurationException ex) {
mConfigError = ex.getMessage();
}
}
/**
* Indicates whether the current configuration is valid.
*/
public boolean isValid() {
return mConfigError == null;
}
/**
* Returns a description of the configuration error, if the configuration is invalid.
*/
@Nullable
public String getConfigurationError() {
return mConfigError;
}
@Nullable
public String getClientId() {
return mClientId;
}
@NonNull
public String getScope() {
return mScope;
}
@NonNull
public Uri getRedirectUri() {
return mRedirectUri;
}
@Nullable
public Uri getDiscoveryUri() {
return mDiscoveryUri;
}
@Nullable
public Uri getAuthEndpointUri() {
return mAuthEndpointUri;
}
@Nullable
public Uri getTokenEndpointUri() {
return mTokenEndpointUri;
}
@Nullable
public Uri getRegistrationEndpointUri() {
return mRegistrationEndpointUri;
}
public ConnectionBuilder getConnectionBuilder() {
return DefaultConnectionBuilder.INSTANCE;
}
private void readConfiguration() throws InvalidConfigurationException {
mClientId = ""; // TODO: blank?
mScope = "openid email profile";
mRedirectUri = Uri.parse("https://medinet.rocketnine.space/oauth");
if (!isRedirectUriRegistered()) {
throw new InvalidConfigurationException(
"redirect_uri is not handled by any activity in this app! "
+ "Ensure that the appAuthRedirectScheme in your build.gradle file "
+ "is correctly configured, or that an appropriate intent filter "
+ "exists in your app manifest.");
}
/*if (getConfigString("discovery_uri") == null) {
mAuthEndpointUri = getRequiredConfigWebUri("authorization_endpoint_uri");
mTokenEndpointUri = getRequiredConfigWebUri("token_endpoint_uri");
if (mClientId == null) {
mRegistrationEndpointUri = getRequiredConfigWebUri("registration_endpoint_uri");
}
} else {
mDiscoveryUri = getRequiredConfigWebUri("discovery_uri");
}*/
mDiscoveryUri = Uri.parse("");
}
private boolean isRedirectUriRegistered() {
// ensure that the redirect URI declared in the configuration is handled by some activity
// in the app, by querying the package manager speculatively
Intent redirectIntent = new Intent();
redirectIntent.setPackage(mContext.getPackageName());
redirectIntent.setAction(Intent.ACTION_VIEW);
redirectIntent.addCategory(Intent.CATEGORY_BROWSABLE);
redirectIntent.setData(mRedirectUri);
return !mContext.getPackageManager().queryIntentActivities(redirectIntent, 0).isEmpty();
}
public static final class InvalidConfigurationException extends Exception {
InvalidConfigurationException(String reason) {
super(reason);
}
InvalidConfigurationException(String reason, Throwable cause) {
super(reason, cause);
}
}
}

@ -0,0 +1,225 @@
package sh.ftp.rocketninelabs.meditationassistant;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.MainThread;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.support.design.widget.Snackbar;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import net.openid.appauth.AppAuthConfiguration;
import net.openid.appauth.AuthState;
import net.openid.appauth.AuthorizationException;
import net.openid.appauth.AuthorizationResponse;
import net.openid.appauth.AuthorizationService;
import net.openid.appauth.AuthorizationServiceDiscovery;
import net.openid.appauth.ClientAuthentication;
import net.openid.appauth.TokenRequest;
import net.openid.appauth.TokenResponse;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import okio.Okio;
/**
* Displays the authorized state of the user. This activity is provided with the outcome of the
* authorization flow, which it uses to negotiate the final authorized state,
* by performing an authorization code exchange if necessary. After this, the activity provides
* additional post-authorization operations if available, such as fetching user info and refreshing
* access tokens.
*/
public class AuthResultActivity extends Activity {
MeditationAssistant ma = null;
private static final String KEY_USER_INFO = "userInfo";
private AuthorizationService mAuthService;
private AuthStateManager mStateManager;
private final AtomicReference<JSONObject> mUserInfoJson = new AtomicReference<>();
private ExecutorService mExecutor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mStateManager = AuthStateManager.getInstance(this);
mExecutor = Executors.newSingleThreadExecutor();
AuthConfiguration config = AuthConfiguration.getInstance(this);
mAuthService = new AuthorizationService(
this,
new AppAuthConfiguration.Builder()
.setConnectionBuilder(config.getConnectionBuilder())
.build());
displayLoading("Restoring state...");
if (savedInstanceState != null) {
try {
mUserInfoJson.set(new JSONObject(savedInstanceState.getString(KEY_USER_INFO)));
} catch (JSONException ex) {
Log.e("MA", "Failed to parse saved user info JSON, discarding", ex);
}
}
}
@Override
protected void onStart() {
super.onStart();
if (mExecutor.isShutdown()) {
mExecutor = Executors.newSingleThreadExecutor();
}
if (mStateManager.getCurrent().isAuthorized()) {
updateState();
return;
}
// the stored AuthState is incomplete, so check if we are currently receiving the result of
// the authorization flow from the browser.
AuthorizationResponse response = AuthorizationResponse.fromIntent(getIntent());
AuthorizationException ex = AuthorizationException.fromIntent(getIntent());
if (response != null || ex != null) {
mStateManager.updateAfterAuthorization(response, ex);
}
if (response != null && response.authorizationCode != null) {
// authorization code exchange is required
mStateManager.updateAfterAuthorization(response, ex);
exchangeAuthorizationCode(response);
} else if (ex != null) {
// TODO: handle failure "Authorization flow failed: " + ex.getMessage());
} else {
// TODO: handle failure No authorization state retained - reauthorization required");
}
}
@Override
protected void onSaveInstanceState(Bundle state) {
// user info is retained to survive activity restarts, such as when rotating the
// device or switching apps. This isn't essential, but it helps provide a less
// jarring UX when these events occur - data does not just disappear from the view.
if (mUserInfoJson.get() != null) {
state.putString(KEY_USER_INFO, mUserInfoJson.toString());
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mAuthService.dispose();
mExecutor.shutdownNow();
}
@MainThread
private void displayLoading(String message) {
Log.d("MA", "Auth: " + message);
}
@MainThread
private void updateState() {
AuthState state = mStateManager.getCurrent();
if (state.getAccessToken() == null) {
Log.d("MA", "Access token was null: ");
// TODO: Handle auth failure, prompt to retry
finish();
return;
}
Long expiresAt = state.getAccessTokenExpirationTime();
if (expiresAt != null && expiresAt < System.currentTimeMillis()) {
refreshAccessToken();
return;
}
Log.d("MA", "Got token");
getMeditationAssistant().getMediNET().signInWithAuthToken(state.getAccessToken());
finish();
}
@MainThread
private void refreshAccessToken() {
displayLoading("Refreshing access token");
performTokenRequest(
mStateManager.getCurrent().createTokenRefreshRequest(),
this::handleAccessTokenResponse);
}
@MainThread
private void exchangeAuthorizationCode(AuthorizationResponse authorizationResponse) {
displayLoading("Exchanging authorization code");
performTokenRequest(
authorizationResponse.createTokenExchangeRequest(),
this::handleCodeExchangeResponse);
}
@MainThread
private void performTokenRequest(
TokenRequest request,
AuthorizationService.TokenResponseCallback callback) {
ClientAuthentication clientAuthentication;
try {
clientAuthentication = mStateManager.getCurrent().getClientAuthentication();
} catch (ClientAuthentication.UnsupportedAuthenticationMethod ex) {
Log.d("MA", "Token request cannot be made, client authentication for the token "
+ "endpoint could not be constructed (%s)", ex);
// TODO handle failure Client authentication method is unsupported");
return;
}
mAuthService.performTokenRequest(
request,
clientAuthentication,
callback);
}
@WorkerThread
private void handleAccessTokenResponse(
@Nullable TokenResponse tokenResponse,
@Nullable AuthorizationException authException) {
mStateManager.updateAfterTokenResponse(tokenResponse, authException);
runOnUiThread(this::updateState);
}
@WorkerThread
private void handleCodeExchangeResponse(
@Nullable TokenResponse tokenResponse,
@Nullable AuthorizationException authException) {
mStateManager.updateAfterTokenResponse(tokenResponse, authException);
if (!mStateManager.getCurrent().isAuthorized()) {
final String message = "Authorization Code exchange failed"
+ ((authException != null) ? authException.error : "");
// WrongThread inference is incorrect for lambdas
//noinspection WrongThread
// TODO: Handle not authorized, show error
} else {
runOnUiThread(this::updateState);
}
}
public MeditationAssistant getMeditationAssistant() {
if (ma == null) {
ma = (MeditationAssistant) this.getApplication();
}
return ma;
}
}

@ -0,0 +1,154 @@
package sh.ftp.rocketninelabs.meditationassistant;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import net.openid.appauth.AuthState;
import net.openid.appauth.AuthorizationException;
import net.openid.appauth.AuthorizationResponse;
import net.openid.appauth.RegistrationResponse;
import net.openid.appauth.TokenResponse;
import org.json.JSONException;
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
/**
* An example persistence mechanism for an {@link AuthState} instance.
* This stores the instance in a shared preferences file, and provides thread-safe access and
* mutation.
*/
public class AuthStateManager {
private static final AtomicReference<WeakReference<AuthStateManager>> INSTANCE_REF =
new AtomicReference<>(new WeakReference<AuthStateManager>(null));
private static final String TAG = "AuthStateManager";
private static final String STORE_NAME = "AuthState";
private static final String KEY_STATE = "state";
private final SharedPreferences mPrefs;
private final ReentrantLock mPrefsLock;
private final AtomicReference<AuthState> mCurrentAuthState;
@AnyThread
public static AuthStateManager getInstance(@NonNull Context context) {
AuthStateManager manager = INSTANCE_REF.get().get();
if (manager == null) {
manager = new AuthStateManager(context.getApplicationContext());
INSTANCE_REF.set(new WeakReference<>(manager));
}
return manager;
}
private AuthStateManager(Context context) {
mPrefs = context.getSharedPreferences(STORE_NAME, Context.MODE_PRIVATE);
mPrefsLock = new ReentrantLock();
mCurrentAuthState = new AtomicReference<>();
}
@AnyThread
@NonNull
public AuthState getCurrent() {
if (mCurrentAuthState.get() != null) {
return mCurrentAuthState.get();
}
AuthState state = readState();
if (mCurrentAuthState.compareAndSet(null, state)) {
return state;
} else {
return mCurrentAuthState.get();
}
}
@AnyThread
@NonNull
public AuthState replace(@NonNull AuthState state) {
writeState(state);
mCurrentAuthState.set(state);
return state;
}
@AnyThread
@NonNull
public AuthState updateAfterAuthorization(
@Nullable AuthorizationResponse response,
@Nullable AuthorizationException ex) {
AuthState current = getCurrent();
current.update(response, ex);
return replace(current);
}
@AnyThread
@NonNull
public AuthState updateAfterTokenResponse(
@Nullable TokenResponse response,
@Nullable AuthorizationException ex) {
AuthState current = getCurrent();
current.update(response, ex);
return replace(current);
}
@AnyThread
@NonNull
public AuthState updateAfterRegistration(
RegistrationResponse response,
AuthorizationException ex) {
AuthState current = getCurrent();
if (ex != null) {
return current;
}
current.update(response);
return replace(current);
}
@AnyThread
@NonNull
private AuthState readState() {
mPrefsLock.lock();
try {
String currentState = mPrefs.getString(KEY_STATE, null);
if (currentState == null) {
return new AuthState();
}
try {
return AuthState.jsonDeserialize(currentState);
} catch (JSONException ex) {
Log.w(TAG, "Failed to deserialize stored auth state - discarding");
return new AuthState();
}
} finally {
mPrefsLock.unlock();
}
}
@AnyThread
private void writeState(@Nullable AuthState state) {
mPrefsLock.lock();
try {
SharedPreferences.Editor editor = mPrefs.edit();
if (state == null) {
editor.remove(KEY_STATE);
} else {
editor.putString(KEY_STATE, state.jsonSerializeString());
}
if (!editor.commit()) {
throw new IllegalStateException("Failed to write state to shared prefs");
}
} finally {
mPrefsLock.unlock();
}
}
}

@ -341,7 +341,7 @@ public class CompleteActivity extends Activity {
public void postMediNET(View view) {
if (getMeditationAssistant().getMediNETKey() == "") {
getMeditationAssistant().showSignInDialog(this);
getMeditationAssistant().startAuth(true);
return;
}

@ -20,7 +20,7 @@ public class JavaScriptInterface implements JavascriptCallback {
MeditationAssistant ma = (MeditationAssistant) this.activity
.getApplication();
this.activity.finish();
ma.getMediNET().askToSignIn();
ma.startAuth(false);
}
@JavascriptInterface

@ -9,7 +9,6 @@ import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.preference.ListPreference;
import android.util.AttributeSet;
import android.util.Log;

@ -21,7 +21,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.text.InputType;
import android.util.Log;
import android.view.KeyEvent;
@ -63,7 +62,6 @@ public class MainActivity extends Activity implements OnShowcaseEventListener {
public static int ID_DELAY = 77702;
public static int ID_INTERVAL = 77701;
public static int ID_END = 77703;
private static final int PERMISSION_REQUEST_GET_ACCOUNTS = 3001;
public MeditationAssistant ma = null;
SharedPreferences.OnSharedPreferenceChangeListener sharedPrefslistener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@ -494,7 +492,7 @@ public class MainActivity extends Activity implements OnShowcaseEventListener {
case DialogInterface.BUTTON_POSITIVE:
startActivity(new Intent(
Intent.ACTION_VIEW,
Uri.parse("https://medinet.ftp.sh/translate")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
Uri.parse("https://medinet.rocketnine.space/translate")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
break;
@ -2467,14 +2465,8 @@ public class MainActivity extends Activity implements OnShowcaseEventListener {
)
)
.setTitle(
getString(R.string.signOutOfMediNETConfirmTitle))
.setMessage(
String.format(
getString(R.string.signOutOfMediNETConfirm),
getMeditationAssistant()
.getMediNETProvider()
)
)
getString(R.string.signOut))
.setMessage(getString(R.string.signOutOfMediNETConfirmTitle))
.setPositiveButton(getString(R.string.signOut),
dialogClickListener)
.setNegativeButton(getString(R.string.cancel),
@ -2486,15 +2478,7 @@ public class MainActivity extends Activity implements OnShowcaseEventListener {
}
public void askToSignIn() {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.GET_ACCOUNTS)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.GET_ACCOUNTS},
PERMISSION_REQUEST_GET_ACCOUNTS);
} else {
getMeditationAssistant().getMediNET().askToSignIn();
}
getMeditationAssistant().startAuth(false);
}
public void stopMediaPlayer() {
@ -2548,49 +2532,4 @@ public class MainActivity extends Activity implements OnShowcaseEventListener {
public void onShowcaseViewTouchBlocked(MotionEvent motionEvent) {
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_GET_ACCOUNTS: {
if ((grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) || !ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.GET_ACCOUNTS)) {
getMeditationAssistant().getMediNET().askToSignIn();
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(
getResources()
.getDrawable(
getTheme()
.obtainStyledAttributes(
getMeditationAssistant()
.getMATheme(true),
new int[]{R.attr.actionIconSettings}
)
.getResourceId(0, 0)
)
)
.setTitle(getString(R.string.permissionRequest))
.setMessage(
getString(R.string.permissionRequestAccounts))
.setPositiveButton(getString(R.string.tryAgain),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
askToSignIn();
}
})
.setNegativeButton(getString(R.string.deny),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
getMeditationAssistant().getMediNET().askToSignIn();
}
}).show();
}
}
}
}
}

@ -19,11 +19,11 @@ import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* Created by root on 11/2/13.
*/
public class MediNET {
public static Integer version = 5;
public static Integer version = 6;
// API v1-5 was used on pre Android 4.1 (discontinued) releases
// API v6 signifies non-discontinued (1.4.1+, Android 4.1+) app version
public String status = "disconnected";
public String result = "";
public MainActivity activity;
@ -71,46 +71,6 @@ public class MediNET {
return String.valueOf(hours) + ":" + String.format("%02d", minutes);
}
public void askToSignIn() {
/*if (activity == null || activity.stopped) {
Log.d("MeditationAssistant",
"MainActivity null or stopped, restarting... Stopped: "
+ activity.stopped.toString());*/
if (activity == null) {
Intent openActivity = new Intent(getMeditationAssistant()
.getApplicationContext(), MainActivity.class);
openActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
openActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.d("MeditationAssistant",
"Open MainActivity runnable is now running...");
askToSignIn();
}
}, 400);
getMeditationAssistant().getApplicationContext().startActivity(
openActivity);
return;
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (activity == null) {
Log.d("MeditationAssistant",
"askToSignIn activity is null, returning...");
return;
}
getMeditationAssistant().showSignInDialog(activity);
}
});
}
public void browseTo(MainActivity act, String page) {
activity = act;
browsetopage = page;
@ -129,7 +89,7 @@ public class MediNET {
}
});
} else {
askToSignIn();
getMeditationAssistant().startAuth(false);
}
}
@ -150,7 +110,7 @@ public class MediNET {
}
if (getMeditationAssistant().getMediNETKey().equals("")) {
askToSignIn();
getMeditationAssistant().startAuth(false);
return false;
}

@ -33,7 +33,6 @@ public class MediNETActivity extends Activity {
private String provider = "";
private MeditationAssistant ma = null;
private Handler handler = new Handler();
private boolean signing_in = false;
private boolean hide_refresh = false;
public static Intent newEmailIntent(Context context, String address, String subject, String body, String cc) {
@ -57,7 +56,7 @@ public class MediNETActivity extends Activity {
TimeZone tz = TimeZone.getDefault();
Date now = new Date();
return "https://medinet.ftp.sh/client_android.php?v="
return "https://medinet.rocketnine.space/client_android.php?v="
+ MediNET.version.toString() + "&avn="
+ String.valueOf(getMeditationAssistant().getMAAppVersionNumber()) + "&page=" + page + "&th="
+ ma.getMAThemeString() + "&tz="
@ -68,20 +67,8 @@ public class MediNETActivity extends Activity {
public void goTo(String go_to) {
String url;
if (go_to.equals("Google") || go_to.equals("Facebook")
|| go_to.equals("Twitter") || go_to.equals("AOL")
|| go_to.equals("OpenID") || go_to.equals("Live")
|| go_to.equals("LinkedIn")) {
setTitle(String.format(getString(R.string.signInWithProvider),
go_to));
url = "https://medinet.ftp.sh/client_android_login.php?v="
+ MediNET.version.toString() + "&avn="
+ String.valueOf(getMeditationAssistant().getMAAppVersionNumber()) + "&provider=" + go_to;
provider = go_to;
signing_in = true;
} else if (go_to.equals("gpl") || go_to.equals("lgpl")) {
if (go_to.equals("gpl") || go_to.equals("lgpl")) {
setTitle("");
signing_in = true;
hide_refresh = true;
if (go_to.equals("gpl")) {
@ -90,24 +77,30 @@ public class MediNETActivity extends Activity {
url = "file:///android_asset/lgpl.html";
}
} else {
if (go_to.equals("community")) {
setTitle(getString(R.string.community));
} else if (go_to.equals("sessions")) {
setTitle(getString(R.string.sessions));
} else if (go_to.equals("account")) {
setTitle(getString(R.string.account));
} else if (go_to.equals("forum")) {
setTitle(getString(R.string.forum));
} else if (go_to.equals("groups")) {
setTitle(getString(R.string.groups));
} else if (go_to.equals("signout")) {
} else {
return;
switch (go_to) {
case "community":
setTitle(getString(R.string.community));
break;
case "sessions":
setTitle(getString(R.string.sessions));
break;
case "account":
setTitle(getString(R.string.account));
break;
case "forum":
setTitle(getString(R.string.forum));
break;
case "groups":
setTitle(getString(R.string.groups));
break;
case "signout":
break;
default:
return;
}
url = getPageUrl(go_to);
signing_in = false;
}
Log.d("MeditationAssistant", go_to + " - Going to: " + url);
@ -200,12 +193,11 @@ public class MediNETActivity extends Activity {
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
if (Uri.parse(url) != null && Uri.parse(url).getHost() != null && Uri.parse(url).getHost().equals("medinet.ftp.sh")) {
if (Uri.parse(url) != null && Uri.parse(url).getHost() != null && Uri.parse(url).getHost().equals("medinet.rocketnine.space")) {
if (webView.getTitle() != null
&& !webView.getTitle().trim().equals("")) {
setTitle(webView.getTitle());
}
signing_in = url.contains("/hybridauth/");
} else {
/*
* setTitle(String.format(getString(R.string.
@ -219,36 +211,15 @@ public class MediNETActivity extends Activity {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
/*getMeditationAssistant().setWebViewScale(
(int) (100 * view.getScale()));*/
Log.d("MeditationAssistant",
"Signing_in: " + String.valueOf(signing_in) + " - "
+ url
);
if (url != null && url.startsWith("mailto:")) {
MailTo mt = MailTo.parse(url);
Intent i = newEmailIntent(MediNETActivity.this, mt.getTo(), mt.getSubject(), mt.getBody(), mt.getCc());
startActivity(i);
view.reload();
return true;
/*} else if (url != null && Uri.parse(url) != null && Uri.parse(url).getHost() != null
&& !Uri.parse(url).getHost()
.equals("medinet.ftp.sh")
&& webView.getUrl() != null
&& Uri.parse(webView.getUrl()).getHost() != null && Uri.parse(webView.getUrl()).getHost()
.equals("medinet.ftp.sh")
&& !webView.getUrl().contains("provider=OpenID")) {
Log.d("MA", "!!!!!!!!!!!!!!!!! OPENING!!!");
Intent browserIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse(url));
startActivity(browserIntent);*/
} else {
view.loadUrl(url);
}
Log.d("MA", Uri.parse(url).toString());
Log.d("MA", Uri.parse(url).getHost());
return true;
}
});
@ -275,16 +246,7 @@ public class MediNETActivity extends Activity {
// webView.getSettings().setDefaultZoom(WebSettings.ZoomDensity.FAR);
if (activityOnCreate) {
if (getIntent().hasExtra("provider")) {
provider = getIntent().getStringExtra("provider");
if (provider.equals("Google") || provider.equals("Facebook")
|| provider.equals("Twitter") || provider.equals("AOL")
|| provider.equals("OpenID") || provider.equals("Live")
|| provider.equals("LinkedIn")) {
signing_in = true;
goTo(provider);
}
} else if (getIntent().hasExtra("page")
if (getIntent().hasExtra("page")
&& (getIntent().getStringExtra("page").equals("community")
|| getIntent().getStringExtra("page").equals(
"sessions")
@ -296,7 +258,6 @@ public class MediNETActivity extends Activity {
"forum")
|| getIntent().getStringExtra("page").equals("gpl") || getIntent()
.getStringExtra("page").equals("lgpl"))) {
signing_in = false;
goTo(getIntent().getStringExtra("page"));
}
} else {
@ -414,12 +375,6 @@ public class MediNETActivity extends Activity {
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (signing_in) {
menu.findItem(R.id.menuMediNET).setVisible(false);
} else {
menu.findItem(R.id.menuMediNET).setVisible(true);
}
if (hide_refresh) {
menu.findItem(R.id.refreshMediNET).setVisible(false);
} else {
@ -475,16 +430,7 @@ public class MediNETActivity extends Activity {
getResources().getDrawable(
android.R.drawable.background_holo_dark));*/
webView.getSettings();
if (signing_in) {
webView.setBackgroundColor(Color.WHITE);
} else {
if (Build.VERSION.SDK_INT < 11) {
webView.setBackgroundColor(Color.TRANSPARENT);
} else {
// Fix background flicker
webView.setBackgroundColor(Color.argb(1, 0, 0, 0));
}
}
webView.setBackgroundColor(Color.argb(1, 0, 0, 0)); // TODO: Is this still necessary?
}
}
}

@ -15,6 +15,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
@ -50,7 +51,7 @@ public class MediNETTask extends AsyncTask<MediNET, Integer, MediNET> {
String appVersion = getMeditationAssistant().getMAAppVersion() + BuildConfig.FLAVOR;
if (this.nextURL == null) {
this.nextURL = "https://medinet.ftp.sh/client_android.php?v="
this.nextURL = "https://medinet.rocketnine.space/client_android.php?v="
+ MediNET.version.toString() + "&av="
+ appVersion + "&am="
+ getMeditationAssistant().getMarketName() + "&avn="
@ -59,7 +60,7 @@ public class MediNETTask extends AsyncTask<MediNET, Integer, MediNET> {
}
if (action.equals("signin")) {
this.nextURL = "https://medinet.ftp.sh/client_android_login_oauth2.php?v="
this.nextURL = "https://medinet.rocketnine.space/client_android_login_oauth2.php?v="
+ MediNET.version.toString() + "&av="
+ appVersion + "&avn="
+ String.valueOf(getMeditationAssistant().getMAAppVersionNumber()) + "&tz="
@ -130,6 +131,11 @@ public class MediNETTask extends AsyncTask<MediNET, Integer, MediNET> {
getMeditationAssistant().longToast(getMeditationAssistant().getString(R.string.sessionNotPosted));
e.printStackTrace();
}
try {
Log.i("MA", "Post data: " + getMeditationAssistant().getPostDataString(postData));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
/*
@ -152,8 +158,8 @@ public class MediNETTask extends AsyncTask<MediNET, Integer, MediNET> {
try {
URL medinetURL = new URL(this.nextURL);
medinetConnection = (HttpURLConnection) medinetURL.openConnection();
medinetConnection.setChunkedStreamingMode(0);
medinetConnection.setReadTimeout(10000);
medinetConnection.setConnectTimeout(15000);
medinetConnection.setRequestMethod("POST");
medinetConnection.setDoInput(true);
medinetConnection.setDoOutput(true);
@ -162,12 +168,11 @@ public class MediNETTask extends AsyncTask<MediNET, Integer, MediNET> {
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(os, "UTF-8"));
writer.write(getMeditationAssistant().getPostDataString(postData));
medinetConnection.connect();
writer.flush();
writer.close();
os.close();
medinetConnection.connect();
int responseCode = medinetConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
@ -200,7 +205,7 @@ public class MediNETTask extends AsyncTask<MediNET, Integer, MediNET> {
if (medinetConnection.getHeaderField("x-MediNET") != null) {
if (medinetConnection.getHeaderField("x-MediNET")
.equals("signin")) {
this.medinet.askToSignIn();
getMeditationAssistant().startAuth(false);
} else {
if (action.equals("signin") && medinetConnection.getHeaderField("x-MediNET-Key") != null) { /* Oauth2 sign in */
Log.d("MeditationAssistant", "Header key: "

@ -1,6 +1,5 @@
package sh.ftp.rocketninelabs.meditationassistant;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
@ -23,11 +22,10 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.provider.Settings;
@ -41,6 +39,11 @@ import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.Toast;
import net.openid.appauth.AuthorizationRequest;
import net.openid.appauth.AuthorizationService;
import net.openid.appauth.AuthorizationServiceConfiguration;
import net.openid.appauth.ResponseTypeValues;
import org.acra.ACRA;
import org.acra.annotation.ReportsCrashes;
@ -58,7 +61,7 @@ import java.util.Map;
import java.util.TimeZone;
@ReportsCrashes(
formUri = "https://medinet.ftp.sh/acra/acra.php"
formUri = "https://medinet.rocketnine.space/acra/acra.php"
)
public class MeditationAssistant extends Application {
@ -66,6 +69,7 @@ public class MeditationAssistant extends Application {
public static String ACTION_PRESET = "sh.ftp.rocketninelabs.meditationassistant.PRESET";
public static String ACTION_REMINDER = "sh.ftp.rocketninelabs.meditationassistant.DAILY_NOTIFICATION";
public static String ACTION_UPDATED = "sh.ftp.rocketninelabs.meditationassistant.DAILY_NOTIFICATION_UPDATED";
public static String ACTION_AUTH = "sh.ftp.rocketninelabs.meditationassistant.AUTH";
public static int REQUEST_FIT = 22;
public static int MEDIA_DELAY = 1000;
public Boolean debug_widgets = false; // Debug
@ -227,7 +231,7 @@ public class MeditationAssistant extends Application {
return mNotificationManager.getCurrentInterruptionFilter();
}
return -1;
return 0;
}
public String getDurationFormatted() {
@ -333,6 +337,37 @@ public class MeditationAssistant extends Application {
return medinetprovider;
}
public void startAuth(boolean showToast) {
if (showToast) {
shortToast(getString(R.string.signInToMediNET));
}
AsyncTask.execute(() -> {
AuthorizationServiceConfiguration serviceConfiguration = new AuthorizationServiceConfiguration(
Uri.parse("https://accounts.google.com/o/oauth2/v2/auth") /* auth endpoint */,
Uri.parse("https://www.googleapis.com/oauth2/v4/token") /* token endpoint */
);
String clientId = BuildConfig.GOOGLEOAUTHKEY;
Uri redirectUri = Uri.parse(BuildConfig.APPLICATION_ID + ":/oauth");
AuthorizationRequest.Builder builder = new AuthorizationRequest.Builder(
serviceConfiguration,
clientId,
ResponseTypeValues.CODE,
redirectUri
);
builder.setScopes("profile");
AuthorizationRequest request = builder.build();
AuthorizationService authorizationService = new AuthorizationService(MeditationAssistant.this);
authorizationService.performAuthorizationRequest(
request,
PendingIntent.getActivity(MeditationAssistant.this, 0, new Intent(MeditationAssistant.this, AuthResultActivity.class), 0),
PendingIntent.getActivity(MeditationAssistant.this, 0, new Intent(MeditationAssistant.this, AuthResultActivity.class), 0));
});
}
public void recalculateMeditationStreak() {
Calendar dayCalendar = new GregorianCalendar();
Integer daysback = 0;
@ -1005,203 +1040,6 @@ public class MeditationAssistant extends Application {
notificationManager.notify(0, notification);
}
public AlertDialog showSignInDialog(final Activity activity) {
signin_activity = activity;
AccountManager accountManager = AccountManager
.get(getApplicationContext());
final Account[] accounts = accountManager
.getAccountsByType("com.google");
final int size = accounts.length;
if (size > 0) {
String[] names = new String[size + 1];