diff --git a/play-services-basement/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt b/play-services-basement/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt new file mode 100644 index 00000000..cb87e5eb --- /dev/null +++ b/play-services-basement/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt @@ -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 getSettings(context: Context, uri: Uri, projection: Array?, 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"} + } + +} diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 38092b8c..2f0ef9b6 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -129,6 +129,13 @@ android:exported="true" android:permission="org.microg.gms.PROVISION" /> + + + + - - diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManager.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManager.java index deb2acbf..5b8282cf 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManager.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManager.java @@ -21,22 +21,22 @@ import android.accounts.AccountManager; import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; -import android.preference.PreferenceManager; import android.util.Log; import org.microg.gms.common.PackageUtils; +import org.microg.gms.settings.SettingsContract; import java.io.IOException; import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; +import static org.microg.gms.auth.AuthPrefs.isTrustGooglePermitted; public class AuthManager { private static final String TAG = "GmsAuthManager"; 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 = "auth_manager_visible"; + public static final String PREF_AUTH_VISIBLE = SettingsContract.Auth.VISIBLE; public static final int ONE_HOUR_IN_SECONDS = 60 * 60; 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() { try { int flags = context.getPackageManager().getApplicationInfo(packageName, 0).flags; diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java index 8365adb9..8dda8e4c 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java @@ -113,7 +113,7 @@ public class AuthRequest extends HttpFormClient.Request { public AuthRequest fromContext(Context context) { build(Utils.getBuild(context)); locale(Utils.getLocale(context)); - androidIdHex = Long.toHexString(LastCheckinInfo.read(context).androidId); + androidIdHex = Long.toHexString(LastCheckinInfo.read(context).getAndroidId()); return this; } diff --git a/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java b/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java index 13a26b2e..94706145 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java @@ -21,7 +21,6 @@ import android.accounts.AccountManager; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; -import android.graphics.Bitmap; import android.graphics.Color; import android.net.ConnectivityManager; import android.net.NetworkInfo; @@ -36,7 +35,6 @@ import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.webkit.CookieManager; import android.webkit.JavascriptInterface; -import android.webkit.ValueCallback; import android.webkit.WebSettings; import android.webkit.WebView; 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.VISIBLE; 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_VERSION_CODE; @@ -136,7 +135,7 @@ public class LoginActivity extends AssistantActivity { AccountManager accountManager = AccountManager.get(this); Account account = new Account(getIntent().getStringExtra(EXTRA_EMAIL), accountType); 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); } retrieveGmsToken(account); @@ -223,7 +222,7 @@ public class LoginActivity extends AssistantActivity { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = cm.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.isConnected()) { - if (LastCheckinInfo.read(this).androidId == 0) { + if (LastCheckinInfo.read(this).getAndroidId() == 0) { new Thread(() -> { Runnable next; next = checkin(false) ? this::loadLoginPage : () -> showError(R.string.auth_general_error_desc); @@ -425,7 +424,7 @@ public class LoginActivity extends AssistantActivity { @JavascriptInterface public final String getAndroidId() { - long androidId = LastCheckinInfo.read(LoginActivity.this).androidId; + long androidId = LastCheckinInfo.read(LoginActivity.this).getAndroidId(); Log.d(TAG, "JSBridge: getAndroidId " + androidId); if (androidId == 0 || androidId == -1) return null; return Long.toHexString(androidId); diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java index 3ecc0999..0b56919f 100644 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java @@ -18,15 +18,12 @@ package org.microg.gms.checkin; import android.util.Log; -import com.squareup.wire.Wire; - import org.microg.gms.common.Build; import org.microg.gms.common.DeviceConfiguration; import org.microg.gms.common.DeviceIdentifier; import org.microg.gms.common.PhoneInfo; import org.microg.gms.common.Utils; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -46,8 +43,8 @@ import java.util.zip.GZIPOutputStream; public class CheckinClient { private static final String TAG = "GmsCheckinClient"; private static final Object TODO = null; // TODO - private static final List TODO_LIST_STRING = new ArrayList(); // TODO - private static final List TODO_LIST_CHECKIN = new ArrayList(); // TODO + private static final List TODO_LIST_STRING = new ArrayList<>(); // TODO + private static final List TODO_LIST_CHECKIN = new ArrayList<>(); // TODO private static final String SERVICE_URL = "https://android.clients.google.com/checkin"; public static CheckinResponse request(CheckinRequest request) throws IOException { @@ -84,8 +81,8 @@ public class CheckinClient { LastCheckinInfo checkinInfo, Locale locale, List accounts) { CheckinRequest.Builder builder = new CheckinRequest.Builder() - .accountCookie(new ArrayList()) - .androidId(checkinInfo.androidId) + .accountCookie(new ArrayList<>()) + .androidId(checkinInfo.getAndroidId()) .checkin(new CheckinRequest.Checkin.Builder() .build(new CheckinRequest.Checkin.Build.Builder() .bootloader(build.bootloader) @@ -105,11 +102,11 @@ public class CheckinClient { .build()) .cellOperator(phoneInfo.cellOperator) .event(Collections.singletonList(new CheckinRequest.Checkin.Event.Builder() - .tag(checkinInfo.androidId == 0 ? "event_log_start" : "system_update") - .value(checkinInfo.androidId == 0 ? null : "1536,0,-1,NULL") + .tag(checkinInfo.getAndroidId() == 0 ? "event_log_start" : "system_update") + .value(checkinInfo.getAndroidId() == 0 ? null : "1536,0,-1,NULL") .timeMs(new Date().getTime()) .build())) - .lastCheckinMs(checkinInfo.lastCheckin) + .lastCheckinMs(checkinInfo.getLastCheckin()) .requestedGroup(TODO_LIST_STRING) .roaming(phoneInfo.roaming) .simOperator(phoneInfo.simOperator) @@ -133,7 +130,7 @@ public class CheckinClient { .touchScreen(deviceConfiguration.touchScreen) .widthPixels(deviceConfiguration.widthPixels) .build()) - .digest(checkinInfo.digest) + .digest(checkinInfo.getDigest()) .esn(deviceIdent.esn) .fragment(0) .locale(locale.toString()) @@ -154,8 +151,8 @@ public class CheckinClient { builder.macAddress(Arrays.asList(deviceIdent.wifiMac)) .macAddressType(Arrays.asList("wifi")); } - if (checkinInfo.securityToken != 0) { - builder.securityToken(checkinInfo.securityToken) + if (checkinInfo.getSecurityToken() != 0) { + builder.securityToken(checkinInfo.getSecurityToken()) .fragment(1); } return builder.build(); diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java index 631db813..95b81984 100644 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java @@ -39,9 +39,9 @@ public class CheckinManager { @SuppressWarnings("MissingPermission") public static synchronized LastCheckinInfo checkin(Context context, boolean force) throws IOException { 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; - if (!CheckinPrefs.get(context).isEnabled()) + if (!CheckinPrefs.isEnabled(context)) return null; List accounts = new ArrayList(); AccountManager accountManager = AccountManager.get(context); @@ -63,13 +63,7 @@ public class CheckinManager { } private static LastCheckinInfo handleResponse(Context context, CheckinResponse response) { - LastCheckinInfo info = new LastCheckinInfo(); - info.androidId = response.androidId; - info.lastCheckin = response.timeMs; - info.securityToken = response.securityToken; - info.digest = response.digest; - info.versionInfo = response.versionInfo; - info.deviceDataVersionInfo = response.deviceDataVersionInfo; + LastCheckinInfo info = new LastCheckinInfo(response); info.write(context); ContentResolver resolver = context.getContentResolver(); diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinPrefs.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinPrefs.java deleted file mode 100644 index ad073059..00000000 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinPrefs.java +++ /dev/null @@ -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); - } - } -} diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java index 125d5ebb..9db1ca5d 100644 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java @@ -57,7 +57,7 @@ public class CheckinService extends IntentService { private ICheckinService iface = new ICheckinService.Stub() { @Override 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) { try { 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)); 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; for (Account account : AccountManager.get(this).getAccountsByType(accountType)) { PeopleManager.loadUserInfo(this, account); @@ -86,7 +86,7 @@ public class CheckinService extends IntentService { ResultReceiver receiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER); if (receiver != null) { 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); } } @@ -115,6 +115,6 @@ public class CheckinService extends IntentService { static void schedule(Context context) { 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); - 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); } } diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/LastCheckinInfo.java b/play-services-core/src/main/java/org/microg/gms/checkin/LastCheckinInfo.java deleted file mode 100644 index 30e0a1af..00000000 --- a/play-services-core/src/main/java/org/microg/gms/checkin/LastCheckinInfo.java +++ /dev/null @@ -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(); - } -} diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java b/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java index 3c8978a2..00a27f11 100644 --- a/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java @@ -38,8 +38,8 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { try { boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction()); - if (CheckinPrefs.get(context).isEnabled() || force) { - if (LastCheckinInfo.read(context).lastCheckin > System.currentTimeMillis() - REGULAR_CHECKIN_INTERVAL && !force) { + if (CheckinPrefs.isEnabled(context) || force) { + if (LastCheckinInfo.read(context).getLastCheckin() > System.currentTimeMillis() - REGULAR_CHECKIN_INTERVAL && !force) { CheckinService.schedule(context); return; } diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/GcmPrefs.java b/play-services-core/src/main/java/org/microg/gms/gcm/GcmPrefs.java deleted file mode 100644 index d3714770..00000000 --- a/play-services-core/src/main/java/org/microg/gms/gcm/GcmPrefs.java +++ /dev/null @@ -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 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(); - } -} diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java index 55ee3b7a..e5c405a4 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java @@ -73,6 +73,7 @@ import okio.ByteString; import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; 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.EXTRA_APP; 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) { + warnIfNotPersistentProcess(McsService.class); if (inputStream == null || !inputStream.isAlive() || outputStream == null || !outputStream.isAlive()) { logd(null, "Connection is not enabled or dead."); return false; @@ -244,13 +246,14 @@ public class McsService extends Service implements Handler.Callback { closeAll(); } 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"); - GcmPrefs.get(context).learnTimeout(activeNetworkPref); + GcmPrefs.get(context).learnTimeout(context, activeNetworkPref); return false; } return true; } public static long getStartTimestamp() { + warnIfNotPersistentProcess(McsService.class); return startTimestamp; } @@ -460,7 +463,7 @@ public class McsService extends Service implements Handler.Callback { private void handleLoginResponse(LoginResponse loginResponse) { if (loginResponse.error == null) { - GcmPrefs.get(this).clearLastPersistedId(); + GcmPrefs.clearLastPersistedId(this); logd(this, "Logged in"); wakeLock.release(); } else { @@ -470,7 +473,7 @@ public class McsService extends Service implements Handler.Callback { private void handleCloudMessage(DataMessageStanza message) { 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)) { handleSelfMessage(message); @@ -488,7 +491,7 @@ public class McsService extends Service implements Handler.Callback { } 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(); wakeLock.release(); } @@ -498,13 +501,13 @@ public class McsService extends Service implements Handler.Callback { return new LoginRequest.Builder() .adaptive_heartbeat(false) .auth_service(LoginRequest.AuthService.ANDROID_ID) - .auth_token(Long.toString(info.securityToken)) + .auth_token(Long.toString(info.getSecurityToken())) .id("android-" + SDK_INT) .domain("mcs.android.com") - .device_id("android-" + Long.toHexString(info.androidId)) + .device_id("android-" + Long.toHexString(info.getAndroidId())) .network_type(1) - .resource(Long.toString(info.androidId)) - .user(Long.toString(info.androidId)) + .resource(Long.toString(info.getAndroidId())) + .user(Long.toString(info.getAndroidId())) .use_rmq2(true) .setting(Collections.singletonList(new Setting.Builder().name("new_vc").value("1").build())) .received_persistent_id(GcmPrefs.get(this).getLastPersistedIds()) diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java index b3ffca9e..16220724 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java @@ -87,8 +87,8 @@ public class PushRegisterManager { if (!request.delete) { if (!prefs.isEnabled() || (app != null && !app.allowRegister) || - LastCheckinInfo.read(context).lastCheckin <= 0 || - (app == null && prefs.isConfirmNewApps())) { + LastCheckinInfo.read(context).getLastCheckin() <= 0 || + (app == null && prefs.getConfirmNewApps())) { Bundle bundle = new Bundle(); bundle.putString(EXTRA_ERROR, ERROR_SERVICE_NOT_AVAILABLE); callback.onResult(bundle); diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java b/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java index e4cfb851..43f04028 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java @@ -70,8 +70,8 @@ public class RegisterRequest extends HttpFormClient.Request { } public RegisterRequest checkin(LastCheckinInfo lastCheckinInfo) { - androidId = lastCheckinInfo.androidId; - securityToken = lastCheckinInfo.securityToken; + androidId = lastCheckinInfo.getAndroidId(); + securityToken = lastCheckinInfo.getSecurityToken(); return this; } diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java index 58cbd510..10cc6d68 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java @@ -65,7 +65,7 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { McsService.resetCurrentDelay(); } - if (LastCheckinInfo.read(context).androidId == 0) { + if (LastCheckinInfo.read(context).getAndroidId() == 0) { Log.d(TAG, "Ignoring " + intent + ": need to checkin first."); return; } diff --git a/play-services-core/src/main/java/org/microg/gms/wearable/MessageHandler.java b/play-services-core/src/main/java/org/microg/gms/wearable/MessageHandler.java index b648b3ae..6a84253c 100644 --- a/play-services-core/src/main/java/org/microg/gms/wearable/MessageHandler.java +++ b/play-services-core/src/main/java/org/microg/gms/wearable/MessageHandler.java @@ -48,7 +48,7 @@ public class MessageHandler extends ServerMessageListener { private String peerNodeId; 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) { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt new file mode 100644 index 00000000..f5797f56 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt @@ -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 + } + } + +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt new file mode 100644 index 00000000..bd122c35 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt @@ -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 + } + } + +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt new file mode 100644 index 00000000..ccdb31af --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt @@ -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) + } +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt index d8902b8f..e61eb4d1 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt @@ -5,89 +5,38 @@ package org.microg.gms.checkin -import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.content.IntentFilter -import android.util.Log +import kotlinx.coroutines.Dispatchers +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 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 ServiceConfiguration(val enabled: Boolean) : Serializable { - fun saveToPrefs(context: Context) { - CheckinPrefs.setEnabled(context, enabled) +data class ServiceConfiguration(val enabled: Boolean) : Serializable + +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) - -class ServiceInfoReceiver : BroadcastReceiver() { - private fun sendInfoResponse(context: Context) { - context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { - setPackage(context.packageName) - val checkinInfo = LastCheckinInfo.read(context) - putExtra(EXTRA_SERVICE_INFO, ServiceInfo(CheckinPrefs.get(context).toConfiguration(), checkinInfo.lastCheckin, checkinInfo.androidId)) - }, null) +suspend fun setCheckinServiceConfiguration(context: Context, configuration: ServiceConfiguration) = withContext(Dispatchers.IO) { + val serviceInfo = getCheckinServiceInfo(context) + if (serviceInfo.configuration == configuration) return@withContext + // enabled state is not already set, setting it now + setSettings(context, CheckIn.CONTENT_URI) { + put(CheckIn.ENABLED, configuration.enabled) } - - override fun onReceive(context: Context, intent: Intent) { - try { - when (intent.action) { - ACTION_UPDATE_CONFIGURATION -> { - (intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context) - } - } - sendInfoResponse(context) - } catch (e: Exception) { - Log.w(TAG, e) - } + if (configuration.enabled) { + context.sendOrderedBroadcast(Intent(context, TriggerReceiver::class.java), null) } } - - - -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) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt new file mode 100644 index 00000000..1f1e75d4 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt @@ -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 + 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) + } + } + +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt index 02fe6f0a..3f228476 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt @@ -30,7 +30,7 @@ import kotlin.coroutines.suspendCoroutine private const val TAG = "GmsGcmRegister" 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 if (lastCheckin < System.currentTimeMillis() - CheckinService.MAX_VALID_CHECKIN_AGE) { 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) { if (!GcmPrefs.get(context).isEnabled) throw RuntimeException("GCM disabled") 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 i = Intent(context, AskPushPermission::class.java) i.putExtra(AskPushPermission.EXTRA_REQUESTED_PACKAGE, packageName) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt index cc400f22..5d6a0392 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt @@ -10,52 +10,40 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext 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.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 EXTRA_SERVICE_INFO = "org.microg.gms.gcm.SERVICE_INFO" -private const val EXTRA_CONFIGURATION = "org.microg.gms.gcm.CONFIGURATION" 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 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 - } - } -} +data class ServiceConfiguration(val enabled: Boolean, val confirmNewApps: Boolean, val mobile: Int, val wifi: Int, val roaming: Int, val other: Int) : Serializable -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() { - 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) { try { - when (intent.action) { - ACTION_UPDATE_CONFIGURATION -> { - (intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context) - } - } - sendInfoResponse(context) + context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { + setPackage(context.packageName) + val prefs = GcmPrefs.get(context) + val info = ServiceInfo( + configuration = prefs.toConfiguration(), + 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) { Log.w(TAG, e) } @@ -87,12 +75,11 @@ private suspend fun sendToServiceInfoReceiver(intent: Intent, context: Context): } 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 { action = ACTION_SERVICE_INFO_REQUEST }, context) -suspend fun setGcmServiceConfiguration(context: Context, configuration: ServiceConfiguration): ServiceInfo = sendToServiceInfoReceiver( - Intent(context, ServiceInfoReceiver::class.java).apply { - action = ACTION_UPDATE_CONFIGURATION - putExtra(EXTRA_CONFIGURATION, configuration) - }, context) +suspend fun setGcmServiceConfiguration(context: Context, configuration: ServiceConfiguration) = withContext(Dispatchers.IO) { + GcmPrefs.write(context, configuration) +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt b/play-services-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt new file mode 100644 index 00000000..f635539c --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt @@ -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?, + selection: String?, + selectionArgs: Array?, + 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? + ): 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): 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): 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): 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): 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, + 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?): 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 + } +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt index b56ec0dc..4a59ffb4 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt @@ -34,11 +34,12 @@ class DeviceRegistrationFragment : Fragment(R.layout.device_registration_fragmen lifecycleScope.launchWhenResumed { val info = getCheckinServiceInfo(requireContext()) 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 } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt index f0e78ec2..d0cb577e 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt @@ -33,11 +33,12 @@ class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) { lifecycleScope.launchWhenResumed { val info = getGcmServiceInfo(requireContext()) 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 } diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsFragment.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsFragment.kt index 91b2d8ae..6cc00f5e 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsFragment.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsFragment.kt @@ -35,7 +35,8 @@ class ExposureNotificationsFragment : Fragment(R.layout.exposure_notifications_f lifecycleScope.launchWhenResumed { val info = getExposureNotificationsServiceInfo(requireContext()) val newConfiguration = info.configuration.copy(enabled = newStatus) - displayServiceInfo(setExposureNotificationsServiceConfiguration(requireContext(), newConfiguration)) + setExposureNotificationsServiceConfiguration(requireContext(), newConfiguration) + displayServiceInfo(info.copy(configuration = newConfiguration)) } } diff --git a/play-services-nearby-core/src/main/AndroidManifest.xml b/play-services-nearby-core/src/main/AndroidManifest.xml index c04bacee..a9f717dc 100644 --- a/play-services-nearby-core/src/main/AndroidManifest.xml +++ b/play-services-nearby-core/src/main/AndroidManifest.xml @@ -43,10 +43,6 @@ - - diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt index 0fdfd03c..03f4bc0a 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt @@ -7,22 +7,23 @@ package org.microg.gms.nearby.exposurenotification import android.content.Context import android.content.Intent -import android.content.SharedPreferences -import androidx.preference.PreferenceManager -import org.microg.gms.common.PackageUtils +import org.microg.gms.settings.SettingsContract.Exposure.CONTENT_URI +import org.microg.gms.settings.SettingsContract.Exposure.LAST_CLEANUP +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) { - private var preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - - init { - PackageUtils.warnIfNotPersistentProcess(ExposurePreferences::class.java) - } var enabled - get() = preferences.getBoolean(PREF_SCANNER_ENABLED, false) + get() = getSettings(context, CONTENT_URI, arrayOf(SCANNER_ENABLED)) { c -> + c.getInt(0) != 0 + } set(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 (newStatus) { context.sendOrderedBroadcast(Intent(context, ServiceTrigger::class.java), null) @@ -33,11 +34,11 @@ class ExposurePreferences(private val context: Context) { } var lastCleanup - get() = preferences.getLong(PREF_LAST_CLEANUP, 0) - set(value) = preferences.edit().putLong(PREF_LAST_CLEANUP, value).apply() + get() = getSettings(context, CONTENT_URI, arrayOf(LAST_CLEANUP)) { c -> + 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" - } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceInfo.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceInfo.kt index 1379e9ce..602dc738 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceInfo.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceInfo.kt @@ -5,21 +5,10 @@ package org.microg.gms.nearby.exposurenotification -import android.content.BroadcastReceiver import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext 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 @@ -31,59 +20,12 @@ data class ServiceConfiguration(val enabled: Boolean) : Serializable { private fun ExposurePreferences.toConfiguration(): ServiceConfiguration = ServiceConfiguration(enabled) -class ServiceInfoReceiver : BroadcastReceiver() { - private fun sendInfoResponse(context: Context) { - context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { - setPackage(context.packageName) - putExtra(EXTRA_SERVICE_INFO, ServiceInfo(ExposurePreferences(context).toConfiguration())) - }, null) +suspend fun getExposureNotificationsServiceInfo(context: Context): ServiceInfo = + withContext(Dispatchers.IO) { + ServiceInfo(ExposurePreferences(context).toConfiguration()) } - override fun onReceive(context: Context, intent: Intent) { - try { - when (intent.action) { - ACTION_UPDATE_CONFIGURATION -> { - (intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context) - } - } - sendInfoResponse(context) - } catch (e: Exception) { - Log.w(TAG, e) - } +suspend fun setExposureNotificationsServiceConfiguration(context: Context, configuration: ServiceConfiguration) = + withContext(Dispatchers.IO) { + ExposurePreferences(context).enabled = configuration.enabled } -} - -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)