|
|
|
@ -24,6 +24,7 @@ import android.content.pm.PackageManager; |
|
|
|
|
import android.net.Uri; |
|
|
|
|
import android.os.Bundle; |
|
|
|
|
import android.preference.PreferenceManager; |
|
|
|
|
import android.support.annotation.NonNull; |
|
|
|
|
import android.support.v7.app.AppCompatActivity; |
|
|
|
|
import android.text.method.LinkMovementMethod; |
|
|
|
|
import android.view.View; |
|
|
|
@ -58,25 +59,90 @@ public class LoginActivity extends AppCompatActivity { |
|
|
|
|
@BindView(R.id.edit_text_domain) EditText editText; |
|
|
|
|
@BindView(R.id.button_login) Button button; |
|
|
|
|
@BindView(R.id.whats_an_instance) TextView whatsAnInstance; |
|
|
|
|
@BindView(R.id.debug_log_display) TextView debugLogDisplay; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Chain together the key-value pairs into a query string, for either appending to a URL or |
|
|
|
|
* as the content of an HTTP request. |
|
|
|
|
*/ |
|
|
|
|
private static String toQueryString(Map<String, String> parameters) { |
|
|
|
|
StringBuilder s = new StringBuilder(); |
|
|
|
|
String between = ""; |
|
|
|
|
for (Map.Entry<String, String> entry : parameters.entrySet()) { |
|
|
|
|
s.append(between); |
|
|
|
|
s.append(Uri.encode(entry.getKey())); |
|
|
|
|
s.append("="); |
|
|
|
|
s.append(Uri.encode(entry.getValue())); |
|
|
|
|
between = "&"; |
|
|
|
|
@Override |
|
|
|
|
protected void onCreate(Bundle savedInstanceState) { |
|
|
|
|
super.onCreate(savedInstanceState); |
|
|
|
|
|
|
|
|
|
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("lightTheme", false)) { |
|
|
|
|
setTheme(R.style.AppTheme_Light); |
|
|
|
|
} |
|
|
|
|
return s.toString(); |
|
|
|
|
|
|
|
|
|
setContentView(R.layout.activity_login); |
|
|
|
|
ButterKnife.bind(this); |
|
|
|
|
|
|
|
|
|
if (savedInstanceState != null) { |
|
|
|
|
domain = savedInstanceState.getString("domain"); |
|
|
|
|
clientId = savedInstanceState.getString("clientId"); |
|
|
|
|
clientSecret = savedInstanceState.getString("clientSecret"); |
|
|
|
|
} else { |
|
|
|
|
domain = null; |
|
|
|
|
clientId = null; |
|
|
|
|
clientSecret = null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
preferences = getSharedPreferences( |
|
|
|
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE); |
|
|
|
|
|
|
|
|
|
button.setOnClickListener(new View.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(View v) { |
|
|
|
|
onButtonClick(editText); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
final Context context = this; |
|
|
|
|
|
|
|
|
|
whatsAnInstance.setOnClickListener(new View.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(View v) { |
|
|
|
|
AlertDialog dialog = new AlertDialog.Builder(context) |
|
|
|
|
.setMessage(R.string.dialog_whats_an_instance) |
|
|
|
|
.setPositiveButton(R.string.action_close, |
|
|
|
|
new DialogInterface.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(DialogInterface dialog, int which) { |
|
|
|
|
dialog.dismiss(); |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
.show(); |
|
|
|
|
TextView textView = (TextView) dialog.findViewById(android.R.id.message); |
|
|
|
|
textView.setMovementMethod(LinkMovementMethod.getInstance()); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// Apply any updates needed.
|
|
|
|
|
int versionCode = 1; |
|
|
|
|
try { |
|
|
|
|
versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; |
|
|
|
|
} catch (PackageManager.NameNotFoundException e) { |
|
|
|
|
Log.e(TAG, "The app version was not found. " + e.getMessage()); |
|
|
|
|
} |
|
|
|
|
if (preferences.getInt("lastUpdateVersion", 0) != versionCode) { |
|
|
|
|
SharedPreferences.Editor editor = preferences.edit(); |
|
|
|
|
if (versionCode == 14) { |
|
|
|
|
/* This version switches the order of scheme and host in the OAuth redirect URI. |
|
|
|
|
* But to fix it requires forcing the app to re-authenticate with servers. So, clear |
|
|
|
|
* out the stored client id/secret pairs. The only other things that are lost are |
|
|
|
|
* "rememberedVisibility", "loggedInUsername", and "loggedInAccountId". */ |
|
|
|
|
editor.clear(); |
|
|
|
|
} |
|
|
|
|
editor.putInt("lastUpdateVersion", versionCode); |
|
|
|
|
editor.apply(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
protected void onSaveInstanceState(Bundle outState) { |
|
|
|
|
outState.putString("domain", domain); |
|
|
|
|
outState.putString("clientId", clientId); |
|
|
|
|
outState.putString("clientSecret", clientSecret); |
|
|
|
|
super.onSaveInstanceState(outState); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** Make sure the user-entered text is just a fully-qualified domain name. */ |
|
|
|
|
@NonNull |
|
|
|
|
private static String validateDomain(String s) { |
|
|
|
|
// Strip any schemes out.
|
|
|
|
|
s = s.replaceFirst("http://", ""); |
|
|
|
@ -95,25 +161,6 @@ public class LoginActivity extends AppCompatActivity { |
|
|
|
|
return scheme + "://" + host + "/"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void redirectUserToAuthorizeAndLogin(EditText editText) { |
|
|
|
|
/* To authorize this app and log in it's necessary to redirect to the domain given, |
|
|
|
|
* activity_login there, and the server will redirect back to the app with its response. */ |
|
|
|
|
String endpoint = MastodonAPI.ENDPOINT_AUTHORIZE; |
|
|
|
|
String redirectUri = getOauthRedirectUri(); |
|
|
|
|
Map<String, String> parameters = new HashMap<>(); |
|
|
|
|
parameters.put("client_id", clientId); |
|
|
|
|
parameters.put("redirect_uri", redirectUri); |
|
|
|
|
parameters.put("response_type", "code"); |
|
|
|
|
parameters.put("scope", OAUTH_SCOPES); |
|
|
|
|
String url = "https://" + domain + endpoint + "?" + toQueryString(parameters); |
|
|
|
|
Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); |
|
|
|
|
if (viewIntent.resolveActivity(getPackageManager()) != null) { |
|
|
|
|
startActivity(viewIntent); |
|
|
|
|
} else { |
|
|
|
|
editText.setError(getString(R.string.error_no_web_browser_found)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private MastodonAPI getApiFor(String domain) { |
|
|
|
|
Retrofit retrofit = new Retrofit.Builder() |
|
|
|
|
.baseUrl("https://" + domain) |
|
|
|
@ -143,10 +190,14 @@ public class LoginActivity extends AppCompatActivity { |
|
|
|
|
clientSecret = prefClientSecret; |
|
|
|
|
redirectUserToAuthorizeAndLogin(editText); |
|
|
|
|
} else { |
|
|
|
|
Log.watchTag(OkHttpUtils.TAG); |
|
|
|
|
|
|
|
|
|
Callback<AppCredentials> callback = new Callback<AppCredentials>() { |
|
|
|
|
@Override |
|
|
|
|
public void onResponse(Call<AppCredentials> call, Response<AppCredentials> response) { |
|
|
|
|
public void onResponse(Call<AppCredentials> call, |
|
|
|
|
Response<AppCredentials> response) { |
|
|
|
|
if (!response.isSuccessful()) { |
|
|
|
|
debugLogDisplay.setText(Log.getWatchedMessages()); |
|
|
|
|
editText.setError(getString(R.string.error_failed_app_registration)); |
|
|
|
|
Log.e(TAG, "App authentication failed. " + response.message()); |
|
|
|
|
return; |
|
|
|
@ -158,11 +209,13 @@ public class LoginActivity extends AppCompatActivity { |
|
|
|
|
editor.putString(domain + "/client_id", clientId); |
|
|
|
|
editor.putString(domain + "/client_secret", clientSecret); |
|
|
|
|
editor.apply(); |
|
|
|
|
Log.watchTag(null); |
|
|
|
|
redirectUserToAuthorizeAndLogin(editText); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public void onFailure(Call<AppCredentials> call, Throwable t) { |
|
|
|
|
debugLogDisplay.setText(Log.getWatchedMessages()); |
|
|
|
|
editText.setError(getString(R.string.error_failed_app_registration)); |
|
|
|
|
t.printStackTrace(); |
|
|
|
|
} |
|
|
|
@ -179,96 +232,44 @@ public class LoginActivity extends AppCompatActivity { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
protected void onCreate(Bundle savedInstanceState) { |
|
|
|
|
super.onCreate(savedInstanceState); |
|
|
|
|
|
|
|
|
|
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("lightTheme", false)) { |
|
|
|
|
setTheme(R.style.AppTheme_Light); |
|
|
|
|
/** |
|
|
|
|
* Chain together the key-value pairs into a query string, for either appending to a URL or |
|
|
|
|
* as the content of an HTTP request. |
|
|
|
|
*/ |
|
|
|
|
@NonNull |
|
|
|
|
private static String toQueryString(Map<String, String> parameters) { |
|
|
|
|
StringBuilder s = new StringBuilder(); |
|
|
|
|
String between = ""; |
|
|
|
|
for (Map.Entry<String, String> entry : parameters.entrySet()) { |
|
|
|
|
s.append(between); |
|
|
|
|
s.append(Uri.encode(entry.getKey())); |
|
|
|
|
s.append("="); |
|
|
|
|
s.append(Uri.encode(entry.getValue())); |
|
|
|
|
between = "&"; |
|
|
|
|
} |
|
|
|
|
return s.toString(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
setContentView(R.layout.activity_login); |
|
|
|
|
ButterKnife.bind(this); |
|
|
|
|
|
|
|
|
|
if (savedInstanceState != null) { |
|
|
|
|
domain = savedInstanceState.getString("domain"); |
|
|
|
|
clientId = savedInstanceState.getString("clientId"); |
|
|
|
|
clientSecret = savedInstanceState.getString("clientSecret"); |
|
|
|
|
private void redirectUserToAuthorizeAndLogin(EditText editText) { |
|
|
|
|
/* To authorize this app and log in it's necessary to redirect to the domain given, |
|
|
|
|
* activity_login there, and the server will redirect back to the app with its response. */ |
|
|
|
|
String endpoint = MastodonAPI.ENDPOINT_AUTHORIZE; |
|
|
|
|
String redirectUri = getOauthRedirectUri(); |
|
|
|
|
Map<String, String> parameters = new HashMap<>(); |
|
|
|
|
parameters.put("client_id", clientId); |
|
|
|
|
parameters.put("redirect_uri", redirectUri); |
|
|
|
|
parameters.put("response_type", "code"); |
|
|
|
|
parameters.put("scope", OAUTH_SCOPES); |
|
|
|
|
String url = "https://" + domain + endpoint + "?" + toQueryString(parameters); |
|
|
|
|
Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); |
|
|
|
|
if (viewIntent.resolveActivity(getPackageManager()) != null) { |
|
|
|
|
startActivity(viewIntent); |
|
|
|
|
} else { |
|
|
|
|
domain = null; |
|
|
|
|
clientId = null; |
|
|
|
|
clientSecret = null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
preferences = getSharedPreferences( |
|
|
|
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE); |
|
|
|
|
|
|
|
|
|
button.setOnClickListener(new View.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(View v) { |
|
|
|
|
onButtonClick(editText); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
final Context context = this; |
|
|
|
|
|
|
|
|
|
whatsAnInstance.setOnClickListener(new View.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(View v) { |
|
|
|
|
AlertDialog dialog = new AlertDialog.Builder(context) |
|
|
|
|
.setMessage(R.string.dialog_whats_an_instance) |
|
|
|
|
.setPositiveButton(R.string.action_close, |
|
|
|
|
new DialogInterface.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(DialogInterface dialog, int which) { |
|
|
|
|
dialog.dismiss(); |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
.show(); |
|
|
|
|
TextView textView = (TextView) dialog.findViewById(android.R.id.message); |
|
|
|
|
textView.setMovementMethod(LinkMovementMethod.getInstance()); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// Apply any updates needed.
|
|
|
|
|
int versionCode = 1; |
|
|
|
|
try { |
|
|
|
|
versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; |
|
|
|
|
} catch (PackageManager.NameNotFoundException e) { |
|
|
|
|
Log.e(TAG, "The app version was not found. " + e.getMessage()); |
|
|
|
|
} |
|
|
|
|
if (preferences.getInt("lastUpdateVersion", 0) != versionCode) { |
|
|
|
|
SharedPreferences.Editor editor = preferences.edit(); |
|
|
|
|
if (versionCode == 14) { |
|
|
|
|
/* This version switches the order of scheme and host in the OAuth redirect URI. |
|
|
|
|
* But to fix it requires forcing the app to re-authenticate with servers. So, clear |
|
|
|
|
* out the stored client id/secret pairs. The only other things that are lost are |
|
|
|
|
* "rememberedVisibility", "loggedInUsername", and "loggedInAccountId". */ |
|
|
|
|
editor.clear(); |
|
|
|
|
} |
|
|
|
|
editor.putInt("lastUpdateVersion", versionCode); |
|
|
|
|
editor.apply(); |
|
|
|
|
editText.setError(getString(R.string.error_no_web_browser_found)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
protected void onSaveInstanceState(Bundle outState) { |
|
|
|
|
outState.putString("domain", domain); |
|
|
|
|
outState.putString("clientId", clientId); |
|
|
|
|
outState.putString("clientSecret", clientSecret); |
|
|
|
|
super.onSaveInstanceState(outState); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void onLoginSuccess(String accessToken) { |
|
|
|
|
SharedPreferences.Editor editor = preferences.edit(); |
|
|
|
|
editor.putString("domain", domain); |
|
|
|
|
editor.putString("accessToken", accessToken); |
|
|
|
|
editor.commit(); |
|
|
|
|
Intent intent = new Intent(this, MainActivity.class); |
|
|
|
|
startActivity(intent); |
|
|
|
|
finish(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
protected void onStop() { |
|
|
|
|
super.onStop(); |
|
|
|
@ -350,4 +351,14 @@ public class LoginActivity extends AppCompatActivity { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void onLoginSuccess(String accessToken) { |
|
|
|
|
SharedPreferences.Editor editor = preferences.edit(); |
|
|
|
|
editor.putString("domain", domain); |
|
|
|
|
editor.putString("accessToken", accessToken); |
|
|
|
|
editor.commit(); |
|
|
|
|
Intent intent = new Intent(this, MainActivity.class); |
|
|
|
|
startActivity(intent); |
|
|
|
|
finish(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|