You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale-android/android/src/main/java/com/tailscale/ipn/App.java

237 lines
7.4 KiB
Java

// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package com.tailscale.ipn;
import android.app.Application;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.BroadcastReceiver;
import android.provider.Settings;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.net.VpnService;
import android.view.View;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import java.io.IOException;
import java.io.File;
import java.io.FileOutputStream;
import java.security.GeneralSecurityException;
import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKey;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import androidx.browser.customtabs.CustomTabsIntent;
import org.gioui.Gio;
public class App extends Application {
private final static int REQUEST_SIGNIN = 1001;
private final static int REQUEST_PREPARE_VPN = 1002;
private final static String PEER_TAG = "peer";
private final static Handler mainHandler = new Handler(Looper.getMainLooper());
@Override public void onCreate() {
super.onCreate();
// Load and initialize the Go library.
Gio.init(this);
registerNetworkCallback();
}
private void registerNetworkCallback() {
BroadcastReceiver connectivityChanged = new BroadcastReceiver() {
@Override public void onReceive(Context ctx, Intent intent) {
boolean noconn = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
onConnectivityChanged(!noconn);
}
};
registerReceiver(connectivityChanged, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
public void startVPN() {
Intent intent = new Intent(this, IPNService.class);
intent.setAction(IPNService.ACTION_CONNECT);
startService(intent);
}
public void stopVPN() {
Intent intent = new Intent(this, IPNService.class);
intent.setAction(IPNService.ACTION_DISCONNECT);
startService(intent);
}
// encryptToPref a byte array of data using the Jetpack Security
// library and writes it to a global encrypted preference store.
public void encryptToPref(String prefKey, String plaintext) throws IOException, GeneralSecurityException {
getEncryptedPrefs().edit().putString(prefKey, plaintext).commit();
}
// decryptFromPref decrypts a encrypted preference using the Jetpack Security
// library and returns the plaintext.
public String decryptFromPref(String prefKey) throws IOException, GeneralSecurityException {
return getEncryptedPrefs().getString(prefKey, null);
}
private SharedPreferences getEncryptedPrefs() throws IOException, GeneralSecurityException {
MasterKey key = new MasterKey.Builder(this)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
return EncryptedSharedPreferences.create(
this,
"secret_shared_prefs",
key,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
}
void googleSignOut() {
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.build();
GoogleSignInClient client = GoogleSignIn.getClient(this, gso);
client.signOut();
}
void setTileStatus(boolean wantRunning) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return;
}
QuickToggleService.setStatus(this, wantRunning);
}
String getHostname() {
String userConfiguredDeviceName = getUserConfiguredDeviceName();
if (!isEmpty(userConfiguredDeviceName)) return userConfiguredDeviceName;
return getModelName();
}
String getModelName() {
String manu = Build.MANUFACTURER;
String model = Build.MODEL;
// Strip manufacturer from model.
int idx = model.toLowerCase().indexOf(manu.toLowerCase());
if (idx != -1) {
model = model.substring(idx + manu.length());
model = model.trim();
}
return manu + " " + model;
}
String getOSVersion() {
return Build.VERSION.RELEASE;
}
// get user defined nickname from Settings
// returns null if not available
private String getUserConfiguredDeviceName() {
String nameFromSystemBluetooth = Settings.System.getString(getContentResolver(), "bluetooth_name");
String nameFromSecureBluetooth = Settings.Secure.getString(getContentResolver(), "bluetooth_name");
String nameFromSystemDevice = Settings.Secure.getString(getContentResolver(), "device_name");
if (!isEmpty(nameFromSystemBluetooth)) return nameFromSystemBluetooth;
if (!isEmpty(nameFromSecureBluetooth)) return nameFromSecureBluetooth;
if (!isEmpty(nameFromSystemDevice)) return nameFromSystemDevice;
return null;
}
private static boolean isEmpty(String str) {
return str == null || str.length() == 0;
}
// attachPeer adds a Peer fragment for tracking the Activity
// lifecycle.
void attachPeer(Activity act) {
FragmentTransaction ft = act.getFragmentManager().beginTransaction();
ft.add(new Peer(), PEER_TAG);
ft.commit();
act.getFragmentManager().executePendingTransactions();
}
boolean isChromeOS() {
return getPackageManager().hasSystemFeature("android.hardware.type.pc");
}
void googleSignIn(Activity act, String serverOAuthID) {
act.runOnUiThread(new Runnable() {
@Override public void run() {
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(serverOAuthID)
.requestEmail()
.build();
GoogleSignInClient client = GoogleSignIn.getClient(act, gso);
Intent signInIntent = client.getSignInIntent();
startActivityForResult(act, signInIntent, REQUEST_SIGNIN);
}
});
}
void prepareVPN(Activity act) {
act.runOnUiThread(new Runnable() {
@Override public void run() {
Intent intent = VpnService.prepare(act);
if (intent == null) {
onVPNPrepared();
} else {
startActivityForResult(act, intent, REQUEST_PREPARE_VPN);
}
}
});
}
private void startActivityForResult(Activity act, Intent intent, int request) {
Fragment f = act.getFragmentManager().findFragmentByTag(PEER_TAG);
f.startActivityForResult(intent, request);
}
static void onActivityResult(Activity act, int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case App.REQUEST_SIGNIN:
if (resultCode == Activity.RESULT_OK) {
GoogleSignInAccount acc = GoogleSignIn.getLastSignedInAccount(act);
onSignin(acc.getIdToken());
} else {
onSignin(null);
}
case App.REQUEST_PREPARE_VPN:
if (resultCode == Activity.RESULT_OK) {
onVPNPrepared();
}
}
}
void showURL(Activity act, String url) {
act.runOnUiThread(new Runnable() {
@Override public void run() {
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
int headerColor = 0xff496495;
builder.setToolbarColor(headerColor);
CustomTabsIntent intent = builder.build();
intent.launchUrl(act, Uri.parse(url));
}
});
}
static native void onSignin(String idToken);
static native void onVPNPrepared();
private static native void onConnectivityChanged(boolean connected);
}