Refactor settings access to use a SettingsProvider

to enable safe usage of settings no matter which process is getting/setting them.
Previously, different processes were accessing settings in an unsafe way and the warn methods were throwing Runtime exceptions which went largely unnoticed, but happened, especially on a fresh start of the OS.

Change-Id: Ie4134e7be2a7ca4a373790f45fbcbd09bf02ad86
This commit is contained in:
Torsten Grote 2021-05-31 17:01:07 -03:00 committed by Marvin W
parent fbcb6066c3
commit aa3a2d65cd
31 changed files with 814 additions and 703 deletions

View File

@ -0,0 +1,120 @@
package org.microg.gms.settings
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.net.Uri
object SettingsContract {
const val AUTHORITY = "org.microg.gms.settings"
val AUTHORITY_URI: Uri = Uri.parse("content://$AUTHORITY")
object CheckIn {
private const val id = "check-in"
val CONTENT_URI: Uri = Uri.withAppendedPath(AUTHORITY_URI, id)
const val CONTENT_TYPE = "vnd.android.cursor.item/vnd.$AUTHORITY.$id"
const val ENABLED = "checkin_enable_service"
const val ANDROID_ID = "androidId"
const val DIGEST = "digest"
const val LAST_CHECK_IN = "lastCheckin"
const val SECURITY_TOKEN = "securityToken"
const val VERSION_INFO = "versionInfo"
const val DEVICE_DATA_VERSION_INFO = "deviceDataVersionInfo"
val PROJECTION = arrayOf(
ENABLED,
ANDROID_ID,
DIGEST,
LAST_CHECK_IN,
SECURITY_TOKEN,
VERSION_INFO,
DEVICE_DATA_VERSION_INFO,
)
const val PREFERENCES_NAME = "checkin"
const val INITIAL_DIGEST = "1-929a0dca0eee55513280171a8585da7dcd3700f8"
}
object Gcm {
private const val id = "gcm"
val CONTENT_URI: Uri = Uri.withAppendedPath(AUTHORITY_URI, id)
const val CONTENT_TYPE = "vnd.android.cursor.item/vnd.$AUTHORITY.$id"
const val FULL_LOG = "gcm_full_log"
const val LAST_PERSISTENT_ID = "gcm_last_persistent_id"
const val CONFIRM_NEW_APPS = "gcm_confirm_new_apps"
const val ENABLE_GCM = "gcm_enable_mcs_service"
const val NETWORK_MOBILE = "gcm_network_mobile"
const val NETWORK_WIFI = "gcm_network_wifi"
const val NETWORK_ROAMING = "gcm_network_roaming"
const val NETWORK_OTHER = "gcm_network_other"
const val LEARNT_MOBILE = "gcm_learnt_mobile"
const val LEARNT_WIFI = "gcm_learnt_wifi"
const val LEARNT_OTHER = "gcm_learnt_other"
val PROJECTION = arrayOf(
FULL_LOG,
LAST_PERSISTENT_ID,
CONFIRM_NEW_APPS,
ENABLE_GCM,
NETWORK_MOBILE,
NETWORK_WIFI,
NETWORK_ROAMING,
NETWORK_OTHER,
LEARNT_MOBILE,
LEARNT_WIFI,
LEARNT_OTHER,
)
}
object Auth {
private const val id = "auth"
val CONTENT_URI: Uri = Uri.withAppendedPath(AUTHORITY_URI, id)
const val CONTENT_TYPE = "vnd.android.cursor.item/vnd.$AUTHORITY.$id"
const val TRUST_GOOGLE = "auth_manager_trust_google"
const val VISIBLE = "auth_manager_visible"
val PROJECTION = arrayOf(
TRUST_GOOGLE,
VISIBLE,
)
}
object Exposure {
private const val id = "exposureNotification"
val CONTENT_URI: Uri = Uri.withAppendedPath(AUTHORITY_URI, id)
const val CONTENT_TYPE = "vnd.android.cursor.item/vnd.$AUTHORITY.$id"
const val SCANNER_ENABLED = "exposure_scanner_enabled"
const val LAST_CLEANUP = "exposure_last_cleanup"
val PROJECTION = arrayOf(
SCANNER_ENABLED,
LAST_CLEANUP,
)
}
object SafetyNet {
private const val id = "safety-net"
val CONTENT_URI: Uri = Uri.withAppendedPath(AUTHORITY_URI, id)
const val CONTENT_TYPE = "vnd.android.cursor.item/vnd.$AUTHORITY.$id"
}
fun <T> getSettings(context: Context, uri: Uri, projection: Array<out String>?, f: (Cursor) -> T): T {
context.contentResolver.query(uri, projection, null, null, null).use { c ->
require(c != null) { "Cursor for query $uri ${projection?.toList()} was null" }
if (!c.moveToFirst()) error("Cursor for query $uri ${projection?.toList()} was empty")
return f.invoke(c)
}
}
fun setSettings(context: Context, uri: Uri, v: ContentValues.() -> Unit) {
val values = ContentValues().apply { v.invoke(this) }
val affected = context.contentResolver.update(uri, values, null, null)
require(affected == 1) { "Update for $uri with $values affected 0 rows"}
}
}

View File

@ -129,6 +129,13 @@
android:exported="true" android:exported="true"
android:permission="org.microg.gms.PROVISION" /> android:permission="org.microg.gms.PROVISION" />
<!-- Internal Settings -->
<provider
android:name="org.microg.gms.settings.SettingsProvider"
android:authorities="org.microg.gms.settings"
android:exported="false" />
<!-- Location --> <!-- Location -->
<activity <activity
@ -198,8 +205,6 @@
</intent-filter> </intent-filter>
</service> </service>
<receiver android:name="org.microg.gms.checkin.ServiceInfoReceiver" />
<receiver android:name="org.microg.gms.checkin.TriggerReceiver"> <receiver android:name="org.microg.gms.checkin.TriggerReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />

View File

@ -21,22 +21,22 @@ import android.accounts.AccountManager;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import org.microg.gms.common.PackageUtils; import org.microg.gms.common.PackageUtils;
import org.microg.gms.settings.SettingsContract;
import java.io.IOException; import java.io.IOException;
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
import static org.microg.gms.auth.AuthPrefs.isTrustGooglePermitted;
public class AuthManager { public class AuthManager {
private static final String TAG = "GmsAuthManager"; private static final String TAG = "GmsAuthManager";
public static final String PERMISSION_TREE_BASE = "com.google.android.googleapps.permission.GOOGLE_AUTH."; public static final String PERMISSION_TREE_BASE = "com.google.android.googleapps.permission.GOOGLE_AUTH.";
private static final String PREF_AUTH_TRUST_GOOGLE = "auth_manager_trust_google"; public static final String PREF_AUTH_VISIBLE = SettingsContract.Auth.VISIBLE;
public static final String PREF_AUTH_VISIBLE = "auth_manager_visible";
public static final int ONE_HOUR_IN_SECONDS = 60 * 60; public static final int ONE_HOUR_IN_SECONDS = 60 * 60;
private final Context context; private final Context context;
@ -185,14 +185,6 @@ public class AuthManager {
} }
} }
public static boolean isTrustGooglePermitted(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_AUTH_TRUST_GOOGLE, true);
}
public static boolean isAuthVisible(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_AUTH_VISIBLE, false);
}
private boolean isSystemApp() { private boolean isSystemApp() {
try { try {
int flags = context.getPackageManager().getApplicationInfo(packageName, 0).flags; int flags = context.getPackageManager().getApplicationInfo(packageName, 0).flags;

View File

@ -113,7 +113,7 @@ public class AuthRequest extends HttpFormClient.Request {
public AuthRequest fromContext(Context context) { public AuthRequest fromContext(Context context) {
build(Utils.getBuild(context)); build(Utils.getBuild(context));
locale(Utils.getLocale(context)); locale(Utils.getLocale(context));
androidIdHex = Long.toHexString(LastCheckinInfo.read(context).androidId); androidIdHex = Long.toHexString(LastCheckinInfo.read(context).getAndroidId());
return this; return this;
} }

View File

@ -21,7 +21,6 @@ import android.accounts.AccountManager;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
@ -36,7 +35,6 @@ import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.webkit.CookieManager; import android.webkit.CookieManager;
import android.webkit.JavascriptInterface; import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebSettings; import android.webkit.WebSettings;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
@ -72,6 +70,7 @@ import static android.view.KeyEvent.KEYCODE_BACK;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT; import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
import static org.microg.gms.auth.AuthPrefs.isAuthVisible;
import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME;
import static org.microg.gms.common.Constants.GMS_VERSION_CODE; import static org.microg.gms.common.Constants.GMS_VERSION_CODE;
@ -136,7 +135,7 @@ public class LoginActivity extends AssistantActivity {
AccountManager accountManager = AccountManager.get(this); AccountManager accountManager = AccountManager.get(this);
Account account = new Account(getIntent().getStringExtra(EXTRA_EMAIL), accountType); Account account = new Account(getIntent().getStringExtra(EXTRA_EMAIL), accountType);
accountManager.addAccountExplicitly(account, getIntent().getStringExtra(EXTRA_TOKEN), null); accountManager.addAccountExplicitly(account, getIntent().getStringExtra(EXTRA_TOKEN), null);
if (AuthManager.isAuthVisible(this) && SDK_INT >= Build.VERSION_CODES.O) { if (isAuthVisible(this) && SDK_INT >= Build.VERSION_CODES.O) {
accountManager.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, VISIBILITY_USER_MANAGED_VISIBLE); accountManager.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, VISIBILITY_USER_MANAGED_VISIBLE);
} }
retrieveGmsToken(account); retrieveGmsToken(account);
@ -223,7 +222,7 @@ public class LoginActivity extends AssistantActivity {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = cm.getActiveNetworkInfo(); NetworkInfo networkInfo = cm.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) { if (networkInfo != null && networkInfo.isConnected()) {
if (LastCheckinInfo.read(this).androidId == 0) { if (LastCheckinInfo.read(this).getAndroidId() == 0) {
new Thread(() -> { new Thread(() -> {
Runnable next; Runnable next;
next = checkin(false) ? this::loadLoginPage : () -> showError(R.string.auth_general_error_desc); next = checkin(false) ? this::loadLoginPage : () -> showError(R.string.auth_general_error_desc);
@ -425,7 +424,7 @@ public class LoginActivity extends AssistantActivity {
@JavascriptInterface @JavascriptInterface
public final String getAndroidId() { public final String getAndroidId() {
long androidId = LastCheckinInfo.read(LoginActivity.this).androidId; long androidId = LastCheckinInfo.read(LoginActivity.this).getAndroidId();
Log.d(TAG, "JSBridge: getAndroidId " + androidId); Log.d(TAG, "JSBridge: getAndroidId " + androidId);
if (androidId == 0 || androidId == -1) return null; if (androidId == 0 || androidId == -1) return null;
return Long.toHexString(androidId); return Long.toHexString(androidId);

View File

@ -18,15 +18,12 @@ package org.microg.gms.checkin;
import android.util.Log; import android.util.Log;
import com.squareup.wire.Wire;
import org.microg.gms.common.Build; import org.microg.gms.common.Build;
import org.microg.gms.common.DeviceConfiguration; import org.microg.gms.common.DeviceConfiguration;
import org.microg.gms.common.DeviceIdentifier; import org.microg.gms.common.DeviceIdentifier;
import org.microg.gms.common.PhoneInfo; import org.microg.gms.common.PhoneInfo;
import org.microg.gms.common.Utils; import org.microg.gms.common.Utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -46,8 +43,8 @@ import java.util.zip.GZIPOutputStream;
public class CheckinClient { public class CheckinClient {
private static final String TAG = "GmsCheckinClient"; private static final String TAG = "GmsCheckinClient";
private static final Object TODO = null; // TODO private static final Object TODO = null; // TODO
private static final List<String> TODO_LIST_STRING = new ArrayList<String>(); // TODO private static final List<String> TODO_LIST_STRING = new ArrayList<>(); // TODO
private static final List<CheckinRequest.Checkin.Statistic> TODO_LIST_CHECKIN = new ArrayList<CheckinRequest.Checkin.Statistic>(); // TODO private static final List<CheckinRequest.Checkin.Statistic> TODO_LIST_CHECKIN = new ArrayList<>(); // TODO
private static final String SERVICE_URL = "https://android.clients.google.com/checkin"; private static final String SERVICE_URL = "https://android.clients.google.com/checkin";
public static CheckinResponse request(CheckinRequest request) throws IOException { public static CheckinResponse request(CheckinRequest request) throws IOException {
@ -84,8 +81,8 @@ public class CheckinClient {
LastCheckinInfo checkinInfo, Locale locale, LastCheckinInfo checkinInfo, Locale locale,
List<Account> accounts) { List<Account> accounts) {
CheckinRequest.Builder builder = new CheckinRequest.Builder() CheckinRequest.Builder builder = new CheckinRequest.Builder()
.accountCookie(new ArrayList<String>()) .accountCookie(new ArrayList<>())
.androidId(checkinInfo.androidId) .androidId(checkinInfo.getAndroidId())
.checkin(new CheckinRequest.Checkin.Builder() .checkin(new CheckinRequest.Checkin.Builder()
.build(new CheckinRequest.Checkin.Build.Builder() .build(new CheckinRequest.Checkin.Build.Builder()
.bootloader(build.bootloader) .bootloader(build.bootloader)
@ -105,11 +102,11 @@ public class CheckinClient {
.build()) .build())
.cellOperator(phoneInfo.cellOperator) .cellOperator(phoneInfo.cellOperator)
.event(Collections.singletonList(new CheckinRequest.Checkin.Event.Builder() .event(Collections.singletonList(new CheckinRequest.Checkin.Event.Builder()
.tag(checkinInfo.androidId == 0 ? "event_log_start" : "system_update") .tag(checkinInfo.getAndroidId() == 0 ? "event_log_start" : "system_update")
.value(checkinInfo.androidId == 0 ? null : "1536,0,-1,NULL") .value(checkinInfo.getAndroidId() == 0 ? null : "1536,0,-1,NULL")
.timeMs(new Date().getTime()) .timeMs(new Date().getTime())
.build())) .build()))
.lastCheckinMs(checkinInfo.lastCheckin) .lastCheckinMs(checkinInfo.getLastCheckin())
.requestedGroup(TODO_LIST_STRING) .requestedGroup(TODO_LIST_STRING)
.roaming(phoneInfo.roaming) .roaming(phoneInfo.roaming)
.simOperator(phoneInfo.simOperator) .simOperator(phoneInfo.simOperator)
@ -133,7 +130,7 @@ public class CheckinClient {
.touchScreen(deviceConfiguration.touchScreen) .touchScreen(deviceConfiguration.touchScreen)
.widthPixels(deviceConfiguration.widthPixels) .widthPixels(deviceConfiguration.widthPixels)
.build()) .build())
.digest(checkinInfo.digest) .digest(checkinInfo.getDigest())
.esn(deviceIdent.esn) .esn(deviceIdent.esn)
.fragment(0) .fragment(0)
.locale(locale.toString()) .locale(locale.toString())
@ -154,8 +151,8 @@ public class CheckinClient {
builder.macAddress(Arrays.asList(deviceIdent.wifiMac)) builder.macAddress(Arrays.asList(deviceIdent.wifiMac))
.macAddressType(Arrays.asList("wifi")); .macAddressType(Arrays.asList("wifi"));
} }
if (checkinInfo.securityToken != 0) { if (checkinInfo.getSecurityToken() != 0) {
builder.securityToken(checkinInfo.securityToken) builder.securityToken(checkinInfo.getSecurityToken())
.fragment(1); .fragment(1);
} }
return builder.build(); return builder.build();

View File

@ -39,9 +39,9 @@ public class CheckinManager {
@SuppressWarnings("MissingPermission") @SuppressWarnings("MissingPermission")
public static synchronized LastCheckinInfo checkin(Context context, boolean force) throws IOException { public static synchronized LastCheckinInfo checkin(Context context, boolean force) throws IOException {
LastCheckinInfo info = LastCheckinInfo.read(context); LastCheckinInfo info = LastCheckinInfo.read(context);
if (!force && info.lastCheckin > System.currentTimeMillis() - MIN_CHECKIN_INTERVAL) if (!force && info.getLastCheckin() > System.currentTimeMillis() - MIN_CHECKIN_INTERVAL)
return null; return null;
if (!CheckinPrefs.get(context).isEnabled()) if (!CheckinPrefs.isEnabled(context))
return null; return null;
List<CheckinClient.Account> accounts = new ArrayList<CheckinClient.Account>(); List<CheckinClient.Account> accounts = new ArrayList<CheckinClient.Account>();
AccountManager accountManager = AccountManager.get(context); AccountManager accountManager = AccountManager.get(context);
@ -63,13 +63,7 @@ public class CheckinManager {
} }
private static LastCheckinInfo handleResponse(Context context, CheckinResponse response) { private static LastCheckinInfo handleResponse(Context context, CheckinResponse response) {
LastCheckinInfo info = new LastCheckinInfo(); LastCheckinInfo info = new LastCheckinInfo(response);
info.androidId = response.androidId;
info.lastCheckin = response.timeMs;
info.securityToken = response.securityToken;
info.digest = response.digest;
info.versionInfo = response.versionInfo;
info.deviceDataVersionInfo = response.deviceDataVersionInfo;
info.write(context); info.write(context);
ContentResolver resolver = context.getContentResolver(); ContentResolver resolver = context.getContentResolver();

View File

@ -1,75 +0,0 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.checkin;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import org.microg.gms.common.PackageUtils;
import java.io.File;
public class CheckinPrefs implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final String PREF_ENABLE_CHECKIN = "checkin_enable_service";
private static CheckinPrefs INSTANCE;
public static CheckinPrefs get(Context context) {
if (INSTANCE == null) {
PackageUtils.warnIfNotMainProcess(context, CheckinPrefs.class);
if (context == null) return new CheckinPrefs(null);
INSTANCE = new CheckinPrefs(context.getApplicationContext());
}
return INSTANCE;
}
private SharedPreferences preferences;
private SharedPreferences systemDefaultPreferences;
private boolean checkinEnabled = false;
private CheckinPrefs(Context context) {
if (context != null) {
preferences = PreferenceManager.getDefaultSharedPreferences(context);
preferences.registerOnSharedPreferenceChangeListener(this);
try {
systemDefaultPreferences = (SharedPreferences) Context.class.getDeclaredMethod("getSharedPreferences", File.class, int.class).invoke(context, new File("/system/etc/microg.xml"), Context.MODE_PRIVATE);
} catch (Exception ignored) {
}
update();
}
}
private boolean getSettingsBoolean(String key, boolean def) {
if (systemDefaultPreferences != null) {
def = systemDefaultPreferences.getBoolean(key, def);
}
return preferences.getBoolean(key, def);
}
private void update() {
checkinEnabled = getSettingsBoolean(PREF_ENABLE_CHECKIN, false);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
update();
}
public boolean isEnabled() {
return checkinEnabled;
}
public static void setEnabled(Context context, boolean newStatus) {
boolean changed = CheckinPrefs.get(context).isEnabled() != newStatus;
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(PREF_ENABLE_CHECKIN, newStatus).commit();
if (!changed) return;
if (newStatus) {
context.sendOrderedBroadcast(new Intent(context, TriggerReceiver.class), null);
}
}
}

View File

@ -57,7 +57,7 @@ public class CheckinService extends IntentService {
private ICheckinService iface = new ICheckinService.Stub() { private ICheckinService iface = new ICheckinService.Stub() {
@Override @Override
public String getDeviceDataVersionInfo() throws RemoteException { public String getDeviceDataVersionInfo() throws RemoteException {
return LastCheckinInfo.read(CheckinService.this).deviceDataVersionInfo; return LastCheckinInfo.read(CheckinService.this).getDeviceDataVersionInfo();
} }
}; };
@ -70,10 +70,10 @@ public class CheckinService extends IntentService {
protected void onHandleIntent(Intent intent) { protected void onHandleIntent(Intent intent) {
try { try {
ForegroundServiceContext.completeForegroundService(this, intent, TAG); ForegroundServiceContext.completeForegroundService(this, intent, TAG);
if (CheckinPrefs.get(this).isEnabled()) { if (CheckinPrefs.isEnabled(this)) {
LastCheckinInfo info = CheckinManager.checkin(this, intent.getBooleanExtra(EXTRA_FORCE_CHECKIN, false)); LastCheckinInfo info = CheckinManager.checkin(this, intent.getBooleanExtra(EXTRA_FORCE_CHECKIN, false));
if (info != null) { if (info != null) {
Log.d(TAG, "Checked in as " + Long.toHexString(info.androidId)); Log.d(TAG, "Checked in as " + Long.toHexString(info.getAndroidId()));
String accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE; String accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE;
for (Account account : AccountManager.get(this).getAccountsByType(accountType)) { for (Account account : AccountManager.get(this).getAccountsByType(accountType)) {
PeopleManager.loadUserInfo(this, account); PeopleManager.loadUserInfo(this, account);
@ -86,7 +86,7 @@ public class CheckinService extends IntentService {
ResultReceiver receiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER); ResultReceiver receiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
if (receiver != null) { if (receiver != null) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putLong(EXTRA_NEW_CHECKIN_TIME, info.lastCheckin); bundle.putLong(EXTRA_NEW_CHECKIN_TIME, info.getLastCheckin());
receiver.send(Activity.RESULT_OK, bundle); receiver.send(Activity.RESULT_OK, bundle);
} }
} }
@ -115,6 +115,6 @@ public class CheckinService extends IntentService {
static void schedule(Context context) { static void schedule(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = PendingIntent.getService(context, TriggerReceiver.class.getName().hashCode(), new Intent(context, TriggerReceiver.class), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent pendingIntent = PendingIntent.getService(context, TriggerReceiver.class.getName().hashCode(), new Intent(context, TriggerReceiver.class), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.set(AlarmManager.RTC, Math.max(LastCheckinInfo.read(context).lastCheckin + REGULAR_CHECKIN_INTERVAL, System.currentTimeMillis() + BACKUP_CHECKIN_DELAY), pendingIntent); alarmManager.set(AlarmManager.RTC, Math.max(LastCheckinInfo.read(context).getLastCheckin() + REGULAR_CHECKIN_INTERVAL, System.currentTimeMillis() + BACKUP_CHECKIN_DELAY), pendingIntent);
} }
} }

View File

@ -1,60 +0,0 @@
/*
* Copyright (C) 2013-2017 microG Project Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.microg.gms.checkin;
import android.content.Context;
import android.content.SharedPreferences;
public class LastCheckinInfo {
public static final String PREFERENCES_NAME = "checkin";
public static final String PREF_ANDROID_ID = "androidId";
public static final String PREF_DIGEST = "digest";
public static final String PREF_LAST_CHECKIN = "lastCheckin";
public static final String PREF_SECURITY_TOKEN = "securityToken";
public static final String PREF_VERSION_INFO = "versionInfo";
public static final String PREF_DEVICE_DATA_VERSION_INFO = "deviceDataVersionInfo";
public static final String INITIAL_DIGEST = "1-929a0dca0eee55513280171a8585da7dcd3700f8";
public long lastCheckin;
public long androidId;
public long securityToken;
public String digest;
public String versionInfo;
public String deviceDataVersionInfo;
public static LastCheckinInfo read(Context context) {
LastCheckinInfo info = new LastCheckinInfo();
SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
info.androidId = preferences.getLong(PREF_ANDROID_ID, 0);
info.digest = preferences.getString(PREF_DIGEST, INITIAL_DIGEST);
info.lastCheckin = preferences.getLong(PREF_LAST_CHECKIN, 0);
info.securityToken = preferences.getLong(PREF_SECURITY_TOKEN, 0);
info.versionInfo = preferences.getString(PREF_VERSION_INFO, "");
info.deviceDataVersionInfo = preferences.getString(PREF_DEVICE_DATA_VERSION_INFO, "");
return info;
}
public void write(Context context) {
context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE).edit()
.putLong(PREF_ANDROID_ID, androidId)
.putString(PREF_DIGEST, digest)
.putLong(PREF_LAST_CHECKIN, lastCheckin)
.putLong(PREF_SECURITY_TOKEN, securityToken)
.putString(PREF_VERSION_INFO, versionInfo)
.putString(PREF_DEVICE_DATA_VERSION_INFO, deviceDataVersionInfo)
.commit();
}
}

View File

@ -38,8 +38,8 @@ public class TriggerReceiver extends WakefulBroadcastReceiver {
try { try {
boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction()); boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction());
if (CheckinPrefs.get(context).isEnabled() || force) { if (CheckinPrefs.isEnabled(context) || force) {
if (LastCheckinInfo.read(context).lastCheckin > System.currentTimeMillis() - REGULAR_CHECKIN_INTERVAL && !force) { if (LastCheckinInfo.read(context).getLastCheckin() > System.currentTimeMillis() - REGULAR_CHECKIN_INTERVAL && !force) {
CheckinService.schedule(context); CheckinService.schedule(context);
return; return;
} }

View File

@ -1,305 +0,0 @@
/*
* Copyright (C) 2013-2017 microG Project Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.microg.gms.gcm;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.preference.PreferenceManager;
import android.util.Log;
import org.microg.gms.common.PackageUtils;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class GcmPrefs implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final String PREF_FULL_LOG = "gcm_full_log";
public static final String PREF_LAST_PERSISTENT_ID = "gcm_last_persistent_id";
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 final int MIN_INTERVAL = 5 * 60 * 1000; // 5 minutes
private static final int MAX_INTERVAL = 30 * 60 * 1000; // 30 minutes
private static GcmPrefs INSTANCE;
public static GcmPrefs get(Context context) {
if (INSTANCE == null) {
PackageUtils.warnIfNotPersistentProcess(GcmPrefs.class);
INSTANCE = new GcmPrefs(context.getApplicationContext());
}
return INSTANCE;
}
private boolean gcmLogEnabled = true;
private String lastPersistedId = "";
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 final Context context;
private SharedPreferences preferences;
private SharedPreferences systemDefaultPreferences;
private GcmPrefs(Context context) {
this.context = context;
preferences = PreferenceManager.getDefaultSharedPreferences(context);
preferences.registerOnSharedPreferenceChangeListener(this);
try {
systemDefaultPreferences = (SharedPreferences) Context.class.getDeclaredMethod("getSharedPreferences", File.class, int.class).invoke(context, new File("/system/etc/microg.xml"), Context.MODE_PRIVATE);
} catch (Exception ignored) {
}
update();
}
private boolean getSettingsBoolean(String key, boolean def) {
if (systemDefaultPreferences != null) {
def = systemDefaultPreferences.getBoolean(key, def);
}
return preferences.getBoolean(key, def);
}
public void update() {
gcmEnabled = getSettingsBoolean(PREF_ENABLE_GCM, false);
gcmLogEnabled = getSettingsBoolean(PREF_FULL_LOG, true);
confirmNewApps = getSettingsBoolean(PREF_CONFIRM_NEW_APPS, false);
lastPersistedId = preferences.getString(PREF_LAST_PERSISTENT_ID, "");
networkMobile = Integer.parseInt(preferences.getString(PREF_NETWORK_MOBILE, "0"));
networkWifi = Integer.parseInt(preferences.getString(PREF_NETWORK_WIFI, "0"));
networkRoaming = Integer.parseInt(preferences.getString(PREF_NETWORK_ROAMING, "0"));
networkOther = Integer.parseInt(preferences.getString(PREF_NETWORK_OTHER, "0"));
learntMobile = preferences.getInt(PREF_LEARNT_MOBILE, 300000);
learntWifi = preferences.getInt(PREF_LEARNT_WIFI, 300000);
learntOther = preferences.getInt(PREF_LEARNT_OTHER, 300000);
}
public String getNetworkPrefForInfo(NetworkInfo info) {
if (info == null) return PREF_NETWORK_OTHER;
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));
}
public int getMobileInterval() {
return networkMobile;
}
public int getWifiInterval() {
return networkWifi;
}
public int getRoamingInterval() {
return networkRoaming;
}
public int getOtherInterval() {
return networkOther;
}
public void setMobileInterval(int value) {
this.networkMobile = value;
preferences.edit().putString(PREF_NETWORK_MOBILE, Integer.toString(networkMobile)).apply();
}
public void setWifiInterval(int value) {
this.networkWifi = value;
preferences.edit().putString(PREF_NETWORK_WIFI, Integer.toString(networkWifi)).apply();
}
public void setRoamingInterval(int value) {
this.networkRoaming = value;
preferences.edit().putString(PREF_NETWORK_ROAMING, Integer.toString(networkRoaming)).apply();
}
public void setOtherInterval(int value) {
this.networkOther = value;
preferences.edit().putString(PREF_NETWORK_OTHER, Integer.toString(networkOther)).apply();
}
public int getHeartbeatMsFor(String pref) {
if (PREF_NETWORK_ROAMING.equals(pref)) {
if (networkRoaming != 0) return networkRoaming * 60000;
else return learntMobile;
} 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;
}
updateLearntValues();
}
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;
}
updateLearntValues();
}
private void updateLearntValues() {
learntMobile = Math.max(MIN_INTERVAL, Math.min(learntMobile, MAX_INTERVAL));
learntWifi = Math.max(MIN_INTERVAL, Math.min(learntWifi, MAX_INTERVAL));
learntOther = Math.max(MIN_INTERVAL, Math.min(learntOther, MAX_INTERVAL));
preferences.edit().putInt(PREF_LEARNT_MOBILE, learntMobile).putInt(PREF_LEARNT_WIFI, learntWifi).putInt(PREF_LEARNT_OTHER, learntOther).apply();
}
public int getLearntMobileInterval() {
return learntMobile;
}
public int getLearntWifiInterval() {
return learntWifi;
}
public int getLearntOtherInterval() {
return learntOther;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
update();
}
public boolean isEnabled() {
return gcmEnabled;
}
public void setEnabled(boolean value) {
boolean changed = gcmEnabled != value;
preferences.edit().putBoolean(GcmPrefs.PREF_ENABLE_GCM, value).apply();
if (!changed) return;
if (!value) {
McsService.stop(context);
} else {
context.sendBroadcast(new Intent(TriggerReceiver.FORCE_TRY_RECONNECT, null, context, TriggerReceiver.class));
}
}
public boolean isEnabledFor(NetworkInfo info) {
return isEnabled() && info != null && getHeartbeatMsFor(info) >= 0;
}
public boolean isGcmLogEnabled() {
return gcmLogEnabled;
}
public boolean isConfirmNewApps() {
return confirmNewApps;
}
public void setConfirmNewApps(boolean value) {
confirmNewApps = value;
preferences.edit().putBoolean(PREF_CONFIRM_NEW_APPS, value).apply();
}
public List<String> getLastPersistedIds() {
if (lastPersistedId.isEmpty()) return Collections.emptyList();
return Arrays.asList(lastPersistedId.split("\\|"));
}
public void extendLastPersistedId(String id) {
if (!lastPersistedId.isEmpty()) lastPersistedId += "|";
lastPersistedId += id;
preferences.edit().putString(PREF_LAST_PERSISTENT_ID, lastPersistedId).apply();
}
public void clearLastPersistedId() {
lastPersistedId = "";
preferences.edit().putString(PREF_LAST_PERSISTENT_ID, lastPersistedId).apply();
}
}

View File

@ -73,6 +73,7 @@ import okio.ByteString;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static org.microg.gms.common.PackageUtils.warnIfNotPersistentProcess;
import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_RECEIVE; import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_RECEIVE;
import static org.microg.gms.gcm.GcmConstants.EXTRA_APP; import static org.microg.gms.gcm.GcmConstants.EXTRA_APP;
import static org.microg.gms.gcm.GcmConstants.EXTRA_APP_OVERRIDE; import static org.microg.gms.gcm.GcmConstants.EXTRA_APP_OVERRIDE;
@ -234,6 +235,7 @@ public class McsService extends Service implements Handler.Callback {
} }
public synchronized static boolean isConnected(Context context) { public synchronized static boolean isConnected(Context context) {
warnIfNotPersistentProcess(McsService.class);
if (inputStream == null || !inputStream.isAlive() || outputStream == null || !outputStream.isAlive()) { if (inputStream == null || !inputStream.isAlive() || outputStream == null || !outputStream.isAlive()) {
logd(null, "Connection is not enabled or dead."); logd(null, "Connection is not enabled or dead.");
return false; return false;
@ -244,13 +246,14 @@ public class McsService extends Service implements Handler.Callback {
closeAll(); closeAll();
} else if (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime > 2 * heartbeatMs) { } else if (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime > 2 * heartbeatMs) {
logd(null, "No heartbeat for " + (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime) / 1000 + " seconds, connection assumed to be dead after " + 2 * heartbeatMs / 1000 + " seconds"); logd(null, "No heartbeat for " + (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime) / 1000 + " seconds, connection assumed to be dead after " + 2 * heartbeatMs / 1000 + " seconds");
GcmPrefs.get(context).learnTimeout(activeNetworkPref); GcmPrefs.get(context).learnTimeout(context, activeNetworkPref);
return false; return false;
} }
return true; return true;
} }
public static long getStartTimestamp() { public static long getStartTimestamp() {
warnIfNotPersistentProcess(McsService.class);
return startTimestamp; return startTimestamp;
} }
@ -460,7 +463,7 @@ public class McsService extends Service implements Handler.Callback {
private void handleLoginResponse(LoginResponse loginResponse) { private void handleLoginResponse(LoginResponse loginResponse) {
if (loginResponse.error == null) { if (loginResponse.error == null) {
GcmPrefs.get(this).clearLastPersistedId(); GcmPrefs.clearLastPersistedId(this);
logd(this, "Logged in"); logd(this, "Logged in");
wakeLock.release(); wakeLock.release();
} else { } else {
@ -470,7 +473,7 @@ public class McsService extends Service implements Handler.Callback {
private void handleCloudMessage(DataMessageStanza message) { private void handleCloudMessage(DataMessageStanza message) {
if (message.persistent_id != null) { if (message.persistent_id != null) {
GcmPrefs.get(this).extendLastPersistedId(message.persistent_id); GcmPrefs.get(this).extendLastPersistedId(this, message.persistent_id);
} }
if (SELF_CATEGORY.equals(message.category)) { if (SELF_CATEGORY.equals(message.category)) {
handleSelfMessage(message); handleSelfMessage(message);
@ -488,7 +491,7 @@ public class McsService extends Service implements Handler.Callback {
} }
private void handleHeartbeatAck(HeartbeatAck ack) { private void handleHeartbeatAck(HeartbeatAck ack) {
GcmPrefs.get(this).learnReached(activeNetworkPref, SystemClock.elapsedRealtime() - lastIncomingNetworkRealtime); GcmPrefs.get(this).learnReached(this, activeNetworkPref, SystemClock.elapsedRealtime() - lastIncomingNetworkRealtime);
lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime(); lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime();
wakeLock.release(); wakeLock.release();
} }
@ -498,13 +501,13 @@ public class McsService extends Service implements Handler.Callback {
return new LoginRequest.Builder() return new LoginRequest.Builder()
.adaptive_heartbeat(false) .adaptive_heartbeat(false)
.auth_service(LoginRequest.AuthService.ANDROID_ID) .auth_service(LoginRequest.AuthService.ANDROID_ID)
.auth_token(Long.toString(info.securityToken)) .auth_token(Long.toString(info.getSecurityToken()))
.id("android-" + SDK_INT) .id("android-" + SDK_INT)
.domain("mcs.android.com") .domain("mcs.android.com")
.device_id("android-" + Long.toHexString(info.androidId)) .device_id("android-" + Long.toHexString(info.getAndroidId()))
.network_type(1) .network_type(1)
.resource(Long.toString(info.androidId)) .resource(Long.toString(info.getAndroidId()))
.user(Long.toString(info.androidId)) .user(Long.toString(info.getAndroidId()))
.use_rmq2(true) .use_rmq2(true)
.setting(Collections.singletonList(new Setting.Builder().name("new_vc").value("1").build())) .setting(Collections.singletonList(new Setting.Builder().name("new_vc").value("1").build()))
.received_persistent_id(GcmPrefs.get(this).getLastPersistedIds()) .received_persistent_id(GcmPrefs.get(this).getLastPersistedIds())

View File

@ -87,8 +87,8 @@ public class PushRegisterManager {
if (!request.delete) { if (!request.delete) {
if (!prefs.isEnabled() || if (!prefs.isEnabled() ||
(app != null && !app.allowRegister) || (app != null && !app.allowRegister) ||
LastCheckinInfo.read(context).lastCheckin <= 0 || LastCheckinInfo.read(context).getLastCheckin() <= 0 ||
(app == null && prefs.isConfirmNewApps())) { (app == null && prefs.getConfirmNewApps())) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putString(EXTRA_ERROR, ERROR_SERVICE_NOT_AVAILABLE); bundle.putString(EXTRA_ERROR, ERROR_SERVICE_NOT_AVAILABLE);
callback.onResult(bundle); callback.onResult(bundle);

View File

@ -70,8 +70,8 @@ public class RegisterRequest extends HttpFormClient.Request {
} }
public RegisterRequest checkin(LastCheckinInfo lastCheckinInfo) { public RegisterRequest checkin(LastCheckinInfo lastCheckinInfo) {
androidId = lastCheckinInfo.androidId; androidId = lastCheckinInfo.getAndroidId();
securityToken = lastCheckinInfo.securityToken; securityToken = lastCheckinInfo.getSecurityToken();
return this; return this;
} }

View File

@ -65,7 +65,7 @@ public class TriggerReceiver extends WakefulBroadcastReceiver {
McsService.resetCurrentDelay(); McsService.resetCurrentDelay();
} }
if (LastCheckinInfo.read(context).androidId == 0) { if (LastCheckinInfo.read(context).getAndroidId() == 0) {
Log.d(TAG, "Ignoring " + intent + ": need to checkin first."); Log.d(TAG, "Ignoring " + intent + ": need to checkin first.");
return; return;
} }

View File

@ -48,7 +48,7 @@ public class MessageHandler extends ServerMessageListener {
private String peerNodeId; private String peerNodeId;
public MessageHandler(WearableImpl wearable, ConnectionConfiguration config) { public MessageHandler(WearableImpl wearable, ConnectionConfiguration config) {
this(wearable, config, new Build().model, config.nodeId, LastCheckinInfo.read(wearable.getContext()).androidId); this(wearable, config, new Build().model, config.nodeId, LastCheckinInfo.read(wearable.getContext()).getAndroidId());
} }
private MessageHandler(WearableImpl wearable, ConnectionConfiguration config, String name, String networkId, long androidId) { private MessageHandler(WearableImpl wearable, ConnectionConfiguration config, String name, String networkId, long androidId) {

View File

@ -0,0 +1,23 @@
package org.microg.gms.auth
import android.content.Context
import org.microg.gms.settings.SettingsContract
import org.microg.gms.settings.SettingsContract.Auth
object AuthPrefs {
@JvmStatic
fun isTrustGooglePermitted(context: Context): Boolean {
return SettingsContract.getSettings(context, Auth.CONTENT_URI, arrayOf(Auth.TRUST_GOOGLE)) { c ->
c.getInt(0) != 0
}
}
@JvmStatic
fun isAuthVisible(context: Context): Boolean {
return SettingsContract.getSettings(context, Auth.CONTENT_URI, arrayOf(Auth.VISIBLE)) { c ->
c.getInt(0) != 0
}
}
}

View File

@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.checkin
import android.content.Context
import org.microg.gms.settings.SettingsContract
import org.microg.gms.settings.SettingsContract.CheckIn
object CheckinPrefs {
@JvmStatic
fun isEnabled(context: Context): Boolean {
val projection = arrayOf(CheckIn.ENABLED)
return SettingsContract.getSettings(context, CheckIn.CONTENT_URI, projection) { c ->
c.getInt(0) != 0
}
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2013-2017 microG Project Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.microg.gms.checkin
import android.content.Context
import org.microg.gms.settings.SettingsContract
import org.microg.gms.settings.SettingsContract.CheckIn
data class LastCheckinInfo(
val lastCheckin: Long,
val androidId: Long,
val securityToken: Long,
val digest: String,
val versionInfo: String,
val deviceDataVersionInfo: String,
) {
constructor(r: CheckinResponse) : this(
lastCheckin = r.timeMs ?: 0L,
androidId = r.androidId ?: 0L,
securityToken = r.securityToken ?: 0L,
digest = r.digest ?: CheckIn.INITIAL_DIGEST,
versionInfo = r.versionInfo ?: "",
deviceDataVersionInfo = r.deviceDataVersionInfo ?: "",
)
companion object {
@JvmStatic
fun read(context: Context): LastCheckinInfo {
val projection = arrayOf(
CheckIn.ANDROID_ID,
CheckIn.DIGEST,
CheckIn.LAST_CHECK_IN,
CheckIn.SECURITY_TOKEN,
CheckIn.VERSION_INFO,
CheckIn.DEVICE_DATA_VERSION_INFO,
)
return SettingsContract.getSettings(context, CheckIn.CONTENT_URI, projection) { c ->
LastCheckinInfo(
androidId = c.getLong(0),
digest = c.getString(1),
lastCheckin = c.getLong(2),
securityToken = c.getLong(3),
versionInfo = c.getString(4),
deviceDataVersionInfo = c.getString(5),
)
}
}
@JvmStatic
fun clear(context: Context) = SettingsContract.setSettings(context, CheckIn.CONTENT_URI) {
put(CheckIn.ANDROID_ID, 0L)
put(CheckIn.DIGEST, CheckIn.INITIAL_DIGEST)
put(CheckIn.LAST_CHECK_IN, 0L)
put(CheckIn.SECURITY_TOKEN, 0L)
put(CheckIn.VERSION_INFO, "")
put(CheckIn.DEVICE_DATA_VERSION_INFO, "")
}
}
fun write(context: Context) = SettingsContract.setSettings(context, CheckIn.CONTENT_URI) {
put(CheckIn.ANDROID_ID, androidId)
put(CheckIn.DIGEST, digest)
put(CheckIn.LAST_CHECK_IN, lastCheckin)
put(CheckIn.SECURITY_TOKEN, securityToken)
put(CheckIn.VERSION_INFO, versionInfo)
put(CheckIn.DEVICE_DATA_VERSION_INFO, deviceDataVersionInfo)
}
}

View File

@ -5,89 +5,38 @@
package org.microg.gms.checkin package org.microg.gms.checkin
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 kotlinx.coroutines.Dispatchers
import android.util.Log import kotlinx.coroutines.withContext
import org.microg.gms.settings.SettingsContract.CheckIn
import org.microg.gms.settings.SettingsContract.getSettings
import org.microg.gms.settings.SettingsContract.setSettings
import java.io.Serializable import java.io.Serializable
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
private const val ACTION_SERVICE_INFO_REQUEST = "org.microg.gms.checkin.SERVICE_INFO_REQUEST"
private const val ACTION_UPDATE_CONFIGURATION = "org.microg.gms.checkin.UPDATE_CONFIGURATION"
private const val ACTION_SERVICE_INFO_RESPONSE = "org.microg.gms.checkin.SERVICE_INFO_RESPONSE"
private const val EXTRA_SERVICE_INFO = "org.microg.gms.checkin.SERVICE_INFO"
private const val EXTRA_CONFIGURATION = "org.microg.gms.checkin.CONFIGURATION"
private const val TAG = "GmsCheckinStatusInfo"
data class ServiceInfo(val configuration: ServiceConfiguration, val lastCheckin: Long, val androidId: Long) : Serializable data class ServiceInfo(val configuration: ServiceConfiguration, val lastCheckin: Long, val androidId: Long) : Serializable
data class ServiceConfiguration(val enabled: Boolean) : Serializable { data class ServiceConfiguration(val enabled: Boolean) : Serializable
fun saveToPrefs(context: Context) {
CheckinPrefs.setEnabled(context, enabled) suspend fun getCheckinServiceInfo(context: Context): ServiceInfo = withContext(Dispatchers.IO) {
val projection = arrayOf(CheckIn.ENABLED, CheckIn.LAST_CHECK_IN, CheckIn.ANDROID_ID)
getSettings(context, CheckIn.CONTENT_URI, projection) { c ->
ServiceInfo(
configuration = ServiceConfiguration(c.getInt(0) != 0),
lastCheckin = c.getLong(1),
androidId = c.getLong(2),
)
} }
} }
private fun CheckinPrefs.toConfiguration(): ServiceConfiguration = ServiceConfiguration(isEnabled) suspend fun setCheckinServiceConfiguration(context: Context, configuration: ServiceConfiguration) = withContext(Dispatchers.IO) {
val serviceInfo = getCheckinServiceInfo(context)
class ServiceInfoReceiver : BroadcastReceiver() { if (serviceInfo.configuration == configuration) return@withContext
private fun sendInfoResponse(context: Context) { // enabled state is not already set, setting it now
context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { setSettings(context, CheckIn.CONTENT_URI) {
setPackage(context.packageName) put(CheckIn.ENABLED, configuration.enabled)
val checkinInfo = LastCheckinInfo.read(context)
putExtra(EXTRA_SERVICE_INFO, ServiceInfo(CheckinPrefs.get(context).toConfiguration(), checkinInfo.lastCheckin, checkinInfo.androidId))
}, null)
} }
if (configuration.enabled) {
override fun onReceive(context: Context, intent: Intent) { context.sendOrderedBroadcast(Intent(context, TriggerReceiver::class.java), null)
try {
when (intent.action) {
ACTION_UPDATE_CONFIGURATION -> {
(intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context)
}
}
sendInfoResponse(context)
} catch (e: Exception) {
Log.w(TAG, e)
}
} }
} }
private suspend fun sendToServiceInfoReceiver(intent: Intent, context: Context): ServiceInfo = suspendCoroutine {
context.registerReceiver(object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
context.unregisterReceiver(this)
val serviceInfo = try {
intent.getSerializableExtra(EXTRA_SERVICE_INFO) as ServiceInfo
} catch (e: Exception) {
it.resumeWithException(e)
return
}
try {
it.resume(serviceInfo)
} catch (e: Exception) {
Log.w(TAG, e)
}
}
}, IntentFilter(ACTION_SERVICE_INFO_RESPONSE))
try {
context.sendOrderedBroadcast(intent, null)
} catch (e: Exception) {
it.resumeWithException(e)
}
}
suspend fun getCheckinServiceInfo(context: Context): ServiceInfo = sendToServiceInfoReceiver(
Intent(context, ServiceInfoReceiver::class.java).apply {
action = ACTION_SERVICE_INFO_REQUEST
}, context)
suspend fun setCheckinServiceConfiguration(context: Context, configuration: ServiceConfiguration): ServiceInfo = sendToServiceInfoReceiver(
Intent(context, ServiceInfoReceiver::class.java).apply {
action = ACTION_UPDATE_CONFIGURATION
putExtra(EXTRA_CONFIGURATION, configuration)
}, context)

View File

@ -0,0 +1,181 @@
package org.microg.gms.gcm
import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.net.NetworkInfo
import android.util.Log
import org.microg.gms.gcm.TriggerReceiver.FORCE_TRY_RECONNECT
import org.microg.gms.settings.SettingsContract
import org.microg.gms.settings.SettingsContract.Gcm
import org.microg.gms.settings.SettingsContract.setSettings
import kotlin.math.max
import kotlin.math.min
data class GcmPrefs(
val isGcmLogEnabled: Boolean,
val lastPersistedId: String?,
val confirmNewApps: Boolean,
val gcmEnabled: Boolean,
val networkMobile: Int,
val networkWifi: Int,
val networkRoaming: Int,
val networkOther: Int,
val learntMobileInterval: Int,
val learntWifiInterval: Int,
val learntOtherInterval: Int,
) {
val isEnabled: Boolean get() = gcmEnabled
val lastPersistedIds: List<String>
get() = if (lastPersistedId.isNullOrEmpty()) emptyList() else lastPersistedId.split("\\|")
companion object {
const val PREF_CONFIRM_NEW_APPS = Gcm.CONFIRM_NEW_APPS
const val PREF_NETWORK_MOBILE = Gcm.NETWORK_MOBILE
const val PREF_NETWORK_WIFI = Gcm.NETWORK_WIFI
const val PREF_NETWORK_ROAMING = Gcm.NETWORK_ROAMING
const val PREF_NETWORK_OTHER = Gcm.NETWORK_OTHER
private const val MIN_INTERVAL = 5 * 60 * 1000 // 5 minutes
private const val MAX_INTERVAL = 30 * 60 * 1000 // 30 minutes
@JvmStatic
fun get(context: Context): GcmPrefs {
return SettingsContract.getSettings(context, Gcm.CONTENT_URI, Gcm.PROJECTION) { c ->
GcmPrefs(
isGcmLogEnabled = c.getInt(0) != 0,
lastPersistedId = c.getString(1),
confirmNewApps = c.getInt(2) != 0,
gcmEnabled = c.getInt(3) != 0,
networkMobile = c.getInt(4),
networkWifi = c.getInt(5),
networkRoaming = c.getInt(6),
networkOther = c.getInt(7),
learntMobileInterval = c.getInt(8),
learntWifiInterval = c.getInt(9),
learntOtherInterval = c.getInt(10),
)
}
}
fun write(context: Context, config: ServiceConfiguration) {
val gcmPrefs = get(context)
setSettings(context, Gcm.CONTENT_URI) {
put(Gcm.ENABLE_GCM, config.enabled)
put(Gcm.CONFIRM_NEW_APPS, config.confirmNewApps)
put(Gcm.NETWORK_MOBILE, config.mobile)
put(Gcm.NETWORK_WIFI, config.wifi)
put(Gcm.NETWORK_ROAMING, config.roaming)
put(Gcm.NETWORK_OTHER, config.other)
}
gcmPrefs.setEnabled(context, config.enabled)
}
@JvmStatic
fun clearLastPersistedId(context: Context) {
setSettings(context, Gcm.CONTENT_URI) {
put(Gcm.LAST_PERSISTENT_ID, "")
}
}
}
/**
* Call this whenever the enabled state of GCM has changed.
*/
private fun setEnabled(context: Context, enabled: Boolean) {
if (gcmEnabled == enabled) return
if (enabled) {
val i = Intent(FORCE_TRY_RECONNECT, null, context, TriggerReceiver::class.java)
context.sendBroadcast(i)
} else {
McsService.stop(context)
}
}
@Suppress("DEPRECATION")
fun getNetworkPrefForInfo(info: NetworkInfo?): String {
if (info == null) return PREF_NETWORK_OTHER
return if (info.isRoaming) PREF_NETWORK_ROAMING else when (info.type) {
ConnectivityManager.TYPE_MOBILE -> PREF_NETWORK_MOBILE
ConnectivityManager.TYPE_WIFI -> PREF_NETWORK_WIFI
else -> PREF_NETWORK_OTHER
}
}
@Suppress("DEPRECATION")
fun getHeartbeatMsFor(info: NetworkInfo?): Int {
return getHeartbeatMsFor(getNetworkPrefForInfo(info))
}
fun getHeartbeatMsFor(pref: String): Int {
return if (PREF_NETWORK_ROAMING == pref) {
if (networkRoaming != 0) networkRoaming * 60000 else learntMobileInterval
} else if (PREF_NETWORK_MOBILE == pref) {
if (networkMobile != 0) networkMobile * 60000 else learntMobileInterval
} else if (PREF_NETWORK_WIFI == pref) {
if (networkWifi != 0) networkWifi * 60000 else learntWifiInterval
} else {
if (networkOther != 0) networkOther * 60000 else learntOtherInterval
}
}
fun learnTimeout(context: Context, pref: String) {
Log.d("GmsGcmPrefs", "learnTimeout: $pref")
when (pref) {
PREF_NETWORK_MOBILE, PREF_NETWORK_ROAMING -> setSettings(context, Gcm.CONTENT_URI) {
put(Gcm.LEARNT_MOBILE, (learntMobileInterval * 0.95).toInt())
}
PREF_NETWORK_WIFI -> setSettings(context, Gcm.CONTENT_URI) {
put(Gcm.LEARNT_WIFI, (learntWifiInterval * 0.95).toInt())
}
else -> setSettings(context, Gcm.CONTENT_URI) {
put(Gcm.LEARNT_OTHER, (learntOtherInterval * 0.95).toInt())
}
}
}
fun learnReached(context: Context, pref: String, time: Long) {
Log.d("GmsGcmPrefs", "learnReached: $pref / $time")
when (pref) {
PREF_NETWORK_MOBILE, PREF_NETWORK_ROAMING -> {
if (time > learntMobileInterval / 4 * 3) {
val newInterval = (learntMobileInterval * 1.02).toInt()
setSettings(context, Gcm.CONTENT_URI) {
put(Gcm.LEARNT_MOBILE, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL)))
}
}
}
PREF_NETWORK_WIFI -> {
if (time > learntWifiInterval / 4 * 3) {
val newInterval = (learntWifiInterval * 1.02).toInt()
setSettings(context, Gcm.CONTENT_URI) {
put(Gcm.LEARNT_WIFI, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL)))
}
}
}
else -> {
if (time > learntOtherInterval / 4 * 3) {
val newInterval = (learntOtherInterval * 1.02).toInt()
setSettings(context, Gcm.CONTENT_URI) {
put(Gcm.LEARNT_OTHER, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL)))
}
}
}
}
}
@Suppress("DEPRECATION")
fun isEnabledFor(info: NetworkInfo?): Boolean {
return isEnabled && info != null && getHeartbeatMsFor(info) >= 0
}
fun extendLastPersistedId(context: Context, id: String) {
val newId = if (lastPersistedId.isNullOrEmpty()) id else "$lastPersistedId|$id"
setSettings(context, Gcm.CONTENT_URI) {
put(Gcm.LAST_PERSISTENT_ID, newId)
}
}
}

View File

@ -30,7 +30,7 @@ import kotlin.coroutines.suspendCoroutine
private const val TAG = "GmsGcmRegister" private const val TAG = "GmsGcmRegister"
private suspend fun ensureCheckinIsUpToDate(context: Context) { private suspend fun ensureCheckinIsUpToDate(context: Context) {
if (!CheckinPrefs.get(context).isEnabled) throw RuntimeException("Checkin disabled") if (!CheckinPrefs.isEnabled(context)) throw RuntimeException("Checkin disabled")
val lastCheckin = LastCheckinInfo.read(context).lastCheckin val lastCheckin = LastCheckinInfo.read(context).lastCheckin
if (lastCheckin < System.currentTimeMillis() - CheckinService.MAX_VALID_CHECKIN_AGE) { if (lastCheckin < System.currentTimeMillis() - CheckinService.MAX_VALID_CHECKIN_AGE) {
val resultData: Bundle = suspendCoroutine { continuation -> val resultData: Bundle = suspendCoroutine { continuation ->
@ -55,7 +55,7 @@ private suspend fun ensureCheckinIsUpToDate(context: Context) {
private suspend fun ensureAppRegistrationAllowed(context: Context, database: GcmDatabase, packageName: String) { private suspend fun ensureAppRegistrationAllowed(context: Context, database: GcmDatabase, packageName: String) {
if (!GcmPrefs.get(context).isEnabled) throw RuntimeException("GCM disabled") if (!GcmPrefs.get(context).isEnabled) throw RuntimeException("GCM disabled")
val app = database.getApp(packageName) val app = database.getApp(packageName)
if (app == null && GcmPrefs.get(context).isConfirmNewApps) { if (app == null && GcmPrefs.get(context).confirmNewApps) {
val accepted: Boolean = suspendCoroutine { continuation -> val accepted: Boolean = suspendCoroutine { continuation ->
val i = Intent(context, AskPushPermission::class.java) val i = Intent(context, AskPushPermission::class.java)
i.putExtra(AskPushPermission.EXTRA_REQUESTED_PACKAGE, packageName) i.putExtra(AskPushPermission.EXTRA_REQUESTED_PACKAGE, packageName)

View File

@ -10,52 +10,40 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.util.Log import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.Serializable import java.io.Serializable
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
private const val ACTION_SERVICE_INFO_REQUEST = "org.microg.gms.gcm.SERVICE_INFO_REQUEST" private const val ACTION_SERVICE_INFO_REQUEST = "org.microg.gms.gcm.SERVICE_INFO_REQUEST"
private const val ACTION_UPDATE_CONFIGURATION = "org.microg.gms.gcm.UPDATE_CONFIGURATION"
private const val ACTION_SERVICE_INFO_RESPONSE = "org.microg.gms.gcm.SERVICE_INFO_RESPONSE" private const val ACTION_SERVICE_INFO_RESPONSE = "org.microg.gms.gcm.SERVICE_INFO_RESPONSE"
private const val EXTRA_SERVICE_INFO = "org.microg.gms.gcm.SERVICE_INFO" private const val EXTRA_SERVICE_INFO = "org.microg.gms.gcm.SERVICE_INFO"
private const val EXTRA_CONFIGURATION = "org.microg.gms.gcm.CONFIGURATION"
private const val TAG = "GmsGcmStatusInfo" private const val TAG = "GmsGcmStatusInfo"
data class ServiceInfo(val configuration: ServiceConfiguration, val connected: Boolean, val startTimestamp: Long, val learntMobileInterval: Int, val learntWifiInterval: Int, val learntOtherInterval: Int) : Serializable data class ServiceInfo(val configuration: ServiceConfiguration, val connected: Boolean, val startTimestamp: Long, val learntMobileInterval: Int, val learntWifiInterval: Int, val learntOtherInterval: Int) : Serializable
data class ServiceConfiguration(val enabled: Boolean, val confirmNewApps: Boolean, val mobile: Int, val wifi: Int, val roaming: Int, val other: Int) : Serializable { data class ServiceConfiguration(val enabled: Boolean, val confirmNewApps: Boolean, val mobile: Int, val wifi: Int, val roaming: Int, val other: Int) : Serializable
fun saveToPrefs(context: Context) {
GcmPrefs.get(context).apply {
isEnabled = enabled
isConfirmNewApps = confirmNewApps
mobileInterval = mobile
wifiInterval = wifi
roamingInterval = roaming
otherInterval = other
}
}
}
private fun GcmPrefs.toConfiguration(): ServiceConfiguration = ServiceConfiguration(isEnabled, isConfirmNewApps, mobileInterval, wifiInterval, roamingInterval, otherInterval) private fun GcmPrefs.toConfiguration(): ServiceConfiguration = ServiceConfiguration(isEnabled, confirmNewApps, networkMobile, networkWifi, networkRoaming, networkOther)
class ServiceInfoReceiver : BroadcastReceiver() { class ServiceInfoReceiver : BroadcastReceiver() {
private fun sendInfoResponse(context: Context) {
context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply {
setPackage(context.packageName)
val prefs = GcmPrefs.get(context)
putExtra(EXTRA_SERVICE_INFO, ServiceInfo(prefs.toConfiguration(), McsService.isConnected(context), McsService.getStartTimestamp(), prefs.learntMobileInterval, prefs.learntWifiInterval, prefs.learntOtherInterval))
}, null)
}
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
try { try {
when (intent.action) { context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply {
ACTION_UPDATE_CONFIGURATION -> { setPackage(context.packageName)
(intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context) val prefs = GcmPrefs.get(context)
} val info = ServiceInfo(
} configuration = prefs.toConfiguration(),
sendInfoResponse(context) connected = McsService.isConnected(context),
startTimestamp = McsService.getStartTimestamp(),
learntMobileInterval = prefs.learntMobileInterval,
learntWifiInterval = prefs.learntWifiInterval,
learntOtherInterval = prefs.learntOtherInterval
)
putExtra(EXTRA_SERVICE_INFO, info)
}, null)
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, e) Log.w(TAG, e)
} }
@ -87,12 +75,11 @@ private suspend fun sendToServiceInfoReceiver(intent: Intent, context: Context):
} }
suspend fun getGcmServiceInfo(context: Context): ServiceInfo = sendToServiceInfoReceiver( suspend fun getGcmServiceInfo(context: Context): ServiceInfo = sendToServiceInfoReceiver(
// this is still using a broadcast, because it calls into McsService in the persistent process
Intent(context, ServiceInfoReceiver::class.java).apply { Intent(context, ServiceInfoReceiver::class.java).apply {
action = ACTION_SERVICE_INFO_REQUEST action = ACTION_SERVICE_INFO_REQUEST
}, context) }, context)
suspend fun setGcmServiceConfiguration(context: Context, configuration: ServiceConfiguration): ServiceInfo = sendToServiceInfoReceiver( suspend fun setGcmServiceConfiguration(context: Context, configuration: ServiceConfiguration) = withContext(Dispatchers.IO) {
Intent(context, ServiceInfoReceiver::class.java).apply { GcmPrefs.write(context, configuration)
action = ACTION_UPDATE_CONFIGURATION }
putExtra(EXTRA_CONFIGURATION, configuration)
}, context)

View File

@ -0,0 +1,256 @@
package org.microg.gms.settings
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import android.util.Log
import androidx.preference.PreferenceManager
import org.microg.gms.common.PackageUtils.warnIfNotMainProcess
import org.microg.gms.settings.SettingsContract.AUTHORITY
import org.microg.gms.settings.SettingsContract.Auth
import org.microg.gms.settings.SettingsContract.CheckIn
import org.microg.gms.settings.SettingsContract.Exposure
import org.microg.gms.settings.SettingsContract.Gcm
import java.io.File
/**
* All settings access should go through this [ContentProvider],
* because it provides safe access from different processes which normal [SharedPreferences] don't.
*/
class SettingsProvider : ContentProvider() {
private val preferences: SharedPreferences by lazy {
PreferenceManager.getDefaultSharedPreferences(context)
}
private val checkInPrefs by lazy {
context!!.getSharedPreferences(CheckIn.PREFERENCES_NAME, MODE_PRIVATE)
}
private val systemDefaultPreferences: SharedPreferences? by lazy {
try {
Context::class.java.getDeclaredMethod(
"getSharedPreferences",
File::class.java,
Int::class.javaPrimitiveType
).invoke(context, File("/system/etc/microg.xml"), MODE_PRIVATE) as SharedPreferences
} catch (ignored: Exception) {
null
}
}
override fun onCreate(): Boolean {
return true
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? = when (uri) {
CheckIn.CONTENT_URI -> queryCheckIn(projection ?: CheckIn.PROJECTION)
Gcm.CONTENT_URI -> queryGcm(projection ?: Gcm.PROJECTION)
Auth.CONTENT_URI -> queryAuth(projection ?: Auth.PROJECTION)
Exposure.CONTENT_URI -> queryExposure(projection ?: Exposure.PROJECTION)
else -> null
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
warnIfNotMainProcess(context, this.javaClass)
if (values == null) return 0
when (uri) {
CheckIn.CONTENT_URI -> updateCheckIn(values)
Gcm.CONTENT_URI -> updateGcm(values)
Auth.CONTENT_URI -> updateAuth(values)
Exposure.CONTENT_URI -> updateExposure(values)
else -> return 0
}
return 1
}
private fun queryCheckIn(p: Array<out String>): Cursor = MatrixCursor(p).addRow(p) { key ->
when (key) {
CheckIn.ENABLED -> getSettingsBoolean(key, true)
CheckIn.ANDROID_ID -> checkInPrefs.getLong(key, 0)
CheckIn.DIGEST -> checkInPrefs.getString(key, CheckIn.INITIAL_DIGEST)
?: CheckIn.INITIAL_DIGEST
CheckIn.LAST_CHECK_IN -> checkInPrefs.getLong(key, 0)
CheckIn.SECURITY_TOKEN -> checkInPrefs.getLong(key, 0)
CheckIn.VERSION_INFO -> checkInPrefs.getString(key, "") ?: ""
CheckIn.DEVICE_DATA_VERSION_INFO -> checkInPrefs.getString(key, "") ?: ""
else -> throw IllegalArgumentException()
}
}
private fun updateCheckIn(values: ContentValues) {
if (values.size() == 0) return
if (values.size() == 1 && values.containsKey(CheckIn.ENABLED)) {
// special case: only changing enabled state
updateCheckInEnabled(values.getAsBoolean(CheckIn.ENABLED))
return
}
val editor = checkInPrefs.edit()
values.valueSet().forEach { (key, value) ->
// TODO remove log
Log.e("TEST", "check-in update: $key = $value")
if (key == CheckIn.ENABLED) {
// special case: not saved in checkInPrefs
updateCheckInEnabled(value as Boolean)
}
when (key) {
CheckIn.ANDROID_ID -> editor.putLong(key, value as Long)
CheckIn.DIGEST -> editor.putString(key, value as String?)
CheckIn.LAST_CHECK_IN -> editor.putLong(key, value as Long)
CheckIn.SECURITY_TOKEN -> editor.putLong(key, value as Long)
CheckIn.VERSION_INFO -> editor.putString(key, value as String?)
CheckIn.DEVICE_DATA_VERSION_INFO -> editor.putString(key, value as String?)
}
}
editor.apply()
}
private fun updateCheckInEnabled(enabled: Boolean) {
preferences.edit()
.putBoolean(CheckIn.ENABLED, enabled)
.apply()
}
private fun queryGcm(p: Array<out String>): Cursor = MatrixCursor(p).addRow(p) { key ->
when (key) {
Gcm.ENABLE_GCM -> getSettingsBoolean(key, false)
Gcm.FULL_LOG -> getSettingsBoolean(key, true)
Gcm.CONFIRM_NEW_APPS -> getSettingsBoolean(key, false)
Gcm.LAST_PERSISTENT_ID -> preferences.getString(key, "") ?: ""
Gcm.NETWORK_MOBILE -> Integer.parseInt(preferences.getString(key, "0") ?: "0")
Gcm.NETWORK_WIFI -> Integer.parseInt(preferences.getString(key, "0") ?: "0")
Gcm.NETWORK_ROAMING -> Integer.parseInt(preferences.getString(key, "0") ?: "0")
Gcm.NETWORK_OTHER -> Integer.parseInt(preferences.getString(key, "0") ?: "0")
Gcm.LEARNT_MOBILE -> preferences.getInt(key, 300000)
Gcm.LEARNT_WIFI -> preferences.getInt(key, 300000)
Gcm.LEARNT_OTHER -> preferences.getInt(key, 300000)
else -> throw IllegalArgumentException("Unknown key: $key")
}
}
private fun updateGcm(values: ContentValues) {
if (values.size() == 0) return
val editor = preferences.edit()
values.valueSet().forEach { (key, value) ->
// TODO remove log
Log.e("TEST", "gcm update: $key = $value")
when (key) {
Gcm.ENABLE_GCM -> editor.putBoolean(key, value as Boolean)
Gcm.FULL_LOG -> editor.putBoolean(key, value as Boolean)
Gcm.CONFIRM_NEW_APPS -> editor.putBoolean(key, value as Boolean)
Gcm.LAST_PERSISTENT_ID -> editor.putString(key, value as String?)
Gcm.NETWORK_MOBILE -> editor.putString(key, (value as Int).toString())
Gcm.NETWORK_WIFI -> editor.putString(key, (value as Int).toString())
Gcm.NETWORK_ROAMING -> editor.putString(key, (value as Int).toString())
Gcm.NETWORK_OTHER -> editor.putString(key, (value as Int).toString())
Gcm.LEARNT_MOBILE -> editor.putInt(key, value as Int)
Gcm.LEARNT_WIFI -> editor.putInt(key, value as Int)
Gcm.LEARNT_OTHER -> editor.putInt(key, value as Int)
else -> throw IllegalArgumentException("Unknown key: $key")
}
}
editor.apply()
}
private fun queryAuth(p: Array<out String>): Cursor = MatrixCursor(p).addRow(p) { key ->
when (key) {
Auth.TRUST_GOOGLE -> getSettingsBoolean(key, true)
Auth.VISIBLE -> getSettingsBoolean(key, false)
else -> throw IllegalArgumentException("Unknown key: $key")
}
}
private fun updateAuth(values: ContentValues) {
if (values.size() == 0) return
val editor = preferences.edit()
values.valueSet().forEach { (key, value) ->
// TODO remove log
Log.e("TEST", "auth update: $key = $value")
when (key) {
Auth.TRUST_GOOGLE -> editor.putBoolean(key, value as Boolean)
Auth.VISIBLE -> editor.putBoolean(key, value as Boolean)
else -> throw IllegalArgumentException("Unknown key: $key")
}
}
editor.apply()
}
private fun queryExposure(p: Array<out String>): Cursor = MatrixCursor(p).addRow(p) { key ->
when (key) {
Exposure.SCANNER_ENABLED -> getSettingsBoolean(key, false)
Exposure.LAST_CLEANUP -> preferences.getLong(key, 0L)
else -> throw IllegalArgumentException("Unknown key: $key")
}
}
private fun updateExposure(values: ContentValues) {
if (values.size() == 0) return
val editor = preferences.edit()
values.valueSet().forEach { (key, value) ->
// TODO remove log
Log.e("TEST", "exposure update: $key = $value")
when (key) {
Exposure.SCANNER_ENABLED -> editor.putBoolean(key, value as Boolean)
Exposure.LAST_CLEANUP -> editor.putLong(key, value as Long)
else -> throw IllegalArgumentException("Unknown key: $key")
}
}
editor.apply()
}
private fun MatrixCursor.addRow(
p: Array<out String>,
valueGetter: (String) -> Any
): MatrixCursor {
val row = newRow()
for (key in p) row.add(valueGetter.invoke(key).apply {
// TODO remove log
Log.e("TEST", "$key = $this")
})
return this
}
override fun getType(uri: Uri): String {
return "vnd.android.cursor.item/vnd.$AUTHORITY.${uri.path}"
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
throw UnsupportedOperationException()
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
throw UnsupportedOperationException()
}
/**
* Returns the current setting of the given [key]
* using the default value from [systemDefaultPreferences] or [def] if not available.
* @return the current setting as [Int], because [ContentProvider] does not support [Boolean].
*/
private fun getSettingsBoolean(key: String, def: Boolean): Int {
val default = systemDefaultPreferences?.getBoolean(key, def) ?: def
return if (preferences.getBoolean(key, default)) 1 else 0
}
}

View File

@ -34,11 +34,12 @@ class DeviceRegistrationFragment : Fragment(R.layout.device_registration_fragmen
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenResumed {
val info = getCheckinServiceInfo(requireContext()) val info = getCheckinServiceInfo(requireContext())
val newConfiguration = info.configuration.copy(enabled = newStatus) val newConfiguration = info.configuration.copy(enabled = newStatus)
displayServiceInfo(setCheckinServiceConfiguration(requireContext(), newConfiguration)) setCheckinServiceConfiguration(requireContext(), newConfiguration)
displayServiceInfo(info.copy(configuration = newConfiguration))
} }
} }
fun displayServiceInfo(serviceInfo: ServiceInfo) { private fun displayServiceInfo(serviceInfo: ServiceInfo) {
binding.checkinEnabled = serviceInfo.configuration.enabled binding.checkinEnabled = serviceInfo.configuration.enabled
} }

View File

@ -33,11 +33,12 @@ class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) {
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenResumed {
val info = getGcmServiceInfo(requireContext()) val info = getGcmServiceInfo(requireContext())
val newConfiguration = info.configuration.copy(enabled = newStatus) val newConfiguration = info.configuration.copy(enabled = newStatus)
displayServiceInfo(setGcmServiceConfiguration(requireContext(), newConfiguration)) setGcmServiceConfiguration(requireContext(), newConfiguration)
displayServiceInfo(info.copy(configuration = newConfiguration))
} }
} }
fun displayServiceInfo(serviceInfo: ServiceInfo) { private fun displayServiceInfo(serviceInfo: ServiceInfo) {
binding.gcmEnabled = serviceInfo.configuration.enabled binding.gcmEnabled = serviceInfo.configuration.enabled
} }

View File

@ -35,7 +35,8 @@ class ExposureNotificationsFragment : Fragment(R.layout.exposure_notifications_f
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenResumed {
val info = getExposureNotificationsServiceInfo(requireContext()) val info = getExposureNotificationsServiceInfo(requireContext())
val newConfiguration = info.configuration.copy(enabled = newStatus) val newConfiguration = info.configuration.copy(enabled = newStatus)
displayServiceInfo(setExposureNotificationsServiceConfiguration(requireContext(), newConfiguration)) setExposureNotificationsServiceConfiguration(requireContext(), newConfiguration)
displayServiceInfo(info.copy(configuration = newConfiguration))
} }
} }

View File

@ -43,10 +43,6 @@
</intent-filter> </intent-filter>
</service> </service>
<receiver
android:name="org.microg.gms.nearby.exposurenotification.ServiceInfoReceiver"
android:process=":persistent" />
<receiver <receiver
android:name="org.microg.gms.nearby.exposurenotification.ServiceTrigger" android:name="org.microg.gms.nearby.exposurenotification.ServiceTrigger"
android:process=":persistent"> android:process=":persistent">

View File

@ -7,22 +7,23 @@ package org.microg.gms.nearby.exposurenotification
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import org.microg.gms.settings.SettingsContract.Exposure.CONTENT_URI
import androidx.preference.PreferenceManager import org.microg.gms.settings.SettingsContract.Exposure.LAST_CLEANUP
import org.microg.gms.common.PackageUtils import org.microg.gms.settings.SettingsContract.Exposure.SCANNER_ENABLED
import org.microg.gms.settings.SettingsContract.getSettings
import org.microg.gms.settings.SettingsContract.setSettings
class ExposurePreferences(private val context: Context) { class ExposurePreferences(private val context: Context) {
private var preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
init {
PackageUtils.warnIfNotPersistentProcess(ExposurePreferences::class.java)
}
var enabled var enabled
get() = preferences.getBoolean(PREF_SCANNER_ENABLED, false) get() = getSettings(context, CONTENT_URI, arrayOf(SCANNER_ENABLED)) { c ->
c.getInt(0) != 0
}
set(newStatus) { set(newStatus) {
val changed = enabled != newStatus val changed = enabled != newStatus
preferences.edit().putBoolean(PREF_SCANNER_ENABLED, newStatus).commit() setSettings(context, CONTENT_URI) {
put(SCANNER_ENABLED, newStatus)
}
if (!changed) return if (!changed) return
if (newStatus) { if (newStatus) {
context.sendOrderedBroadcast(Intent(context, ServiceTrigger::class.java), null) context.sendOrderedBroadcast(Intent(context, ServiceTrigger::class.java), null)
@ -33,11 +34,11 @@ class ExposurePreferences(private val context: Context) {
} }
var lastCleanup var lastCleanup
get() = preferences.getLong(PREF_LAST_CLEANUP, 0) get() = getSettings(context, CONTENT_URI, arrayOf(LAST_CLEANUP)) { c ->
set(value) = preferences.edit().putLong(PREF_LAST_CLEANUP, value).apply() c.getLong(0)
}
set(value) = setSettings(context, CONTENT_URI) {
put(LAST_CLEANUP, value)
}
companion object {
private const val PREF_SCANNER_ENABLED = "exposure_scanner_enabled"
private const val PREF_LAST_CLEANUP = "exposure_last_cleanup"
}
} }

View File

@ -5,21 +5,10 @@
package org.microg.gms.nearby.exposurenotification package org.microg.gms.nearby.exposurenotification
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import kotlinx.coroutines.Dispatchers
import android.content.IntentFilter import kotlinx.coroutines.withContext
import android.util.Log
import java.io.Serializable import java.io.Serializable
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
private const val ACTION_SERVICE_INFO_REQUEST = "org.microg.gms.nearby.exposurenotification.SERVICE_INFO_REQUEST"
private const val ACTION_UPDATE_CONFIGURATION = "org.microg.gms.nearby.exposurenotification.UPDATE_CONFIGURATION"
private const val ACTION_SERVICE_INFO_RESPONSE = "org.microg.gms.nearby.exposurenotification.SERVICE_INFO_RESPONSE"
private const val EXTRA_SERVICE_INFO = "org.microg.gms.nearby.exposurenotification.SERVICE_INFO"
private const val EXTRA_CONFIGURATION = "org.microg.gms.nearby.exposurenotification.CONFIGURATION"
data class ServiceInfo(val configuration: ServiceConfiguration) : Serializable data class ServiceInfo(val configuration: ServiceConfiguration) : Serializable
@ -31,59 +20,12 @@ data class ServiceConfiguration(val enabled: Boolean) : Serializable {
private fun ExposurePreferences.toConfiguration(): ServiceConfiguration = ServiceConfiguration(enabled) private fun ExposurePreferences.toConfiguration(): ServiceConfiguration = ServiceConfiguration(enabled)
class ServiceInfoReceiver : BroadcastReceiver() { suspend fun getExposureNotificationsServiceInfo(context: Context): ServiceInfo =
private fun sendInfoResponse(context: Context) { withContext(Dispatchers.IO) {
context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { ServiceInfo(ExposurePreferences(context).toConfiguration())
setPackage(context.packageName)
putExtra(EXTRA_SERVICE_INFO, ServiceInfo(ExposurePreferences(context).toConfiguration()))
}, null)
} }
override fun onReceive(context: Context, intent: Intent) { suspend fun setExposureNotificationsServiceConfiguration(context: Context, configuration: ServiceConfiguration) =
try { withContext(Dispatchers.IO) {
when (intent.action) { ExposurePreferences(context).enabled = configuration.enabled
ACTION_UPDATE_CONFIGURATION -> {
(intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context)
}
}
sendInfoResponse(context)
} catch (e: Exception) {
Log.w(TAG, e)
}
} }
}
private suspend fun sendToServiceInfoReceiver(intent: Intent, context: Context): ServiceInfo = suspendCoroutine {
context.registerReceiver(object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
context.unregisterReceiver(this)
val serviceInfo = try {
intent.getSerializableExtra(EXTRA_SERVICE_INFO) as ServiceInfo
} catch (e: Exception) {
it.resumeWithException(e)
return
}
try {
it.resume(serviceInfo)
} catch (e: Exception) {
Log.w(TAG, e)
}
}
}, IntentFilter(ACTION_SERVICE_INFO_RESPONSE))
try {
context.sendOrderedBroadcast(intent, null)
} catch (e: Exception) {
it.resumeWithException(e)
}
}
suspend fun getExposureNotificationsServiceInfo(context: Context): ServiceInfo = sendToServiceInfoReceiver(
Intent(context, ServiceInfoReceiver::class.java).apply {
action = ACTION_SERVICE_INFO_REQUEST
}, context)
suspend fun setExposureNotificationsServiceConfiguration(context: Context, configuration: ServiceConfiguration): ServiceInfo = sendToServiceInfoReceiver(
Intent(context, ServiceInfoReceiver::class.java).apply {
action = ACTION_UPDATE_CONFIGURATION
putExtra(EXTRA_CONFIGURATION, configuration)
}, context)