From 5316e0220a47be54724838149716924d5b372d8e Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 16 Nov 2016 00:52:49 +0100 Subject: [PATCH] Squashed commit - Update all submodules, used sdk version, etc - Settings UI rebuild - Some GCM features and fixes - Fix newest Cast Framework for some apps (tested with "ZDF Mediathek") Fixes #224, #223, #145 --- .travis.yml | 5 +- build.gradle | 6 +- extern/GmsApi | 2 +- extern/GmsLib | 2 +- extern/UnifiedNlp | 2 +- play-services-cast-framework-api | 1 + play-services-core/build.gradle | 3 + .../src/main/AndroidManifest.xml | 59 +++- .../gms/auth/AccountContentProvider.java | 2 +- .../java/org/microg/gms/auth/AuthManager.java | 2 +- .../microg/gms/auth/login/LoginActivity.java | 3 +- .../loginservice/AccountAuthenticator.java | 3 +- .../microg/gms/checkin/CheckinManager.java | 3 +- .../microg/gms/checkin/CheckinService.java | 3 +- .../microg/gms/checkin/LastCheckinInfo.java | 2 +- .../org/microg/gms/common/HttpFormClient.java | 29 +- .../java/org/microg/gms/gcm/GcmDatabase.java | 323 ++++++++++++++++++ .../gcm/{GcmData.java => GcmLegacyData.java} | 52 +-- .../java/org/microg/gms/gcm/GcmPrefs.java | 25 +- .../java/org/microg/gms/gcm/McsService.java | 36 +- .../microg/gms/gcm/PushRegisterService.java | 117 ++++--- .../org/microg/gms/gcm/RegisterResponse.java | 3 + .../org/microg/gms/gcm/TriggerReceiver.java | 3 +- .../microg/gms/gcm/UnregisterReceiver.java | 21 ++ .../microg/gms/people/PeopleServiceImpl.java | 3 +- .../java/org/microg/gms/ui/AboutFragment.java | 50 +++ .../gms/ui/AccountSettingsActivity.java | 27 ++ .../org/microg/gms/ui/AskPushPermission.java | 88 +++++ .../java/org/microg/gms/ui/Conditions.java | 86 +++++ .../org/microg/gms/ui/GcmAppFragment.java | 202 +++++++++++ .../java/org/microg/gms/ui/GcmFragment.java | 262 ++++++++++++++ .../gms/ui/GcmRegisteredAppsFragment.java | 163 --------- .../gms/ui/LocationSettingsActivity.java | 22 ++ .../microg/gms/ui/PlacePickerActivity.java | 3 +- .../org/microg/gms/ui/SelfCheckFragment.java | 83 +++++ .../org/microg/gms/ui/SettingsActivity.java | 144 ++------ .../org/microg/gms/wearable/WearableImpl.java | 30 +- .../src/main/res/drawable/device_login.xml | 11 + .../src/main/res/drawable/gcm_bell.xml | 11 + .../src/main/res/layout/ask_gcm.xml | 133 ++++++++ .../src/main/res/layout/ask_permission.xml | 4 +- .../src/main/res/layout/games_info.xml | 2 +- .../src/main/res/layout/gcm_app.xml | 75 ---- .../src/main/res/layout/gcm_apps_list.xml | 21 -- .../src/main/res/layout/list_no_item.xml | 50 +++ .../src/main/res/layout/pick_place.xml | 2 +- .../src/main/res/values-de/strings.xml | 8 +- .../src/main/res/values-es/strings.xml | 2 - .../src/main/res/values-pl/strings.xml | 2 - .../src/main/res/values-ro/strings.xml | 2 - .../src/main/res/values-sr/strings.xml | 2 - .../{menu/gcm_app.xml => values/arrays.xml} | 24 +- .../src/main/res/values/strings.xml | 72 ++-- .../src/main/res/xml/authenticator.xml | 4 +- .../src/main/res/xml/gms_preferences.xml | 63 ---- ...references.xml => preferences_account.xml} | 20 +- .../src/main/res/xml/preferences_auth.xml | 33 ++ .../src/main/res/xml/preferences_gcm.xml | 32 ++ .../main/res/xml/preferences_gcm_advanced.xml | 70 ++++ .../res/xml/preferences_gcm_app_detail.xml | 35 ++ .../src/main/res/xml/preferences_start.xml | 62 ++++ settings.gradle | 1 + 62 files changed, 1987 insertions(+), 624 deletions(-) create mode 120000 play-services-cast-framework-api create mode 100644 play-services-core/src/main/java/org/microg/gms/gcm/GcmDatabase.java rename play-services-core/src/main/java/org/microg/gms/gcm/{GcmData.java => GcmLegacyData.java} (62%) create mode 100644 play-services-core/src/main/java/org/microg/gms/gcm/UnregisterReceiver.java create mode 100644 play-services-core/src/main/java/org/microg/gms/ui/AboutFragment.java create mode 100644 play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java create mode 100644 play-services-core/src/main/java/org/microg/gms/ui/AskPushPermission.java create mode 100644 play-services-core/src/main/java/org/microg/gms/ui/Conditions.java create mode 100644 play-services-core/src/main/java/org/microg/gms/ui/GcmAppFragment.java create mode 100644 play-services-core/src/main/java/org/microg/gms/ui/GcmFragment.java delete mode 100644 play-services-core/src/main/java/org/microg/gms/ui/GcmRegisteredAppsFragment.java create mode 100644 play-services-core/src/main/java/org/microg/gms/ui/LocationSettingsActivity.java create mode 100644 play-services-core/src/main/java/org/microg/gms/ui/SelfCheckFragment.java create mode 100644 play-services-core/src/main/res/drawable/device_login.xml create mode 100644 play-services-core/src/main/res/drawable/gcm_bell.xml create mode 100644 play-services-core/src/main/res/layout/ask_gcm.xml delete mode 100644 play-services-core/src/main/res/layout/gcm_app.xml delete mode 100644 play-services-core/src/main/res/layout/gcm_apps_list.xml create mode 100644 play-services-core/src/main/res/layout/list_no_item.xml rename play-services-core/src/main/res/{menu/gcm_app.xml => values/arrays.xml} (50%) delete mode 100644 play-services-core/src/main/res/xml/gms_preferences.xml rename play-services-core/src/main/res/xml/{account_preferences.xml => preferences_account.xml} (74%) create mode 100644 play-services-core/src/main/res/xml/preferences_auth.xml create mode 100644 play-services-core/src/main/res/xml/preferences_gcm.xml create mode 100644 play-services-core/src/main/res/xml/preferences_gcm_advanced.xml create mode 100644 play-services-core/src/main/res/xml/preferences_gcm_app_detail.xml create mode 100644 play-services-core/src/main/res/xml/preferences_start.xml diff --git a/.travis.yml b/.travis.yml index b892c349..f6117322 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,10 +12,9 @@ script: - ./gradlew assemble android: components: - - platform-tools - tools - - build-tools-24.0.2 - - android-23 + - platform-tools + - build-tools-24.0.3 - android-24 - extra-android-m2repository before_cache: diff --git a/build.gradle b/build.gradle index 25ec211a..3ec4bfce 100644 --- a/build.gradle +++ b/build.gradle @@ -19,14 +19,14 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:2.2.2' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' } } allprojects { apply plugin: 'idea' - ext.androidBuildVersionTools = "24.0.2" + ext.androidBuildVersionTools = "24.0.3" ext.isReleaseVersion = false } @@ -34,7 +34,7 @@ def androidCompileSdk() { return 24 } def androidTargetSdk() { return 24 } -def androidMinSdk() { return 10 } +def androidMinSdk() { return 9 } def versionCode() { def stdout = new ByteArrayOutputStream() diff --git a/extern/GmsApi b/extern/GmsApi index 9ff42ae7..0b4f43c6 160000 --- a/extern/GmsApi +++ b/extern/GmsApi @@ -1 +1 @@ -Subproject commit 9ff42ae73a02ea971dc557f657b612113dfa6e24 +Subproject commit 0b4f43c6a6a091dbdeb0ec544d533373a83ea319 diff --git a/extern/GmsLib b/extern/GmsLib index 4bffa579..a0300494 160000 --- a/extern/GmsLib +++ b/extern/GmsLib @@ -1 +1 @@ -Subproject commit 4bffa5799a9ce406a268941e26fbb4a637d42446 +Subproject commit a0300494f6cf65334c1d2f969571ef6069579bca diff --git a/extern/UnifiedNlp b/extern/UnifiedNlp index d9923a0d..2f810b14 160000 --- a/extern/UnifiedNlp +++ b/extern/UnifiedNlp @@ -1 +1 @@ -Subproject commit d9923a0d10edded17ad96d61e1e247f557bad652 +Subproject commit 2f810b14fde67cb690b440b3249f1887435c2e92 diff --git a/play-services-cast-framework-api b/play-services-cast-framework-api new file mode 120000 index 00000000..f9196119 --- /dev/null +++ b/play-services-cast-framework-api @@ -0,0 +1 @@ +extern/GmsApi/play-services-cast-framework-api \ No newline at end of file diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 95396597..f421ea9f 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -64,6 +64,9 @@ android { // We are not allowed to freely choose the hundreds column as it defines the device type versionCode(10084400 + x % 100 + ((int) (x / 100)) * 1000) + minSdkVersion androidMinSdk() + targetSdkVersion androidTargetSdk() + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86" } diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index ca1ee6d1..ba244228 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -17,10 +17,6 @@ - - + + + + + + + android:theme="@style/Theme.AppCompat.Settings.Dashboard"> + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AccountContentProvider.java b/play-services-core/src/main/java/org/microg/gms/auth/AccountContentProvider.java index 16f09301..ebaa12d6 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AccountContentProvider.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AccountContentProvider.java @@ -59,7 +59,7 @@ public class AccountContentProvider extends ContentProvider { if (getContext().checkCallingPermission(Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) throw new SecurityException("Access denied, missing GET_ACCOUNTS or EXTENDED_ACCESS permission"); } - if (PROVIDER_METHOD_GET_ACCOUNTS.equals(method) && getContext().getString(R.string.google_account_type).equals(arg)) { + if (PROVIDER_METHOD_GET_ACCOUNTS.equals(method) && AuthConstants.DEFAULT_ACCOUNT_TYPE.equals(arg)) { Bundle result = new Bundle(); result.putParcelableArray(PROVIDER_EXTRA_ACCOUNTS, AccountManager.get(getContext()).getAccountsByType(arg)); return result; 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 4e17288c..d2e2b031 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 @@ -56,7 +56,7 @@ public class AuthManager { public String getAccountType() { if (accountType == null) - accountType = context.getString(R.string.google_account_type); + accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE; return accountType; } 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 59efa4b2..dc4fa3b3 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 @@ -44,6 +44,7 @@ import android.widget.TextView; import com.google.android.gms.R; import org.json.JSONArray; +import org.microg.gms.auth.AuthConstants; import org.microg.gms.auth.AuthManager; import org.microg.gms.auth.AuthRequest; import org.microg.gms.auth.AuthResponse; @@ -90,7 +91,7 @@ public class LoginActivity extends AssistantActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - accountType = getString(R.string.google_account_type); + accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE; accountManager = AccountManager.get(LoginActivity.this); inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); webView = createWebView(this); diff --git a/play-services-core/src/main/java/org/microg/gms/auth/loginservice/AccountAuthenticator.java b/play-services-core/src/main/java/org/microg/gms/auth/loginservice/AccountAuthenticator.java index 754f701e..15d8399a 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/loginservice/AccountAuthenticator.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/loginservice/AccountAuthenticator.java @@ -30,6 +30,7 @@ import android.util.Log; import com.google.android.gms.R; import org.microg.gms.auth.AskPermissionActivity; +import org.microg.gms.auth.AuthConstants; import org.microg.gms.auth.AuthManager; import org.microg.gms.auth.AuthResponse; import org.microg.gms.auth.login.LoginActivity; @@ -55,7 +56,7 @@ class AccountAuthenticator extends AbstractAccountAuthenticator { public AccountAuthenticator(Context context) { super(context); this.context = context; - this.accountType = context.getString(R.string.google_account_type); + this.accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE; } @Override 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 c7517e6d..651f7cbb 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 @@ -23,6 +23,7 @@ import android.content.Context; import com.google.android.gms.R; +import org.microg.gms.auth.AuthConstants; import org.microg.gms.auth.AuthRequest; import org.microg.gms.common.Constants; import org.microg.gms.common.DeviceConfiguration; @@ -43,7 +44,7 @@ public class CheckinManager { return null; List accounts = new ArrayList(); AccountManager accountManager = AccountManager.get(context); - String accountType = context.getString(R.string.google_account_type); + String accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE; for (Account account : accountManager.getAccountsByType(accountType)) { String token = new AuthRequest() .email(account.name).token(accountManager.getPassword(account)) 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 046221bc..67680fcd 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 @@ -28,6 +28,7 @@ import android.util.Log; import com.google.android.gms.R; import com.google.android.gms.checkin.internal.ICheckinService; +import org.microg.gms.auth.AuthConstants; import org.microg.gms.gcm.McsService; import org.microg.gms.people.PeopleManager; @@ -55,7 +56,7 @@ public class CheckinService extends IntentService { LastCheckinInfo info = CheckinManager.checkin(this, intent.getBooleanExtra(EXTRA_FORCE_CHECKIN, false)); if (info != null) { Log.d(TAG, "Checked in as " + Long.toHexString(info.androidId)); - String accountType = getString(R.string.google_account_type); + String accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE; for (Account account : AccountManager.get(this).getAccountsByType(accountType)) { PeopleManager.loadUserInfo(this, account); } 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 index 5e5b848a..113f6b1c 100644 --- 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 @@ -55,6 +55,6 @@ public class LastCheckinInfo { .putLong(PREF_SECURITY_TOKEN, securityToken) .putString(PREF_VERSION_INFO, versionInfo) .putString(PREF_DEVICE_DATA_VERSION_INFO, deviceDataVersionInfo) - .commit(); + .apply(); } } diff --git a/play-services-core/src/main/java/org/microg/gms/common/HttpFormClient.java b/play-services-core/src/main/java/org/microg/gms/common/HttpFormClient.java index 31e6bbc6..879d11fb 100644 --- a/play-services-core/src/main/java/org/microg/gms/common/HttpFormClient.java +++ b/play-services-core/src/main/java/org/microg/gms/common/HttpFormClient.java @@ -86,7 +86,7 @@ public class HttpFormClient { String result = new String(Utils.readStreamToEnd(connection.getInputStream())); Log.d(TAG, "-- Response --\n" + result); - return parseResponse(tClass, connection.getHeaderFields(), result); + return parseResponse(tClass, connection, result); } private static String valueFromBoolVal(String value, Boolean boolVal, boolean truePresent, boolean falsePresent) { @@ -103,7 +103,8 @@ public class HttpFormClient { } } - private static T parseResponse(Class tClass, Map> headerFields, String result) { + private static T parseResponse(Class tClass, HttpURLConnection connection, String result) throws IOException { + Map> headerFields = connection.getHeaderFields(); T response; try { response = tClass.getConstructor().newInstance(); @@ -153,6 +154,20 @@ public class HttpFormClient { Log.w(TAG, e); } } + if (field.isAnnotationPresent(ResponseStatusCode.class) && field.getType() == int.class) { + try { + field.setInt(response, connection.getResponseCode()); + } catch (IllegalAccessException e) { + Log.w(TAG, e); + } + } + if (field.isAnnotationPresent(ResponseStatusText.class) && field.getType() == String.class) { + try { + field.set(response, connection.getResponseMessage()); + } catch (IllegalAccessException e) { + Log.w(TAG, e); + } + } } return response; } @@ -217,4 +232,14 @@ public class HttpFormClient { public @interface ResponseHeader { public String value(); } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface ResponseStatusCode { + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface ResponseStatusText { + } } diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/GcmDatabase.java b/play-services-core/src/main/java/org/microg/gms/gcm/GcmDatabase.java new file mode 100644 index 00000000..cea5ccb7 --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/gcm/GcmDatabase.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2013-2016 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.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class GcmDatabase extends SQLiteOpenHelper { + private static final String TAG = GcmDatabase.class.getSimpleName(); + public static final String DB_NAME = "gcmstatus"; + private static int DB_VERSION = 1; + private static final String CREATE_TABLE_APPS = "CREATE TABLE apps (" + + "package_name TEXT," + + "last_error TEXT DEFAULT ''," + + "last_message_timestamp INTEGER," + + "total_message_count INTEGER," + + "total_message_bytes INTEGER," + + "allow_register INTEGER DEFAULT 1," + + "wake_for_delivery INTEGER DEFAULT 1," + + "PRIMARY KEY (package_name));"; + private static final String TABLE_APPS = "apps"; + private static final String FIELD_PACKAGE_NAME = "package_name"; + private static final String FIELD_LAST_ERROR = "last_error"; + private static final String FIELD_LAST_MESSAGE_TIMESTAMP = "last_message_timestamp"; + private static final String FIELD_TOTAL_MESSAGE_COUNT = "total_message_count"; + private static final String FIELD_TOTAL_MESSAGE_BYTES = "total_message_bytes"; + private static final String FIELD_ALLOW_REGISTER = "allow_register"; + private static final String FIELD_WAKE_FOR_DELIVERY = "wake_for_delivery"; + + private static final String CREATE_TABLE_REGISTRATIONS = "CREATE TABLE registrations (" + + "package_name TEXT," + + "signature TEXT," + + "timestamp INTEGER," + + "register_id TEXT," + + "PRIMARY KEY (package_name, signature));"; + private static final String TABLE_REGISTRATIONS = "registrations"; + private static final String FIELD_SIGNATURE = "signature"; + private static final String FIELD_TIMESTAMP = "timestamp"; + private static final String FIELD_REGISTER_ID = "register_id"; + + private Context context; + + public GcmDatabase(Context context) { + super(context, DB_NAME, null, DB_VERSION); + this.context = context; + } + + public static class App { + public final String packageName; + public final String lastError; + public final long lastMessageTimestamp; + public final long totalMessageCount; + public final long totalMessageBytes; + public final boolean allowRegister; + public final boolean wakeForDelivery; + + private App(Cursor cursor) { + packageName = cursor.getString(cursor.getColumnIndex(FIELD_PACKAGE_NAME)); + lastError = cursor.getString(cursor.getColumnIndex(FIELD_LAST_ERROR)); + lastMessageTimestamp = cursor.getLong(cursor.getColumnIndex(FIELD_LAST_MESSAGE_TIMESTAMP)); + totalMessageCount = cursor.getLong(cursor.getColumnIndex(FIELD_TOTAL_MESSAGE_COUNT)); + totalMessageBytes = cursor.getLong(cursor.getColumnIndex(FIELD_TOTAL_MESSAGE_BYTES)); + allowRegister = cursor.getLong(cursor.getColumnIndex(FIELD_ALLOW_REGISTER)) == 1; + wakeForDelivery = cursor.getLong(cursor.getColumnIndex(FIELD_WAKE_FOR_DELIVERY)) == 1; + } + + public boolean hasError() { + return !TextUtils.isEmpty(lastError); + } + } + + public static class Registration { + public final String packageName; + public final String signature; + public final long timestamp; + public final String registerId; + + public Registration(Cursor cursor) { + packageName = cursor.getString(cursor.getColumnIndex(FIELD_PACKAGE_NAME)); + signature = cursor.getString(cursor.getColumnIndex(FIELD_SIGNATURE)); + timestamp = cursor.getLong(cursor.getColumnIndex(FIELD_TIMESTAMP)); + registerId = cursor.getString(cursor.getColumnIndex(FIELD_REGISTER_ID)); + } + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(CREATE_TABLE_APPS); + db.execSQL(CREATE_TABLE_REGISTRATIONS); + importLegacyData(db); + } + + public synchronized List getAppList() { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query(TABLE_APPS, null, null, null, null, null, null); + if (cursor != null) { + List result = new ArrayList<>(); + while (cursor.moveToNext()) { + result.add(new App(cursor)); + } + cursor.close(); + return result; + } + return Collections.emptyList(); + } + + public synchronized List getRegistrationList() { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query(TABLE_REGISTRATIONS, null, null, null, null, null, null); + if (cursor != null) { + List result = new ArrayList<>(); + while (cursor.moveToNext()) { + result.add(new Registration(cursor)); + } + cursor.close(); + return result; + } + return Collections.emptyList(); + } + + + public synchronized List getRegistrationsByApp(String packageName) { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query(TABLE_REGISTRATIONS, null, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName}, null, null, null); + if (cursor != null) { + List result = new ArrayList<>(); + while (cursor.moveToNext()) { + result.add(new Registration(cursor)); + } + cursor.close(); + return result; + } + return Collections.emptyList(); + } + + public synchronized void setAppAllowRegister(String packageName, boolean allowRegister) { + SQLiteDatabase db = getWritableDatabase(); + ContentValues cv = new ContentValues(); + cv.put(FIELD_ALLOW_REGISTER, allowRegister ? 1 : 0); + db.update(TABLE_APPS, cv, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName}); + } + + public synchronized void setAppWakeForDelivery(String packageName, boolean wakeForDelivery) { + SQLiteDatabase db = getWritableDatabase(); + ContentValues cv = new ContentValues(); + cv.put(FIELD_WAKE_FOR_DELIVERY, wakeForDelivery ? 1 : 0); + db.update(TABLE_APPS, cv, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName}); + } + + + public synchronized void removeApp(String packageName) { + SQLiteDatabase db = getWritableDatabase(); + db.delete(TABLE_REGISTRATIONS, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName}); + db.delete(TABLE_APPS, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName}); + } + + public synchronized void noteAppRegistrationError(String packageName, String error) { + SQLiteDatabase db = getWritableDatabase(); + ContentValues cv = new ContentValues(); + cv.put(FIELD_LAST_ERROR, error); + db.update(TABLE_APPS, cv, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName}); + } + + public synchronized void noteAppKnown(String packageName, boolean allowRegister) { + SQLiteDatabase db = getWritableDatabase(); + db.beginTransaction(); + + App app = getApp(db, packageName); + ContentValues cv = new ContentValues(); + cv.put(FIELD_ALLOW_REGISTER, allowRegister); + if (app == null) { + cv.put(FIELD_PACKAGE_NAME, packageName); + db.insert(TABLE_APPS, null, cv); + } else { + db.update(TABLE_APPS, cv, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName}); + } + + db.setTransactionSuccessful(); + db.endTransaction(); + } + + public synchronized void noteAppMessage(String packageName, int numBytes) { + SQLiteDatabase db = getWritableDatabase(); + db.beginTransaction(); + + App app = getApp(db, packageName); + ContentValues cv = new ContentValues(); + cv.put(FIELD_LAST_MESSAGE_TIMESTAMP, System.currentTimeMillis()); + if (app == null) { + cv.put(FIELD_PACKAGE_NAME, packageName); + cv.put(FIELD_TOTAL_MESSAGE_COUNT, 1); + cv.put(FIELD_TOTAL_MESSAGE_BYTES, numBytes); + db.insert(TABLE_APPS, null, cv); + } else { + cv.put(FIELD_TOTAL_MESSAGE_COUNT, app.totalMessageCount + 1); + cv.put(FIELD_TOTAL_MESSAGE_BYTES, app.totalMessageBytes + numBytes); + db.update(TABLE_APPS, cv, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName}); + } + + db.setTransactionSuccessful(); + db.endTransaction(); + } + + public synchronized void noteAppRegistered(String packageName, String signature, String registrationId) { + SQLiteDatabase db = getWritableDatabase(); + db.beginTransaction(); + + App app = getApp(db, packageName); + if (app == null) { + ContentValues cv = new ContentValues(); + cv.put(FIELD_PACKAGE_NAME, packageName); + db.insert(TABLE_APPS, null, cv); + } else { + ContentValues cv = new ContentValues(); + cv.put(FIELD_LAST_ERROR, ""); + db.update(TABLE_APPS, cv, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName}); + } + + ContentValues cv = new ContentValues(); + cv.put(FIELD_PACKAGE_NAME, packageName); + cv.put(FIELD_SIGNATURE, signature); + cv.put(FIELD_REGISTER_ID, registrationId); + cv.put(FIELD_TIMESTAMP, System.currentTimeMillis()); + db.insertWithOnConflict(TABLE_REGISTRATIONS, null, cv, SQLiteDatabase.CONFLICT_REPLACE); + + db.setTransactionSuccessful(); + db.endTransaction(); + } + + public synchronized void noteAppUnregistered(String packageName, String signature) { + SQLiteDatabase db = getWritableDatabase(); + db.delete(TABLE_REGISTRATIONS, FIELD_PACKAGE_NAME + " LIKE ? AND " + FIELD_SIGNATURE + " LIKE ?", new String[]{packageName, signature}); + } + + public App getApp(String packageName) { + return getApp(getReadableDatabase(), packageName); + } + + private App getApp(SQLiteDatabase db, String packageName) { + Cursor cursor = db.query(TABLE_APPS, null, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName}, null, null, null, "1"); + if (cursor != null) { + try { + if (cursor.moveToNext()) { + return new App(cursor); + } + } finally { + cursor.close(); + } + } + return null; + } + + public Registration getRegistration(String packageName, String signature) { + return getRegistration(getReadableDatabase(), packageName, signature); + } + + private Registration getRegistration(SQLiteDatabase db, String packageName, String signature) { + Cursor cursor = db.query(TABLE_REGISTRATIONS, null, FIELD_PACKAGE_NAME + " LIKE ? AND " + FIELD_SIGNATURE + " LIKE ?", new String[]{packageName, signature}, null, null, null, "1"); + if (cursor != null) { + try { + if (cursor.moveToNext()) { + return new Registration(cursor); + } + } finally { + cursor.close(); + } + } + return null; + } + + + @SuppressWarnings("deprecation") + private void importLegacyData(SQLiteDatabase db) { + db.beginTransaction(); + + GcmLegacyData legacyData = new GcmLegacyData(context); + for (GcmLegacyData.LegacyAppInfo appInfo : legacyData.getAppsInfo()) { + ContentValues cv = new ContentValues(); + cv.put(FIELD_PACKAGE_NAME, appInfo.app); + cv.put(FIELD_TOTAL_MESSAGE_COUNT, legacyData.getAppMessageCount(appInfo.app)); + cv.put(FIELD_LAST_ERROR, appInfo.hasUnregistrationError() ? "Unregistration error" : null); + db.insert(TABLE_APPS, null, cv); + cv.clear(); + if (appInfo.isRegistered()) { + cv.put(FIELD_PACKAGE_NAME, appInfo.app); + cv.put(FIELD_SIGNATURE, appInfo.appSignature); + cv.put(FIELD_REGISTER_ID, appInfo.registerID); + db.insert(TABLE_REGISTRATIONS, null, cv); + } + } + + db.setTransactionSuccessful(); + db.endTransaction(); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + throw new IllegalStateException("Upgrades not supported"); + } + +} diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/GcmData.java b/play-services-core/src/main/java/org/microg/gms/gcm/GcmLegacyData.java similarity index 62% rename from play-services-core/src/main/java/org/microg/gms/gcm/GcmData.java rename to play-services-core/src/main/java/org/microg/gms/gcm/GcmLegacyData.java index 3170e843..6ede2884 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/GcmData.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/GcmLegacyData.java @@ -23,20 +23,22 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -public class GcmData { +@Deprecated +public class GcmLegacyData { private static final String GCM_REGISTRATION_PREF = "gcm_registrations"; private static final String GCM_MESSAGES_PREF = "gcm_messages"; - private static final String REMOVED = "%%REMOVED%%"; - private static final String ERROR = "%%ERROR%%"; + static final String REMOVED = "%%REMOVED%%"; + static final String ERROR = "%%ERROR%%"; private Context context; - public GcmData(Context context) { + public GcmLegacyData(Context context) { this.context = context; } - public static class AppInfo implements Comparable { + @Deprecated + public static class LegacyAppInfo implements Comparable { public String app = null; public String appSignature = null; public String registerID = null; @@ -46,7 +48,7 @@ public class GcmData { private final int STATE_REGISTERED = 3; private int state; - public AppInfo(String key, String value) { + public LegacyAppInfo(String key, String value) { if (ERROR.equals(value)) { state = STATE_ERROR; } else if (REMOVED.equals(value)) { @@ -73,42 +75,19 @@ public class GcmData { } @Override - public int compareTo(AppInfo another) { + public int compareTo(LegacyAppInfo another) { return app.compareTo(another.app); } } - public void noteAppRegistered(String app, String signature, String regId) { - getInfoSharedPreferences().edit().putString(app + ":" + signature, regId).apply(); - } - - public void noteAppRegistrationError(String app, String signature) { - getInfoSharedPreferences().edit().putString(app + ":" + signature, "-").apply(); - } - - public void noteAppUnregistered(String app, String signature) { - getInfoSharedPreferences().edit().putString(app + ":" + signature, REMOVED).apply(); - } - - public void noteAppUnregistrationError(String app, String signature) { - getInfoSharedPreferences().edit().putString(app + ":" + signature, ERROR).apply(); - } - - public void incrementAppMessageCount(String app, int i) { - int messageCount = getStatsSharedPreferences().getInt(app, 0); - getStatsSharedPreferences().edit().putInt(app, messageCount + i).apply(); - } - + @Deprecated public int getAppMessageCount(String app) { return getStatsSharedPreferences().getInt(app, 0); } - public AppInfo getAppInfo(String app, String signature) { - return getAppInfo(app + ":" + signature); - } - - public List getAppsInfo() { - ArrayList ret = new ArrayList<>(); + @Deprecated + public List getAppsInfo() { + ArrayList ret = new ArrayList<>(); Set keys = getInfoSharedPreferences().getAll().keySet(); for (String key : keys) { ret.add(getAppInfo(key)); @@ -116,8 +95,9 @@ public class GcmData { return ret; } - private AppInfo getAppInfo(String key) { - return new AppInfo(key, getInfoSharedPreferences().getString(key, "")); + @Deprecated + private LegacyAppInfo getAppInfo(String key) { + return new LegacyAppInfo(key, getInfoSharedPreferences().getString(key, "")); } private SharedPreferences getInfoSharedPreferences() { 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 index 9e74901a..5ede7317 100644 --- 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 @@ -25,9 +25,12 @@ import java.util.Collections; import java.util.List; public class GcmPrefs implements SharedPreferences.OnSharedPreferenceChangeListener { - private static final String PREF_GCM_HEARTBEAT = "gcm_heartbeat_interval"; - private static final String PREF_GCM_LOG = "gcm_full_log"; - private static final String PREF_LAST_PERSISTENT_ID = "gcm_last_persistent_id"; + public static final String PREF_HEARTBEAT = "gcm_heartbeat_interval"; + 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"; + private static GcmPrefs INSTANCE; public static GcmPrefs get(Context context) { @@ -41,6 +44,8 @@ public class GcmPrefs implements SharedPreferences.OnSharedPreferenceChangeListe private int heartbeatMs = 300000; private boolean gcmLogEnabled = true; private String lastPersistedId = ""; + private boolean confirmNewApps = false; + private boolean gcmEnabled = false; private SharedPreferences defaultPreferences; @@ -53,9 +58,11 @@ public class GcmPrefs implements SharedPreferences.OnSharedPreferenceChangeListe } public void update() { - heartbeatMs = Integer.parseInt(defaultPreferences.getString(PREF_GCM_HEARTBEAT, "300")) * 1000; - gcmLogEnabled = defaultPreferences.getBoolean(PREF_GCM_LOG, true); + heartbeatMs = Integer.parseInt(defaultPreferences.getString(PREF_HEARTBEAT, "300")) * 1000; + gcmLogEnabled = defaultPreferences.getBoolean(PREF_FULL_LOG, true); lastPersistedId = defaultPreferences.getString(PREF_LAST_PERSISTENT_ID, ""); + confirmNewApps = defaultPreferences.getBoolean(PREF_CONFIRM_NEW_APPS, false); + gcmEnabled = defaultPreferences.getBoolean(PREF_ENABLE_GCM, false); } public int getHeartbeatMs() { @@ -67,10 +74,18 @@ public class GcmPrefs implements SharedPreferences.OnSharedPreferenceChangeListe update(); } + public boolean isGcmEnabled() { + return gcmEnabled; + } + public boolean isGcmLogEnabled() { return gcmLogEnabled; } + public boolean isConfirmNewApps() { + return confirmNewApps; + } + public List getLastPersistedIds() { if (lastPersistedId.isEmpty()) return Collections.emptyList(); return Arrays.asList(lastPersistedId.split("\\|")); 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 800fb7f4..e1f416ea 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 @@ -90,6 +90,7 @@ public class McsService extends Service implements Handler.Callback { private static final int WAKELOCK_TIMEOUT = 5000; private static long lastHeartbeatAckElapsedRealtime = -1; + private static long startTimestamp = 0; private static Socket sslSocket; private static McsInputStream inputStream; @@ -100,7 +101,7 @@ public class McsService extends Service implements Handler.Callback { private static HandlerThread handlerThread; private static Handler rootHandler; - private GcmData gcmStorage = new GcmData(this); + private GcmDatabase database; private AlarmManager alarmManager; private PowerManager powerManager; @@ -139,6 +140,7 @@ public class McsService extends Service implements Handler.Callback { @Override public void onCreate() { super.onCreate(); + database = new GcmDatabase(this); heartbeatIntent = PendingIntent.getService(this, 0, new Intent(ACTION_HEARTBEAT, null, this, McsService.class), 0); alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); powerManager = (PowerManager) getSystemService(POWER_SERVICE); @@ -168,6 +170,10 @@ public class McsService extends Service implements Handler.Callback { return true; } + public static long getStartTimestamp() { + return startTimestamp; + } + public static void scheduleReconnect(Context context) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); long delay = getCurrentDelay(); @@ -240,6 +246,7 @@ public class McsService extends Service implements Handler.Callback { inputStream.start(); outputStream.start(); + startTimestamp = System.currentTimeMillis(); lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime(); scheduleHeartbeat(this); } catch (Exception e) { @@ -305,27 +312,30 @@ public class McsService extends Service implements Handler.Callback { } private void handleAppMessage(DataMessageStanza msg) { + database.noteAppMessage(msg.category, msg.getSerializedSize()); + GcmDatabase.App app = database.getApp(msg.category); + Intent intent = new Intent(); intent.setAction(ACTION_C2DM_RECEIVE); intent.setPackage(msg.category); intent.putExtra(EXTRA_FROM, msg.from); + if (app.wakeForDelivery) intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); if (msg.token != null) intent.putExtra(EXTRA_COLLAPSE_KEY, msg.token); for (AppData appData : msg.app_data) { intent.putExtra(appData.key, appData.value); } - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - // FIXME: #75 - List infos = getPackageManager().queryBroadcastReceivers(intent, PackageManager.GET_RESOLVED_FILTER); - for (ResolveInfo resolveInfo : infos) { - logd("Target: " + resolveInfo); - Intent targetIntent = new Intent(intent); - targetIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)); - sendOrderedBroadcast(targetIntent, msg.category + ".permission.C2D_MESSAGE"); - } - if (infos.isEmpty()) - logd("No target for message, wut?"); - gcmStorage.incrementAppMessageCount(msg.category, 1); + List infos = getPackageManager().queryBroadcastReceivers(intent, PackageManager.GET_RESOLVED_FILTER); + if (infos == null || infos.isEmpty()) { + logd("No target for message, wut?"); + } else { + for (ResolveInfo resolveInfo : infos) { + logd("Target: " + resolveInfo); + Intent targetIntent = new Intent(intent); + targetIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)); + sendOrderedBroadcast(targetIntent, msg.category + ".permission.C2D_MESSAGE"); + } + } } private void handleSelfMessage(DataMessageStanza msg) { diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterService.java b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterService.java index 0daad99f..3ddfca74 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterService.java @@ -16,22 +16,32 @@ package org.microg.gms.gcm; +import android.app.AlertDialog; import android.app.IntentService; import android.app.PendingIntent; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.os.Build; import android.os.Message; import android.os.Messenger; +import android.text.Html; import android.util.Log; import org.microg.gms.checkin.CheckinService; import org.microg.gms.checkin.LastCheckinInfo; import org.microg.gms.common.PackageUtils; import org.microg.gms.common.Utils; +import org.microg.gms.ui.AskPushPermission; import java.io.IOException; +import static android.os.Build.VERSION.SDK_INT; +import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH; +import static android.os.Build.VERSION_CODES.JELLY_BEAN; +import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTER; import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTRATION; import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_UNREGISTER; @@ -40,6 +50,7 @@ import static org.microg.gms.gcm.GcmConstants.EXTRA_APP; import static org.microg.gms.gcm.GcmConstants.EXTRA_DELETE; import static org.microg.gms.gcm.GcmConstants.EXTRA_ERROR; import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSENGER; +import static org.microg.gms.gcm.GcmConstants.EXTRA_PENDING_INTENT; import static org.microg.gms.gcm.GcmConstants.EXTRA_REGISTRATION_ID; import static org.microg.gms.gcm.GcmConstants.EXTRA_RETRY_AFTER; import static org.microg.gms.gcm.GcmConstants.EXTRA_SENDER; @@ -49,30 +60,39 @@ public class PushRegisterService extends IntentService { private static final String TAG = "GmsGcmRegisterSvc"; private static final String EXTRA_SKIP_TRY_CHECKIN = "skip_checkin"; - private GcmData gcmStorage = new GcmData(this); + private GcmDatabase database; + private static boolean requestPending = false; public PushRegisterService() { super(TAG); setIntentRedelivery(false); } - public static RegisterResponse register(Context context, String app, String appSignature, String sender, String info) { - RegisterResponse response = register(context, app, appSignature, sender, info, false); + @Override + public void onCreate() { + super.onCreate(); + database = new GcmDatabase(this); + } + + public static RegisterResponse register(Context context, String packageName, String pkgSignature, String sender, String info) { + GcmDatabase database = new GcmDatabase(context); + RegisterResponse response = register(context, packageName, pkgSignature, sender, info, false); String regId = response.token; if (regId != null) { - (new GcmData(context)).noteAppRegistered(app, appSignature, regId); + database.noteAppRegistered(packageName, pkgSignature, regId); } else { - (new GcmData(context)).noteAppRegistrationError(app, appSignature); + database.noteAppRegistrationError(packageName, response.responseText); } return response; } - public static RegisterResponse unregister(Context context, String app, String appSignature, String sender, String info) { - RegisterResponse response = register(context, app, appSignature, sender, info, true); - if (!app.equals(response.deleted)) { - (new GcmData(context)).noteAppUnregistrationError(app, appSignature); + public static RegisterResponse unregister(Context context, String packageName, String pkgSignature, String sender, String info) { + GcmDatabase database = new GcmDatabase(context); + RegisterResponse response = register(context, packageName, pkgSignature, sender, info, true); + if (!packageName.equals(response.deleted)) { + database.noteAppRegistrationError(packageName, response.responseText); } else { - (new GcmData(context)).noteAppUnregistered(app, appSignature); + database.noteAppUnregistered(packageName, pkgSignature); } return response; } @@ -103,23 +123,49 @@ public class PushRegisterService extends IntentService { @SuppressWarnings("deprecation") private String packageFromPendingIntent(PendingIntent pi) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { return pi.getTargetPackage(); } else { return pi.getCreatorPackage(); } } - private void register(Intent intent) { + private void register(final Intent intent) { PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_APP); - String sender = intent.getStringExtra(EXTRA_SENDER); - String app = packageFromPendingIntent(pendingIntent); + final String packageName = packageFromPendingIntent(pendingIntent); Log.d(TAG, "register[req]: " + intent.toString() + " extras=" + intent.getExtras()); - Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION); - String appSignature = PackageUtils.firstSignatureDigest(this, app); + GcmDatabase.App app = database.getApp(packageName); + if (app == null && GcmPrefs.get(this).isConfirmNewApps()) { + try { + PackageManager pm = getPackageManager(); + ApplicationInfo info = pm.getApplicationInfo(packageName, 0); + Intent i = new Intent(this, AskPushPermission.class); + i.putExtra(EXTRA_PENDING_INTENT, intent); + i.putExtra(EXTRA_APP, packageName); + startActivity(i); + } catch (PackageManager.NameNotFoundException e) { + replyNotAvailable(this, intent, packageName); + } + } else if (app != null && !app.allowRegister) { + replyNotAvailable(this, intent, packageName); + } else { + registerAndReply(this, intent, packageName); + } + } - String regId = register(this, app, appSignature, sender, null).token; + public static void replyNotAvailable(Context context, Intent intent, String packageName) { + Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION); + outIntent.putExtra(EXTRA_ERROR, ERROR_SERVICE_NOT_AVAILABLE); + Log.d(TAG, "registration not allowed"); + sendReply(context, intent, packageName, outIntent); + } + + public static void registerAndReply(Context context, Intent intent, String packageName) { + Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION); + String sender = intent.getStringExtra(EXTRA_SENDER); + String appSignature = PackageUtils.firstSignatureDigest(context, packageName); + String regId = register(context, packageName, appSignature, sender, null).token; if (regId != null) { outIntent.putExtra(EXTRA_REGISTRATION_ID, regId); } else { @@ -127,6 +173,10 @@ public class PushRegisterService extends IntentService { } Log.d(TAG, "register[res]: " + outIntent + " extras=" + outIntent.getExtras()); + sendReply(context, intent, packageName, outIntent); + } + + private static void sendReply(Context context, Intent intent, String packageName, Intent outIntent) { try { if (intent.hasExtra(EXTRA_MESSENGER)) { Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER); @@ -139,8 +189,8 @@ public class PushRegisterService extends IntentService { Log.w(TAG, e); } - outIntent.setPackage(app); - sendOrderedBroadcast(outIntent, null); + outIntent.setPackage(packageName); + context.sendOrderedBroadcast(outIntent, null); } public static RegisterResponse register(Context context, String app, String appSignature, String sender, String info, boolean delete) { @@ -164,41 +214,28 @@ public class PushRegisterService extends IntentService { private void unregister(Intent intent) { PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_APP); - String app = packageFromPendingIntent(pendingIntent); + String packageName = packageFromPendingIntent(pendingIntent); Log.d(TAG, "unregister[req]: " + intent.toString() + " extras=" + intent.getExtras()); Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION); - String appSignature = PackageUtils.firstSignatureDigest(this, app); + String appSignature = PackageUtils.firstSignatureDigest(this, packageName); - if (!gcmStorage.getAppInfo(app, appSignature).isRemoved()) { - outIntent.putExtra(EXTRA_UNREGISTERED, app); + if (database.getRegistration(packageName, appSignature) == null) { + outIntent.putExtra(EXTRA_UNREGISTERED, packageName); } else { - RegisterResponse response = unregister(this, app, appSignature, null, null); - if (!app.equals(response.deleted)) { + RegisterResponse response = unregister(this, packageName, appSignature, null, null); + if (!packageName.equals(response.deleted)) { outIntent.putExtra(EXTRA_ERROR, ERROR_SERVICE_NOT_AVAILABLE); if (response.retryAfter != null && !response.retryAfter.contains(":")) { outIntent.putExtra(EXTRA_RETRY_AFTER, Long.parseLong(response.retryAfter)); } } else { - outIntent.putExtra(EXTRA_UNREGISTERED, app); + outIntent.putExtra(EXTRA_UNREGISTERED, packageName); } } Log.d(TAG, "unregister[res]: " + outIntent.toString() + " extras=" + outIntent.getExtras()); - try { - if (intent.hasExtra(EXTRA_MESSENGER)) { - Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER); - Message message = Message.obtain(); - message.obj = outIntent; - messenger.send(message); - return; - } - } catch (Exception e) { - Log.w(TAG, e); - } - - outIntent.setPackage(app); - sendOrderedBroadcast(outIntent, null); + sendReply(this, intent, packageName, outIntent); } } diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/RegisterResponse.java b/play-services-core/src/main/java/org/microg/gms/gcm/RegisterResponse.java index 4557b479..cce466e9 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/RegisterResponse.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/RegisterResponse.java @@ -17,6 +17,7 @@ package org.microg.gms.gcm; import org.microg.gms.common.HttpFormClient.ResponseHeader; +import org.microg.gms.common.HttpFormClient.ResponseStatusText; import static org.microg.gms.common.HttpFormClient.ResponseField; @@ -27,6 +28,8 @@ public class RegisterResponse { public String retryAfter; @ResponseField("deleted") public String deleted; + @ResponseStatusText + public String responseText; @Override public String toString() { 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 96ec5b94..ecee6aa7 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 @@ -32,14 +32,13 @@ import static org.microg.gms.gcm.McsConstants.EXTRA_REASON; public class TriggerReceiver extends WakefulBroadcastReceiver { private static final String TAG = "GmsGcmTrigger"; - private static final String PREF_ENABLE_GCM = "gcm_enable_mcs_service"; @Override public void onReceive(Context context, Intent intent) { boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction()); ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_ENABLE_GCM, false) || force) { + if (GcmPrefs.get(context).isGcmEnabled() || force) { if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { McsService.resetCurrentDelay(); } diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/UnregisterReceiver.java b/play-services-core/src/main/java/org/microg/gms/gcm/UnregisterReceiver.java new file mode 100644 index 00000000..6900876e --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/gcm/UnregisterReceiver.java @@ -0,0 +1,21 @@ +package org.microg.gms.gcm; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import static android.content.Intent.ACTION_PACKAGE_REMOVED; +import static android.content.Intent.EXTRA_DATA_REMOVED; + +public class UnregisterReceiver extends BroadcastReceiver { + private static final String TAG = "GmsGcmUnregisterRcvr"; + + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "Package removed: " + intent); + if (ACTION_PACKAGE_REMOVED.contains(intent.getAction()) && intent.getBooleanExtra(EXTRA_DATA_REMOVED, false)) { + Log.d(TAG, "Package removed: " + intent.getData()); + } + } +} diff --git a/play-services-core/src/main/java/org/microg/gms/people/PeopleServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/people/PeopleServiceImpl.java index d602a6ec..dfef8ebf 100644 --- a/play-services-core/src/main/java/org/microg/gms/people/PeopleServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/people/PeopleServiceImpl.java @@ -33,6 +33,7 @@ import com.google.android.gms.people.internal.IPeopleCallbacks; import com.google.android.gms.people.internal.IPeopleService; import com.google.android.gms.people.model.AccountMetadata; +import org.microg.gms.auth.AuthConstants; import org.microg.gms.common.NonCancelToken; import org.microg.gms.common.PackageUtils; @@ -53,7 +54,7 @@ public class PeopleServiceImpl extends IPeopleService.Stub { PackageUtils.assertExtendedAccess(context); AccountManager accountManager = AccountManager.get(context); Bundle accountMetadata = new Bundle(); - String accountType = context.getString(R.string.google_account_type); + String accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE; for (Account account : accountManager.getAccountsByType(accountType)) { if (accountName == null || account.name.equals(accountName)) { accountMetadata.putParcelable(account.name, new AccountMetadata()); diff --git a/play-services-core/src/main/java/org/microg/gms/ui/AboutFragment.java b/play-services-core/src/main/java/org/microg/gms/ui/AboutFragment.java new file mode 100644 index 00000000..80b997c6 --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/ui/AboutFragment.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013-2016 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.ui; + +import android.support.v4.app.Fragment; + +import org.microg.tools.ui.AbstractAboutFragment; +import org.microg.tools.ui.AbstractSettingsActivity; + +import java.util.List; + +public class AboutFragment extends AbstractAboutFragment { + + @Override + protected void collectLibraries(List libraries) { + libraries.add(new AbstractAboutFragment.Library("org.microg.gms.api", "microG GmsApi", "Apache License 2.0, microG Team")); + libraries.add(new AbstractAboutFragment.Library("org.microg.safeparcel", "microG SafeParcel", "Apache License 2.0, microG Team")); + libraries.add(new AbstractAboutFragment.Library("org.microg.nlp", "microG UnifiedNlp", "Apache License 2.0, microG Team")); + libraries.add(new AbstractAboutFragment.Library("org.microg.nlp.api", "microG UnifiedNlp Api", "Apache License 2.0, microG Team")); + libraries.add(new AbstractAboutFragment.Library("org.microg.wearable", "microG Wearable", "Apache License 2.0, microG Team")); + libraries.add(new AbstractAboutFragment.Library("de.hdodenhof.circleimageview", "CircleImageView", "Apache License 2.0, Henning Dodenhof")); + libraries.add(new AbstractAboutFragment.Library("org.oscim.android", "V™", "GNU LGPLv3, Hannes Janetzek and devemux86")); + libraries.add(new AbstractAboutFragment.Library("com.squareup.wire", "Wire Protocol Buffers", "Apache License 2.0, Square Inc.")); + } + + public static class AsActivity extends AbstractSettingsActivity { + public AsActivity() { + showHomeAsUp = true; + } + + @Override + protected Fragment getFragment() { + return new AboutFragment(); + } + } +} diff --git a/play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java new file mode 100644 index 00000000..e52b7bdf --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2013-2016 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.ui; + +import com.google.android.gms.R; + +import org.microg.tools.ui.AbstractSettingsActivity; + +public class AccountSettingsActivity extends AbstractSettingsActivity { + public AccountSettingsActivity() { + preferencesResource = R.xml.preferences_account; + } +} diff --git a/play-services-core/src/main/java/org/microg/gms/ui/AskPushPermission.java b/play-services-core/src/main/java/org/microg/gms/ui/AskPushPermission.java new file mode 100644 index 00000000..570275d8 --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/ui/AskPushPermission.java @@ -0,0 +1,88 @@ +package org.microg.gms.ui; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.text.Html; +import android.view.View; +import android.widget.TextView; + +import com.google.android.gms.R; + +import org.microg.gms.gcm.GcmDatabase; +import org.microg.gms.gcm.PushRegisterService; + +import static org.microg.gms.gcm.GcmConstants.EXTRA_APP; +import static org.microg.gms.gcm.GcmConstants.EXTRA_PENDING_INTENT; + +public class AskPushPermission extends FragmentActivity { + + private GcmDatabase database; + + private String packageName; + private Intent intent; + private boolean answered; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + database = new GcmDatabase(this); + + packageName = getIntent().getStringExtra(EXTRA_APP); + intent = getIntent().getParcelableExtra(EXTRA_PENDING_INTENT); + + if (database.getApp(packageName) != null) { + finish(); + return; + } + + setContentView(R.layout.ask_gcm); + + try { + PackageManager pm = getPackageManager(); + final ApplicationInfo info = pm.getApplicationInfo(packageName, 0); + CharSequence label = pm.getApplicationLabel(info); + + ((TextView) findViewById(R.id.permission_message)).setText(Html.fromHtml("Allow " + label + " to register for push notifications?")); + findViewById(R.id.permission_allow_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (answered) return; + database.noteAppKnown(packageName, true); + answered = true; + new Thread(new Runnable() { + @Override + public void run() { + PushRegisterService.registerAndReply(AskPushPermission.this, intent, packageName); + } + }).start(); + finish(); + } + }); + findViewById(R.id.permission_deny_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (answered) return; + database.noteAppKnown(packageName, false); + answered = true; + PushRegisterService.replyNotAvailable(AskPushPermission.this, intent, packageName); + finish(); + } + }); + } catch (PackageManager.NameNotFoundException e) { + finish(); + } + } + + @Override + protected void onStop() { + super.onStop(); + if (!answered) { + PushRegisterService.replyNotAvailable(AskPushPermission.this, intent, packageName); + answered = true; + } + } +} diff --git a/play-services-core/src/main/java/org/microg/gms/ui/Conditions.java b/play-services-core/src/main/java/org/microg/gms/ui/Conditions.java new file mode 100644 index 00000000..c49fcddc --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/ui/Conditions.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2013-2016 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.ui; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.PowerManager; +import android.provider.Settings; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.view.View; + +import com.google.android.gms.R; + +import org.microg.gms.gcm.GcmPrefs; +import org.microg.tools.ui.Condition; + +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.GET_ACCOUNTS; +import static android.Manifest.permission.READ_PHONE_STATE; +import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Build.VERSION.SDK_INT; + +public class Conditions { + public static final Condition GCM_BATTERY_OPTIMIZATIONS = new Condition.Builder() + .title(R.string.cond_gcm_bat_title) + .summary(R.string.cond_gcm_bat_summary) + .evaluation(new Condition.Evaluation() { + @Override + public boolean isActive(Context context) { + if (SDK_INT < 23) return false; + if (!GcmPrefs.get(context).isGcmEnabled()) return false; + PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + return !pm.isIgnoringBatteryOptimizations(context.getPackageName()); + } + }) + .firstAction(R.string.cond_gcm_bat_action, new View.OnClickListener() { + @Override + public void onClick(View v) { + if (SDK_INT < 23) return; + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + intent.setData(Uri.parse("package:" + v.getContext().getPackageName())); + v.getContext().startActivity(intent); + } + }).build(); + + private static final String[] REQUIRED_PERMISSIONS = new String[]{ACCESS_COARSE_LOCATION, WRITE_EXTERNAL_STORAGE, GET_ACCOUNTS, READ_PHONE_STATE}; + public static final Condition PERMISSIONS = new Condition.Builder() + .title(R.string.cond_perm_title) + .summary(R.string.cond_perm_summary) + .evaluation(new Condition.Evaluation() { + @Override + public boolean isActive(Context context) { + for (String permission : REQUIRED_PERMISSIONS) { + if (ContextCompat.checkSelfPermission(context, permission) != PERMISSION_GRANTED) + return true; + } + return false; + } + }) + .firstAction(R.string.cond_perm_action, new View.OnClickListener() { + @Override + public void onClick(View v) { + if (v.getContext() instanceof Activity) { + ActivityCompat.requestPermissions((Activity) v.getContext(), REQUIRED_PERMISSIONS, 0); + } + } + }).build(); +} diff --git a/play-services-core/src/main/java/org/microg/gms/ui/GcmAppFragment.java b/play-services-core/src/main/java/org/microg/gms/ui/GcmAppFragment.java new file mode 100644 index 00000000..2b413823 --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/ui/GcmAppFragment.java @@ -0,0 +1,202 @@ +package org.microg.gms.ui; + +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; +import android.support.v4.app.Fragment; +import android.support.v7.app.AlertDialog; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.text.format.DateUtils; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.google.android.gms.R; + +import org.microg.gms.gcm.GcmDatabase; +import org.microg.gms.gcm.PushRegisterService; +import org.microg.tools.ui.AbstractSettingsActivity; +import org.microg.tools.ui.ResourceSettingsFragment; + +import java.util.List; + +import static android.text.format.DateUtils.FORMAT_SHOW_TIME; +import static android.text.format.DateUtils.MINUTE_IN_MILLIS; +import static android.text.format.DateUtils.WEEK_IN_MILLIS; + +public class GcmAppFragment extends ResourceSettingsFragment { + public static final String EXTRA_PACKAGE_NAME = "package_name"; + + public static final String PREF_WAKE_FOR_DELIVERY = "gcm_app_wake_for_delivery"; + public static final String PREF_ALLOW_REGISTER = "gcm_app_allow_register"; + public static final String PREF_REGISTER_DETAILS = "gcm_app_register_details"; + public static final String PREF_MESSAGE_DETAILS = "gcm_app_message_details"; + + protected String packageName; + private String appName; + private GcmDatabase database; + + public GcmAppFragment() { + preferencesResource = R.xml.preferences_gcm_app_detail; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + packageName = getArguments().getString(EXTRA_PACKAGE_NAME); + + AbstractSettingsActivity activity = (AbstractSettingsActivity) getActivity(); + + if (packageName != null && activity != null) { + activity.setCustomBarLayout(R.layout.app_bar); + try { + PackageManager pm = activity.getPackageManager(); + ApplicationInfo info = pm.getApplicationInfo(packageName, 0); + ((ImageView) activity.findViewById(R.id.app_icon)).setImageDrawable(pm.getApplicationIcon(info)); + appName = pm.getApplicationLabel(info).toString(); + ((TextView) activity.findViewById(R.id.app_name)).setText(appName); + View view = activity.findViewById(R.id.app_bar); + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + Uri uri = Uri.fromParts("package", packageName, null); + intent.setData(uri); + getContext().startActivity(intent); + } + }); + view.setClickable(true); + } catch (Exception e) { + ((TextView) activity.findViewById(R.id.app_name)).setText(packageName); + } + } + + database = new GcmDatabase(getContext()); + updateAppDetails(); + } + + @Override + public void onResume() { + super.onResume(); + if (database != null) { + updateAppDetails(); + } + } + + private void updateAppDetails() { + GcmDatabase.App app = database.getApp(packageName); + PreferenceScreen root = getPreferenceScreen(); + + SwitchPreference wakeForDelivery = (SwitchPreference) root.findPreference(PREF_WAKE_FOR_DELIVERY); + wakeForDelivery.setChecked(app.wakeForDelivery); + wakeForDelivery.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (newValue instanceof Boolean) { + database.setAppWakeForDelivery(packageName, (Boolean) newValue); + return true; + } + return false; + } + }); + + SwitchPreference allowRegister = (SwitchPreference) root.findPreference(PREF_ALLOW_REGISTER); + allowRegister.setChecked(app.allowRegister); + allowRegister.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (newValue instanceof Boolean) { + database.setAppAllowRegister(packageName, (Boolean) newValue); + return true; + } + return false; + } + }); + + Preference registerDetails = root.findPreference(PREF_REGISTER_DETAILS); + final List registrations = database.getRegistrationsByApp(packageName); + if (registrations.isEmpty()) { + registerDetails.setTitle(""); + registerDetails.setSelectable(false); + registerDetails.setSummary(R.string.gcm_not_registered); + } else { + StringBuilder sb = new StringBuilder(); + for (GcmDatabase.Registration registration : registrations) { + if (sb.length() != 0) sb.append("\n"); + if (registration.timestamp == 0) { + sb.append(getString(R.string.gcm_registered)); + } else { + sb.append(getString(R.string.gcm_registered_since, DateUtils.getRelativeDateTimeString(getContext(), registration.timestamp, MINUTE_IN_MILLIS, WEEK_IN_MILLIS, FORMAT_SHOW_TIME))); + } + } + registerDetails.setTitle(R.string.gcm_unregister_app); + registerDetails.setSummary(sb.toString()); + registerDetails.setSelectable(true); + registerDetails.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + new AlertDialog.Builder(getContext()) + .setTitle(String.format(getString(R.string.gcm_unregister_confirm_title), appName)) + .setMessage(R.string.gcm_unregister_confirm_message) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + new Thread(new Runnable() { + @Override + public void run() { + for (GcmDatabase.Registration registration : registrations) { + PushRegisterService.unregister(getContext(), registration.packageName, registration.signature, null, null); + } + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + updateAppDetails(); + } + }); + } + }).start(); + } + }) + .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing + } + }).create().show(); + return true; + } + }); + } + + Preference messageDetails = root.findPreference(PREF_MESSAGE_DETAILS); + if (app.totalMessageCount == 0) { + messageDetails.setSummary(R.string.gcm_no_message_yet); + } else { + String s = getString(R.string.gcm_messages_counter, app.totalMessageCount, app.totalMessageBytes); + if (app.lastMessageTimestamp != 0) { + s += "\n" + getString(R.string.gcm_last_message_at, DateUtils.getRelativeDateTimeString(getContext(), app.lastMessageTimestamp, MINUTE_IN_MILLIS, WEEK_IN_MILLIS, FORMAT_SHOW_TIME)); + } + messageDetails.setSummary(s); + } + } + + public static class AsActivity extends AbstractSettingsActivity { + public AsActivity() { + showHomeAsUp = true; + } + + @Override + protected Fragment getFragment() { + GcmAppFragment fragment = new GcmAppFragment(); + fragment.setArguments(getIntent().getExtras()); + return fragment; + } + } +} diff --git a/play-services-core/src/main/java/org/microg/gms/ui/GcmFragment.java b/play-services-core/src/main/java/org/microg/gms/ui/GcmFragment.java new file mode 100644 index 00000000..5f032396 --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/ui/GcmFragment.java @@ -0,0 +1,262 @@ +/* + * Copyright 2013-2015 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.ui; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceGroup; +import android.support.v7.preference.PreferenceScreen; +import android.support.v7.preference.PreferenceViewHolder; +import android.support.v7.widget.SwitchCompat; +import android.text.format.DateUtils; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import com.google.android.gms.R; + +import org.microg.gms.gcm.GcmDatabase; +import org.microg.gms.gcm.GcmPrefs; +import org.microg.gms.gcm.McsService; +import org.microg.tools.ui.AbstractSettingsActivity; +import org.microg.tools.ui.DimmableIconPreference; +import org.microg.tools.ui.ResourceSettingsFragment; +import org.microg.tools.ui.SwitchBar; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import static android.text.format.DateUtils.FORMAT_SHOW_TIME; +import static android.text.format.DateUtils.MINUTE_IN_MILLIS; +import static android.text.format.DateUtils.WEEK_IN_MILLIS; + +public class GcmFragment extends ResourceSettingsFragment implements SwitchBar.OnSwitchChangeListener { + + public static final String PREF_GCM_STATUS = "pref_gcm_status"; + public static final String PREF_GCM_APPS = "gcm_apps"; + + private GcmDatabase database; + + private SwitchBar switchBar; + private SwitchCompat switchCompat; + private boolean listenerSetup = false; + + private final int MENU_ADVANCED = Menu.FIRST; + + public GcmFragment() { + preferencesResource = R.xml.preferences_gcm; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + AbstractSettingsActivity activity = (AbstractSettingsActivity) getActivity(); + + setHasOptionsMenu(true); + switchBar = activity.getSwitchBar(); + switchBar.show(); + switchCompat = switchBar.getSwitch(); + switchBar.setChecked(GcmPrefs.get(getContext()).isGcmEnabled()); + } + + @Override + public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) { + super.onCreatePreferencesFix(savedInstanceState, rootKey); + + database = new GcmDatabase(getContext()); + + updateContent(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + switchBar.hide(); + } + + @Override + public void onResume() { + super.onResume(); + if (!listenerSetup) { + switchBar.addOnSwitchChangeListener(this); + listenerSetup = true; + } + updateContent(); + } + + @Override + public void onPause() { + if (listenerSetup) { + switchBar.removeOnSwitchChangeListener(this); + listenerSetup = false; + } + super.onPause(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, MENU_ADVANCED, 0, R.string.menu_advanced); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_ADVANCED: + Intent intent = new Intent(getContext(), AdvancedAsActivity.class); + startActivity(intent); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onSwitchChanged(SwitchCompat switchView, boolean isChecked) { + if (switchView == switchCompat) { + getPreferenceManager().getSharedPreferences().edit().putBoolean(GcmPrefs.PREF_ENABLE_GCM, isChecked).apply(); + } + } + + private static void addPreferencesSorted(List prefs, PreferenceGroup container) { + // If there's some items to display, sort the items and add them to the container. + Collections.sort(prefs, new Comparator() { + @Override + public int compare(Preference lhs, Preference rhs) { + return lhs.getTitle().toString().compareTo(rhs.getTitle().toString()); + } + }); + for (Preference entry : prefs) { + container.addPreference(entry); + } + } + + private void updateContent() { + PreferenceScreen root = getPreferenceScreen(); + + if (McsService.isConnected()) { + root.findPreference(PREF_GCM_STATUS).setSummary(getString(R.string.gcm_state_connected, DateUtils.getRelativeTimeSpanString(McsService.getStartTimestamp(), System.currentTimeMillis(), 0))); + } else { + root.findPreference(PREF_GCM_STATUS).setSummary(getString(R.string.gcm_state_disconnected)); + } + + PreferenceCategory appList = (PreferenceCategory) root.findPreference(PREF_GCM_APPS); + appList.removeAll(); + List list = database.getAppList(); + if (!list.isEmpty()) { + List appListPrefs = new ArrayList<>(); + PackageManager pm = getContext().getPackageManager(); + for (GcmDatabase.App app : list) { + try { + pm.getApplicationInfo(app.packageName, 0); + appListPrefs.add(new GcmAppPreference(getPreferenceManager().getContext(), app)); + } catch (PackageManager.NameNotFoundException e) { + final List registrations = database.getRegistrationsByApp(app.packageName); + if (registrations.isEmpty()) { + database.removeApp(app.packageName); + } else { + appListPrefs.add(new GcmAppPreference(getPreferenceManager().getContext(), app)); + } + } + } + addPreferencesSorted(appListPrefs, appList); + } else { + // If there's no item to display, add a "None" item. + Preference banner = new Preference(getPreferenceManager().getContext()); + banner.setLayoutResource(R.layout.list_no_item); + banner.setTitle(R.string.list_no_item_none); + banner.setSelectable(false); + appList.addPreference(banner); + } + } + + public static class GcmAppPreference extends DimmableIconPreference implements Preference.OnPreferenceClickListener { + + private GcmDatabase database; + private GcmDatabase.App app; + + public GcmAppPreference(Context context, GcmDatabase.App app) { + super(context); + this.app = app; + this.database = new GcmDatabase(context); + setKey(app.packageName); + + PackageManager packageManager = context.getPackageManager(); + try { + ApplicationInfo applicationInfo = packageManager.getApplicationInfo(app.packageName, 0); + setTitle(packageManager.getApplicationLabel(applicationInfo)); + setIcon(packageManager.getApplicationIcon(applicationInfo)); + } catch (PackageManager.NameNotFoundException e) { + setTitle(app.packageName); + } + setOnPreferenceClickListener(this); + updateViewDetails(); + } + + private void updateViewDetails() { + if (database.getRegistrationsByApp(app.packageName).isEmpty()) { + setSummary(R.string.gcm_not_registered); + } else if (app.lastMessageTimestamp > 0) { + setSummary(getContext().getString(R.string.gcm_last_message_at, DateUtils.getRelativeDateTimeString(getContext(), app.lastMessageTimestamp, MINUTE_IN_MILLIS, WEEK_IN_MILLIS, FORMAT_SHOW_TIME))); + } else { + setSummary(R.string.gcm_no_message_yet); + } + } + + @Override + public void onBindViewHolder(PreferenceViewHolder view) { + updateViewDetails(); + super.onBindViewHolder(view); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(getContext(), GcmAppFragment.AsActivity.class); + intent.putExtra(GcmAppFragment.EXTRA_PACKAGE_NAME, app.packageName); + getContext().startActivity(intent); + return true; + } + } + + public static class AsActivity extends AbstractSettingsActivity { + public AsActivity() { + showHomeAsUp = true; + } + + @Override + protected Fragment getFragment() { + return new GcmFragment(); + } + } + + public static class AdvancedAsActivity extends AbstractSettingsActivity { + public AdvancedAsActivity() { + showHomeAsUp = true; + preferencesResource = R.xml.preferences_gcm_advanced; + } + } +} \ No newline at end of file diff --git a/play-services-core/src/main/java/org/microg/gms/ui/GcmRegisteredAppsFragment.java b/play-services-core/src/main/java/org/microg/gms/ui/GcmRegisteredAppsFragment.java deleted file mode 100644 index 39f2be96..00000000 --- a/play-services-core/src/main/java/org/microg/gms/ui/GcmRegisteredAppsFragment.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2013-2015 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.ui; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; -import android.view.ContextMenu; -import android.view.LayoutInflater; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; - -import com.google.android.gms.R; - -import org.microg.gms.gcm.GcmData; -import org.microg.gms.gcm.PushRegisterService; - -import java.util.ArrayList; - -public class GcmRegisteredAppsFragment extends Fragment { - - private AppsAdapter appsAdapter = null; - private GcmData gcmStorage = null; - - @Override - @NonNull - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - gcmStorage = new GcmData(getContext()); - - View view = inflater.inflate(R.layout.gcm_apps_list, container, false); - ListView listView = (ListView) view.findViewById(R.id.list_view); - registerForContextMenu(listView); - updateListView(); - listView.setAdapter(appsAdapter); - return listView; - } - - @Override - public void onResume() { - super.onResume(); - updateListView(); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - GcmData.AppInfo appInfo = appsAdapter.getItem(((AdapterView.AdapterContextMenuInfo) menuInfo).position); - MenuInflater menuInflater = getActivity().getMenuInflater(); - menuInflater.inflate(R.menu.gcm_app, menu); - - PackageManager packageManager = getContext().getPackageManager(); - try { - ApplicationInfo applicationInfo = packageManager.getApplicationInfo(appInfo.app, 0); - menu.setHeaderTitle(packageManager.getApplicationLabel(applicationInfo)); - } catch (PackageManager.NameNotFoundException e) { - menu.setHeaderTitle(appInfo.app); - } - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); - final GcmData.AppInfo appInfo = appsAdapter.getItem(info.position); - - new AsyncTask() { - - @Override - protected Void doInBackground(Void... params) { - PushRegisterService.unregister(getContext(), appInfo.app, appInfo.appSignature, null, null); - return null; - } - - protected void onPostExecute(Void result) { - updateListView(); - } - }.execute(); - return true; - } - - public synchronized void updateListView() { - ArrayList registeredApps = new ArrayList(); - for (GcmData.AppInfo appInfo : gcmStorage.getAppsInfo()) { - if (appInfo.isRegistered()) { - registeredApps.add(appInfo); - } - } - if (appsAdapter != null) { - appsAdapter.update(registeredApps.toArray(new GcmData.AppInfo[registeredApps.size()])); - } else { - appsAdapter = new AppsAdapter(getContext(), registeredApps.toArray(new GcmData.AppInfo[registeredApps.size()])); - } - } - - private class AppsAdapter extends ArrayAdapter { - - public AppsAdapter(Context context, GcmData.AppInfo[] libraries) { - super(context, R.layout.gcm_app, R.id.title); - addAll(libraries); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View v = super.getView(position, convertView, parent); - ImageView image = (ImageView) v.findViewById(R.id.image); - TextView title = (TextView) v.findViewById(R.id.title); - TextView sub = (TextView) v.findViewById(R.id.sub); - TextView warning = (TextView) v.findViewById(R.id.warning); - - GcmData.AppInfo appInfo = getItem(position); - PackageManager packageManager = getContext().getPackageManager(); - try { - ApplicationInfo applicationInfo = packageManager.getApplicationInfo(appInfo.app, 0); - title.setText(packageManager.getApplicationLabel(applicationInfo).toString()); - image.setImageDrawable(packageManager.getApplicationIcon(applicationInfo)); - if (appInfo.hasUnregistrationError()) { - warning.setVisibility(View.VISIBLE); - warning.setText(getString(R.string.gcm_app_error_unregistering)); - } else { - warning.setVisibility(View.GONE); - } - } catch (PackageManager.NameNotFoundException e) { - title.setText(appInfo.app); - image.setImageDrawable(null); - warning.setVisibility(View.VISIBLE); - warning.setText(getString(R.string.gcm_app_not_installed_anymore)); - } - sub.setText(getString(R.string.gcm_messages_received_no, gcmStorage.getAppMessageCount(appInfo.app))); - - return v; - } - - public void update(GcmData.AppInfo[] appInfos) { - setNotifyOnChange(false); - clear(); - addAll(appInfos); - notifyDataSetChanged(); - } - } -} \ No newline at end of file diff --git a/play-services-core/src/main/java/org/microg/gms/ui/LocationSettingsActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/LocationSettingsActivity.java new file mode 100644 index 00000000..ab3bf963 --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/ui/LocationSettingsActivity.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2013-2016 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.ui; + +import android.app.Activity; + +public class LocationSettingsActivity extends Activity { +} diff --git a/play-services-core/src/main/java/org/microg/gms/ui/PlacePickerActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/PlacePickerActivity.java index 5fc75b59..9fd15cfe 100644 --- a/play-services-core/src/main/java/org/microg/gms/ui/PlacePickerActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/PlacePickerActivity.java @@ -25,6 +25,7 @@ import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; +import android.support.v4.view.MenuItemCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.SearchView; import android.support.v7.widget.Toolbar; @@ -153,7 +154,7 @@ public class PlacePickerActivity extends AppCompatActivity implements Map.Update super.onCreateOptionsMenu(menu); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.pick_place, menu); - SearchView searchView = (SearchView) menu.findItem(R.id.menu_action_search).getActionView(); + SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.menu_action_search)); // TODO: search return true; } diff --git a/play-services-core/src/main/java/org/microg/gms/ui/SelfCheckFragment.java b/play-services-core/src/main/java/org/microg/gms/ui/SelfCheckFragment.java new file mode 100644 index 00000000..36b8915f --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/ui/SelfCheckFragment.java @@ -0,0 +1,83 @@ +/* + * Copyright 2013-2016 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.ui; + +import android.content.Intent; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; + +import org.microg.tools.selfcheck.InstalledPackagesChecks; +import org.microg.tools.selfcheck.NlpOsCompatChecks; +import org.microg.tools.selfcheck.NlpStatusChecks; +import org.microg.tools.selfcheck.PermissionCheckGroup; +import org.microg.tools.selfcheck.RomSpoofSignatureChecks; +import org.microg.tools.selfcheck.SelfCheckGroup; +import org.microg.tools.selfcheck.SystemChecks; +import org.microg.tools.ui.AbstractSelfCheckFragment; +import org.microg.tools.ui.AbstractSettingsActivity; + +import java.util.List; + +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.GET_ACCOUNTS; +import static android.Manifest.permission.READ_PHONE_STATE; +import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; +import static android.os.Build.VERSION.SDK_INT; +import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; + +public class SelfCheckFragment extends AbstractSelfCheckFragment { + + @Override + protected void prepareSelfCheckList(List checks) { + checks.add(new RomSpoofSignatureChecks()); + checks.add(new InstalledPackagesChecks()); + if (SDK_INT > LOLLIPOP_MR1) { + checks.add(new PermissionCheckGroup(ACCESS_COARSE_LOCATION, WRITE_EXTERNAL_STORAGE, GET_ACCOUNTS, READ_PHONE_STATE)); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + checks.add(new SystemChecks()); + } + checks.add(new NlpOsCompatChecks()); + checks.add(new NlpStatusChecks()); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + reset(LayoutInflater.from(getContext())); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == SystemChecks.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) + reset(LayoutInflater.from(getContext())); + else + super.onActivityResult(requestCode, resultCode, data); + } + + public static class AsActivity extends AbstractSettingsActivity { + public AsActivity() { + showHomeAsUp = true; + } + + @Override + protected Fragment getFragment() { + return new SelfCheckFragment(); + } + } +} diff --git a/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java index b9390bfe..f9d6007f 100644 --- a/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java @@ -16,137 +16,51 @@ package org.microg.gms.ui; -import android.content.Intent; -import android.os.Build; import android.os.Bundle; -import android.preference.Preference; -import android.support.annotation.NonNull; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.preference.PreferenceFragment; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.LayoutInflater; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v7.preference.PreferenceManager; import com.google.android.gms.R; -import org.microg.tools.selfcheck.InstalledPackagesChecks; -import org.microg.tools.selfcheck.NlpOsCompatChecks; -import org.microg.tools.selfcheck.NlpStatusChecks; -import org.microg.tools.selfcheck.PermissionCheckGroup; -import org.microg.tools.selfcheck.RomSpoofSignatureChecks; -import org.microg.tools.selfcheck.SelfCheckGroup; -import org.microg.tools.selfcheck.SystemChecks; -import org.microg.tools.ui.AbstractAboutFragment; -import org.microg.tools.ui.AbstractSelfCheckFragment; +import org.microg.gms.gcm.GcmDatabase; +import org.microg.gms.gcm.GcmPrefs; +import org.microg.tools.ui.AbstractDashboardActivity; +import org.microg.tools.ui.ResourceSettingsFragment; -import java.util.List; +public class SettingsActivity extends AbstractDashboardActivity { -import static android.Manifest.permission.ACCESS_COARSE_LOCATION; -import static android.Manifest.permission.GET_ACCOUNTS; -import static android.Manifest.permission.READ_PHONE_STATE; -import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; -import static android.os.Build.VERSION.SDK_INT; -import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; + public SettingsActivity() { + preferencesResource = R.xml.preferences_start; + addCondition(Conditions.GCM_BATTERY_OPTIMIZATIONS); + addCondition(Conditions.PERMISSIONS); + } -public class SettingsActivity extends AppCompatActivity { @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.settings_activity); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - getSupportFragmentManager().beginTransaction() - .replace(R.id.content_wrapper, new MyPreferenceFragment()) - .commit(); + protected Fragment getFragment() { + return new FragmentImpl(); } - public static class MyPreferenceFragment extends PreferenceFragment { - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + public static class FragmentImpl extends ResourceSettingsFragment { - addPreferencesFromResource(R.xml.gms_preferences); + public static final String PREF_ABOUT = "pref_about"; + public static final String PREF_GCM = "pref_gcm"; - findPreference(getString(R.string.self_check_title)) - .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - getFragmentManager().beginTransaction() - .addToBackStack("root") - .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) - .replace(R.id.content_wrapper, new MySelfCheckFragment()) - .commit(); - return true; - } - }); - findPreference(getString(R.string.pref_about_title)) - .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - getFragmentManager().beginTransaction() - .addToBackStack("root") - .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) - .replace(R.id.content_wrapper, new MyAboutFragment()) - .commit(); - return true; - } - }); - findPreference(getString(R.string.pref_gcm_apps)) - .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - getFragmentManager().beginTransaction() - .addToBackStack("root") - .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) - .replace(R.id.content_wrapper, new GcmRegisteredAppsFragment()) - .commit(); - return true; - } - }); + public FragmentImpl() { + preferencesResource = R.xml.preferences_start; } - } - - public static class MySelfCheckFragment extends AbstractSelfCheckFragment { @Override - protected void prepareSelfCheckList(List checks) { - checks.add(new RomSpoofSignatureChecks()); - checks.add(new InstalledPackagesChecks()); - if (SDK_INT > LOLLIPOP_MR1) { - checks.add(new PermissionCheckGroup(ACCESS_COARSE_LOCATION, WRITE_EXTERNAL_STORAGE, GET_ACCOUNTS, READ_PHONE_STATE)); + public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) { + super.onCreatePreferencesFix(savedInstanceState, rootKey); + PreferenceManager prefs = getPreferenceManager(); + prefs.findPreference(PREF_ABOUT).setSummary(getString(R.string.about_version_str, AboutFragment.getSelfVersion(getContext()))); + if (GcmPrefs.get(getContext()).isGcmEnabled()) { + int regCount = new GcmDatabase(getContext()).getRegistrationList().size(); + prefs.findPreference(PREF_GCM).setSummary(getString(R.string.v7_preference_on) + " / " + getContext().getString(R.string.gcm_registered_apps_counter, regCount)); + } else { + prefs.findPreference(PREF_GCM).setSummary(R.string.v7_preference_off); } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - checks.add(new SystemChecks()); - } - checks.add(new NlpOsCompatChecks()); - checks.add(new NlpStatusChecks()); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - reset(LayoutInflater.from(getContext())); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == SystemChecks.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) - reset(LayoutInflater.from(getContext())); - else - super.onActivityResult(requestCode, resultCode, data); - } - } - - public static class MyAboutFragment extends AbstractAboutFragment { - - @Override - protected void collectLibraries(List libraries) { - libraries.add(new Library("org.microg.gms.api", "microG GmsApi", "Apache License 2.0 by microG Team")); - libraries.add(new Library("org.microg.safeparcel", "microG SafeParcel", "Apache License 2.0 by microG Team")); - libraries.add(new Library("org.microg.nlp", "microG UnifiedNlp", "Apache License 2.0 by microG Team")); - libraries.add(new Library("org.microg.nlp.api", "microG UnifiedNlp Api", "Apache License 2.0, by microG Team")); - libraries.add(new Library("org.microg.wearable", "microG Wearable", "Apache License 2.0 by microG Team")); - libraries.add(new Library("de.hdodenhof.circleimageview", "CircleImageView", "Apache License 2.0 by Henning Dodenhof")); - libraries.add(new Library("org.oscim.android", ">map", "GNU LGPLv3 by Hannes Janetzek")); - libraries.add(new Library("com.squareup.wire", "Wire Protocol Buffers", "Apache License 2.0 by Square Inc.")); } } } diff --git a/play-services-core/src/main/java/org/microg/gms/wearable/WearableImpl.java b/play-services-core/src/main/java/org/microg/gms/wearable/WearableImpl.java index 64d110cf..f6505d10 100644 --- a/play-services-core/src/main/java/org/microg/gms/wearable/WearableImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/wearable/WearableImpl.java @@ -16,6 +16,7 @@ package org.microg.gms.wearable; +import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.database.Cursor; @@ -67,6 +68,8 @@ import java.util.Set; import okio.ByteString; +import static android.os.Build.VERSION.SDK_INT; + public class WearableImpl { private static final String TAG = "GmsWear"; @@ -456,16 +459,7 @@ public class WearableImpl { return null; } Cursor dataHolderItems = nodeDatabase.getDataItemsForDataHolderByHostAndPath(packageName, firstSignature, fixHost(uri.getHost(), false), uri.getPath()); - int j = 0; - while (dataHolderItems.moveToNext()) { - for (int i = 0; i < dataHolderItems.getColumnCount(); i++) { - if (dataHolderItems.getType(i) == Cursor.FIELD_TYPE_STRING) { - Log.d(TAG, "getDataItems[" + j + "]: " + dataHolderItems.getColumnName(i) + "=" + dataHolderItems.getString(i)); - } - if (dataHolderItems.getType(i) == Cursor.FIELD_TYPE_INTEGER) - Log.d(TAG, "getDataItems[" + j + "]: " + dataHolderItems.getColumnName(i) + "=" + dataHolderItems.getLong(i)); - } - } + maybeDebugCursor("getDataItems",dataHolderItems); dataHolderItems.moveToFirst(); dataHolderItems.moveToPrevious(); DataHolder dataHolder = new DataHolder(dataHolderItems, 0, null); @@ -473,6 +467,22 @@ public class WearableImpl { return dataHolder; } + @TargetApi(11) + private void maybeDebugCursor(String what, Cursor cursor) { + if (SDK_INT >= 11) { + int j = 0; + while (cursor.moveToNext()) { + for (int i = 0; i < cursor.getColumnCount(); i++) { + if (cursor.getType(i) == Cursor.FIELD_TYPE_STRING) { + Log.d(TAG, what+"[" + j + "]: " + cursor.getColumnName(i) + "=" + cursor.getString(i)); + } + if (cursor.getType(i) == Cursor.FIELD_TYPE_INTEGER) + Log.d(TAG, what+"[" + j + "]: " + cursor.getColumnName(i) + "=" + cursor.getLong(i)); + } + } + } + } + public synchronized void addListener(String packageName, IWearableListener listener) { if (!listeners.containsKey(packageName)) { listeners.put(packageName, new ArrayList()); diff --git a/play-services-core/src/main/res/drawable/device_login.xml b/play-services-core/src/main/res/drawable/device_login.xml new file mode 100644 index 00000000..f1a4e1e4 --- /dev/null +++ b/play-services-core/src/main/res/drawable/device_login.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/drawable/gcm_bell.xml b/play-services-core/src/main/res/drawable/gcm_bell.xml new file mode 100644 index 00000000..89a1cae4 --- /dev/null +++ b/play-services-core/src/main/res/drawable/gcm_bell.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/layout/ask_gcm.xml b/play-services-core/src/main/res/layout/ask_gcm.xml new file mode 100644 index 00000000..9d3e8be8 --- /dev/null +++ b/play-services-core/src/main/res/layout/ask_gcm.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-core/src/main/res/layout/ask_permission.xml b/play-services-core/src/main/res/layout/ask_permission.xml index bbe19dae..a9cda623 100644 --- a/play-services-core/src/main/res/layout/ask_permission.xml +++ b/play-services-core/src/main/res/layout/ask_permission.xml @@ -118,14 +118,14 @@