initial-include VPN demo securephone
This commit is contained in:
1 parent
1da580593f
commit
ce61f6be0a
4131 files changed
+184244
-36
No files matched your search
@@ -0,0 +1,609 @@
|
||||
package backone.cloud.call;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.res.Resources;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.AnimationSet;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.net.VpnService;
|
||||
|
||||
import cloud.backone.backoneconnect.BackOneConnectApplication;
|
||||
import cloud.backone.backoneconnect.events.NodeStatusEvent;
|
||||
import cloud.backone.backoneconnect.events.RequestNodeStatusEvent;
|
||||
import cloud.backone.backoneconnect.model.Network;
|
||||
import cloud.backone.backoneconnect.model.NetworkDao;
|
||||
import cloud.backone.backoneconnect.service.ZeroTierOneService;
|
||||
import com.zerotier.sdk.NodeStatus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
// the main view with tabs
|
||||
public class MainActivity extends MeshengerActivity implements ServiceConnection, AdapterView.OnItemClickListener {
|
||||
private static final int MIC_PERMISSION_REQUEST_CODE = 2;
|
||||
private static final long STATUS_REFRESH_INTERVAL_MS = 30_000L;
|
||||
/** Ignore brief ZeroTier EVENT_OFFLINE flaps while the TUN interface is still up. */
|
||||
private static final long VPN_OFFLINE_DEBOUNCE_MS = 8_000L;
|
||||
|
||||
public MainService.MainBinder binder;
|
||||
private ListView contactListView;
|
||||
private TextView ownStatusText;
|
||||
private boolean fabExpanded = false;
|
||||
private FloatingActionButton fabScan;
|
||||
private FloatingActionButton fabGen;
|
||||
private FloatingActionButton fab;
|
||||
SwipeRefreshLayout swipeRefreshLayout;
|
||||
|
||||
private boolean serviceBound = false;
|
||||
private boolean activityResumed = false;
|
||||
private boolean vpnOnline = false;
|
||||
private final EventBus eventBus = EventBus.getDefault();
|
||||
private final Handler statusRefreshHandler = new Handler(Looper.getMainLooper());
|
||||
private final Runnable vpnOfflineRunnable = () -> {
|
||||
// Only show offline after debounce if the TUN interface is really gone.
|
||||
if (Utils.getFirstTunIpv4() == null) {
|
||||
vpnOnline = false;
|
||||
updateOwnStatusText();
|
||||
} else {
|
||||
vpnOnline = true;
|
||||
updateOwnStatusText();
|
||||
}
|
||||
};
|
||||
private final Runnable statusRefreshRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
refreshContactStatuses();
|
||||
refreshVpnFromTunnel();
|
||||
statusRefreshHandler.postDelayed(this, STATUS_REFRESH_INTERVAL_MS);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
log("onItemClick");
|
||||
if (binder == null) {
|
||||
return;
|
||||
}
|
||||
List<Contact> contacts = binder.getContactsCopy();
|
||||
if (i < 0 || i >= contacts.size()) {
|
||||
return;
|
||||
}
|
||||
Contact contact = contacts.get(i);
|
||||
if (!Utils.hasMicPermission(this)) {
|
||||
Utils.requestMicPermission(this, MIC_PERMISSION_REQUEST_CODE);
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(this, CallActivity.class);
|
||||
intent.setAction("ACTION_OUTGOING_CALL");
|
||||
intent.putExtra("EXTRA_CONTACT", contact);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
log("onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
fab = findViewById(R.id.fab);
|
||||
fabScan = findViewById(R.id.fabScan);
|
||||
fabGen = findViewById(R.id.fabGenerate);
|
||||
contactListView = findViewById(R.id.contactList);
|
||||
ownStatusText = findViewById(R.id.ownStatusText);
|
||||
swipeRefreshLayout = findViewById(R.id.swipe_refresh);
|
||||
|
||||
swipeRefreshLayout.setOnRefreshListener(() -> refreshContactStatuses());
|
||||
|
||||
|
||||
fabScan.setOnClickListener(v -> startActivity(new Intent(this, QRScanActivity.class)));
|
||||
fabGen.setOnClickListener(v -> startActivity(new Intent(this, QRShowActivity.class)));
|
||||
fab.setOnClickListener(this::runFabAnimation);
|
||||
|
||||
// ask for audio recording permissions
|
||||
if (!Utils.hasMicPermission(this)) {
|
||||
Utils.requestMicPermission(this, MIC_PERMISSION_REQUEST_CODE);
|
||||
}
|
||||
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(refreshContactListReceiver, new IntentFilter("refresh_contact_list"));
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(ownStatusReceiver, new IntentFilter(MainService.ACTION_OWN_STATUS_CHANGED));
|
||||
}
|
||||
|
||||
private void refreshContactStatuses() {
|
||||
if (!activityResumed || binder == null) {
|
||||
stopSwipeRefresh();
|
||||
return;
|
||||
}
|
||||
binder.checkOwnConnectivity();
|
||||
binder.pingContacts();
|
||||
eventBus.post(new RequestNodeStatusEvent());
|
||||
statusRefreshHandler.removeCallbacks(refreshTimeoutRunnable);
|
||||
statusRefreshHandler.postDelayed(refreshTimeoutRunnable, 15_000L);
|
||||
}
|
||||
|
||||
private final Runnable refreshTimeoutRunnable = () -> stopSwipeRefresh();
|
||||
|
||||
private void stopSwipeRefresh() {
|
||||
if (swipeRefreshLayout != null) {
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOwnStatusText() {
|
||||
if (ownStatusText == null || !activityResumed) {
|
||||
return;
|
||||
}
|
||||
String signaling;
|
||||
if (binder == null) {
|
||||
signaling = getString(R.string.own_status_checking);
|
||||
} else {
|
||||
switch (binder.getOwnStatus()) {
|
||||
case LISTENING:
|
||||
signaling = getString(R.string.own_status_ready);
|
||||
break;
|
||||
case IN_CALL:
|
||||
signaling = getString(R.string.own_status_in_call);
|
||||
break;
|
||||
case NOT_LISTENING:
|
||||
signaling = getString(R.string.own_status_not_listening);
|
||||
break;
|
||||
default:
|
||||
signaling = getString(R.string.own_status_checking);
|
||||
break;
|
||||
}
|
||||
}
|
||||
String vpn = vpnOnline
|
||||
? getString(R.string.own_status_vpn_online)
|
||||
: getString(R.string.own_status_vpn_offline);
|
||||
ownStatusText.setText(signaling + " · " + vpn);
|
||||
}
|
||||
|
||||
void refreshContactList() {
|
||||
log("refreshContactList");
|
||||
if (binder == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
new Handler(getMainLooper()).post(() -> {
|
||||
if (binder == null || contactListView == null) {
|
||||
return;
|
||||
}
|
||||
List<Contact> contacts = binder.getContactsCopy();
|
||||
contactListView.setAdapter(new ContactListAdapter(this, R.layout.item_contact, contacts));
|
||||
contactListView.setOnItemClickListener(this);
|
||||
contactListView.setOnItemLongClickListener((AdapterView<?> adapterView, View view, int i, long l) -> {
|
||||
if (i < 0 || i >= contacts.size()) {
|
||||
return true;
|
||||
}
|
||||
Contact contact = contacts.get(i);
|
||||
PopupMenu menu = new PopupMenu(this, view);
|
||||
Resources res = getResources();
|
||||
String delete = res.getString(R.string.delete);
|
||||
String rename = res.getString(R.string.rename);
|
||||
String block = res.getString(R.string.block);
|
||||
String unblock = res.getString(R.string.unblock);
|
||||
String share = res.getString(R.string.share);
|
||||
String qr = "QR-ify";
|
||||
|
||||
menu.getMenu().add(delete);
|
||||
menu.getMenu().add(rename);
|
||||
menu.getMenu().add(share);
|
||||
if (contact.getBlocked()) {
|
||||
menu.getMenu().add(unblock);
|
||||
} else {
|
||||
menu.getMenu().add(block);
|
||||
}
|
||||
menu.getMenu().add(qr);
|
||||
|
||||
menu.setOnMenuItemClickListener((MenuItem menuItem) -> {
|
||||
String title = menuItem.getTitle().toString();
|
||||
byte[] publicKey = contact.getPublicKey();
|
||||
if (title.equals(delete)) {
|
||||
showDeleteDialog(publicKey, contact.getName());
|
||||
} else if (title.equals(rename)) {
|
||||
showContactEditDialog(publicKey, contact.getName());
|
||||
} else if (title.equals(share)) {
|
||||
shareContact(contact);
|
||||
} else if (title.equals(block)) {
|
||||
setBlocked(publicKey, true);
|
||||
} else if (title.equals(unblock)) {
|
||||
setBlocked(publicKey, false);
|
||||
} else if (title.equals(qr)) {
|
||||
Intent intent = new Intent(this, QRShowActivity.class);
|
||||
intent.putExtra("EXTRA_CONTACT", contact);
|
||||
startActivity(intent);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
menu.show();
|
||||
return true;
|
||||
});
|
||||
stopSwipeRefresh();
|
||||
});
|
||||
}
|
||||
|
||||
private void showDeleteDialog(byte[] publicKey, String name) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
|
||||
builder.setTitle(R.string.confirm);
|
||||
builder.setMessage(getResources().getString(R.string.contact_remove) + " " + name);
|
||||
builder.setCancelable(false); // prevent key shortcut to cancel dialog
|
||||
|
||||
builder.setPositiveButton(R.string.yes, (DialogInterface dialog, int id) -> {
|
||||
binder.deleteContact(publicKey);
|
||||
dialog.cancel();
|
||||
});
|
||||
|
||||
builder.setNegativeButton(R.string.no, (DialogInterface dialog, int id) -> {
|
||||
dialog.cancel();
|
||||
});
|
||||
|
||||
// create dialog box
|
||||
AlertDialog alert = builder.create();
|
||||
alert.show();
|
||||
}
|
||||
|
||||
private void setBlocked(byte[] publicKey, boolean blocked) {
|
||||
Contact contact = binder.getContactByPublicKey(publicKey);
|
||||
if (contact != null) {
|
||||
contact.setBlocked(blocked);
|
||||
binder.addContact(contact);
|
||||
}
|
||||
}
|
||||
|
||||
private void shareContact(Contact c) {
|
||||
log("shareContact");
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_TEXT, Contact.exportJSON(c, false).toString());
|
||||
startActivity(intent);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void showContactEditDialog(byte[] publicKey, String name) {
|
||||
log("showContactEditDialog");
|
||||
EditText et = new EditText(this);
|
||||
et.setText(name);
|
||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.contact_edit)
|
||||
.setView(et)
|
||||
.setNegativeButton(getResources().getString(R.string.cancel), null)
|
||||
.setPositiveButton(R.string.ok, (DialogInterface dialogInterface, int i) -> {
|
||||
String newName = et.getText().toString();
|
||||
if (newName.length() > 0) {
|
||||
Contact contact = binder.getContactByPublicKey(publicKey);
|
||||
if (contact != null) {
|
||||
contact.setName(newName);
|
||||
binder.addContact(contact);
|
||||
}
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
private void runFabAnimation(View fab) {
|
||||
log("runFabAnimation");
|
||||
AnimationSet scanSet = new AnimationSet(this, null);
|
||||
AnimationSet generateSet = new AnimationSet(this, null);
|
||||
|
||||
final int distance = 200;
|
||||
final int duration = 300;
|
||||
TranslateAnimation scanAnimation;
|
||||
TranslateAnimation generateAnimnation;
|
||||
AlphaAnimation alphaAnimation;
|
||||
|
||||
if (fabExpanded) {
|
||||
scanAnimation = new TranslateAnimation(0, 0, -distance, 0);
|
||||
generateAnimnation = new TranslateAnimation(0, 0, -distance * 2, 0);
|
||||
alphaAnimation = new AlphaAnimation(1.0f, 0.0f);
|
||||
((FloatingActionButton) fab).setImageResource(R.drawable.qr_glass);
|
||||
fabScan.setY(fabScan.getY() + distance);
|
||||
fabGen.setY(fabGen.getY() + distance * 2);
|
||||
} else {
|
||||
scanAnimation = new TranslateAnimation(0, 0, distance, 0);
|
||||
generateAnimnation = new TranslateAnimation(0, 0, distance * 2, 0);
|
||||
alphaAnimation = new AlphaAnimation(0.0f, 1.0f);
|
||||
((FloatingActionButton) fab).setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
|
||||
fabScan.setY(fabScan.getY() - distance);
|
||||
fabGen.setY(fabGen.getY() - distance * 2);
|
||||
}
|
||||
|
||||
scanSet.addAnimation(scanAnimation);
|
||||
scanSet.addAnimation(alphaAnimation);
|
||||
scanSet.setFillAfter(true);
|
||||
|
||||
generateSet.addAnimation(generateAnimnation);
|
||||
generateSet.addAnimation(alphaAnimation);
|
||||
generateSet.setFillAfter(true);
|
||||
|
||||
scanSet.setDuration(duration);
|
||||
generateSet.setDuration(duration);
|
||||
|
||||
fabScan.show();
|
||||
fabGen.show();
|
||||
|
||||
fabScan.startAnimation(scanSet);
|
||||
fabGen.startAnimation(generateSet);
|
||||
|
||||
fabExpanded = !fabExpanded;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
log("onDestroy");
|
||||
activityResumed = false;
|
||||
statusRefreshHandler.removeCallbacks(statusRefreshRunnable);
|
||||
statusRefreshHandler.removeCallbacks(refreshTimeoutRunnable);
|
||||
if (serviceBound) {
|
||||
try {
|
||||
unbindService(this);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
serviceBound = false;
|
||||
binder = null;
|
||||
}
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(refreshContactListReceiver);
|
||||
try {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(ownStatusReceiver);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
((AudioManager)this.getSystemService(Context.AUDIO_SERVICE)).setSpeakerphoneOn(false);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||
log("OnServiceConnected");
|
||||
this.binder = (MainService.MainBinder) iBinder;
|
||||
this.serviceBound = true;
|
||||
refreshContactList();
|
||||
updateOwnStatusText();
|
||||
refreshContactStatuses();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
log("OnServiceDisconnected");
|
||||
this.serviceBound = false;
|
||||
this.binder = null;
|
||||
updateOwnStatusText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
log("onOptionsItemSelected");
|
||||
int id = item.getItemId();
|
||||
|
||||
switch (id) {
|
||||
case R.id.action_settings: {
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
startActivity(intent);
|
||||
break;
|
||||
}
|
||||
case R.id.action_backup: {
|
||||
//startActivity(new Intent(this, BackupActivity.class));
|
||||
break;
|
||||
}
|
||||
case R.id.action_about: {
|
||||
startActivity(new Intent(this, AboutActivity.class));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private final BroadcastReceiver refreshContactListReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (contactListView == null || binder == null) {
|
||||
return;
|
||||
}
|
||||
refreshContactList();
|
||||
stopSwipeRefresh();
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver ownStatusReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (activityResumed) {
|
||||
updateOwnStatusText();
|
||||
}
|
||||
stopSwipeRefresh();
|
||||
}
|
||||
};
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onNodeStatus(NodeStatusEvent event) {
|
||||
NodeStatus status = event.getStatus();
|
||||
if (status != null && status.isOnline()) {
|
||||
statusRefreshHandler.removeCallbacks(vpnOfflineRunnable);
|
||||
vpnOnline = true;
|
||||
// Keep own ID/QR address aligned with current TUN IPv4.
|
||||
if (binder != null && binder.getSettings() != null
|
||||
&& Utils.syncIdentityFromTun(binder.getSettings())) {
|
||||
try {
|
||||
binder.saveDatabase();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} else if (status != null && !status.isOnline()) {
|
||||
// NodeStatus.isOnline() is planet reachability, not VpnService/TUN state.
|
||||
// During/after calls it can flap OFFLINE while the system VPN key stays up.
|
||||
if (Utils.getFirstTunIpv4() != null) {
|
||||
statusRefreshHandler.removeCallbacks(vpnOfflineRunnable);
|
||||
vpnOnline = true;
|
||||
} else if (binder != null && binder.getCurrentCall() != null) {
|
||||
vpnOnline = true;
|
||||
} else {
|
||||
statusRefreshHandler.removeCallbacks(vpnOfflineRunnable);
|
||||
statusRefreshHandler.postDelayed(vpnOfflineRunnable, VPN_OFFLINE_DEBOUNCE_MS);
|
||||
}
|
||||
}
|
||||
updateOwnStatusText();
|
||||
stopSwipeRefresh();
|
||||
}
|
||||
|
||||
/** Prefer TUN presence over flaky NodeStatus for the VPN status line. */
|
||||
private void refreshVpnFromTunnel() {
|
||||
if (Utils.getFirstTunIpv4() != null) {
|
||||
statusRefreshHandler.removeCallbacks(vpnOfflineRunnable);
|
||||
if (!vpnOnline) {
|
||||
vpnOnline = true;
|
||||
updateOwnStatusText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
if (!eventBus.isRegistered(this)) {
|
||||
eventBus.register(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
statusRefreshHandler.removeCallbacks(statusRefreshRunnable);
|
||||
if (eventBus.isRegistered(this)) {
|
||||
eventBus.unregister(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
log("OnResume");
|
||||
super.onResume();
|
||||
activityResumed = true;
|
||||
|
||||
if (!serviceBound) {
|
||||
bindService(new Intent(this, MainService.class), this, Service.BIND_AUTO_CREATE);
|
||||
} else if (binder != null) {
|
||||
refreshContactList();
|
||||
refreshVpnFromTunnel();
|
||||
ensureVpnServiceRunning();
|
||||
updateOwnStatusText();
|
||||
refreshContactStatuses();
|
||||
}
|
||||
eventBus.post(new RequestNodeStatusEvent());
|
||||
statusRefreshHandler.removeCallbacks(statusRefreshRunnable);
|
||||
statusRefreshHandler.postDelayed(statusRefreshRunnable, STATUS_REFRESH_INTERVAL_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* After a call crash/error the process may leave ZeroTier stopped while the UI
|
||||
* still thinks VPN should be up. Restart the last-activated network if TUN is gone.
|
||||
*/
|
||||
private void ensureVpnServiceRunning() {
|
||||
if (Utils.getFirstTunIpv4() != null) {
|
||||
vpnOnline = true;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (VpnService.prepare(this) != null) {
|
||||
return;
|
||||
}
|
||||
NetworkDao networkDao = ((BackOneConnectApplication) getApplication())
|
||||
.getDaoSession()
|
||||
.getNetworkDao();
|
||||
List<Network> activated = networkDao.queryBuilder()
|
||||
.where(NetworkDao.Properties.LastActivated.eq(true))
|
||||
.list();
|
||||
if (activated == null || activated.isEmpty()) {
|
||||
activated = networkDao.loadAll();
|
||||
}
|
||||
if (activated == null || activated.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Network network = activated.get(0);
|
||||
Intent intent = new Intent(this, ZeroTierOneService.class);
|
||||
intent.putExtra(ZeroTierOneService.ZT1_NETWORK_ID, network.getNetworkId());
|
||||
intent.putExtra(ZeroTierOneService.ZT1_USE_DEFAULT_ROUTE, network.getUseDefaultRoute());
|
||||
startService(intent);
|
||||
log("restarted VPN service for network " + network.getNetworkIdStr());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
log("onPause");
|
||||
activityResumed = false;
|
||||
super.onPause();
|
||||
collapseFab();
|
||||
statusRefreshHandler.removeCallbacks(statusRefreshRunnable);
|
||||
statusRefreshHandler.removeCallbacks(refreshTimeoutRunnable);
|
||||
}
|
||||
|
||||
private void collapseFab() {
|
||||
if (fabExpanded) {
|
||||
fab.setImageResource(R.drawable.qr_glass);
|
||||
fabScan.clearAnimation();
|
||||
fabGen.clearAnimation();
|
||||
fabScan.setY(fabScan.getY() + 200);
|
||||
fabGen.setY(fabGen.getY() + 200 * 2);
|
||||
fabExpanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == MIC_PERMISSION_REQUEST_CODE
|
||||
&& !Utils.allGranted(grantResults)) {
|
||||
Toast.makeText(this, R.string.permission_mic, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
log("onCreateOptionsMenu");
|
||||
getMenuInflater().inflate(R.menu.menu_main_activity, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void log(String s) {
|
||||
Log.d(this, s);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user