Configure GCM by network type and automatically learn ping interval

Related to: #187 #192
This commit is contained in:
Marvin W 2017-04-26 22:03:34 +02:00
parent 8d078fb7ec
commit 01f154dcdb
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
9 changed files with 253 additions and 47 deletions

View File

@ -18,7 +18,10 @@ package org.microg.gms.gcm;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.preference.PreferenceManager;
import android.util.Log;
import java.util.Arrays;
import java.util.Collections;
@ -31,6 +34,15 @@ public class GcmPrefs implements SharedPreferences.OnSharedPreferenceChangeListe
public static final String PREF_CONFIRM_NEW_APPS = "gcm_confirm_new_apps";
public static final String PREF_ENABLE_GCM = "gcm_enable_mcs_service";
public static final String PREF_NETWORK_MOBILE = "gcm_network_mobile";
public static final String PREF_NETWORK_WIFI = "gcm_network_wifi";
public static final String PREF_NETWORK_ROAMING = "gcm_network_roaming";
public static final String PREF_NETWORK_OTHER = "gcm_network_other";
public static final String PREF_LEARNT_MOBILE = "gcm_learnt_mobile";
public static final String PREF_LEARNT_WIFI = "gcm_learnt_wifi";
public static final String PREF_LEARNT_OTHER = "gcm_learnt_other";
private static GcmPrefs INSTANCE;
public static GcmPrefs get(Context context) {
@ -47,6 +59,15 @@ public class GcmPrefs implements SharedPreferences.OnSharedPreferenceChangeListe
private boolean confirmNewApps = false;
private boolean gcmEnabled = false;
private int networkMobile = 0;
private int networkWifi = 0;
private int networkRoaming = 0;
private int networkOther = 0;
private int learntWifi = 300000;
private int learntMobile = 300000;
private int learntOther = 300000;
private SharedPreferences defaultPreferences;
private GcmPrefs(Context context) {
@ -63,21 +84,115 @@ public class GcmPrefs implements SharedPreferences.OnSharedPreferenceChangeListe
lastPersistedId = defaultPreferences.getString(PREF_LAST_PERSISTENT_ID, "");
confirmNewApps = defaultPreferences.getBoolean(PREF_CONFIRM_NEW_APPS, false);
gcmEnabled = defaultPreferences.getBoolean(PREF_ENABLE_GCM, false);
networkMobile = Integer.parseInt(defaultPreferences.getString(PREF_NETWORK_MOBILE, "0"));
networkWifi = Integer.parseInt(defaultPreferences.getString(PREF_NETWORK_WIFI, "0"));
networkRoaming = Integer.parseInt(defaultPreferences.getString(PREF_NETWORK_ROAMING, "0"));
networkOther = Integer.parseInt(defaultPreferences.getString(PREF_NETWORK_OTHER, "0"));
learntMobile = defaultPreferences.getInt(PREF_LEARNT_MOBILE, 300000);
learntWifi = defaultPreferences.getInt(PREF_LEARNT_WIFI, 300000);
learntOther = defaultPreferences.getInt(PREF_LEARNT_OTHER, 300000);
}
public int getHeartbeatMs() {
return heartbeatMs;
}
public String getNetworkPrefForInfo(NetworkInfo info) {
if (info.isRoaming()) return PREF_NETWORK_ROAMING;
switch (info.getType()) {
case ConnectivityManager.TYPE_MOBILE:
return PREF_NETWORK_MOBILE;
case ConnectivityManager.TYPE_WIFI:
return PREF_NETWORK_WIFI;
default:
return PREF_NETWORK_OTHER;
}
}
public int getHeartbeatMsFor(NetworkInfo info) {
return getHeartbeatMsFor(getNetworkPrefForInfo(info), false);
}
public int getHeartbeatMsFor(String pref, boolean rawRoaming) {
if (PREF_NETWORK_ROAMING.equals(pref) && (rawRoaming || networkRoaming != 0)) {
return networkRoaming * 6000;
} else if (PREF_NETWORK_MOBILE.equals(pref)) {
if (networkMobile != 0) return networkMobile * 60000;
else return learntMobile;
} else if (PREF_NETWORK_WIFI.equals(pref)) {
if (networkWifi != 0) return networkWifi * 60000;
else return learntWifi;
} else {
if (networkOther != 0) return networkOther * 60000;
else return learntOther;
}
}
public int getNetworkValue(String pref) {
switch (pref) {
case PREF_NETWORK_MOBILE:
return networkMobile;
case PREF_NETWORK_ROAMING:
return networkRoaming;
case PREF_NETWORK_WIFI:
return networkWifi;
default:
return networkOther;
}
}
public void learnTimeout(String pref) {
Log.d("GmsGcmPrefs", "learnTimeout: " + pref);
switch (pref) {
case PREF_NETWORK_MOBILE:
case PREF_NETWORK_ROAMING:
learntMobile *= 0.95;
break;
case PREF_NETWORK_WIFI:
learntWifi *= 0.95;
break;
default:
learntOther *= 0.95;
break;
}
defaultPreferences.edit().putInt(PREF_LEARNT_MOBILE, learntMobile).putInt(PREF_LEARNT_WIFI, learntWifi).putInt(PREF_LEARNT_OTHER, learntOther).apply();
}
public void learnReached(String pref, long time) {
Log.d("GmsGcmPrefs", "learnReached: " + pref + " / " + time);
switch (pref) {
case PREF_NETWORK_MOBILE:
case PREF_NETWORK_ROAMING:
if (time > learntMobile / 4 * 3)
learntMobile *= 1.02;
break;
case PREF_NETWORK_WIFI:
if (time > learntWifi / 4 * 3)
learntWifi *= 1.02;
break;
default:
if (time > learntOther / 4 * 3)
learntOther *= 1.02;
break;
}
defaultPreferences.edit().putInt(PREF_LEARNT_MOBILE, learntMobile).putInt(PREF_LEARNT_WIFI, learntWifi).putInt(PREF_LEARNT_OTHER, learntOther).apply();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
update();
}
public boolean isGcmEnabled() {
public boolean isEnabled() {
return gcmEnabled;
}
public boolean isEnabledFor(NetworkInfo info) {
return isEnabled() && getHeartbeatMsFor(info) >= 0;
}
public boolean isGcmLogEnabled() {
return gcmLogEnabled;
}

View File

@ -24,6 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.ConnectivityManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@ -105,7 +106,9 @@ public class McsService extends Service implements Handler.Callback {
private static final int WAKELOCK_TIMEOUT = 5000;
private static long lastHeartbeatAckElapsedRealtime = -1;
private static long lastIncomingNetworkRealtime = 0;
private static long startTimestamp = 0;
public static String activeNetworkPref = null;
private static Socket sslSocket;
private static McsInputStream inputStream;
@ -194,8 +197,12 @@ public class McsService extends Service implements Handler.Callback {
return false;
}
// consider connection to be dead if we did not receive an ack within twice the heartbeat interval
if (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime > 2 * GcmPrefs.get(null).getHeartbeatMs()) {
logd("No heartbeat for " + (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime) / 1000 + " seconds, connection assumed to be dead after " + 2 * GcmPrefs.get(null).getHeartbeatMs() / 1000 + " seconds");
int heartbeatMs = GcmPrefs.get(null).getHeartbeatMsFor(activeNetworkPref, false);
if (heartbeatMs < 0) {
closeAll();
} else if (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime > 2 * heartbeatMs) {
logd("No heartbeat for " + (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime) / 1000 + " seconds, connection assumed to be dead after " + 2 * heartbeatMs / 1000 + " seconds");
GcmPrefs.get(null).learnTimeout(activeNetworkPref);
return false;
}
return true;
@ -215,15 +222,18 @@ public class McsService extends Service implements Handler.Callback {
public void scheduleHeartbeat(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE);
logd("Scheduling heartbeat in " + GcmPrefs.get(this).getHeartbeatMs() / 1000 + " seconds...");
int heartbeatMs = GcmPrefs.get(this).getHeartbeatMs();
int heartbeatMs = GcmPrefs.get(this).getHeartbeatMsFor(activeNetworkPref, false);
if (heartbeatMs < 0) {
closeAll();
}
logd("Scheduling heartbeat in " + heartbeatMs / 1000 + " seconds...");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
alarmManager.set(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, heartbeatIntent);
} else {
// with KitKat, the alarms become inexact by default, but with the newly available setWindow we can get inexact alarms with guarantees.
// Schedule the alarm to fire within the interval [heartbeatMs/2, heartbeatMs]
alarmManager.setWindow(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs / 2, heartbeatMs / 2,
// Schedule the alarm to fire within the interval [heartbeatMs/3*4, heartbeatMs]
alarmManager.setWindow(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs / 4 * 3, heartbeatMs / 4,
heartbeatIntent);
}
@ -356,6 +366,13 @@ public class McsService extends Service implements Handler.Callback {
private synchronized void connect() {
try {
closeAll();
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
activeNetworkPref = GcmPrefs.get(this).getNetworkPrefForInfo(cm.getActiveNetworkInfo());
if (!GcmPrefs.get(this).isEnabledFor(cm.getActiveNetworkInfo())) {
scheduleReconnect(this);
return;
}
logd("Starting MCS connection...");
Socket socket = new Socket(SERVICE_HOST, SERVICE_PORT);
logd("Connected to " + SERVICE_HOST + ":" + SERVICE_PORT);
@ -368,6 +385,7 @@ public class McsService extends Service implements Handler.Callback {
startTimestamp = System.currentTimeMillis();
lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime();
lastIncomingNetworkRealtime = SystemClock.elapsedRealtime();
scheduleHeartbeat(this);
} catch (Exception e) {
Log.w(TAG, "Exception while connecting!", e);
@ -409,6 +427,7 @@ public class McsService extends Service implements Handler.Callback {
}
private void handleHeartbeatAck(HeartbeatAck ack) {
GcmPrefs.get(this).learnReached(activeNetworkPref, SystemClock.elapsedRealtime() - lastIncomingNetworkRealtime);
lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime();
wakeLock.release();
}
@ -573,6 +592,7 @@ public class McsService extends Service implements Handler.Callback {
Log.w(TAG, "Unknown message: " + message);
}
resetCurrentDelay();
lastIncomingNetworkRealtime = SystemClock.elapsedRealtime();
} catch (Exception e) {
rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, e));
}

View File

@ -101,7 +101,7 @@ public class PushRegisterService extends IntentService {
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "onHandleIntent: " + intent);
Log.d(TAG, "onHandleIntent: " + intent.getExtras());
if (GcmPrefs.get(this).isGcmEnabled()) {
if (GcmPrefs.get(this).isEnabled()) {
if (LastCheckinInfo.read(this).lastCheckin > 0) {
try {
if (ACTION_C2DM_UNREGISTER.equals(intent.getAction()) ||

View File

@ -34,6 +34,7 @@ import static org.microg.gms.gcm.McsConstants.EXTRA_REASON;
public class TriggerReceiver extends WakefulBroadcastReceiver {
private static final String TAG = "GmsGcmTrigger";
public static final String FORCE_TRY_RECONNECT = "org.microg.gms.gcm.FORCE_TRY_RECONNECT";
private static boolean registered = false;
/**
@ -50,40 +51,51 @@ public class TriggerReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction());
boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction()) || FORCE_TRY_RECONNECT.equals(intent.getAction());
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (GcmPrefs.get(context).isGcmEnabled() || force) {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
McsService.resetCurrentDelay();
}
if (LastCheckinInfo.read(context).androidId == 0) {
Log.d(TAG, "Ignoring " + intent + ": need to checkin first.");
return;
}
if (!GcmPrefs.get(context).isEnabled() && !force) {
Log.d(TAG, "Ignoring " + intent + ": gcm is disabled");
return;
}
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected() || force || "android.intent.action.BOOT_COMPLETED".equals(intent.getAction())) {
if (!McsService.isConnected() || force) {
Log.d(TAG, "Not connected to GCM but should be, asking the service to start up. Triggered by: " + intent);
startWakefulService(context, new Intent(ACTION_CONNECT, null, context, McsService.class)
.putExtra(EXTRA_REASON, intent));
} else {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
Log.d(TAG, "Ignoring " + intent + ": service is running. schedule reconnect instead.");
McsService.scheduleReconnect(context);
} else {
Log.d(TAG, "Ignoring " + intent + ": service is running. heartbeat instead.");
startWakefulService(context, new Intent(ACTION_HEARTBEAT, null, context, McsService.class)
.putExtra(EXTRA_REASON, intent));
}
}
} else {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
McsService.resetCurrentDelay();
}
if (LastCheckinInfo.read(context).androidId == 0) {
Log.d(TAG, "Ignoring " + intent + ": need to checkin first.");
return;
}
force |= "android.intent.action.BOOT_COMPLETED".equals(intent.getAction());
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
if (!force) {
if (networkInfo == null || !networkInfo.isConnected()) {
Log.d(TAG, "Ignoring " + intent + ": network is offline, scheduling new attempt.");
McsService.scheduleReconnect(context);
return;
} else if (!GcmPrefs.get(context).isEnabledFor(networkInfo)) {
Log.d(TAG, "Ignoring " + intent + ": gcm is disabled for " + networkInfo.getTypeName());
return;
}
}
if (!McsService.isConnected() || force) {
Log.d(TAG, "Not connected to GCM but should be, asking the service to start up. Triggered by: " + intent);
startWakefulService(context, new Intent(ACTION_CONNECT, null, context, McsService.class)
.putExtra(EXTRA_REASON, intent));
} else {
Log.d(TAG, "Ignoring " + intent + ": gcm is disabled");
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
Log.d(TAG, "Ignoring " + intent + ": service is running. schedule reconnect instead.");
McsService.scheduleReconnect(context);
} else {
Log.d(TAG, "Ignoring " + intent + ": service is running. heartbeat instead.");
startWakefulService(context, new Intent(ACTION_HEARTBEAT, null, context, McsService.class)
.putExtra(EXTRA_REASON, intent));
}
}
}

View File

@ -46,7 +46,7 @@ public class Conditions {
@Override
public boolean isActive(Context context) {
if (SDK_INT < 23) return false;
if (!GcmPrefs.get(context).isGcmEnabled()) return false;
if (!GcmPrefs.get(context).isEnabled()) return false;
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
return !pm.isIgnoringBatteryOptimizations(context.getPackageName());
}

View File

@ -16,19 +16,84 @@
package org.microg.gms.ui;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.preference.Preference;
import com.google.android.gms.R;
import org.microg.gms.gcm.GcmPrefs;
import org.microg.gms.gcm.McsService;
import org.microg.gms.gcm.TriggerReceiver;
import org.microg.tools.ui.AbstractSettingsActivity;
import org.microg.tools.ui.ResourceSettingsFragment;
import java.util.Objects;
public class GcmAdvancedFragment extends ResourceSettingsFragment {
private static String[] HEARTBEAT_PREFS = new String[]{GcmPrefs.PREF_NETWORK_MOBILE, GcmPrefs.PREF_NETWORK_ROAMING, GcmPrefs.PREF_NETWORK_WIFI, GcmPrefs.PREF_NETWORK_OTHER};
public GcmAdvancedFragment() {
preferencesResource = R.xml.preferences_gcm_advanced;
}
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
super.onCreatePreferencesFix(savedInstanceState, rootKey);
for (String pref : HEARTBEAT_PREFS) {
findPreference(pref).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
getPreferenceManager().getSharedPreferences().edit().putString(preference.getKey(), (String) newValue).apply();
updateContent();
if (newValue.equals("-1") && preference.getKey().equals(McsService.activeNetworkPref)) {
McsService.stop(getContext());
} else if (!McsService.isConnected()) {
getContext().sendBroadcast(new Intent(TriggerReceiver.FORCE_TRY_RECONNECT, null, getContext(), TriggerReceiver.class));
}
return true;
}
});
}
updateContent();
}
@Override
public void onResume() {
super.onResume();
updateContent();
}
private void updateContent() {
GcmPrefs prefs = GcmPrefs.get(getContext());
for (String pref : HEARTBEAT_PREFS) {
Preference preference = findPreference(pref);
int state = prefs.getNetworkValue(pref);
if (state == 0) {
int heartbeat = prefs.getHeartbeatMsFor(preference.getKey(), true);
if (heartbeat == 0) {
preference.setSummary("ON / Automatic");
} else {
preference.setSummary("ON / Automatic: " + getHeartbeatString(heartbeat));
}
} else if (state == -1) {
preference.setSummary("OFF");
} else {
preference.setSummary("ON / Manual: " + getHeartbeatString(state * 60000));
}
}
}
private String getHeartbeatString(int heartbeatMs) {
if (heartbeatMs < 120000) {
return (heartbeatMs / 1000) + " seconds";
}
return (heartbeatMs / 60000) + " minutes";
}
public static class AsActivity extends AbstractSettingsActivity {
public AsActivity() {
showHomeAsUp = true;

View File

@ -39,6 +39,7 @@ import org.microg.gms.gcm.GcmDatabase;
import org.microg.gms.gcm.GcmPrefs;
import org.microg.gms.gcm.McsConstants;
import org.microg.gms.gcm.McsService;
import org.microg.gms.gcm.TriggerReceiver;
import org.microg.tools.ui.AbstractSettingsActivity;
import org.microg.tools.ui.DimmableIconPreference;
import org.microg.tools.ui.SwitchBarResourceSettingsFragment;
@ -70,7 +71,7 @@ public class GcmFragment extends SwitchBarResourceSettingsFragment {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
switchBar.setChecked(GcmPrefs.get(getContext()).isGcmEnabled());
switchBar.setChecked(GcmPrefs.get(getContext()).isEnabled());
}
@Override
@ -104,7 +105,7 @@ public class GcmFragment extends SwitchBarResourceSettingsFragment {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_ADVANCED:
Intent intent = new Intent(getContext(), GcmAdvancedFragment.AsActivity .class);
Intent intent = new Intent(getContext(), GcmAdvancedFragment.AsActivity.class);
startActivity(intent);
return true;
default:
@ -118,7 +119,7 @@ public class GcmFragment extends SwitchBarResourceSettingsFragment {
if (!isChecked) {
McsService.stop(getContext());
} else {
getContext().startService(new Intent(McsConstants.ACTION_CONNECT, null, getContext(), McsService.class));
getContext().sendBroadcast(new Intent(TriggerReceiver.FORCE_TRY_RECONNECT, null, getContext(), TriggerReceiver.class));
}
updateContent();
}

View File

@ -72,7 +72,7 @@ public class SettingsActivity extends AbstractDashboardActivity {
private void updateDetails() {
findPreference(PREF_ABOUT).setSummary(getString(R.string.about_version_str, AboutFragment.getSelfVersion(getContext())));
if (GcmPrefs.get(getContext()).isGcmEnabled()) {
if (GcmPrefs.get(getContext()).isEnabled()) {
GcmDatabase database = new GcmDatabase(getContext());
int regCount = database.getRegistrationList().size();
database.close();

View File

@ -17,12 +17,6 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
android:defaultValue="300"
android:key="gcm_heartbeat_interval"
android:summary="@string/pref_gcm_heartbeat_summary"
android:title="@string/pref_gcm_heartbeat_title"/>
<SwitchPreference
android:defaultValue="false"
android:key="gcm_confirm_new_apps"
@ -30,7 +24,6 @@
android:title="@string/pref_gcm_confirm_new_apps_title"/>
<PreferenceCategory
android:enabled="false"
android:key="prefcat_networks"
android:title="Networks to use for Cloud Messaging">