improved refresh services

This commit is contained in:
Mariotaku Lee 2016-12-17 23:05:02 +08:00
parent dcf608e3ff
commit 5b1ac84a3b
17 changed files with 307 additions and 390 deletions

View File

@ -72,19 +72,10 @@ public interface IntentConstants {
String BROADCAST_NOTIFICATION_DELETED = INTENT_PACKAGE_PREFIX + "NOTIFICATION_DELETED"; String BROADCAST_NOTIFICATION_DELETED = INTENT_PACKAGE_PREFIX + "NOTIFICATION_DELETED";
String BROADCAST_USER_LIST_MEMBERS_DELETED = INTENT_PACKAGE_PREFIX + "USER_LIST_MEMBER_DELETED"; String ACTION_REFRESH_HOME_TIMELINE = INTENT_PACKAGE_PREFIX + "REFRESH_HOME_TIMELINE";
String BROADCAST_REFRESH_HOME_TIMELINE = INTENT_PACKAGE_PREFIX + "REFRESH_HOME_TIMELINE"; String ACTION_REFRESH_NOTIFICATIONS = INTENT_PACKAGE_PREFIX + "REFRESH_NOTIFICATIONS";
String BROADCAST_REFRESH_NOTIFICATIONS = INTENT_PACKAGE_PREFIX + "REFRESH_NOTIFICATIONS"; String ACTION_REFRESH_DIRECT_MESSAGES = INTENT_PACKAGE_PREFIX + "REFRESH_DIRECT_MESSAGES";
String BROADCAST_REFRESH_DIRECT_MESSAGES = INTENT_PACKAGE_PREFIX + "REFRESH_DIRECT_MESSAGES"; String ACTION_REFRESH_TRENDS = INTENT_PACKAGE_PREFIX + "REFRESH_TRENDS";
String BROADCAST_REFRESH_TRENDS = INTENT_PACKAGE_PREFIX + "REFRESH_TRENDS";
String BROADCAST_RESCHEDULE_HOME_TIMELINE_REFRESHING = INTENT_PACKAGE_PREFIX
+ "RESCHEDULE_HOME_TIMELINE_REFRESHING";
String BROADCAST_RESCHEDULE_MENTIONS_REFRESHING = INTENT_PACKAGE_PREFIX
+ "RESCHEDULE_MENTIONS_REFRESHING";
String BROADCAST_RESCHEDULE_DIRECT_MESSAGES_REFRESHING = INTENT_PACKAGE_PREFIX
+ "RESCHEDULE_DIRECT_MESSAGES_REFRESHING";
String BROADCAST_RESCHEDULE_TRENDS_REFRESHING = INTENT_PACKAGE_PREFIX
+ "RESCHEDULE_TRENDS_REFRESHING";
String EXTRA_LATITUDE = "latitude"; String EXTRA_LATITUDE = "latitude";
String EXTRA_LONGITUDE = "longitude"; String EXTRA_LONGITUDE = "longitude";

View File

@ -39,6 +39,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.NFC"/> <uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!-- Used for account management --> <!-- Used for account management -->
<uses-permission <uses-permission
@ -588,14 +589,6 @@
android:scheme="android_secret_code"/> android:scheme="android_secret_code"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".receiver.PowerStateReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_LOW"/>
<action android:name="android.intent.action.BATTERY_OKAY"/>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
</intent-filter>
</receiver>
</application> </application>

View File

@ -0,0 +1,17 @@
package org.mariotaku.twidere.annotation;
import android.support.annotation.StringDef;
/**
* Created by mariotaku on 2016/12/17.
*/
@StringDef({AutoRefreshType.HOME_TIMELINE, AutoRefreshType.INTERACTIONS_TIMELINE, AutoRefreshType.DIRECT_MESSAGES})
public @interface AutoRefreshType {
String HOME_TIMELINE = "home_timeline";
String INTERACTIONS_TIMELINE = "interactions_timeline";
String DIRECT_MESSAGES = "direct_messages";
String[] ALL = {HOME_TIMELINE, INTERACTIONS_TIMELINE, DIRECT_MESSAGES};
}

View File

@ -48,7 +48,6 @@ public class ConnectivityStateReceiver extends BroadcastReceiver implements Cons
if (!ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) return; if (!ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) return;
final TwidereApplication application = TwidereApplication.Companion.getInstance(context); final TwidereApplication application = TwidereApplication.Companion.getInstance(context);
// application.reloadConnectivitySettings(); // application.reloadConnectivitySettings();
Utils.startRefreshServiceIfNeeded(application);
final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE); Context.MODE_PRIVATE);
if (prefs.getBoolean(KEY_USAGE_STATISTICS, false) && prefs.getBoolean(KEY_SETTINGS_WIZARD_COMPLETED, false)) { if (prefs.getBoolean(KEY_USAGE_STATISTICS, false) && prefs.getBoolean(KEY_SETTINGS_WIZARD_COMPLETED, false)) {

View File

@ -1019,8 +1019,11 @@ public class DataStoreUtils implements Constants {
} }
public static void prepareDatabase(@NonNull Context context) { public static void prepareDatabase(@NonNull Context context) {
context.getContentResolver().query(TwidereDataStore.CONTENT_URI_DATABASE_PREPARE, null, final ContentResolver cr = context.getContentResolver();
null, null, null); final Cursor cursor = cr.query(TwidereDataStore.CONTENT_URI_DATABASE_PREPARE, null, null,
null, null);
if (cursor == null) return;
cursor.close();
} }
interface FieldArrayCreator<T> { interface FieldArrayCreator<T> {

View File

@ -47,7 +47,6 @@ import android.net.NetworkInfo;
import android.net.Uri; import android.net.Uri;
import android.nfc.NfcAdapter; import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback; import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.os.AsyncTask;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@ -125,7 +124,6 @@ import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers;
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages;
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.ConversationEntries; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.ConversationEntries;
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses; import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
import org.mariotaku.twidere.service.RefreshService;
import org.mariotaku.twidere.util.TwidereLinkify.HighlightStyle; import org.mariotaku.twidere.util.TwidereLinkify.HighlightStyle;
import org.mariotaku.twidere.view.CardMediaContainer.PreviewStyle; import org.mariotaku.twidere.view.CardMediaContainer.PreviewStyle;
import org.mariotaku.twidere.view.ShapedImageView; import org.mariotaku.twidere.view.ShapedImageView;
@ -1184,27 +1182,6 @@ public final class Utils implements Constants {
showErrorMessage(context, message, long_message); showErrorMessage(context, message, long_message);
} }
public static void startRefreshServiceIfNeeded(@NonNull final Context context) {
final Context appContext = context.getApplicationContext();
if (appContext == null) return;
if (!appContext.getResources().getBoolean(R.bool.use_legacy_refresh_service)) return;
final Intent refreshServiceIntent = new Intent(appContext, RefreshService.class);
DataStoreUtils.prepareDatabase(context);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
if (isNetworkAvailable(appContext) && hasAutoRefreshAccounts(appContext)) {
if (BuildConfig.DEBUG) {
Log.d(LOGTAG, "Start background refresh service");
}
appContext.startService(refreshServiceIntent);
} else {
appContext.stopService(refreshServiceIntent);
}
}
});
}
public static void startStatusShareChooser(final Context context, final ParcelableStatus status) { public static void startStatusShareChooser(final Context context, final ParcelableStatus status) {
final Intent intent = new Intent(Intent.ACTION_SEND); final Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain"); intent.setType("text/plain");
@ -1230,10 +1207,6 @@ public final class Utils implements Constants {
} }
} }
public static String trim(final String str) {
return str != null ? str.trim() : null;
}
public static String trimLineBreak(final String orig) { public static String trimLineBreak(final String orig) {
if (orig == null) return null; if (orig == null) return null;
return orig.replaceAll("\\n+", "\n"); return orig.replaceAll("\\n+", "\n");
@ -1306,11 +1279,6 @@ public final class Utils implements Constants {
} }
public static void setSharedElementTransition(Context context, Window window, int transitionRes) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
UtilsL.setSharedElementTransition(context, window, transitionRes);
}
public static <T> Object findFieldOfTypes(T obj, Class<? extends T> cls, Class<?>... checkTypes) { public static <T> Object findFieldOfTypes(T obj, Class<? extends T> cls, Class<?>... checkTypes) {
labelField: labelField:
for (Field field : cls.getDeclaredFields()) { for (Field field : cls.getDeclaredFields()) {
@ -1476,4 +1444,5 @@ public final class Utils implements Constants {
} }
return true; return true;
} }
} }

View File

@ -62,7 +62,6 @@ import org.mariotaku.twidere.constant.apiLastChangeKey
import org.mariotaku.twidere.constant.bugReportsKey import org.mariotaku.twidere.constant.bugReportsKey
import org.mariotaku.twidere.constant.defaultFeatureLastUpdated import org.mariotaku.twidere.constant.defaultFeatureLastUpdated
import org.mariotaku.twidere.model.DefaultFeatures import org.mariotaku.twidere.model.DefaultFeatures
import org.mariotaku.twidere.service.RefreshService
import org.mariotaku.twidere.util.* import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.content.TwidereSQLiteOpenHelper import org.mariotaku.twidere.util.content.TwidereSQLiteOpenHelper
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
@ -93,6 +92,8 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
lateinit internal var externalThemeManager: ExternalThemeManager lateinit internal var externalThemeManager: ExternalThemeManager
@Inject @Inject
lateinit internal var kPreferences: KPreferences lateinit internal var kPreferences: KPreferences
@Inject
lateinit internal var autoRefreshController: AutoRefreshController
private lateinit var profileImageViewViewProcessor: ProfileImageViewViewProcessor private lateinit var profileImageViewViewProcessor: ProfileImageViewViewProcessor
private lateinit var fontFamilyTagProcessor: FontFamilyTagProcessor private lateinit var fontFamilyTagProcessor: FontFamilyTagProcessor
@ -129,13 +130,10 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
updateEasterEggIcon() updateEasterEggIcon()
migrateUsageStatisticsPreferences() migrateUsageStatisticsPreferences()
if (resources.getBoolean(R.bool.use_job_refresh_service)) {
} else {
Utils.startRefreshServiceIfNeeded(this)
}
GeneralComponentHelper.build(this).inject(this) GeneralComponentHelper.build(this).inject(this)
autoRefreshController.appStarted()
registerActivityLifecycleCallbacks(activityTracker) registerActivityLifecycleCallbacks(activityTracker)
listenExternalThemeChange() listenExternalThemeChange()
@ -288,8 +286,7 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
override fun onSharedPreferenceChanged(preferences: SharedPreferences, key: String) { override fun onSharedPreferenceChanged(preferences: SharedPreferences, key: String) {
when (key) { when (key) {
KEY_REFRESH_INTERVAL -> { KEY_REFRESH_INTERVAL -> {
stopService(Intent(this, RefreshService::class.java)) autoRefreshController.rescheduleAll()
Utils.startRefreshServiceIfNeeded(this)
} }
KEY_ENABLE_PROXY, KEY_PROXY_HOST, KEY_PROXY_PORT, KEY_PROXY_TYPE, KEY_PROXY_USERNAME, KEY_ENABLE_PROXY, KEY_PROXY_HOST, KEY_PROXY_PORT, KEY_PROXY_TYPE, KEY_PROXY_USERNAME,
KEY_PROXY_PASSWORD, KEY_CONNECTION_TIMEOUT, KEY_RETRY_ON_NETWORK_ISSUE -> { KEY_PROXY_PASSWORD, KEY_CONNECTION_TIMEOUT, KEY_RETRY_ON_NETWORK_ISSUE -> {

View File

@ -4,6 +4,7 @@ import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.text.TextUtils import android.text.TextUtils
import org.mariotaku.kpreferences.* import org.mariotaku.kpreferences.*
import org.mariotaku.ktextension.toLong
import org.mariotaku.twidere.BuildConfig import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.Constants.KEY_NO_CLOSE_AFTER_TWEET_SENT import org.mariotaku.twidere.Constants.KEY_NO_CLOSE_AFTER_TWEET_SENT
import org.mariotaku.twidere.TwidereConstants.* import org.mariotaku.twidere.TwidereConstants.*
@ -44,6 +45,18 @@ val randomizeAccountNameKey = KBooleanKey(KEY_RANDOMIZE_ACCOUNT_NAME, false)
val defaultAutoRefreshKey = KBooleanKey(KEY_DEFAULT_AUTO_REFRESH, false) val defaultAutoRefreshKey = KBooleanKey(KEY_DEFAULT_AUTO_REFRESH, false)
val defaultAutoRefreshKeyAsked = KBooleanKey("default_auto_refresh_asked", true) val defaultAutoRefreshKeyAsked = KBooleanKey("default_auto_refresh_asked", true)
object refreshIntervalKey : KSimpleKey<Long>(KEY_REFRESH_INTERVAL, 15) {
override fun read(preferences: SharedPreferences): Long {
return preferences.getString(key, null).toLong(def)
}
override fun write(editor: SharedPreferences.Editor, value: Long): Boolean {
editor.putString(key, value.toString())
return true
}
}
object defaultAPIConfigKey : KPreferenceKey<CustomAPIConfig> { object defaultAPIConfigKey : KPreferenceKey<CustomAPIConfig> {
override fun contains(preferences: SharedPreferences): Boolean { override fun contains(preferences: SharedPreferences): Boolean {
if (preferences.getString(KEY_API_URL_FORMAT, null) == null) return false if (preferences.getString(KEY_API_URL_FORMAT, null) == null) return false

View File

@ -19,11 +19,9 @@
package org.mariotaku.twidere.fragment package org.mariotaku.twidere.fragment
import android.content.SharedPreferences
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_AUTO_REFRESH import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_AUTO_REFRESH
import org.mariotaku.twidere.constant.defaultAutoRefreshKey import org.mariotaku.twidere.constant.defaultAutoRefreshKey
import org.mariotaku.twidere.util.Utils
class AccountRefreshSettingsFragment : BaseAccountPreferenceFragment() { class AccountRefreshSettingsFragment : BaseAccountPreferenceFragment() {
@ -36,9 +34,4 @@ class AccountRefreshSettingsFragment : BaseAccountPreferenceFragment() {
override val switchPreferenceKey: String? override val switchPreferenceKey: String?
get() = KEY_AUTO_REFRESH get() = KEY_AUTO_REFRESH
override fun onSharedPreferenceChanged(preferences: SharedPreferences, key: String) {
if (KEY_AUTO_REFRESH == key) {
Utils.startRefreshServiceIfNeeded(activity)
}
}
} }

View File

@ -1,46 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import edu.tsinghua.hotmobi.model.BatteryRecord
/**
* Created by mariotaku on 15/9/29.
*/
class PowerStateReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
Intent.ACTION_BATTERY_LOW, Intent.ACTION_BATTERY_OKAY, Intent.ACTION_POWER_CONNECTED,
Intent.ACTION_POWER_DISCONNECTED -> {
if (serviceReceiverStarted) return
BatteryRecord.log(context)
}
}
}
companion object {
var serviceReceiverStarted: Boolean = false
}
}

View File

@ -24,17 +24,13 @@ import android.annotation.TargetApi
import android.app.job.JobParameters import android.app.job.JobParameters
import android.app.job.JobService import android.app.job.JobService
import android.os.Build import android.os.Build
import org.mariotaku.abstask.library.AbstractTask import android.util.Log
import org.mariotaku.abstask.library.TaskStarter import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.kpreferences.KPreferences import org.mariotaku.kpreferences.KPreferences
import org.mariotaku.twidere.constant.IntentConstants.* import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.annotation.AutoRefreshType
import org.mariotaku.twidere.constant.stopAutoRefreshWhenBatteryLowKey import org.mariotaku.twidere.constant.stopAutoRefreshWhenBatteryLowKey
import org.mariotaku.twidere.model.AccountPreferences
import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.task.GetActivitiesAboutMeTask
import org.mariotaku.twidere.task.GetHomeTimelineTask
import org.mariotaku.twidere.task.GetReceivedDirectMessagesTask
import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.Utils import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import javax.inject.Inject import javax.inject.Inject
@ -59,10 +55,16 @@ class JobRefreshService : JobService() {
// Low battery, don't refresh // Low battery, don't refresh
return false return false
} }
if (BuildConfig.DEBUG) {
Log.d(LOGTAG, "Running job ${params.jobId}")
}
val task = createJobTask(params) ?: return false val task = run {
val type = getRefreshType(params.jobId) ?: return@run null
return@run RefreshService.createJobTask(this, type)
} ?: return false
task.callback = { task.callback = {
this.jobFinished(params, true) this.jobFinished(params, false)
} }
TaskStarter.execute(task) TaskStarter.execute(task)
return true return true
@ -72,30 +74,23 @@ class JobRefreshService : JobService() {
return false return false
} }
internal fun createJobTask(params: JobParameters): AbstractTask<*, *, () -> Unit>? { companion object {
when (params.extras?.getString(EXTRA_ACTION)) { const val ID_REFRESH_HOME_TIMELINE = 1
BROADCAST_REFRESH_HOME_TIMELINE -> { const val ID_REFRESH_NOTIFICATIONS = 2
val task = GetHomeTimelineTask(this) const val ID_REFRESH_DIRECT_MESSAGES = 3
task.params = RefreshService.AutoRefreshTaskParam(this, AccountPreferences::isAutoRefreshHomeTimelineEnabled) { accountKeys ->
DataStoreUtils.getNewestStatusIds(this, Statuses.CONTENT_URI, accountKeys) fun getJobId(@AutoRefreshType type: String): Int = when (type) {
} AutoRefreshType.HOME_TIMELINE -> JobRefreshService.ID_REFRESH_HOME_TIMELINE
return task AutoRefreshType.INTERACTIONS_TIMELINE -> JobRefreshService.ID_REFRESH_NOTIFICATIONS
} AutoRefreshType.DIRECT_MESSAGES -> JobRefreshService.ID_REFRESH_DIRECT_MESSAGES
BROADCAST_REFRESH_NOTIFICATIONS -> { else -> 0
val task = GetActivitiesAboutMeTask(this) }
task.params = RefreshService.AutoRefreshTaskParam(this, AccountPreferences::isAutoRefreshMentionsEnabled) { accountKeys ->
DataStoreUtils.getNewestActivityMaxPositions(this, Activities.AboutMe.CONTENT_URI, accountKeys) fun getRefreshType(jobId: Int): String? = when (jobId) {
} JobRefreshService.ID_REFRESH_HOME_TIMELINE -> AutoRefreshType.HOME_TIMELINE
return task JobRefreshService.ID_REFRESH_NOTIFICATIONS -> AutoRefreshType.INTERACTIONS_TIMELINE
} JobRefreshService.ID_REFRESH_DIRECT_MESSAGES -> AutoRefreshType.DIRECT_MESSAGES
BROADCAST_REFRESH_DIRECT_MESSAGES -> { else -> null
val task = GetReceivedDirectMessagesTask(this)
task.params = RefreshService.AutoRefreshTaskParam(this, AccountPreferences::isAutoRefreshDirectMessagesEnabled) { accountKeys ->
DataStoreUtils.getNewestMessageIds(this, DirectMessages.Inbox.CONTENT_URI, accountKeys)
}
return task
}
} }
return null
} }
} }

View File

@ -19,269 +19,53 @@
package org.mariotaku.twidere.service package org.mariotaku.twidere.service
import android.app.AlarmManager
import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.os.IBinder import android.os.IBinder
import android.os.SystemClock
import android.util.Log import android.util.Log
import edu.tsinghua.hotmobi.model.BatteryRecord import org.mariotaku.abstask.library.AbstractTask
import edu.tsinghua.hotmobi.model.ScreenEvent import org.mariotaku.ktextension.convert
import org.apache.commons.lang3.math.NumberUtils
import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.TwidereConstants.LOGTAG import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.annotation.AutoRefreshType
import org.mariotaku.twidere.constant.IntentConstants.* import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.constant.SharedPreferenceConstants.*
import org.mariotaku.twidere.model.AccountPreferences import org.mariotaku.twidere.model.AccountPreferences
import org.mariotaku.twidere.model.SimpleRefreshTaskParam import org.mariotaku.twidere.model.SimpleRefreshTaskParam
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.* import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.receiver.PowerStateReceiver import org.mariotaku.twidere.task.GetActivitiesAboutMeTask
import org.mariotaku.twidere.util.AsyncTwitterWrapper import org.mariotaku.twidere.task.GetHomeTimelineTask
import org.mariotaku.twidere.task.GetReceivedDirectMessagesTask
import org.mariotaku.twidere.util.DataStoreUtils import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.SharedPreferencesWrapper import org.mariotaku.twidere.util.SharedPreferencesWrapper
import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import javax.inject.Inject import javax.inject.Inject
class RefreshService : Service(), Constants { class RefreshService : Service() {
@Inject @Inject
internal lateinit var preferences: SharedPreferencesWrapper internal lateinit var preferences: SharedPreferencesWrapper
@Inject
internal lateinit var twitterWrapper: AsyncTwitterWrapper
private lateinit var alarmManager: AlarmManager override fun onBind(intent: Intent): IBinder? = null
private var pendingRefreshHomeTimelineIntent: PendingIntent? = null
private var pendingRefreshMentionsIntent: PendingIntent? = null
private var pendingRefreshDirectMessagesIntent: PendingIntent? = null
private var pendingRefreshTrendsIntent: PendingIntent? = null
private val mStateReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (BuildConfig.DEBUG) {
Log.d(LOGTAG, String.format("Refresh service received action %s", action))
}
when (action) {
BROADCAST_RESCHEDULE_HOME_TIMELINE_REFRESHING -> {
rescheduleHomeTimelineRefreshing()
}
BROADCAST_RESCHEDULE_MENTIONS_REFRESHING -> {
rescheduleMentionsRefreshing()
}
BROADCAST_RESCHEDULE_DIRECT_MESSAGES_REFRESHING -> {
rescheduleDirectMessagesRefreshing()
}
BROADCAST_RESCHEDULE_TRENDS_REFRESHING -> {
rescheduleTrendsRefreshing()
}
BROADCAST_REFRESH_HOME_TIMELINE -> {
if (isAutoRefreshAllowed) {
twitterWrapper.getHomeTimelineAsync(AutoRefreshTaskParam(context,
AccountPreferences::isAutoRefreshHomeTimelineEnabled) { accountKeys ->
DataStoreUtils.getNewestStatusIds(context, Statuses.CONTENT_URI, accountKeys)
})
}
}
BROADCAST_REFRESH_NOTIFICATIONS -> {
if (isAutoRefreshAllowed) {
twitterWrapper.getActivitiesAboutMeAsync(AutoRefreshTaskParam(context,
AccountPreferences::isAutoRefreshMentionsEnabled) { accountKeys ->
DataStoreUtils.getNewestActivityMaxPositions(context,
Activities.AboutMe.CONTENT_URI, accountKeys)
})
}
}
BROADCAST_REFRESH_DIRECT_MESSAGES -> {
if (isAutoRefreshAllowed) {
twitterWrapper.getReceivedDirectMessagesAsync(AutoRefreshTaskParam(context,
AccountPreferences::isAutoRefreshDirectMessagesEnabled) { accountKeys ->
DataStoreUtils.getNewestMessageIds(context,
DirectMessages.Inbox.CONTENT_URI, accountKeys)
})
}
}
BROADCAST_REFRESH_TRENDS -> {
if (isAutoRefreshAllowed) {
val prefs = AccountPreferences.getAccountPreferences(context,
DataStoreUtils.getAccountKeys(context)).filter(AccountPreferences::isAutoRefreshEnabled)
getLocalTrends(prefs.filter(AccountPreferences::isAutoRefreshTrendsEnabled)
.map(AccountPreferences::getAccountKey).toTypedArray())
}
}
}
}
}
private val mPowerStateReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
Intent.ACTION_BATTERY_CHANGED -> {
BatteryRecord.log(context, intent)
}
else -> {
BatteryRecord.log(context)
}
}
}
}
private val mScreenStateReceiver = object : BroadcastReceiver() {
var mPresentTime: Long = -1
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
Intent.ACTION_SCREEN_ON -> {
ScreenEvent.log(context, ScreenEvent.Action.ON, presentDuration)
}
Intent.ACTION_SCREEN_OFF -> {
ScreenEvent.log(context, ScreenEvent.Action.OFF, presentDuration)
mPresentTime = -1
}
Intent.ACTION_USER_PRESENT -> {
mPresentTime = SystemClock.elapsedRealtime()
ScreenEvent.log(context, ScreenEvent.Action.PRESENT, -1)
}
}
}
private val presentDuration: Long
get() {
if (mPresentTime < 0) return -1
return SystemClock.elapsedRealtime() - mPresentTime
}
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
GeneralComponentHelper.build(this).inject(this) GeneralComponentHelper.build(this).inject(this)
alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager }
pendingRefreshHomeTimelineIntent = PendingIntent.getBroadcast(this, 0, Intent(
BROADCAST_REFRESH_HOME_TIMELINE), 0) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
pendingRefreshMentionsIntent = PendingIntent.getBroadcast(this, 0, Intent(BROADCAST_REFRESH_NOTIFICATIONS), 0) Log.d(LOGTAG, "onStartCommand ${intent?.action}")
pendingRefreshDirectMessagesIntent = PendingIntent.getBroadcast(this, 0, Intent( val task = run {
BROADCAST_REFRESH_DIRECT_MESSAGES), 0) val type = intent?.action?.convert { getRefreshType(it) } ?: return@run null
pendingRefreshTrendsIntent = PendingIntent.getBroadcast(this, 0, Intent(BROADCAST_REFRESH_TRENDS), 0) return@run createJobTask(this, type)
val refreshFilter = IntentFilter(BROADCAST_NOTIFICATION_DELETED) } ?: run {
refreshFilter.addAction(BROADCAST_REFRESH_HOME_TIMELINE) stopSelfResult(startId)
refreshFilter.addAction(BROADCAST_REFRESH_NOTIFICATIONS) return START_NOT_STICKY
refreshFilter.addAction(BROADCAST_REFRESH_DIRECT_MESSAGES)
refreshFilter.addAction(BROADCAST_RESCHEDULE_HOME_TIMELINE_REFRESHING)
refreshFilter.addAction(BROADCAST_RESCHEDULE_MENTIONS_REFRESHING)
refreshFilter.addAction(BROADCAST_RESCHEDULE_DIRECT_MESSAGES_REFRESHING)
registerReceiver(mStateReceiver, refreshFilter)
val batteryFilter = IntentFilter()
batteryFilter.addAction(Intent.ACTION_BATTERY_CHANGED)
batteryFilter.addAction(Intent.ACTION_BATTERY_OKAY)
batteryFilter.addAction(Intent.ACTION_BATTERY_LOW)
batteryFilter.addAction(Intent.ACTION_POWER_CONNECTED)
batteryFilter.addAction(Intent.ACTION_POWER_DISCONNECTED)
val screenFilter = IntentFilter()
screenFilter.addAction(Intent.ACTION_SCREEN_ON)
screenFilter.addAction(Intent.ACTION_SCREEN_OFF)
screenFilter.addAction(Intent.ACTION_USER_PRESENT)
registerReceiver(mPowerStateReceiver, batteryFilter)
registerReceiver(mScreenStateReceiver, screenFilter)
PowerStateReceiver.serviceReceiverStarted = true
if (Utils.hasAutoRefreshAccounts(this)) {
startAutoRefresh()
} else {
stopSelf()
} }
} task.callback = {
stopSelfResult(startId)
override fun onDestroy() {
PowerStateReceiver.serviceReceiverStarted = false
unregisterReceiver(mScreenStateReceiver)
unregisterReceiver(mPowerStateReceiver)
unregisterReceiver(mStateReceiver)
if (Utils.hasAutoRefreshAccounts(this)) {
// Auto refresh enabled, so I will try to start service after it was
// stopped.
startService(Intent(this, javaClass))
} }
super.onDestroy() return START_NOT_STICKY
}
protected val isAutoRefreshAllowed: Boolean
get() = Utils.isNetworkAvailable(this) && (Utils.isBatteryOkay(this) || !Utils.shouldStopAutoRefreshOnBatteryLow(this))
private fun getLocalTrends(accountIds: Array<UserKey>) {
val account_id = Utils.getDefaultAccountKey(this)
val woeid = preferences.getInt(KEY_LOCAL_TRENDS_WOEID, 1)
twitterWrapper.getLocalTrendsAsync(account_id, woeid)
}
private val refreshInterval: Long
get() {
val prefValue = NumberUtils.toInt(preferences.getString(KEY_REFRESH_INTERVAL, DEFAULT_REFRESH_INTERVAL), -1)
return (Math.max(prefValue, 3) * 60 * 1000).toLong()
}
private fun rescheduleDirectMessagesRefreshing() {
alarmManager.cancel(pendingRefreshDirectMessagesIntent)
val refreshInterval = refreshInterval
if (refreshInterval > 0) {
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
refreshInterval, pendingRefreshDirectMessagesIntent)
}
}
private fun rescheduleHomeTimelineRefreshing() {
alarmManager.cancel(pendingRefreshHomeTimelineIntent)
val refreshInterval = refreshInterval
if (refreshInterval > 0) {
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
refreshInterval, pendingRefreshHomeTimelineIntent)
}
}
private fun rescheduleMentionsRefreshing() {
alarmManager.cancel(pendingRefreshMentionsIntent)
val refreshInterval = refreshInterval
if (refreshInterval > 0) {
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
refreshInterval, pendingRefreshMentionsIntent)
}
}
private fun rescheduleTrendsRefreshing() {
alarmManager.cancel(pendingRefreshTrendsIntent)
val refreshInterval = refreshInterval
if (refreshInterval > 0) {
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
refreshInterval, pendingRefreshTrendsIntent)
}
}
private fun startAutoRefresh(): Boolean {
stopAutoRefresh()
val refreshInterval = refreshInterval
if (refreshInterval <= 0) return false
rescheduleHomeTimelineRefreshing()
rescheduleMentionsRefreshing()
rescheduleDirectMessagesRefreshing()
rescheduleTrendsRefreshing()
return true
}
private fun stopAutoRefresh() {
alarmManager.cancel(pendingRefreshHomeTimelineIntent)
alarmManager.cancel(pendingRefreshMentionsIntent)
alarmManager.cancel(pendingRefreshDirectMessagesIntent)
alarmManager.cancel(pendingRefreshTrendsIntent)
} }
class AutoRefreshTaskParam( class AutoRefreshTaskParam(
@ -301,4 +85,49 @@ class RefreshService : Service(), Constants {
get() = getSinceIds(accountKeys) get() = getSinceIds(accountKeys)
} }
companion object {
fun createJobTask(context: Context, @AutoRefreshType refreshType: String): AbstractTask<*, *, () -> Unit>? {
when (refreshType) {
AutoRefreshType.HOME_TIMELINE -> {
val task = GetHomeTimelineTask(context)
task.params = RefreshService.AutoRefreshTaskParam(context, AccountPreferences::isAutoRefreshHomeTimelineEnabled) { accountKeys ->
DataStoreUtils.getNewestStatusIds(context, Statuses.CONTENT_URI, accountKeys)
}
return task
}
AutoRefreshType.INTERACTIONS_TIMELINE -> {
val task = GetActivitiesAboutMeTask(context)
task.params = RefreshService.AutoRefreshTaskParam(context, AccountPreferences::isAutoRefreshMentionsEnabled) { accountKeys ->
DataStoreUtils.getNewestActivityMaxPositions(context, Activities.AboutMe.CONTENT_URI, accountKeys)
}
return task
}
AutoRefreshType.DIRECT_MESSAGES -> {
val task = GetReceivedDirectMessagesTask(context)
task.params = RefreshService.AutoRefreshTaskParam(context, AccountPreferences::isAutoRefreshDirectMessagesEnabled) { accountKeys ->
DataStoreUtils.getNewestMessageIds(context, DirectMessages.Inbox.CONTENT_URI, accountKeys)
}
return task
}
}
return null
}
@AutoRefreshType
fun getRefreshType(action: String): String? = when (action) {
ACTION_REFRESH_HOME_TIMELINE -> AutoRefreshType.HOME_TIMELINE
ACTION_REFRESH_NOTIFICATIONS -> AutoRefreshType.INTERACTIONS_TIMELINE
ACTION_REFRESH_DIRECT_MESSAGES -> AutoRefreshType.DIRECT_MESSAGES
else -> null
}
fun getRefreshAction(@AutoRefreshType type: String): String? = when (type) {
AutoRefreshType.HOME_TIMELINE -> ACTION_REFRESH_HOME_TIMELINE
AutoRefreshType.INTERACTIONS_TIMELINE -> ACTION_REFRESH_NOTIFICATIONS
AutoRefreshType.DIRECT_MESSAGES -> ACTION_REFRESH_DIRECT_MESSAGES
else -> null
}
}
} }

View File

@ -0,0 +1,31 @@
package org.mariotaku.twidere.util
import android.content.Context
import org.mariotaku.kpreferences.KPreferences
import org.mariotaku.twidere.annotation.AutoRefreshType
/**
* Created by mariotaku on 2016/12/17.
*/
abstract class AutoRefreshController(
val context: Context,
val kPreferences: KPreferences
) {
abstract fun appStarted()
abstract fun schedule(@AutoRefreshType type: String)
abstract fun unschedule(@AutoRefreshType type: String)
fun reschedule(@AutoRefreshType type: String) {
unschedule(type)
schedule(type)
}
fun rescheduleAll() {
AutoRefreshType.ALL.forEach { reschedule(it) }
}
}

View File

@ -0,0 +1,69 @@
package org.mariotaku.twidere.util
import android.annotation.TargetApi
import android.app.job.JobInfo
import android.app.job.JobScheduler
import android.content.ComponentName
import android.content.Context
import android.os.Build
import org.mariotaku.kpreferences.KPreferences
import org.mariotaku.twidere.annotation.AutoRefreshType
import org.mariotaku.twidere.constant.refreshIntervalKey
import org.mariotaku.twidere.service.JobRefreshService
import java.util.concurrent.TimeUnit
import android.Manifest.permission as AndroidPermissions
/**
* Created by mariotaku on 2016/12/17.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class JobSchedulerAutoRefreshController(
context: Context,
kPreferences: KPreferences
) : AutoRefreshController(context, kPreferences) {
val scheduler: JobScheduler
init {
scheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
}
override fun appStarted() {
val allJobs = scheduler.allPendingJobs
AutoRefreshType.ALL.forEach { type ->
val jobId = JobRefreshService.getJobId(type)
if (allJobs.none { job -> job.id == jobId }) {
// Start non existing job
schedule(type)
}
}
}
override fun schedule(@AutoRefreshType type: String) {
val jobId = JobRefreshService.getJobId(type)
scheduler.cancel(jobId)
scheduleJob(jobId)
}
override fun unschedule(type: String) {
val jobId = JobRefreshService.getJobId(type)
scheduler.cancel(jobId)
}
fun scheduleJob(jobId: Int, persisted: Boolean = true) {
val builder = JobInfo.Builder(jobId, ComponentName(context, JobRefreshService::class.java))
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
builder.setPeriodic(TimeUnit.MINUTES.toMillis(kPreferences[refreshIntervalKey]))
builder.setPersisted(persisted)
try {
scheduler.schedule(builder.build())
} catch (e: IllegalArgumentException) {
if (persisted) {
scheduleJob(jobId, false)
}
}
}
}

View File

@ -0,0 +1,54 @@
package org.mariotaku.twidere.util
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.support.v4.util.ArrayMap
import org.mariotaku.kpreferences.KPreferences
import org.mariotaku.twidere.annotation.AutoRefreshType
import org.mariotaku.twidere.constant.refreshIntervalKey
import org.mariotaku.twidere.service.RefreshService
import java.util.concurrent.TimeUnit
class LegacyAutoRefreshController(
context: Context,
kPreferences: KPreferences
) : AutoRefreshController(context, kPreferences) {
private val alarmManager: AlarmManager
private val pendingIntents: ArrayMap<String, PendingIntent>
init {
alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
pendingIntents = ArrayMap()
AutoRefreshType.ALL.forEach { type ->
val action = RefreshService.getRefreshAction(type) ?: return@forEach
val intent = Intent(context, RefreshService::class.java)
intent.action = action
pendingIntents[type] = PendingIntent.getService(context, 0, intent, 0)
}
}
override fun appStarted() {
rescheduleAll()
}
override fun unschedule(type: String) {
val pendingIntent = pendingIntents[type] ?: return
alarmManager.cancel(pendingIntent)
}
override fun schedule(type: String) {
val pendingIntent = pendingIntents[type] ?: return
val interval = TimeUnit.MINUTES.toMillis(kPreferences[refreshIntervalKey])
if (interval > 0) {
val triggerAt = System.currentTimeMillis() + interval
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, triggerAt, interval, pendingIntent)
}
}
}

View File

@ -42,6 +42,7 @@ import org.mariotaku.mediaviewer.library.MediaDownloader
import org.mariotaku.restfu.http.RestHttpClient import org.mariotaku.restfu.http.RestHttpClient
import org.mariotaku.twidere.BuildConfig import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.Constants import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.SharedPreferenceConstants import org.mariotaku.twidere.constant.SharedPreferenceConstants
import org.mariotaku.twidere.model.DefaultFeatures import org.mariotaku.twidere.model.DefaultFeatures
import org.mariotaku.twidere.util.* import org.mariotaku.twidere.util.*
@ -228,6 +229,14 @@ class ApplicationModule(private val application: Application) {
return BidiFormatter.getInstance() return BidiFormatter.getInstance()
} }
@Provides
fun autoRefreshController(kPreferences: KPreferences): AutoRefreshController {
if (application.resources.getBoolean(R.bool.use_job_refresh_service)) {
return JobSchedulerAutoRefreshController(application, kPreferences)
}
return LegacyAutoRefreshController(application, kPreferences)
}
@Provides @Provides
@Singleton @Singleton
fun defaultFeatures(preferences: SharedPreferencesWrapper): DefaultFeatures { fun defaultFeatures(preferences: SharedPreferencesWrapper): DefaultFeatures {
@ -270,3 +279,4 @@ class ApplicationModule(private val application: Application) {
} }
} }
} }

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!--<bool name="use_job_refresh_service">true</bool>--> <bool name="use_job_refresh_service">true</bool>
<!--<bool name="use_legacy_refresh_service">false</bool>--> <bool name="use_legacy_refresh_service">false</bool>
</resources> </resources>