Merge pull request #1021

Enable syncing while idle on Android M devices
This commit is contained in:
cketti 2016-01-15 10:49:48 +01:00
commit 93df7525c4
8 changed files with 208 additions and 7 deletions

View file

@ -13,7 +13,7 @@ project.ext {
preDexLibs = !project.hasProperty('disablePreDex')
testCoverage = project.hasProperty('testCoverage')
compileSdkVersion = 22
compileSdkVersion = 23
buildToolsVersion = '23.0.1'
}

View file

@ -31,6 +31,9 @@ android {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
// for using Apache HTTP Client
useLibrary 'org.apache.http.legacy'
buildTypes {
debug {
testCoverageEnabled rootProject.testCoverage

View file

@ -0,0 +1,56 @@
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.VisibleForTesting;
public class K9AlarmManager {
private final AlarmManager alarmManager;
private final PowerManager powerManager;
private final String packageName;
@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);
}
public void set(int type, long triggerAtMillis, PendingIntent operation) {
if (isDozeSupported() && isDozeWhiteListed()) {
setAndAllowWhileIdle(type, triggerAtMillis, operation);
} else {
alarmManager.set(type, triggerAtMillis, operation);
}
}
@TargetApi(Build.VERSION_CODES.M)
public 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

@ -19,12 +19,14 @@ package com.fsck.k9.helper;
import java.util.Comparator;
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@ -213,6 +215,12 @@ public class MergeCursor implements Cursor {
return mActiveCursor.getWantsAllOnMoveCalls();
}
@TargetApi(Build.VERSION_CODES.M)
@Override
public void setExtras(Bundle extras) {
mActiveCursor.setExtras(extras);
}
@Override
public boolean isAfterLast() {
int count = getCount();

View file

@ -1,5 +1,6 @@
package com.fsck.k9.provider;
import android.annotation.TargetApi;
import android.app.Application;
import android.content.ContentProvider;
import android.content.ContentResolver;
@ -15,6 +16,7 @@ import android.database.DataSetObserver;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.util.Log;
@ -715,6 +717,12 @@ public class MessageProvider extends ContentProvider {
return mCursor.getWantsAllOnMoveCalls();
}
@TargetApi(Build.VERSION_CODES.M)
@Override
public void setExtras(Bundle extras) {
mCursor.setExtras(extras);
}
@Override
public boolean isAfterLast() {
checkClosed();

View file

@ -12,6 +12,7 @@ import android.net.Uri;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.helper.K9AlarmManager;
public class BootReceiver extends CoreReceiver {
@ -61,7 +62,7 @@ public class BootReceiver extends CoreReceiver {
Log.i(K9.LOG_TAG, "BootReceiver Scheduling intent " + alarmedIntent + " for " + new Date(atTime));
PendingIntent pi = buildPendingIntent(context, intent);
AlarmManager alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
K9AlarmManager alarmMgr = K9AlarmManager.getAlarmManager(context);
alarmMgr.set(AlarmManager.RTC_WAKEUP, atTime, pi);
} else if (CANCEL_INTENT.equals(action)) {
@ -71,7 +72,7 @@ public class BootReceiver extends CoreReceiver {
PendingIntent pi = buildPendingIntent(context, intent);
AlarmManager alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
K9AlarmManager alarmMgr = K9AlarmManager.getAlarmManager(context);
alarmMgr.cancel(pi);
}
@ -119,8 +120,7 @@ public class BootReceiver extends CoreReceiver {
* @param context
*/
public static void purgeSchedule(final Context context) {
final AlarmManager alarmService = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
final K9AlarmManager alarmService = K9AlarmManager.getAlarmManager(context);
alarmService.cancel(PendingIntent.getBroadcast(context, 0, new Intent() {
@Override
public boolean filterEquals(final Intent other) {

View file

@ -0,0 +1,126 @@
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 org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
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();
@Mock
private AlarmManager systemAlarmManager;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void set_withoutDozeSupport_shouldCallSetOnAlarmManager() throws Exception {
K9AlarmManager alarmManager = createK9AlarmManagerWithoutDozeSupport();
alarmManager.set(TIMER_TYPE, TIMEOUT, PENDING_INTENT);
verify(systemAlarmManager).set(TIMER_TYPE, TIMEOUT, PENDING_INTENT);
}
@Test
public void set_withDozeSupportAndNotWhiteListed_shouldCallSetOnAlarmManager() throws Exception {
K9AlarmManager alarmManager = createK9AlarmManagerWithDozeSupport(false);
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);
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);
alarmManager.cancel(PENDING_INTENT);
verify(systemAlarmManager).cancel(PENDING_INTENT);
}
private K9AlarmManager createK9AlarmManagerWithDozeSupport(boolean whiteListed) {
PowerManager powerManager = createPowerManager(whiteListed);
Context context = createContext(powerManager);
return new TestK9AlarmManager(context, true);
}
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 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;
}
}
}

View file

@ -112,7 +112,7 @@ public class PullToRefreshWebView extends PullToRefreshBase<WebView> {
@Override
protected boolean isReadyForPullEnd() {
float exactContentHeight = FloatMath.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale());
double exactContentHeight = Math.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale());
return mRefreshableView.getScrollY() >= (exactContentHeight - mRefreshableView.getHeight());
}
@ -158,7 +158,7 @@ public class PullToRefreshWebView extends PullToRefreshBase<WebView> {
}
private int getScrollRange() {
return (int) Math.max(0, FloatMath.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale())
return (int) Math.max(0, Math.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale())
- (getHeight() - getPaddingBottom() - getPaddingTop()));
}
}