Merge pull request #2545 from k9mail/doze_improvements

Doze improvements
This commit is contained in:
Vincent Breitmoser 2017-06-12 10:44:00 +02:00 committed by GitHub
commit 72af527db0
6 changed files with 193 additions and 81 deletions

View file

@ -38,6 +38,7 @@ import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.ssl.LocalKeyStore;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.power.DeviceIdleManager;
import com.fsck.k9.preferences.Storage;
import com.fsck.k9.preferences.StorageEditor;
import com.fsck.k9.provider.UnreadWidgetProvider;
@ -353,10 +354,22 @@ public class K9 extends Application {
* whether any accounts are configured.
*/
public static void setServicesEnabled(Context context) {
int acctLength = Preferences.getPreferences(context).getAvailableAccounts().size();
Context appContext = context.getApplicationContext();
int acctLength = Preferences.getPreferences(appContext).getAvailableAccounts().size();
boolean enable = acctLength > 0;
setServicesEnabled(context, acctLength > 0, null);
setServicesEnabled(appContext, enable, null);
updateDeviceIdleReceiver(appContext, enable);
}
private static void updateDeviceIdleReceiver(Context context, boolean enable) {
DeviceIdleManager deviceIdleManager = DeviceIdleManager.getInstance(context);
if (enable) {
deviceIdleManager.registerReceiver();
} else {
deviceIdleManager.unregisterReceiver();
}
}
private static void setServicesEnabled(Context context, boolean enabled, Integer wakeLockId) {

View file

@ -1,56 +1,48 @@
package com.fsck.k9.helper;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Build;
import android.os.PowerManager;
import android.support.annotation.RequiresApi;
import android.support.annotation.VisibleForTesting;
import com.fsck.k9.power.DozeChecker;
public class K9AlarmManager {
private final AlarmManager alarmManager;
private final PowerManager powerManager;
private final String packageName;
private final DozeChecker dozeChecker;
@VisibleForTesting
K9AlarmManager(Context context) {
alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
packageName = context.getPackageName();
}
public static K9AlarmManager getAlarmManager(Context context) {
return new K9AlarmManager(context);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
DozeChecker dozeChecker = new DozeChecker(context);
return new K9AlarmManager(alarmManager, dozeChecker);
}
@VisibleForTesting
K9AlarmManager(AlarmManager alarmManager, DozeChecker dozeChecker) {
this.alarmManager = alarmManager;
this.dozeChecker = dozeChecker;
}
public void set(int type, long triggerAtMillis, PendingIntent operation) {
if (isDozeSupported() && isDozeWhiteListed()) {
if (dozeChecker.isDeviceIdleModeSupported() && dozeChecker.isAppWhitelisted()) {
setAndAllowWhileIdle(type, triggerAtMillis, operation);
} else {
alarmManager.set(type, triggerAtMillis, operation);
}
}
@TargetApi(Build.VERSION_CODES.M)
public void setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) {
@RequiresApi(Build.VERSION_CODES.M)
private void setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) {
alarmManager.setAndAllowWhileIdle(type, triggerAtMillis, operation);
}
public void cancel(PendingIntent operation) {
alarmManager.cancel(operation);
}
@VisibleForTesting
protected boolean isDozeSupported() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
@TargetApi(Build.VERSION_CODES.M)
private boolean isDozeWhiteListed() {
return powerManager.isIgnoringBatteryOptimizations(packageName);
}
}

View file

@ -0,0 +1,84 @@
package com.fsck.k9.power;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.IntentFilter;
import android.os.Build;
import android.os.PowerManager;
import timber.log.Timber;
public abstract class DeviceIdleManager {
private static DeviceIdleManager instance;
public static synchronized DeviceIdleManager getInstance(Context context) {
if (instance == null) {
DozeChecker dozeChecker = new DozeChecker(context);
if (dozeChecker.isDeviceIdleModeSupported() && !dozeChecker.isAppWhitelisted()) {
instance = RealDeviceIdleManager.newInstance(context);
} else {
instance = new NoOpDeviceIdleManager();
}
}
return instance;
}
private DeviceIdleManager() {
}
public abstract void registerReceiver();
public abstract void unregisterReceiver();
static class NoOpDeviceIdleManager extends DeviceIdleManager {
@Override
public void registerReceiver() {
// Do nothing
}
@Override
public void unregisterReceiver() {
// Do nothing
}
}
@TargetApi(Build.VERSION_CODES.M)
static class RealDeviceIdleManager extends DeviceIdleManager {
private final Context context;
private final DeviceIdleReceiver deviceIdleReceiver;
private final IntentFilter intentFilter;
private boolean registered;
static RealDeviceIdleManager newInstance(Context context) {
Context appContext = context.getApplicationContext();
return new RealDeviceIdleManager(appContext);
}
private RealDeviceIdleManager(Context context) {
this.context = context;
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
deviceIdleReceiver = new DeviceIdleReceiver(powerManager);
intentFilter = new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
}
@Override
public void registerReceiver() {
Timber.v("Registering DeviceIdleReceiver");
registered = true;
context.registerReceiver(deviceIdleReceiver, intentFilter);
}
@Override
public void unregisterReceiver() {
Timber.v("Unregistering DeviceIdleReceiver");
if (registered) {
context.unregisterReceiver(deviceIdleReceiver);
registered = false;
}
}
}
}

View file

@ -0,0 +1,33 @@
package com.fsck.k9.power;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.PowerManager;
import android.support.annotation.RequiresApi;
import com.fsck.k9.service.MailService;
import timber.log.Timber;
@RequiresApi(api = Build.VERSION_CODES.M)
class DeviceIdleReceiver extends BroadcastReceiver {
private final PowerManager powerManager;
DeviceIdleReceiver(PowerManager powerManager) {
this.powerManager = powerManager;
}
@Override
public void onReceive(Context context, Intent intent) {
boolean deviceInIdleMode = powerManager.isDeviceIdleMode();
Timber.v("Device idle mode changed. Idle: %b", deviceInIdleMode);
if (!deviceInIdleMode) {
MailService.actionReset(context, null);
}
}
}

View file

@ -0,0 +1,28 @@
package com.fsck.k9.power;
import android.content.Context;
import android.os.Build;
import android.os.PowerManager;
import android.support.annotation.RequiresApi;
public class DozeChecker {
private final PowerManager powerManager;
private final String packageName;
public DozeChecker(Context context) {
powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
packageName = context.getPackageName();
}
public boolean isDeviceIdleModeSupported() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
@RequiresApi(api = Build.VERSION_CODES.M)
public boolean isAppWhitelisted() {
return powerManager.isIgnoringBatteryOptimizations(packageName);
}
}

View file

@ -1,13 +1,10 @@
package com.fsck.k9.helper;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Build.VERSION_CODES;
import android.os.PowerManager;
import com.fsck.k9.power.DozeChecker;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
@ -19,7 +16,6 @@ import static org.mockito.Mockito.when;
public class K9AlarmManagerTest {
private static final String PACKAGE_NAME = "org.example.package";
private static final int TIMER_TYPE = AlarmManager.RTC_WAKEUP;
private static final long TIMEOUT = 15L * 60L * 1000L;
private static final PendingIntent PENDING_INTENT = createDummyPendingIntent();
@ -27,16 +23,21 @@ public class K9AlarmManagerTest {
@Mock
private AlarmManager systemAlarmManager;
@Mock
private DozeChecker dozeChecker;
private K9AlarmManager alarmManager;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
alarmManager = new K9AlarmManager(systemAlarmManager, dozeChecker);
}
@Test
public void set_withoutDozeSupport_shouldCallSetOnAlarmManager() throws Exception {
K9AlarmManager alarmManager = createK9AlarmManagerWithoutDozeSupport();
configureDozeSupport(false);
alarmManager.set(TIMER_TYPE, TIMEOUT, PENDING_INTENT);
@ -45,27 +46,27 @@ public class K9AlarmManagerTest {
@Test
public void set_withDozeSupportAndNotWhiteListed_shouldCallSetOnAlarmManager() throws Exception {
K9AlarmManager alarmManager = createK9AlarmManagerWithDozeSupport(false);
configureDozeSupport(true);
alarmManager.set(TIMER_TYPE, TIMEOUT, PENDING_INTENT);
verify(systemAlarmManager).set(TIMER_TYPE, TIMEOUT, PENDING_INTENT);
}
@TargetApi(VERSION_CODES.M)
@Test
public void set_withDozeSupportAndWhiteListed_shouldCallSetAndAllowWhileIdleOnAlarmManager() throws Exception {
K9AlarmManager alarmManager = createK9AlarmManagerWithDozeSupport(true);
configureDozeSupport(true);
addAppToBatteryOptimizationWhitelist();
alarmManager.set(TIMER_TYPE, TIMEOUT, PENDING_INTENT);
verify(systemAlarmManager).setAndAllowWhileIdle(TIMER_TYPE, TIMEOUT, PENDING_INTENT);
}
@TargetApi(VERSION_CODES.M)
@Test
public void cancel_shouldCallCancelOnAlarmManager() throws Exception {
K9AlarmManager alarmManager = createK9AlarmManagerWithDozeSupport(true);
configureDozeSupport(true);
addAppToBatteryOptimizationWhitelist();
alarmManager.cancel(PENDING_INTENT);
@ -73,54 +74,15 @@ public class K9AlarmManagerTest {
}
private K9AlarmManager createK9AlarmManagerWithDozeSupport(boolean whiteListed) {
PowerManager powerManager = createPowerManager(whiteListed);
Context context = createContext(powerManager);
return new TestK9AlarmManager(context, true);
private void configureDozeSupport(boolean supported) {
when(dozeChecker.isDeviceIdleModeSupported()).thenReturn(supported);
}
private K9AlarmManager createK9AlarmManagerWithoutDozeSupport() {
PowerManager powerManager = mock(PowerManager.class);
Context context = createContext(powerManager);
return new TestK9AlarmManager(context, false);
}
@TargetApi(VERSION_CODES.M)
private PowerManager createPowerManager(boolean whiteListed) {
PowerManager powerManager = mock(PowerManager.class);
when(powerManager.isIgnoringBatteryOptimizations(PACKAGE_NAME)).thenReturn(whiteListed);
return powerManager;
}
private Context createContext(PowerManager powerManager) {
Context context = mock(Context.class);
when(context.getPackageName()).thenReturn(PACKAGE_NAME);
when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(systemAlarmManager);
when(context.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
return context;
private void addAppToBatteryOptimizationWhitelist() {
when(dozeChecker.isAppWhitelisted()).thenReturn(true);
}
private static PendingIntent createDummyPendingIntent() {
return mock(PendingIntent.class);
}
class TestK9AlarmManager extends K9AlarmManager {
private final boolean dozeSupported;
TestK9AlarmManager(Context context, boolean dozeSupported) {
super(context);
this.dozeSupported = dozeSupported;
}
@Override
protected boolean isDozeSupported() {
return dozeSupported;
}
}
}