mirror of https://github.com/YTVanced/VancedMicroG
584 lines
22 KiB
Java
584 lines
22 KiB
Java
/*
|
|
* Copyright (C) 2013-2017 microG Project Team
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package org.microg.gms.auth.login;
|
|
|
|
import static android.accounts.AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE;
|
|
import static android.accounts.AccountManager.VISIBILITY_USER_MANAGED_VISIBLE;
|
|
import static android.os.Build.VERSION.SDK_INT;
|
|
import static android.telephony.TelephonyManager.SIM_STATE_UNKNOWN;
|
|
import static android.view.KeyEvent.KEYCODE_BACK;
|
|
import static android.view.View.INVISIBLE;
|
|
import static android.view.View.VISIBLE;
|
|
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
|
import static org.microg.gms.auth.AuthPrefs.isAuthVisible;
|
|
import static org.microg.gms.checkin.CheckinPrefs.hideLauncherIcon;
|
|
import static org.microg.gms.checkin.CheckinPrefs.isSpoofingEnabled;
|
|
import static org.microg.gms.checkin.CheckinPrefs.setSpoofingEnabled;
|
|
import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME;
|
|
import static org.microg.gms.common.Constants.GMS_VERSION_CODE;
|
|
|
|
import android.accounts.Account;
|
|
import android.accounts.AccountManager;
|
|
import android.annotation.SuppressLint;
|
|
import android.content.Context;
|
|
import android.graphics.Color;
|
|
import android.net.ConnectivityManager;
|
|
import android.net.NetworkInfo;
|
|
import android.net.Uri;
|
|
import android.os.Bundle;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
import android.view.KeyEvent;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.inputmethod.InputMethodManager;
|
|
import android.webkit.CookieManager;
|
|
import android.webkit.JavascriptInterface;
|
|
import android.webkit.WebSettings;
|
|
import android.webkit.WebView;
|
|
import android.widget.RelativeLayout;
|
|
import android.widget.TextView;
|
|
|
|
import androidx.annotation.StringRes;
|
|
import androidx.webkit.WebViewClientCompat;
|
|
|
|
import com.mgoogle.android.gms.R;
|
|
|
|
import org.json.JSONArray;
|
|
import org.microg.gms.auth.AuthConstants;
|
|
import org.microg.gms.auth.AuthManager;
|
|
import org.microg.gms.auth.AuthRequest;
|
|
import org.microg.gms.auth.AuthResponse;
|
|
import org.microg.gms.checkin.CheckinManager;
|
|
import org.microg.gms.checkin.LastCheckinInfo;
|
|
import org.microg.gms.common.HttpFormClient;
|
|
import org.microg.gms.common.Utils;
|
|
import org.microg.gms.people.PeopleManager;
|
|
import org.microg.gms.profile.Build;
|
|
import org.microg.gms.profile.ProfileManager;
|
|
import org.microg.gms.ui.UtilsKt;
|
|
|
|
import java.io.IOException;
|
|
import java.util.Locale;
|
|
|
|
public class LoginActivity extends AssistantActivity {
|
|
public static final String TMPL_NEW_ACCOUNT = "new_account";
|
|
public static final String EXTRA_TMPL = "tmpl";
|
|
public static final String EXTRA_EMAIL = "email";
|
|
public static final String EXTRA_TOKEN = "masterToken";
|
|
public static final int STATUS_BAR_DISABLE_BACK = 0x00400000;
|
|
|
|
private static final String TAG = "GmsAuthLoginBrowser";
|
|
private static final String EMBEDDED_SETUP_URL = "https://accounts.google.com/EmbeddedSetup";
|
|
private static final String PROGRAMMATIC_AUTH_URL = "https://accounts.google.com/o/oauth2/programmatic_auth";
|
|
private static final String GOOGLE_SUITE_URL = "https://accounts.google.com/signin/continue";
|
|
private static final String MAGIC_USER_AGENT = " MinuteMaid";
|
|
private static final String COOKIE_OAUTH_TOKEN = "oauth_token";
|
|
|
|
private WebView webView;
|
|
private String accountType;
|
|
private AccountManager accountManager;
|
|
private InputMethodManager inputMethodManager;
|
|
private ViewGroup authContent;
|
|
private int state = 0;
|
|
|
|
private final String HuaweiButtonPreference = "huaweiloginbutton";
|
|
private final String LoginButtonPreference = "standardloginbutton";
|
|
|
|
@SuppressLint("AddJavascriptInterface")
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE;
|
|
accountManager = AccountManager.get(LoginActivity.this);
|
|
inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
|
webView = createWebView(this);
|
|
webView.addJavascriptInterface(new JsBridge(), "mm");
|
|
authContent = (ViewGroup) findViewById(R.id.auth_content);
|
|
((ViewGroup) findViewById(R.id.auth_root)).addView(webView);
|
|
webView.setWebViewClient(new WebViewClientCompat() {
|
|
@Override
|
|
public void onPageFinished(WebView view, String url) {
|
|
Log.d(TAG, "pageFinished: " + view.getUrl());
|
|
Uri uri = Uri.parse(view.getUrl());
|
|
|
|
// Begin login.
|
|
// Only required if client code does not invoke showView() via JSBridge
|
|
if ("identifier".equals(uri.getFragment()) || uri.getPath().endsWith("/identifier"))
|
|
runOnUiThread(() -> webView.setVisibility(VISIBLE));
|
|
|
|
// Normal login.
|
|
if ("close".equals(uri.getFragment()))
|
|
closeWeb(false);
|
|
|
|
// Google Suite login.
|
|
if (url.startsWith(GOOGLE_SUITE_URL))
|
|
closeWeb(false);
|
|
|
|
// IDK when this is called.
|
|
if (url.startsWith(PROGRAMMATIC_AUTH_URL))
|
|
closeWeb(true);
|
|
}
|
|
});
|
|
if (getIntent().hasExtra(EXTRA_TOKEN)) {
|
|
if (getIntent().hasExtra(EXTRA_EMAIL)) {
|
|
AccountManager accountManager = AccountManager.get(this);
|
|
Account account = new Account(getIntent().getStringExtra(EXTRA_EMAIL), accountType);
|
|
accountManager.addAccountExplicitly(account, getIntent().getStringExtra(EXTRA_TOKEN), null);
|
|
if (isAuthVisible(this) && SDK_INT >= 26) {
|
|
accountManager.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, VISIBILITY_USER_MANAGED_VISIBLE);
|
|
}
|
|
retrieveGmsToken(account);
|
|
} else {
|
|
retrieveRtToken(getIntent().getStringExtra(EXTRA_TOKEN));
|
|
}
|
|
} else {
|
|
setMessage(R.string.auth_before_connect);
|
|
setSpoofButtonText(R.string.brand_spoof_button);
|
|
setBackButtonText(android.R.string.cancel);
|
|
setNextButtonText(R.string.auth_sign_in);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onHuaweiButtonClicked() {
|
|
super.onHuaweiButtonClicked();
|
|
state++;
|
|
if (state == 1) {
|
|
if (SDK_INT >= 23) {
|
|
hideLauncherIcon(this, false);
|
|
UtilsKt.hideIcon(this, false);
|
|
}
|
|
if (!isSpoofingEnabled(this)) {
|
|
LastCheckinInfo.clear(this);
|
|
setSpoofingEnabled(this, true);
|
|
}
|
|
init();
|
|
} else if (state == -1) {
|
|
setResult(RESULT_CANCELED);
|
|
finish();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onNextButtonClicked() {
|
|
super.onNextButtonClicked();
|
|
state++;
|
|
if (state == 1) {
|
|
if (isSpoofingEnabled(this)) {
|
|
LastCheckinInfo.clear(this);
|
|
setSpoofingEnabled(this, false);
|
|
}
|
|
init();
|
|
} else if (state == -1) {
|
|
setResult(RESULT_CANCELED);
|
|
finish();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onBackButtonClicked() {
|
|
super.onBackButtonClicked();
|
|
state--;
|
|
if (state == -1) {
|
|
finish();
|
|
}
|
|
}
|
|
|
|
private void init() {
|
|
setTitle(R.string.just_a_sec);
|
|
setBackButtonText(null);
|
|
setSpoofButtonText(null);
|
|
setNextButtonText(null);
|
|
View loading = getLayoutInflater().inflate(R.layout.login_assistant_loading, authContent, false);
|
|
authContent.removeAllViews();
|
|
authContent.addView(loading);
|
|
setMessage(R.string.auth_connecting);
|
|
CookieManager.getInstance().setAcceptCookie(true);
|
|
if (SDK_INT >= 21) {
|
|
CookieManager.getInstance().removeAllCookies(value -> start());
|
|
} else {
|
|
//noinspection deprecation
|
|
CookieManager.getInstance().removeAllCookie();
|
|
start();
|
|
}
|
|
}
|
|
|
|
private static WebView createWebView(Context context) {
|
|
WebView webView = new WebView(context);
|
|
if (SDK_INT < 21) {
|
|
webView.setVisibility(VISIBLE);
|
|
} else {
|
|
webView.setVisibility(INVISIBLE);
|
|
}
|
|
webView.setLayoutParams(new RelativeLayout.LayoutParams(
|
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
|
webView.setBackgroundColor(Color.TRANSPARENT);
|
|
prepareWebViewSettings(context, webView.getSettings());
|
|
return webView;
|
|
}
|
|
|
|
@SuppressLint("SetJavaScriptEnabled")
|
|
private static void prepareWebViewSettings(Context context, WebSettings settings) {
|
|
ProfileManager.ensureInitialized(context);
|
|
settings.setUserAgentString(Build.INSTANCE.generateWebViewUserAgentString(settings.getUserAgentString()) + MAGIC_USER_AGENT);
|
|
settings.setJavaScriptEnabled(true);
|
|
settings.setSupportMultipleWindows(false);
|
|
settings.setSaveFormData(false);
|
|
settings.setAllowFileAccess(false);
|
|
settings.setDatabaseEnabled(false);
|
|
settings.setNeedInitialFocus(false);
|
|
settings.setUseWideViewPort(false);
|
|
settings.setSupportZoom(false);
|
|
settings.setJavaScriptCanOpenWindowsAutomatically(false);
|
|
}
|
|
|
|
private void start() {
|
|
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
|
|
if (networkInfo != null && networkInfo.isConnected()) {
|
|
if (LastCheckinInfo.read(this).getAndroidId() == 0) {
|
|
new Thread(() -> {
|
|
Runnable next;
|
|
next = checkin(false) ? this::loadLoginPage : () -> showError(R.string.auth_general_error_desc);
|
|
LoginActivity.this.runOnUiThread(next);
|
|
}).start();
|
|
} else {
|
|
loadLoginPage();
|
|
}
|
|
} else {
|
|
showError(R.string.no_network_error_desc);
|
|
}
|
|
}
|
|
|
|
private void showError(int errorRes) {
|
|
setTitle(R.string.sorry);
|
|
findViewById(R.id.progress_bar).setVisibility(View.INVISIBLE);
|
|
setMessage(errorRes);
|
|
}
|
|
|
|
private void setMessage(@StringRes int res) {
|
|
setMessage(getText(res));
|
|
}
|
|
|
|
private void setMessage(CharSequence text) {
|
|
((TextView) findViewById(R.id.description_text)).setText(text);
|
|
}
|
|
|
|
private void loadLoginPage() {
|
|
String tmpl = getIntent().hasExtra(EXTRA_TMPL) ? getIntent().getStringExtra(EXTRA_TMPL) : TMPL_NEW_ACCOUNT;
|
|
webView.loadUrl(buildUrl(tmpl, Utils.getLocale(this)));
|
|
}
|
|
|
|
private void closeWeb(boolean programmaticAuth) {
|
|
setMessage(R.string.auth_finalize);
|
|
runOnUiThread(() -> webView.setVisibility(INVISIBLE));
|
|
String cookies = CookieManager.getInstance().getCookie(programmaticAuth ? PROGRAMMATIC_AUTH_URL : EMBEDDED_SETUP_URL);
|
|
String[] temp = cookies.split(";");
|
|
for (String ar1 : temp) {
|
|
if (ar1.trim().startsWith(COOKIE_OAUTH_TOKEN + "=")) {
|
|
String[] temp1 = ar1.split("=");
|
|
retrieveRtToken(temp1[1]);
|
|
return;
|
|
}
|
|
}
|
|
showError(R.string.auth_general_error_desc);
|
|
}
|
|
|
|
private void retrieveRtToken(String oAuthToken) {
|
|
new AuthRequest().fromContext(this)
|
|
.appIsGms()
|
|
.callerIsGms()
|
|
.service("ac2dm")
|
|
.token(oAuthToken).isAccessToken()
|
|
.addAccount()
|
|
.getAccountId()
|
|
.getResponseAsync(new HttpFormClient.Callback<AuthResponse>() {
|
|
@Override
|
|
public void onResponse(AuthResponse response) {
|
|
Account account = new Account(response.email, accountType);
|
|
if (accountManager.addAccountExplicitly(account, response.token, null)) {
|
|
accountManager.setAuthToken(account, "SID", response.Sid);
|
|
accountManager.setAuthToken(account, "LSID", response.LSid);
|
|
accountManager.setUserData(account, "flags", "1");
|
|
accountManager.setUserData(account, "services", response.services);
|
|
accountManager.setUserData(account, "oauthAccessToken", "1");
|
|
accountManager.setUserData(account, "firstName", response.firstName);
|
|
accountManager.setUserData(account, "lastName", response.lastName);
|
|
if (!TextUtils.isEmpty(response.accountId))
|
|
accountManager.setUserData(account, "GoogleUserId", response.accountId);
|
|
|
|
retrieveGmsToken(account);
|
|
setResult(RESULT_OK);
|
|
} else {
|
|
Log.w(TAG, "Account NOT created!");
|
|
runOnUiThread(() -> {
|
|
showError(R.string.auth_general_error_desc);
|
|
setNextButtonText(android.R.string.ok);
|
|
});
|
|
state = -2;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onException(Exception exception) {
|
|
Log.w(TAG, "onException", exception);
|
|
runOnUiThread(() -> {
|
|
showError(R.string.auth_general_error_desc);
|
|
setNextButtonText(android.R.string.ok);
|
|
});
|
|
state = -2;
|
|
}
|
|
});
|
|
}
|
|
|
|
private void retrieveGmsToken(final Account account) {
|
|
final AuthManager authManager = new AuthManager(this, account.name, GMS_PACKAGE_NAME, "ac2dm");
|
|
authManager.setPermitted(true);
|
|
new AuthRequest().fromContext(this)
|
|
.appIsGms()
|
|
.callerIsGms()
|
|
.service(authManager.getService())
|
|
.email(account.name)
|
|
.token(AccountManager.get(this).getPassword(account))
|
|
.systemPartition()
|
|
.hasPermission()
|
|
.addAccount()
|
|
.getAccountId()
|
|
.getResponseAsync(new HttpFormClient.Callback<AuthResponse>() {
|
|
@Override
|
|
public void onResponse(AuthResponse response) {
|
|
authManager.storeResponse(response);
|
|
String accountId = PeopleManager.loadUserInfo(LoginActivity.this, account);
|
|
if (!TextUtils.isEmpty(accountId))
|
|
accountManager.setUserData(account, "GoogleUserId", accountId);
|
|
checkin(true);
|
|
finish();
|
|
}
|
|
@Override
|
|
public void onException(Exception exception) {
|
|
Log.w(TAG, "onException", exception);
|
|
runOnUiThread(() -> {
|
|
showError(R.string.auth_general_error_desc);
|
|
setNextButtonText(android.R.string.ok);
|
|
});
|
|
state = -2;
|
|
}
|
|
});
|
|
}
|
|
|
|
private boolean checkin(boolean force) {
|
|
try {
|
|
CheckinManager.checkin(LoginActivity.this, force);
|
|
return true;
|
|
} catch (IOException e) {
|
|
Log.w(TAG, "Checkin failed", e);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
if ((keyCode == KEYCODE_BACK) && webView.canGoBack() && (webView.getVisibility() == VISIBLE)) {
|
|
webView.goBack();
|
|
return true;
|
|
}
|
|
return super.onKeyDown(keyCode, event);
|
|
}
|
|
|
|
private static String buildUrl(String tmpl, Locale locale) {
|
|
return Uri.parse(EMBEDDED_SETUP_URL).buildUpon()
|
|
.appendQueryParameter("source", "android")
|
|
.appendQueryParameter("xoauth_display_name", "Android Device")
|
|
.appendQueryParameter("lang", locale.getLanguage())
|
|
.appendQueryParameter("cc", locale.getCountry().toLowerCase(Locale.US))
|
|
.appendQueryParameter("langCountry", locale.toString().toLowerCase(Locale.US))
|
|
.appendQueryParameter("hl", locale.toString().replace("_", "-"))
|
|
.appendQueryParameter("tmpl", tmpl)
|
|
.build().toString();
|
|
}
|
|
|
|
private class JsBridge {
|
|
|
|
@JavascriptInterface
|
|
public final void addAccount(String json) {
|
|
Log.d(TAG, "JSBridge: addAccount");
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final void closeView() {
|
|
Log.d(TAG, "JSBridge: closeView");
|
|
closeWeb(false);
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final String fetchVerifiedPhoneNumber() {
|
|
Log.d(TAG, "JSBridge: fetchVerifiedPhoneNumber");
|
|
return null;
|
|
}
|
|
|
|
@SuppressWarnings("MissingPermission")
|
|
@JavascriptInterface
|
|
public final String getAccounts() {
|
|
Log.d(TAG, "JSBridge: getAccounts");
|
|
Account[] accountsByType = accountManager.getAccountsByType(accountType);
|
|
JSONArray json = new JSONArray();
|
|
for (Account account : accountsByType) {
|
|
json.put(account.name);
|
|
}
|
|
return json.toString();
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final String getAllowedDomains() {
|
|
Log.d(TAG, "JSBridge: getAllowedDomains");
|
|
return new JSONArray().toString();
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final String getAndroidId() {
|
|
long androidId = LastCheckinInfo.read(LoginActivity.this).getAndroidId();
|
|
Log.d(TAG, "JSBridge: getAndroidId");
|
|
if (androidId == 0 || androidId == -1) return null;
|
|
return Long.toHexString(androidId);
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final int getAuthModuleVersionCode() {
|
|
return 1;
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final int getBuildVersionSdk() {
|
|
return SDK_INT;
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final int getDeviceDataVersionInfo() {
|
|
return 1;
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final String getFactoryResetChallenges() {
|
|
return new JSONArray().toString();
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final String getPhoneNumber() {
|
|
return null;
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final int getPlayServicesVersionCode() {
|
|
return GMS_VERSION_CODE;
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final String getSimSerial() {
|
|
return null;
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final int getSimState() {
|
|
return SIM_STATE_UNKNOWN;
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final void goBack() {
|
|
Log.d(TAG, "JSBridge: goBack");
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final boolean hasPhoneNumber() {
|
|
return false;
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final boolean hasTelephony() {
|
|
return false;
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final void hideKeyboard() {
|
|
inputMethodManager.hideSoftInputFromWindow(webView.getWindowToken(), 0);
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final boolean isUserOwner() {
|
|
return true;
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final void launchEmergencyDialer() {
|
|
Log.d(TAG, "JSBridge: launchEmergencyDialer");
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final void log(String s) {
|
|
Log.d(TAG, "JSBridge: log");
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final void notifyOnTermsOfServiceAccepted() {
|
|
Log.d(TAG, "JSBridge: notifyOnTermsOfServiceAccepted");
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final void setAccountIdentifier(String accountIdentifier) {
|
|
Log.d(TAG, "JSBridge: setAccountIdentifier");
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final void setBackButtonEnabled(boolean backButtonEnabled) {
|
|
int visibility = getWindow().getDecorView().getSystemUiVisibility();
|
|
if (backButtonEnabled)
|
|
visibility &= -STATUS_BAR_DISABLE_BACK;
|
|
else
|
|
visibility |= STATUS_BAR_DISABLE_BACK;
|
|
getWindow().getDecorView().setSystemUiVisibility(visibility);
|
|
}
|
|
|
|
|
|
@JavascriptInterface
|
|
public final void setNewAccountCreated() {
|
|
Log.d(TAG, "JSBridge: setNewAccountCreated");
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final void showKeyboard() {
|
|
inputMethodManager.showSoftInput(webView, SHOW_IMPLICIT);
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final void showView() {
|
|
runOnUiThread(() -> webView.setVisibility(VISIBLE));
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final void skipLogin() {
|
|
Log.d(TAG, "JSBridge: skipLogin");
|
|
finish();
|
|
}
|
|
|
|
@JavascriptInterface
|
|
public final void startAfw() {
|
|
Log.d(TAG, "JSBridge: startAfw");
|
|
}
|
|
|
|
}
|
|
}
|