android: Ask user to add our app to the device's power whitelist

This is necessary so we can actually schedule events accurately in Doze
mode. Otherwise, we'd only get woken in intervals of several minutes (up to
15 according to the docs) after about an hour.
This commit is contained in:
Tobias Brunner 2020-05-08 12:17:52 +02:00
parent d67a5b0c4d
commit a0d32a2d13
9 changed files with 85 additions and 6 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012-2018 Tobias Brunner
Copyright (C) 2012-2020 Tobias Brunner
Copyright (C) 2012 Giuliano Grassi
Copyright (C) 2012 Ralf Sager
HSR Hochschule fuer Technik Rapperswil
@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application
android:name=".logic.StrongSwanApplication"

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2012-2018 Tobias Brunner
* Copyright (C) 2012-2020 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@ -19,12 +19,17 @@ import android.app.Dialog;
import android.app.Service;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.net.VpnService;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
@ -37,6 +42,7 @@ import org.strongswan.android.data.VpnType.VpnTypeFeature;
import org.strongswan.android.logic.VpnStateService;
import org.strongswan.android.logic.VpnStateService.State;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDialogFragment;
@ -51,6 +57,7 @@ public class VpnProfileControlActivity extends AppCompatActivity
public static final String EXTRA_VPN_PROFILE_ID = "org.strongswan.android.VPN_PROFILE_ID";
private static final int PREPARE_VPN_SERVICE = 0;
private static final int ADD_TO_POWER_WHITELIST = 1;
private static final String WAITING_FOR_RESULT = "WAITING_FOR_RESULT";
private static final String PROFILE_NAME = "PROFILE_NAME";
private static final String PROFILE_REQUIRES_PASSWORD = "REQUIRES_PASSWORD";
@ -181,6 +188,27 @@ public class VpnProfileControlActivity extends AppCompatActivity
}
}
/**
* Check if we are on the system's power whitelist, if necessary, or ask the user
* to add us.
* @return true if profile can be initiated immediately
*/
private boolean checkPowerWhitelist()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
PowerManager pm = (PowerManager)this.getSystemService(Context.POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(this.getPackageName()))
{
PowerWhitelistRequired whitelist = new PowerWhitelistRequired();
mWaitingForResult = true;
whitelist.show(getSupportFragmentManager(), DIALOG_TAG);
return false;
}
}
return true;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
@ -190,11 +218,14 @@ public class VpnProfileControlActivity extends AppCompatActivity
mWaitingForResult = false;
if (resultCode == RESULT_OK && mProfileInfo != null)
{
if (mService != null)
if (checkPowerWhitelist())
{
mService.connect(mProfileInfo, true);
if (mService != null)
{
mService.connect(mProfileInfo, true);
}
finish();
}
finish();
}
else
{ /* this happens if the always-on VPN feature is activated by a different app or the user declined */
@ -207,6 +238,14 @@ public class VpnProfileControlActivity extends AppCompatActivity
VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_no_permission);
}
break;
case ADD_TO_POWER_WHITELIST:
mWaitingForResult = false;
if (mProfileInfo != null && mService != null)
{
mService.connect(mProfileInfo, true);
}
finish();
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
@ -531,6 +570,32 @@ public class VpnProfileControlActivity extends AppCompatActivity
}
}
/**
* Class that displays a warning before asking the user to add the app to the
* device's power whitelist.
*/
public static class PowerWhitelistRequired extends AppCompatDialogFragment
{
@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.power_whitelist_title)
.setMessage(R.string.power_whitelist_text)
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
Uri.parse("package:" + getActivity().getPackageName()));
getActivity().startActivityForResult(intent, ADD_TO_POWER_WHITELIST);
}).create();
}
@Override
public void onCancel(@NonNull DialogInterface dialog)
{
getActivity().finish();
}
}
/**
* Class representing an error message which is displayed if VpnService is
* not supported on the current device.
@ -556,7 +621,6 @@ public class VpnProfileControlActivity extends AppCompatActivity
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.vpn_not_supported_title)
.setMessage(messageId)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
{
@Override

View File

@ -215,6 +215,8 @@
<item quantity="other">Wiederholen in %1$d Sekunden</item>
</plurals>
<string name="cancel_retry">Wiederholen abbrechen</string>
<string name="power_whitelist_title">Akku-Optimierung deaktivieren</string>
<string name="power_whitelist_text">Bitte den nächsten Dialog bestätigen, um die App auf die weisse Liste für Akku-Optimierung zu setzen, so dass sie NAT keepalives und Rekeyings zeitlich korrekt planen kann, um konstant erreichbar zu bleiben während die VPN-Verbindung besteht.</string>
<!-- Quick Settings tile -->
<string name="tile_default">VPN umschalten</string>

View File

@ -215,6 +215,8 @@
<item quantity="other">Retry in %1$d seconds</item>
</plurals>
<string name="cancel_retry">Cancel retry</string>
<string name="power_whitelist_title">Disable battery optimizations</string>
<string name="power_whitelist_text">Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established.</string>
<!-- Quick Settings tile -->
<string name="tile_default">Toggle VPN</string>

View File

@ -212,6 +212,8 @@
<item quantity="other">Retry in %1$d seconds</item>
</plurals>
<string name="cancel_retry">Cancel retry</string>
<string name="power_whitelist_title">Disable battery optimizations</string>
<string name="power_whitelist_text">Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established.</string>
<!-- Quick Settings tile -->
<string name="tile_default">Toggle VPN</string>

View File

@ -213,6 +213,8 @@
<item quantity="other">Retry in %1$d seconds</item>
</plurals>
<string name="cancel_retry">Cancel retry</string>
<string name="power_whitelist_title">Disable battery optimizations</string>
<string name="power_whitelist_text">Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established.</string>
<!-- Quick Settings tile -->
<string name="tile_default">Toggle VPN</string>

View File

@ -212,6 +212,8 @@
<item quantity="other">Retry in %1$d seconds</item>
</plurals>
<string name="cancel_retry">Cancel retry</string>
<string name="power_whitelist_title">Disable battery optimizations</string>
<string name="power_whitelist_text">Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established.</string>
<!-- Quick Settings tile -->
<string name="tile_default">Toggle VPN</string>

View File

@ -212,6 +212,8 @@
<item quantity="other">Retry in %1$d seconds</item>
</plurals>
<string name="cancel_retry">Cancel retry</string>
<string name="power_whitelist_title">Disable battery optimizations</string>
<string name="power_whitelist_text">Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established.</string>
<!-- Quick Settings tile -->
<string name="tile_default">Toggle VPN</string>

View File

@ -215,6 +215,8 @@
<item quantity="other">Retry in %1$d seconds</item>
</plurals>
<string name="cancel_retry">Cancel retry</string>
<string name="power_whitelist_title">Disable battery optimizations</string>
<string name="power_whitelist_text">Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established.</string>
<!-- Quick Settings tile -->
<string name="tile_default">Toggle VPN</string>