diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml deleted file mode 100644 index 1913b6f2..00000000 --- a/.github/workflows/debug.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Debug APK Builder - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: JDK 8 Setup - uses: actions/setup-java@v1 - with: - java-version: 11.0.8 - - - name: Install NDK - run: echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;21.0.6113669" - - - name: Grant rights - run: chmod +x ./gradlew - - - name: Build project - run: ./gradlew build - - - name: Build APK - run: ./gradlew assembleDebug - - - name: Upload to Artifacts - uses: actions/upload-artifact@v2 - with: - name: 'MicroG' - path: play-services-core/build/outputs/apk/debug/play-services-core-debug.apk \ No newline at end of file diff --git a/README.md b/README.md index a9b81f9a..29f47891 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,14 @@ # Vanced+ MicroG -This fork tweaks MicroG to be usable by applications that require Google authentication such as [YouTube Vanced+](https://gitlab.com/cuynu/ytvancedx) or [ReVanced](https://github.com/revanced), [ReVanced Extended](https://github.com/inotia00) / [ReX](https://github.com/YT-Advanced). - -## Donate this project (Not required) -

- Badage +This fork tweaks MicroG to be usable by applications that require Google authentication such as [YouTube Vanced+](https://gitlab.com/cuynu/vancedx). ## Warning! -Vanced+ microG probably not work in ReVanced/RVX in future as recent changes from ReVanced/RVX to use with they specific MicroG called ReVanced MicroG with different package name ! +Vanced+ microG are currently **DOES NOT WORK** with ReVanced/RVX as they now uses their own ReVanced microG with specific package name! However, there are workaround : [Issues #15](https://gitlab.com/cuynu/VancedxMicroG/-/issues/15#note_1837372638) ## Notable changes - No longer a system app -- Package name changed from `com.google.android.gms` to `com.mgoogle.android.gms` to support installation alongside the official MicroG +- Package name changed from `com.google.android.gms` to `com.mgoogle.android.gms` to support installation alongside the official MicroG or device with Google Play Services pre-installed - Removed unnecessary features: - Ads - Analytics diff --git a/build.gradle b/build.gradle index 9de5384a..bcf0bcbe 100644 --- a/build.gradle +++ b/build.gradle @@ -4,26 +4,31 @@ */ buildscript { - ext.kotlinVersion = '1.8.20' + ext.applicationNamespace = "com.google.android.gms" + ext.basePackageName = "com.mgoogle" + + ext.safeParcelVersion = '1.7.1' + + ext.kotlinVersion = '1.7.21' ext.coroutineVersion = '1.6.4' - ext.annotationVersion = '1.6.0' + ext.annotationVersion = '1.5.0' ext.appcompatVersion = '1.6.1' ext.coreVersion = '1.9.0' ext.collectionVersion = '1.3.0-dev01' ext.fragmentVersion = '1.5.5' ext.lifecycleVersion = '2.5.1' - ext.materialVersion = '1.8.0' + ext.materialVersion = '1.7.0' ext.navigationVersion = '2.5.3' ext.preferenceVersion = '1.2.0' - ext.webkitVersion = '1.6.0' + ext.webkitVersion = '1.5.0' ext.circleimageviewVersion = '3.1.0' ext.volleyVersion = '1.2.1' - ext.wireVersion = '4.5.5' + ext.wireVersion = '4.8.0' - ext.androidBuildGradleVersion = '8.0.0' + ext.androidBuildGradleVersion = '8.0.2' ext.androidBuildVersionTools = '33.0.2' ext.androidMinSdk = 23 @@ -36,20 +41,19 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:8.0.2' + classpath "com.android.tools.build:gradle:$androidBuildGradleVersion" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "com.squareup.wire:wire-gradle-plugin:$wireVersion" } } -def gmsVersion = "23.42.54" +def gmsVersion = "25.09.34" def gmsVersionCode = Integer.parseInt(gmsVersion.replaceAll('\\.', '')) -def ourVersionBase = "0.2.29" -def ourVersionMinor = 73 +def ourVersionBase = "0.3.0" -def ourVersionCode = gmsVersionCode * 1000 + ourVersionMinor +def ourVersionCode = gmsVersionCode * 1000 def ourVersionName = "$ourVersionBase.$gmsVersionCode" allprojects { @@ -66,4 +70,13 @@ subprojects { mavenCentral() google() } -} + afterEvaluate { + // Temporary hack for Android Studio + if (project.plugins.hasPlugin('com.android.base')) { + dependencies { + compileOnly "org.microg:safe-parcel:$safeParcelVersion" + testCompileOnly "org.microg:safe-parcel:$safeParcelVersion" + } + } + } +} \ No newline at end of file diff --git a/fake-signature/build.gradle b/fake-signature/build.gradle new file mode 100644 index 00000000..e04d9893 --- /dev/null +++ b/fake-signature/build.gradle @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' + +android { + namespace "org.microg.signature.fake" + + compileSdkVersion androidCompileSdk + buildToolsVersion androidBuildVersionTools + + defaultConfig { + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } +} + +dependencies { +} diff --git a/fake-signature/src/main/AndroidManifest.xml b/fake-signature/src/main/AndroidManifest.xml new file mode 100644 index 00000000..1b3ae15d --- /dev/null +++ b/fake-signature/src/main/AndroidManifest.xml @@ -0,0 +1,1159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fake-signature/src/main/res/values/arrays.xml b/fake-signature/src/main/res/values/arrays.xml new file mode 100644 index 00000000..315cce58 --- /dev/null +++ b/fake-signature/src/main/res/values/arrays.xml @@ -0,0 +1,15 @@ + + + + + com.google.android.gms + com.android.vending + com.spotify.music + + + com.truecaller + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values/signature.xml b/fake-signature/src/main/res/values/signature.xml old mode 100755 new mode 100644 similarity index 56% rename from play-services-core/src/main/res/values/signature.xml rename to fake-signature/src/main/res/values/signature.xml index df53f65c..282e0dc1 --- a/play-services-core/src/main/res/values/signature.xml +++ b/fake-signature/src/main/res/values/signature.xml @@ -1,21 +1,14 @@ - - + 308204433082032ba003020102020900c2e08746644a308d300d06092a864886f70d01010405003074310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e205669657731143012060355040a130b476f6f676c6520496e632e3110300e060355040b1307416e64726f69643110300e06035504031307416e64726f6964301e170d3038303832313233313333345a170d3336303130373233313333345a3074310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e205669657731143012060355040a130b476f6f676c6520496e632e3110300e060355040b1307416e64726f69643110300e06035504031307416e64726f696430820120300d06092a864886f70d01010105000382010d00308201080282010100ab562e00d83ba208ae0a966f124e29da11f2ab56d08f58e2cca91303e9b754d372f640a71b1dcb130967624e4656a7776a92193db2e5bfb724a91e77188b0e6a47a43b33d9609b77183145ccdf7b2e586674c9e1565b1f4c6a5955bff251a63dabf9c55c27222252e875e4f8154a645f897168c0b1bfc612eabf785769bb34aa7984dc7e2ea2764cae8307d8c17154d7ee5f64a51a44a602c249054157dc02cd5f5c0e55fbef8519fbe327f0b1511692c5a06f19d18385f5c4dbc2d6b93f68cc2979c70e18ab93866b3bd5db8999552a0e3b4c99df58fb918bedc182ba35e003c1b4b10dd244a8ee24fffd333872ab5221985edab0fc0d0b145b6aa192858e79020103a381d93081d6301d0603551d0e04160414c77d8cc2211756259a7fd382df6be398e4d786a53081a60603551d2304819e30819b8014c77d8cc2211756259a7fd382df6be398e4d786a5a178a4763074310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e205669657731143012060355040a130b476f6f676c6520496e632e3110300e060355040b1307416e64726f69643110300e06035504031307416e64726f6964820900c2e08746644a308d300c0603551d13040530030101ff300d06092a864886f70d010104050003820101006dd252ceef85302c360aaace939bcff2cca904bb5d7a1661f8ae46b2994204d0ff4a68c7ed1a531ec4595a623ce60763b167297a7ae35712c407f208f0cb109429124d7b106219c084ca3eb3f9ad5fb871ef92269a8be28bf16d44c8d9a08e6cb2f005bb3fe2cb96447e868e731076ad45b33f6009ea19c161e62641aa99271dfd5228c5c587875ddb7f452758d661f6cc0cccb7352e424cc4365c523532f7325137593c4ae341f4db41edda0d0b1071a7c440f0fe9ea01cb627ca674369d084bd2fd911ff06cdbf2cfa10dc0f893ae35762919048c7efc64c7144178342f70581c9de573af55b390dd7fdb9418631895d5f759f30112687ff621410c069308a + + 308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37 + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 86630672..126b7d28 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ android.useAndroidX=true -org.gradle.jvmargs=-Xmx4096m -XX:+UseParallelGC +org.gradle.jvmargs=-Xmx2048m -XX:+UseParallelGC android.defaults.buildfeatures.buildconfig=true android.nonTransitiveRClass=false -android.nonFinalResIds=false \ No newline at end of file +android.nonFinalResIds=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053..d64cd491 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e736c04d..0e1ae8ce 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Thu Jun 29 16:45:22 ICT 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-rc-3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 60cf9e8c..1aa94a42 100755 --- a/gradlew +++ b/gradlew @@ -1,6 +1,5 @@ #!/bin/sh -# SPDX-License-Identifier: Apache-2.0 # # Copyright © 2015-2021 the original authors. # @@ -56,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -81,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -128,26 +125,35 @@ if [ -n "$JAVA_HOME" ] ; then fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -192,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -204,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. @@ -230,4 +246,4 @@ eval "set -- $( tr '\n' ' ' )" '"$@"' -exec "$JAVACMD" "$@" \ No newline at end of file +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 5093609d..93e3f59f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +65,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,17 +72,19 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/play-services-api/build.gradle b/play-services-api/build.gradle index f78981e9..20d8bea5 100644 --- a/play-services-api/build.gradle +++ b/play-services-api/build.gradle @@ -17,8 +17,8 @@ apply plugin: 'com.android.library' android { - compileSdk androidCompileSdk - buildToolsVersion '33.0.2' + compileSdkVersion androidCompileSdk + buildToolsVersion androidBuildVersionTools defaultConfig { minSdkVersion androidMinSdk @@ -26,13 +26,13 @@ android { } compileOptions { - sourceCompatibility = 1.8 - targetCompatibility = "1.8" + sourceCompatibility = 17 + targetCompatibility = 17 } - namespace 'org.microg.gms.api' buildFeatures { aidl true } + namespace 'org.microg.gms.api' } dependencies { diff --git a/play-services-api/src/main/AndroidManifest.xml b/play-services-api/src/main/AndroidManifest.xml new file mode 100644 index 00000000..359a9b71 --- /dev/null +++ b/play-services-api/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + diff --git a/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl b/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl index ae33a334..d6afdf80 100644 --- a/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl +++ b/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl @@ -7,7 +7,7 @@ import com.google.android.gms.auth.AccountChangeEventsResponse; import com.google.android.gms.auth.AccountChangeEventsRequest; import com.google.android.gms.auth.GetHubTokenRequest; import com.google.android.gms.auth.GetHubTokenInternalResponse; -import com.google.android.gms.auth.HasCababilitiesRequest; +import com.google.android.gms.auth.HasCapabilitiesRequest; interface IAuthManagerService { Bundle getToken(String accountName, String scope, in Bundle extras) = 0; @@ -18,6 +18,6 @@ interface IAuthManagerService { Bundle getAccounts(in Bundle extras) = 5; Bundle removeAccount(in Account account) = 6; Bundle requestGoogleAccountsAccess(String packageName) = 7; - int hasCapabilities(in HasCababilitiesRequest request) = 8; + int hasCapabilities(in HasCapabilitiesRequest request) = 8; GetHubTokenInternalResponse getHubToken(in GetHubTokenRequest request, in Bundle extras) = 9; } diff --git a/play-services-api/src/main/aidl/com/google/android/gms/auth/HasCababilitiesRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/auth/HasCapabilitiesRequest.aidl similarity index 52% rename from play-services-api/src/main/aidl/com/google/android/gms/auth/HasCababilitiesRequest.aidl rename to play-services-api/src/main/aidl/com/google/android/gms/auth/HasCapabilitiesRequest.aidl index c0c080cd..b6cb6b9d 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/auth/HasCababilitiesRequest.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/auth/HasCapabilitiesRequest.aidl @@ -1,3 +1,3 @@ package com.google.android.gms.auth; -parcelable HasCababilitiesRequest; +parcelable HasCapabilitiesRequest; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/GoogleCertificatesQuery.aidl b/play-services-api/src/main/aidl/com/google/android/gms/common/internal/GoogleCertificatesQuery.aidl deleted file mode 100644 index ae02e068..00000000 --- a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/GoogleCertificatesQuery.aidl +++ /dev/null @@ -1,3 +0,0 @@ -package com.google.android.gms.common.internal; - -parcelable GoogleCertificatesQuery; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl b/play-services-api/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl deleted file mode 100644 index da539f32..00000000 --- a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl +++ /dev/null @@ -1,12 +0,0 @@ -package com.google.android.gms.common.internal; - -import com.google.android.gms.common.internal.GoogleCertificatesQuery; -import com.google.android.gms.dynamic.IObjectWrapper; - -interface IGoogleCertificatesApi { - IObjectWrapper getGoogleCertficates(); - IObjectWrapper getGoogleReleaseCertificates(); - boolean isGoogleReleaseSigned(String packageName, IObjectWrapper certData); - boolean isGoogleSigned(String packageName, IObjectWrapper certData); - boolean isGoogleOrPlatformSigned(in GoogleCertificatesQuery query, IObjectWrapper packageManager); -} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEvent.java b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEvent.java index d9a19568..0485f3a2 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEvent.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEvent.java @@ -19,5 +19,5 @@ package com.google.android.gms.auth; import org.microg.safeparcel.AutoSafeParcelable; public class AccountChangeEvent extends AutoSafeParcelable { - public static Creator CREATOR = new AutoCreator<>(AccountChangeEvent.class); + public static Creator CREATOR = new AutoCreator(AccountChangeEvent.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsRequest.java index cf8561df..1d404288 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsRequest.java @@ -21,18 +21,18 @@ import static org.microg.gms.auth.AuthConstants.DEFAULT_ACCOUNT_TYPE; import android.accounts.Account; import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; public class AccountChangeEventsRequest extends AutoSafeParcelable { + @Field(1) + private final int versionCode = 1; + @Field(2) + private int since; @Field(3) @Deprecated - private final String accountName; + private String accountName; @Field(4) - private final Account account; - - public AccountChangeEventsRequest(String accountName, Account account) { - this.accountName = accountName; - this.account = account; - } + private Account account; public Account getAccount() { if (account != null) return account; @@ -40,6 +40,6 @@ public class AccountChangeEventsRequest extends AutoSafeParcelable { return null; } - public static Creator CREATOR = new AutoCreator<>(AccountChangeEventsRequest.class); + public static Creator CREATOR = new AutoCreator(AccountChangeEventsRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsResponse.java b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsResponse.java index 10491379..763e5d42 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsResponse.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsResponse.java @@ -17,11 +17,20 @@ package com.google.android.gms.auth; import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; + +import java.util.ArrayList; +import java.util.List; public class AccountChangeEventsResponse extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 1; + @SafeParceled(value = 2, subClass = AccountChangeEvent.class) + private final List events; public AccountChangeEventsResponse() { + events = new ArrayList(); } - public static Creator CREATOR = new AutoCreator<>(AccountChangeEventsResponse.class); + public static Creator CREATOR = new AutoCreator(AccountChangeEventsResponse.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java b/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java index 4845ed41..ccecf291 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java @@ -5,10 +5,17 @@ package com.google.android.gms.auth; +import android.accounts.Account; +import android.content.Intent; + import org.microg.safeparcel.AutoSafeParcelable; public class GetHubTokenInternalResponse extends AutoSafeParcelable { + @Field(1) + public TokenData tokenData; @Field(2) public String status; + @Field(3) + public Intent recoveryIntent; public static final Creator CREATOR = new AutoCreator<>(GetHubTokenInternalResponse.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java index 2c4e973c..cdaffb51 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java @@ -14,5 +14,7 @@ public class GetHubTokenRequest extends AutoSafeParcelable { public String service; @Field(3) public String packageName; + @Field(4) + public int callerUid; public static final Creator CREATOR = new AutoCreator<>(GetHubTokenRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java similarity index 53% rename from play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java rename to play-services-api/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java index c9907fa8..e35ca6d7 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java @@ -9,8 +9,10 @@ import android.accounts.Account; import org.microg.safeparcel.AutoSafeParcelable; -public class HasCababilitiesRequest extends AutoSafeParcelable { +public class HasCapabilitiesRequest extends AutoSafeParcelable { @Field(1) public Account account; - public static final Creator CREATOR = new AutoCreator<>(HasCababilitiesRequest.class); + @Field(2) + public String[] capabilities; + public static final Creator CREATOR = new AutoCreator<>(HasCapabilitiesRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/TokenData.java b/play-services-api/src/main/java/com/google/android/gms/auth/TokenData.java index df3768c8..c6fbe9e5 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/TokenData.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/TokenData.java @@ -24,6 +24,8 @@ import org.microg.safeparcel.SafeParceled; import java.util.List; public class TokenData extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 1; @SafeParceled(2) public final String token; @@ -58,5 +60,5 @@ public class TokenData extends AutoSafeParcelable { this.scopes = null; } - public static final Creator CREATOR = new AutoCreator<>(TokenData.class); + public static final Creator CREATOR = new AutoCreator(TokenData.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/Credential.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/Credential.java index 9ade5cc0..5a92ddc5 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/Credential.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/Credential.java @@ -9,17 +9,21 @@ package com.google.android.gms.auth.api.credentials; import android.net.Uri; +import android.text.TextUtils; import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; import java.util.Arrays; import java.util.List; -import java.util.Objects; +import java.util.Locale; @PublicApi public class Credential extends AutoSafeParcelable { + @Field(1000) + private final int versionCode = 1; + @Field(1) private String id; @Field(2) @@ -51,6 +55,13 @@ public class Credential extends AutoSafeParcelable { return accountType; } + /** + * Returns the generated password for an account hint. + */ + public String getGeneratedPassword() { + return generatedPassword; + } + /** * Returns the credential identifier, typically an email address or user name, though it may * also be some encoded unique identifier for a federated identity account. @@ -59,6 +70,23 @@ public class Credential extends AutoSafeParcelable { return id; } + /** + * Returns the ID tokens that assert the identity of the user, if available. ID tokens provide + * a secure mechanism to verify that the user owns the identity asserted by the credential. + *

+ * This is useful for account hints, where the ID token can replace the need to separately + * verify that the user owns their claimed email address - with a valid ID token, it is not + * necessary to send an account activation link to the address, simplifying the account + * creation process for the user. + *

+ * A signed ID token is returned automatically for credential hints when the credential ID is a + * Google account that is authenticated on the device. This ID token can be sent along with + * your application's account creation operation, where the signature can be verified. + */ + public List getIdTokens() { + return tokens; + } + /** * Returns the display name of the credential, if available. Typically, the display name will * be the name of the user, or some other string which the user can easily recognize and @@ -68,22 +96,47 @@ public class Credential extends AutoSafeParcelable { return name; } + /** + * Returns the password used to sign in the user. + */ + public String getPassword() { + return password; + } + + /** + * Returns the URL to an image of the user, if available. + */ + public Uri getProfilePictureUri() { + return profilePictureUri; + } + + @PublicApi(exclude = true) + public String getAsString() { + if (TextUtils.isEmpty(accountType)) { + return id.toLowerCase(Locale.US) + "|"; + } else { + Uri uri = Uri.parse(accountType); + return id.toLowerCase(Locale.US) + "|" + (TextUtils.isEmpty(uri.getScheme()) ? "" : uri.getScheme().toLowerCase(Locale.US)) + "://" + + (TextUtils.isEmpty(uri.getHost()) ? "unknown" : uri.getHost().toLowerCase(Locale.US)) + ":" + uri.getPort(); + } + } + @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof Credential)) return false; + if (o == null || !(o instanceof Credential)) return false; Credential that = (Credential) o; - if (!Objects.equals(id, that.id)) return false; - if (!Objects.equals(name, that.name)) return false; - if (!Objects.equals(profilePictureUri, that.profilePictureUri)) + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (name != null ? !name.equals(that.name) : that.name != null) return false; + if (profilePictureUri != null ? !profilePictureUri.equals(that.profilePictureUri) : that.profilePictureUri != null) return false; - if (!Objects.equals(password, that.password)) + if (password != null ? !password.equals(that.password) : that.password != null) return false; - if (!Objects.equals(accountType, that.accountType)) + if (accountType != null ? !accountType.equals(that.accountType) : that.accountType != null) return false; - return Objects.equals(generatedPassword, that.generatedPassword); + return generatedPassword != null ? generatedPassword.equals(that.generatedPassword) : that.generatedPassword == null; } @@ -95,14 +148,18 @@ public class Credential extends AutoSafeParcelable { public static class Builder { private final String id; private String name; - private final Uri profilePictureUri; - private final String password; + private Uri profilePictureUri; + private String password; private String accountType; @PublicApi(exclude = true) public List tokens; @PublicApi(exclude = true) - private final String generatedPassword; + private String generatedPassword; + + public Builder(String id) { + this.id = id; + } /** * Copies the information stored in an existing credential, in order to allow that information to be modified. @@ -154,7 +211,24 @@ public class Credential extends AutoSafeParcelable { return this; } + /** + * Sets the password for the credential. Either the password or the account type must be + * set for a credential, but not both. + */ + public Builder setPassword(String password) { + this.password = password; + return this; + } + + /** + * Sets a profile picture associated with the credential, typically a picture the user has + * selected to represent the account. + */ + public Builder setProfilePictureUri(Uri profilePictureUri) { + this.profilePictureUri = profilePictureUri; + return this; + } } - public static final Creator CREATOR = new AutoCreator<>(Credential.class); + public static final Creator CREATOR = new AutoCreator(Credential.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialPickerConfig.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialPickerConfig.java index 41ca2191..4cb1bbc9 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialPickerConfig.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialPickerConfig.java @@ -8,20 +8,24 @@ package com.google.android.gms.auth.api.credentials; -import androidx.annotation.NonNull; - import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; @PublicApi public class CredentialPickerConfig extends AutoSafeParcelable { + @Field(1000) + private final int versionCode = 1; + @Field(1) - private final boolean showAddAccountButton; + private boolean showAddAccountButton; @Field(2) - private final boolean showCancelButton; + private boolean showCancelButton; @Field(3) - private final boolean forNewAccount; + private boolean forNewAccount; + + private CredentialPickerConfig() { + } public CredentialPickerConfig(boolean showAddAccountButton, boolean showCancelButton, boolean forNewAccount) { this.showAddAccountButton = showAddAccountButton; @@ -37,7 +41,14 @@ public class CredentialPickerConfig extends AutoSafeParcelable { return forNewAccount; } - @NonNull + public boolean shouldShowAddAccountButton() { + return showAddAccountButton; + } + + public boolean shouldShowCancelButton() { + return showCancelButton; + } + @Override public String toString() { return "CredentialPickerConfig{" + @@ -46,22 +57,39 @@ public class CredentialPickerConfig extends AutoSafeParcelable { '}'; } - public static class Builder { - private final boolean showAddAccountButton; - private final boolean showCancelButton; - private final boolean forNewAccount; - - public Builder(boolean showAddAccountButton, boolean showCancelButton, boolean forNewAccount) { - this.showAddAccountButton = showAddAccountButton; - this.showCancelButton = showCancelButton; - this.forNewAccount = forNewAccount; - } + public class Builder { + private boolean showAddAccountButton; + private boolean showCancelButton; + private boolean forNewAccount; public CredentialPickerConfig build() { return new CredentialPickerConfig(showAddAccountButton, showCancelButton, forNewAccount); } + /** + * Sets whether the hint request is for a new account sign-up flow. + */ + public Builder setForNewAccount(boolean forNewAccount) { + this.forNewAccount = forNewAccount; + return this; + } + + /** + * Sets whether the add account button should be shown in credential picker dialog. + */ + public Builder setShowAddAccountButton(boolean showAddAccountButton) { + this.showAddAccountButton = showAddAccountButton; + return this; + } + + /** + * Sets whether the cancel button should be shown in credential picker dialog. + */ + public Builder setShowCancelButton(boolean showCancelButton) { + this.showCancelButton = showCancelButton; + return this; + } } - public static final Creator CREATOR = new AutoCreator<>(CredentialPickerConfig.class); + public static final Creator CREATOR = new AutoCreator(CredentialPickerConfig.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialRequest.java index c4961d14..6255fbf9 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialRequest.java @@ -10,13 +10,43 @@ package com.google.android.gms.auth.api.credentials; import org.microg.safeparcel.AutoSafeParcelable; +/** + * Parameters for requesting a Credential, via Auth.CredentialsApi.request(). Instances can be + * created using CredentialRequest.Builder. + */ public class CredentialRequest extends AutoSafeParcelable { - @Field(1) - private final boolean passwordLoginSupported; + @Field(1000) + private final int versionCode = 1; - public CredentialRequest(boolean passwordLoginSupported) { + @Field(1) + private boolean passwordLoginSupported; + @Field(2) + private String[] accountTypes; + @Field(3) + private CredentialPickerConfig credentialPickerConfig; + @Field(4) + private CredentialPickerConfig credentialHintPickerConfig; + + private CredentialRequest() { } + + public CredentialRequest(boolean passwordLoginSupported, String[] accountTypes, CredentialPickerConfig credentialPickerConfig, CredentialPickerConfig credentialHintPickerConfig) { this.passwordLoginSupported = passwordLoginSupported; + this.accountTypes = accountTypes; + this.credentialPickerConfig = credentialPickerConfig; + this.credentialHintPickerConfig = credentialHintPickerConfig; + } + + public String[] getAccountTypes() { + return accountTypes; + } + + public CredentialPickerConfig getCredentialHintPickerConfig() { + return credentialHintPickerConfig; + } + + public CredentialPickerConfig getCredentialPickerConfig() { + return credentialPickerConfig; } /** @@ -34,12 +64,13 @@ public class CredentialRequest extends AutoSafeParcelable { public static final Creator CREATOR = new AutoCreator(CredentialRequest.class); public static class Builder { - - public Builder() { - } + private boolean passwordLoginSupported; + private String[] accountTypes; + private CredentialPickerConfig credentialPickerConfig; + private CredentialPickerConfig credentialHintPickerConfig; public void setAccountTypes(String... accountTypes) { - accountTypes.clone(); + this.accountTypes = accountTypes.clone(); } } } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/HintRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/HintRequest.java index 3f9336f4..1f1cae80 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/HintRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/HintRequest.java @@ -8,8 +8,6 @@ package com.google.android.gms.auth.api.credentials; -import androidx.annotation.NonNull; - import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; @@ -21,21 +19,26 @@ import java.util.Arrays; */ @PublicApi public class HintRequest extends AutoSafeParcelable { + @Field(1000) + private final int versionCode = 2; @Field(1) - private final CredentialPickerConfig hintPickerConfig; + private CredentialPickerConfig hintPickerConfig; @Field(2) - private final boolean emailAddressIdentifierSupported; + private boolean emailAddressIdentifierSupported; @Field(3) - private final boolean phoneNumberIdentifierSupported; + private boolean phoneNumberIdentifierSupported; @Field(4) - private final String[] accountTypes; + private String[] accountTypes; @Field(5) - private final boolean idTokenRequested; + private boolean idTokenRequested = true; @Field(6) - private final String serverClientId; + private String serverClientId; @Field(7) - private final String idTokenNonce; + private String idTokenNonce; + + private HintRequest() { + } public HintRequest(CredentialPickerConfig hintPickerConfig, boolean emailAddressIdentifierSupported, boolean phoneNumberIdentifierSupported, String[] accountTypes, boolean idTokenRequested, String serverClientId, String idTokenNonce) { this.hintPickerConfig = hintPickerConfig; @@ -47,9 +50,36 @@ public class HintRequest extends AutoSafeParcelable { this.idTokenNonce = idTokenNonce; } + public String[] getAccountTypes() { + return accountTypes; + } + + public CredentialPickerConfig getHintPickerConfig() { + return hintPickerConfig; + } + + public String getIdTokenNonce() { + return idTokenNonce; + } + + public String getServerClientId() { + return serverClientId; + } + + public boolean isEmailAddressIdentifierSupported() { + return emailAddressIdentifierSupported; + } + + public boolean isPhoneNumberIdentifierSupported() { + return phoneNumberIdentifierSupported; + } + + public boolean isIdTokenRequested() { + return idTokenRequested; + } + public static final Creator CREATOR = new AutoCreator<>(HintRequest.class); - @NonNull @Override public String toString() { return "HintRequest{" + @@ -64,29 +94,77 @@ public class HintRequest extends AutoSafeParcelable { } public static class Builder { - private final CredentialPickerConfig hintPickerConfig; - private final boolean emailAddressIdentifierSupported; - private final boolean phoneNumberIdentifierSupported; - private final String[] accountTypes; - private final String serverClientId; - private final String idTokenNonce; - - public Builder(CredentialPickerConfig hintPickerConfig, boolean emailAddressIdentifierSupported, boolean phoneNumberIdentifierSupported, String[] accountTypes, String serverClientId, String idTokenNonce) { - this.hintPickerConfig = hintPickerConfig; - this.emailAddressIdentifierSupported = emailAddressIdentifierSupported; - this.phoneNumberIdentifierSupported = phoneNumberIdentifierSupported; - this.accountTypes = accountTypes; - this.serverClientId = serverClientId; - this.idTokenNonce = idTokenNonce; - } + private CredentialPickerConfig hintPickerConfig; + private boolean emailAddressIdentifierSupported; + private boolean phoneNumberIdentifierSupported; + private String[] accountTypes; + private boolean idTokenRequested = true; + private String serverClientId; + private String idTokenNonce; /** * Builds a {@link HintRequest}. */ public HintRequest build() { - boolean idTokenRequested = true; return new HintRequest(hintPickerConfig, emailAddressIdentifierSupported, phoneNumberIdentifierSupported, accountTypes, idTokenRequested, serverClientId, idTokenNonce); } + /** + * Sets the account types (identity providers) that are accepted by this application. + * It is strongly recommended that the strings listed in {@link IdentityProviders} be used for the most common + * identity providers, and strings representing the login domain of the identity provider be used for any + * others which are not listed. + * + * @param accountTypes The list of account types (identity providers) supported by the app. + * typically in the form of the associated login domain for each identity provider. + */ + public void setAccountTypes(String... accountTypes) { + this.accountTypes = accountTypes.clone(); + } + + /** + * Enables returning {@link Credential} hints where the identifier is an email address, intended for use with a password chosen by the user. + */ + public void setEmailAddressIdentifierSupported(boolean emailAddressIdentifierSupported) { + this.emailAddressIdentifierSupported = emailAddressIdentifierSupported; + } + + /** + * Sets the configuration for the hint picker dialog. + */ + public void setHintPickerConfig(CredentialPickerConfig hintPickerConfig) { + this.hintPickerConfig = hintPickerConfig; + } + + /** + * Specify a nonce value that should be included in any generated ID token for this request. + */ + public void setIdTokenNonce(String idTokenNonce) { + this.idTokenNonce = idTokenNonce; + } + + /** + * Specify whether an ID token should be acquired for hints, if available for the selected credential identifier. + * This is enabled by default; disable this if your app does not use ID tokens as part of authentication to decrease latency in retrieving credentials and credential hints. + */ + public void setIdTokenRequested(boolean idTokenRequested) { + this.idTokenRequested = idTokenRequested; + } + + /** + * Enables returning {@link Credential} hints where the identifier is a phone number, intended for use with a password chosen by the user or SMS verification. + */ + public void setPhoneNumberIdentifierSupported(boolean phoneNumberIdentifierSupported) { + this.phoneNumberIdentifierSupported = phoneNumberIdentifierSupported; + } + + /** + * Specify the server client ID for the backend associated with this app. + * If a Google ID token can be generated for a retrieved credential or hint, and the specified server client ID is correctly configured to be associated with the app, then it will be used as the audience of the generated token. + * If a null value is specified, the default audience will be used for the generated ID token. + */ + public void setServerClientId(String serverClientId) { + this.serverClientId = serverClientId; + } } } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdToken.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdToken.java index 5d84423e..aa6d19d4 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdToken.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdToken.java @@ -14,11 +14,17 @@ import org.microg.safeparcel.AutoSafeParcelable; @PublicApi public class IdToken extends AutoSafeParcelable { + @Field(1000) + private final int versionCode = 1; + @Field(1) - private final String accountType; + private String accountType; @Field(2) - private final String id; + private String id; + + private IdToken() { + } public IdToken(String accountType, String id) { this.accountType = accountType; @@ -32,5 +38,12 @@ public class IdToken extends AutoSafeParcelable { return accountType; } - public static final Creator CREATOR = new AutoCreator<>(IdToken.class); + /** + * Returns the ID token, formatted according to the rules defined by the account type. + */ + public String getIdToken() { + return id; + } + + public static final Creator CREATOR = new AutoCreator(IdToken.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdentityProviders.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdentityProviders.java index 1d974993..8cd24eed 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdentityProviders.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdentityProviders.java @@ -8,8 +8,27 @@ package com.google.android.gms.auth.api.credentials; +import android.accounts.Account; + /** * Identity provider constants for use with {@link CredentialRequest.Builder#setAccountTypes(String...)} */ public final class IdentityProviders { + public static final String FACEBOOK = "//www.facebook.com"; + public static final String GOOGLE = "//accounts.google.com"; + public static final String LINKEDIN = "//www.linkedin.com"; + public static final String MICROSOFT = "//login.live.com"; + public static final String PAYPAL = "//www.paypal.com"; + public static final String TWITTER = "//twitter.com"; + public static final String YAHOO = "//login.yahoo.com"; + + /** + * Attempts to translate the account type in the provided account into the string that should be used in the credentials API. + * + * @param account an account on the device. + * @return The identity provider string for use with the Credentials API, or {@code null} if the account type is unknown. + */ + public static String getIdentityProviderForAccount(Account account) { + return null; + } } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/DeleteRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/DeleteRequest.java index e560ddc5..bd7ae6f2 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/DeleteRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/DeleteRequest.java @@ -19,5 +19,5 @@ package com.google.android.gms.auth.api.credentials.internal; import org.microg.safeparcel.AutoSafeParcelable; public class DeleteRequest extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(DeleteRequest.class); + public static final Creator CREATOR = new AutoCreator(DeleteRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/GeneratePasswordRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/GeneratePasswordRequest.java index 5fd6fcb7..3da7a72a 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/GeneratePasswordRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/GeneratePasswordRequest.java @@ -19,5 +19,5 @@ package com.google.android.gms.auth.api.credentials.internal; import org.microg.safeparcel.AutoSafeParcelable; public class GeneratePasswordRequest extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(GeneratePasswordRequest.class); + public static final Creator CREATOR = new AutoCreator(GeneratePasswordRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/SaveRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/SaveRequest.java index 24a9d92f..5ec25998 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/SaveRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/SaveRequest.java @@ -19,5 +19,5 @@ package com.google.android.gms.auth.api.credentials.internal; import org.microg.safeparcel.AutoSafeParcelable; public class SaveRequest extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(SaveRequest.class); + public static final Creator CREATOR = new AutoCreator(SaveRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java index dc1fbd39..acca03a4 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java @@ -5,16 +5,33 @@ package com.google.android.gms.auth.api.proxy; -import androidx.annotation.NonNull; +import android.os.Bundle; import org.microg.safeparcel.AutoSafeParcelable; public class ProxyRequest extends AutoSafeParcelable { + public static final int HTTP_METHOD_GET = 0; + public static final int HTTP_METHOD_POST = 1; + public static final int HTTP_METHOD_PUT = 2; + public static final int HTTP_METHOD_DELETE = 3; + public static final int HTTP_METHOD_HEAD = 4; + public static final int HTTP_METHOD_OPTIONS = 5; + public static final int HTTP_METHOD_TRACE = 6; + public static final int HTTP_METHOD_PATCH = 7; + @Field(1000) + private final int versionCode = 2; @Field(1) public String url; + @Field(2) + public int httpMethod; + @Field(3) + public long timeoutMillis; + @Field(4) + public byte[] body; + @Field(5) + public Bundle headers; - @NonNull @Override public String toString() { return url; diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java index 263d2e20..b18d21be 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java @@ -5,11 +5,26 @@ package com.google.android.gms.auth.api.proxy; +import android.app.PendingIntent; +import android.os.Bundle; + import org.microg.safeparcel.AutoSafeParcelable; public class ProxyResponse extends AutoSafeParcelable { + public static final int STATUS_CODE_NO_CONNECTION = -1; + + @Field(1000) + private final int versionCode = 1; @Field(1) public int gmsStatusCode; + @Field(2) + public PendingIntent recoveryAction; + @Field(3) + public int httpStatusCode; + @Field(4) + public Bundle headers; + @Field(5) + public byte[] body; public static final Creator CREATOR = new AutoCreator<>(ProxyResponse.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRecoveryGuidanceRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRecoveryGuidanceRequest.java index 1c267ed1..d5f1d054 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRecoveryGuidanceRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRecoveryGuidanceRequest.java @@ -24,6 +24,8 @@ import org.microg.safeparcel.SafeParceled; public class AccountRecoveryGuidanceRequest extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 1; @SafeParceled(2) @Deprecated public final String accountName; @@ -40,5 +42,5 @@ public class AccountRecoveryGuidanceRequest extends AutoSafeParcelable { this.account = account; } - public static final Creator CREATOR = new AutoCreator<>(AccountRecoveryGuidanceRequest.class); + public static final Creator CREATOR = new AutoCreator(AccountRecoveryGuidanceRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRemovalRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRemovalRequest.java index dc90a7d0..439825c2 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRemovalRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRemovalRequest.java @@ -24,6 +24,8 @@ import org.microg.safeparcel.SafeParceled; public class AccountRemovalRequest extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 1; @SafeParceled(2) @Deprecated public final String accountName; diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/ConfirmCredentialsRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/ConfirmCredentialsRequest.java index 9edad8b2..16142d6a 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/ConfirmCredentialsRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/ConfirmCredentialsRequest.java @@ -16,9 +16,19 @@ package com.google.android.gms.auth.firstparty.dataservice; +import com.google.android.gms.auth.firstparty.shared.AccountCredentials; +import com.google.android.gms.auth.firstparty.shared.CaptchaSolution; + import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; public class ConfirmCredentialsRequest extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 1; + @SafeParceled(2) + public AccountCredentials accountCredentials; + @SafeParceled(3) + public CaptchaSolution captchaSolution; - public static final Creator CREATOR = new AutoCreator<>(ConfirmCredentialsRequest.class); + public static final Creator CREATOR = new AutoCreator(ConfirmCredentialsRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenRequest.java index 73062668..099e56f4 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenRequest.java @@ -25,6 +25,8 @@ import org.microg.safeparcel.SafeParceled; // TODO public class TokenRequest extends AutoSafeParcelable{ + @SafeParceled(1) + private final int versionCode = 4; @SafeParceled(3) public String accountName; @SafeParceled(4) @@ -38,5 +40,5 @@ public class TokenRequest extends AutoSafeParcelable{ return new Account(accountName, accountType); } - public static final Creator CREATOR = new AutoCreator<>(TokenRequest.class); + public static final Creator CREATOR = new AutoCreator(TokenRequest.class); } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClient.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenResponse.java similarity index 78% rename from play-services-base/src/main/java/org/microg/gms/common/api/ApiClient.java rename to play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenResponse.java index 602e150e..999e9a96 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClient.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenResponse.java @@ -14,14 +14,7 @@ * limitations under the License. */ -package org.microg.gms.common.api; +package com.google.android.gms.auth.firstparty.dataservice; -public interface ApiClient { - void connect(); - - void disconnect(); - - boolean isConnected(); - - boolean isConnecting(); +public class TokenResponse { } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/delegate/ConfirmCredentialsWorkflowRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/delegate/ConfirmCredentialsWorkflowRequest.java index b8e5ed63..d4308ef7 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/delegate/ConfirmCredentialsWorkflowRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/delegate/ConfirmCredentialsWorkflowRequest.java @@ -17,18 +17,27 @@ package com.google.android.gms.auth.firstparty.delegate; import android.accounts.Account; +import android.accounts.AccountAuthenticatorResponse; import android.os.Bundle; +import com.google.android.gms.auth.firstparty.shared.AppDescription; + import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; public class ConfirmCredentialsWorkflowRequest extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 3; @SafeParceled(2) public String accountName; + @SafeParceled(3) + public AppDescription appDescription; @SafeParceled(4) public Bundle extras; @SafeParceled(5) public Account account; + @SafeParceled(6) + public AccountAuthenticatorResponse accountAuthenticatorResponse; - public static final Creator CREATOR = new AutoCreator<>(ConfirmCredentialsWorkflowRequest.class); + public static final Creator CREATOR = new AutoCreator(ConfirmCredentialsWorkflowRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/proximity/data/Permit.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/proximity/data/Permit.java new file mode 100644 index 00000000..139bf29b --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/proximity/data/Permit.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.gms.auth.firstparty.proximity.data; + +public class Permit { +} diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AccountCredentials.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AccountCredentials.java index 67e7c7db..33995a9a 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AccountCredentials.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AccountCredentials.java @@ -23,6 +23,8 @@ import org.microg.safeparcel.SafeParceled; // TODO public class AccountCredentials extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 2; @SafeParceled(3) public String accountName; @SafeParceled(9) @@ -32,5 +34,5 @@ public class AccountCredentials extends AutoSafeParcelable { return new Account(accountName, accountType); } - public static final Creator CREATOR = new AutoCreator<>(AccountCredentials.class); + public static final Creator CREATOR = new AutoCreator(AccountCredentials.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AppDescription.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AppDescription.java index 1ccdf4f6..efa4b2bc 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AppDescription.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AppDescription.java @@ -17,8 +17,19 @@ package com.google.android.gms.auth.firstparty.shared; import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; public class AppDescription extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 1; + @SafeParceled(2) + public int callingUid; + @SafeParceled(3) + public String sessiondId; + @SafeParceled(4) + public String sessiondSig; + @SafeParceled(5) + public String callingPkg; - public static final Creator CREATOR = new AutoCreator<>(AppDescription.class); + public static final Creator CREATOR = new AutoCreator(AppDescription.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/CaptchaSolution.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/CaptchaSolution.java index ce1d3f61..c0cb9326 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/CaptchaSolution.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/CaptchaSolution.java @@ -17,9 +17,13 @@ package com.google.android.gms.auth.firstparty.shared; import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; // TODO public class CaptchaSolution extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(CaptchaSolution.class); + @SafeParceled(1) + private final int versionCode = 1; + + public static final Creator CREATOR = new AutoCreator(CaptchaSolution.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/common/server/FavaDiagnosticsEntity.java b/play-services-api/src/main/java/com/google/android/gms/common/server/FavaDiagnosticsEntity.java index d32e668b..6f10959a 100644 --- a/play-services-api/src/main/java/com/google/android/gms/common/server/FavaDiagnosticsEntity.java +++ b/play-services-api/src/main/java/com/google/android/gms/common/server/FavaDiagnosticsEntity.java @@ -20,5 +20,5 @@ import org.microg.safeparcel.AutoSafeParcelable; public class FavaDiagnosticsEntity extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(FavaDiagnosticsEntity.class); + public static final Creator CREATOR = new AutoCreator(FavaDiagnosticsEntity.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/identity/accounts/api/AccountData.java b/play-services-api/src/main/java/com/google/android/gms/identity/accounts/api/AccountData.java new file mode 100644 index 00000000..d909cbf3 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/identity/accounts/api/AccountData.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.gms.identity.accounts.api; + +public class AccountData { +} diff --git a/play-services-api/src/main/java/com/google/android/gms/identity/intents/model/CountrySpecification.java b/play-services-api/src/main/java/com/google/android/gms/identity/intents/model/CountrySpecification.java new file mode 100644 index 00000000..b9770e36 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/identity/intents/model/CountrySpecification.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.gms.identity.intents.model; + +public class CountrySpecification { +} diff --git a/play-services-api/src/main/java/com/google/android/gms/identity/intents/model/UserAddress.java b/play-services-api/src/main/java/com/google/android/gms/identity/intents/model/UserAddress.java new file mode 100644 index 00000000..aa3959f2 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/identity/intents/model/UserAddress.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.gms.identity.intents.model; + +public class UserAddress { +} diff --git a/play-services-api/src/main/java/com/google/android/gms/people/internal/ParcelableLoadImageOptions.java b/play-services-api/src/main/java/com/google/android/gms/people/internal/ParcelableLoadImageOptions.java index 64322959..d0a7a742 100644 --- a/play-services-api/src/main/java/com/google/android/gms/people/internal/ParcelableLoadImageOptions.java +++ b/play-services-api/src/main/java/com/google/android/gms/people/internal/ParcelableLoadImageOptions.java @@ -17,8 +17,20 @@ package com.google.android.gms.people.internal; import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; public class ParcelableLoadImageOptions extends AutoSafeParcelable { + @SafeParceled(1000) + private final int versionCode = 1; - public static final Creator CREATOR = new AutoCreator<>(ParcelableLoadImageOptions.class); + @SafeParceled(1) + public int imageSize; + + @SafeParceled(2) + public int avatarOptions; + + @SafeParceled(3) + public boolean useLargePictureForCp2Images; + + public static final Creator CREATOR = new AutoCreator(ParcelableLoadImageOptions.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/people/model/AccountMetadata.java b/play-services-api/src/main/java/com/google/android/gms/people/model/AccountMetadata.java index dfb64544..53038bbc 100644 --- a/play-services-api/src/main/java/com/google/android/gms/people/model/AccountMetadata.java +++ b/play-services-api/src/main/java/com/google/android/gms/people/model/AccountMetadata.java @@ -17,8 +17,24 @@ package com.google.android.gms.people.model; import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; public class AccountMetadata extends AutoSafeParcelable { - public static Creator CREATOR = new AutoCreator<>(AccountMetadata.class); + @SafeParceled(1) + private final int versionCode = 2; + + @SafeParceled(2) + public boolean hasGooglePlus = true; + + @SafeParceled(3) + public boolean hasFeature2 = true; + + @SafeParceled(4) + public boolean hasFeature3 = true; + + @SafeParceled(5) + public boolean hasFeature4 = true; + + public static Creator CREATOR = new AutoCreator(AccountMetadata.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/people/model/AvatarReference.java b/play-services-api/src/main/java/com/google/android/gms/people/model/AvatarReference.java index 6e3da8e2..b03af386 100644 --- a/play-services-api/src/main/java/com/google/android/gms/people/model/AvatarReference.java +++ b/play-services-api/src/main/java/com/google/android/gms/people/model/AvatarReference.java @@ -17,11 +17,18 @@ package com.google.android.gms.people.model; import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; public class AvatarReference extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(AvatarReference.class); + @SafeParceled(1000) + private int versionCode; - public AvatarReference() { - } + @SafeParceled(1) + public int source; + + @SafeParceled(2) + public String location; + + public static final Creator CREATOR = new AutoCreator(AvatarReference.class); } diff --git a/play-services-base-api/build.gradle b/play-services-base-api/build.gradle index 59dc1be7..23b6b07c 100644 --- a/play-services-base-api/build.gradle +++ b/play-services-base-api/build.gradle @@ -34,11 +34,11 @@ android { } compileOptions { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = 17 + targetCompatibility = 17 } - namespace 'org.microg.gms.base.api' buildFeatures { aidl true } + namespace 'org.microg.gms.base.api' } \ No newline at end of file diff --git a/play-services-base-api/src/main/AndroidManifest.xml b/play-services-base-api/src/main/AndroidManifest.xml new file mode 100644 index 00000000..055d07d5 --- /dev/null +++ b/play-services-base-api/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/play-services-base-api/src/main/java/com/google/android/gms/common/data/DataHolder.java b/play-services-base-api/src/main/java/com/google/android/gms/common/data/DataHolder.java index 77acbb1b..4af2bcf1 100644 --- a/play-services-base-api/src/main/java/com/google/android/gms/common/data/DataHolder.java +++ b/play-services-base-api/src/main/java/com/google/android/gms/common/data/DataHolder.java @@ -17,13 +17,16 @@ package com.google.android.gms.common.data; import android.annotation.SuppressLint; +import android.content.ContentValues; +import android.database.AbstractWindowedCursor; +import android.database.CharArrayBuffer; import android.database.Cursor; import android.database.CursorWindow; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Parcel; -import androidx.annotation.NonNull; - import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; @@ -42,6 +45,9 @@ import java.util.Map; */ @PublicApi(until = "1") public class DataHolder extends AutoSafeParcelable implements Closeable { + @SafeParceled(1000) + private final int versionCode = 1; + @SafeParceled(1) private final String[] columns; @@ -63,6 +69,13 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { protected static final int FIELD_TYPE_STRING = 3; protected static final int FIELD_TYPE_BLOB = 4; + private DataHolder() { + columns = null; + windows = null; + statusCode = 0; + metadata = null; + } + /** * Creates a data holder with the specified data. * @@ -79,6 +92,17 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { validateContents(); } + /** + * Creates a data holder wrapping the provided cursor, with provided status code and metadata. + * + * @param cursor The cursor containing the data. + * @param statusCode The status code of this {@link DataHolder}. + * @param metadata The metadata associated with this {@link DataHolder} (may be null). + */ + public DataHolder(AbstractWindowedCursor cursor, int statusCode, Bundle metadata) { + this(cursor.getColumnNames(), createCursorWindows(cursor), statusCode, metadata); + } + /** * Creates a data holder wrapping the provided cursor, with provided status code and metadata. * @@ -94,10 +118,38 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { * Get a {@link DataHolder.Builder} to create a new {@link DataHolder} manually. * * @param columns The array of column names that the object supports. + * @param uniqueColumn The non-null column name that must contain unique values. New rows added to the builder with the same value in this column will replace any older rows. + * @return {@link DataHolder.Builder} object to work with. + */ + public static Builder builder(String[] columns, String uniqueColumn) { + return new Builder(columns, uniqueColumn); + } + + /** + * Get a {@link DataHolder.Builder} to create a new {@link DataHolder} manually. + * + * @param columns The array of column names that the object supports. * @return {@link DataHolder.Builder} object to work with. */ public static Builder builder(String[] columns) { - return new Builder(columns); + return builder(columns, null); + } + + /** + * @param statusCode The status code of this {@link DataHolder}. + * @param metadata The metadata associated with this {@link DataHolder} (may be null). + * @return An empty {@link DataHolder} object with the given status and metadata. + */ + public static DataHolder empty(int statusCode, Bundle metadata) { + return new DataHolder(new String[0], new CursorWindow[0], statusCode, metadata); + } + + /** + * @param statusCode The status code of this {@link DataHolder}. + * @return An empty {@link DataHolder} object with the given status and null metadata. + */ + public static DataHolder empty(int statusCode) { + return empty(statusCode, null); } @SuppressLint({"NewApi", "ObsoleteSdkInt"}) @@ -120,6 +172,20 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { } } + /** + * Copies the String content in the given column at the provided position into a {@link CharArrayBuffer}. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @param dataOut The {@link CharArrayBuffer} to copy into. + */ + public void copyToBuffer(String column, int row, int windowIndex, CharArrayBuffer dataOut) { + throw new RuntimeException("Not yet available"); + } + @SuppressWarnings("deprecation") private static CursorWindow[] createCursorWindows(Builder builder) { if (builder.columns.length == 0) return new CursorWindow[0]; @@ -212,6 +278,34 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { return windows.toArray(new CursorWindow[0]); } + /** + * Retrieves the boolean value for a given column at the provided position. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @return The boolean value in that column. + */ + public boolean getBoolean(String column, int row, int windowIndex) { + return windows[windowIndex].getLong(row, columnIndices.get(column)) == 1; + } + + /** + * Retrieves the byte array value for a given column at the provided position. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @return The byte array value in that column. + */ + public byte[] getByteArray(String column, int row, int windowIndex) { + return windows[windowIndex].getBlob(row, columnIndices.get(column)); + } + /** * Gets the number of rows in the data holder. * @@ -227,6 +321,38 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { return c; } + /** + * Retrieves the integer value for a given column at the provided position. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @return The integer value in that column. + */ + public int getInteger(String column, int row, int windowIndex) { + return windows[windowIndex].getInt(row, columnIndices.get(column)); + } + + /** + * Retrieves the long value for a given column at the provided position. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @return The long value in that column. + */ + public long getLong(String column, int row, int windowIndex) { + return windows[windowIndex].getLong(row, columnIndices.get(column)); + } + + public int getStatusCode() { + return statusCode; + } + /** * Retrieves the string value for a given column at the provided position. * This will throw an {@link IllegalArgumentException} if the column does not exist, the @@ -241,7 +367,42 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { return windows[windowIndex].getString(row, columnIndices.get(column)); } - @NonNull + /** + * Returns whether the given column at the provided position contains null. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @return Whether the column value is null at this position. + */ + public boolean isNull(String column, int row, int windowIndex) { + return windows[windowIndex].isNull(row, columnIndices.get(column)); + } + + public boolean isClosed() { + synchronized (this) { + return closed; + } + } + + /** + * Retrieves the column data at the provided position as a URI if possible, checking for null values. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @return The column data as a URI, or null if not present. + */ + public Uri parseUri(String column, int row, int windowIndex) { + String string = getString(column, row, windowIndex); + if (string != null) return Uri.parse(string); + return null; + } + @Override public String toString() { return "DataHolder{" + @@ -267,10 +428,14 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { public static class Builder { private final String[] columns; private final ArrayList> rows; + private final String uniqueColumn; + private final Map uniqueIndizes; - private Builder(String[] columns) { + private Builder(String[] columns, String uniqueColumn) { this.columns = columns; this.rows = new ArrayList<>(); + this.uniqueColumn = uniqueColumn; + this.uniqueIndizes = new HashMap<>(); } /** @@ -301,6 +466,58 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { return rows.size(); } + /** + * Sort the rows in this builder based on the standard data type comparisons for the value in the provided column. + * Calling this multiple times with the same column will not change the sort order of the builder. + * Note that any data which is added after this call will not be sorted. + * + * @param sortColumn The column to sort the rows in this builder by. + * @return {@link DataHolder.Builder} to continue construction. + */ + public Builder sort(String sortColumn) { + throw new RuntimeException("Not yet implemented"); + } + + /** + * Add a new row of data to the {@link DataHolder} this {@link DataHolder.Builder} will create. Note that the data must contain an entry for all columns + *

+ * Currently the only supported value types that are supported are String, Long, and Boolean (Integer is also accepted and will be stored as a Long). + * + * @param values {@link ContentValues} containing row data. + * @return {@link DataHolder.Builder} to continue construction. + */ + public Builder withRow(ContentValues values) { + HashMap row = new HashMap<>(); + for (Map.Entry entry : values.valueSet()) { + row.put(entry.getKey(), entry.getValue()); + } + return withRow(row); + } + + /** + * Add a new row of data to the {@link DataHolder} this {@link DataHolder.Builder} will create. Note that the data must contain an entry for all columns + *

+ * Currently the only supported value types that are supported are String, Long, and Boolean (Integer is also accepted and will be stored as a Long). + * + * @param row Map containing row data. + * @return {@link DataHolder.Builder} to continue construction. + */ + public Builder withRow(HashMap row) { + if (uniqueColumn != null) { + Object val = row.get(uniqueColumn); + if (val != null) { + Integer old = uniqueIndizes.get(val); + if (old != null) { + rows.set(old, row); + return this; + } else { + uniqueIndizes.put(val, rows.size()); + } + } + } + rows.add(row); + return this; + } } public static final Creator CREATOR = new AutoCreator(DataHolder.class) { diff --git a/play-services-base-api/src/main/java/com/google/android/gms/common/data/Freezable.java b/play-services-base-api/src/main/java/com/google/android/gms/common/data/Freezable.java new file mode 100644 index 00000000..51f4847e --- /dev/null +++ b/play-services-base-api/src/main/java/com/google/android/gms/common/data/Freezable.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.gms.common.data; + +public interface Freezable { + /** + * Freeze a volatile representation into an immutable representation. Objects returned from + * this call are safe to cache. + *

+ * Note that the output of {@link #freeze} may not be identical to the parent object, but + * should be equal. + * + * @return A concrete implementation of the data object. + */ + T freeze(); + + /** + * Check to see if this object is valid for use. If the object is still volatile, this method + * will indicate whether or not the object can be safely used. + * The output of a call to {@link #freeze()} will always be valid. + * + * @return whether or not the object is valid for use. + */ + boolean isDataValid(); +} diff --git a/play-services-base-api/src/main/java/com/google/android/gms/common/images/WebImage.java b/play-services-base-api/src/main/java/com/google/android/gms/common/images/WebImage.java index 4b5e4ad7..fee75495 100644 --- a/play-services-base-api/src/main/java/com/google/android/gms/common/images/WebImage.java +++ b/play-services-base-api/src/main/java/com/google/android/gms/common/images/WebImage.java @@ -18,8 +18,6 @@ package com.google.android.gms.common.images; import android.net.Uri; -import androidx.annotation.NonNull; - import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; @@ -60,9 +58,7 @@ public class WebImage extends AutoSafeParcelable { return height; } - @NonNull public String toString() { - assert uri != null; - return String.format(Locale.getDefault(), "Image %dx%d %s", width, height, uri); + return String.format(Locale.getDefault(), "Image %dx%d %s", width, height, uri.toString()); } } diff --git a/play-services-base-core/build.gradle b/play-services-base-core/build.gradle index 5a5dfa30..34288f21 100644 --- a/play-services-base-core/build.gradle +++ b/play-services-base-core/build.gradle @@ -23,6 +23,8 @@ android { defaultConfig { minSdkVersion androidMinSdk targetSdkVersion androidTargetSdk + + buildConfigField "String", "BASE_PACKAGE_NAME", "\"$basePackageName\"" } sourceSets { diff --git a/play-services-base-core/src/main/AndroidManifest.xml b/play-services-base-core/src/main/AndroidManifest.xml index 6585028c..369707e7 100644 --- a/play-services-base-core/src/main/AndroidManifest.xml +++ b/play-services-base-core/src/main/AndroidManifest.xml @@ -3,11 +3,26 @@ ~ SPDX-FileCopyrightText: 2020, microG Project Team ~ SPDX-License-Identifier: Apache-2.0 --> - + + + + + + android:exported="true" + android:readPermission="${applicationId}.permission.READ_SETTINGS" + android:writePermission="${applicationId}.permission.WRITE_SETTINGS" /> + + diff --git a/play-services-base-core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java b/play-services-base-core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java index 9ff67ff8..a0ff2b99 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java +++ b/play-services-base-core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java @@ -16,16 +16,19 @@ package org.microg.gms; +import android.accounts.Account; import android.os.Bundle; import android.os.Parcel; import android.os.RemoteException; import android.util.Log; +import com.google.android.gms.common.api.Scope; import com.google.android.gms.common.internal.GetServiceRequest; import com.google.android.gms.common.internal.IGmsCallbacks; import com.google.android.gms.common.internal.IGmsServiceBroker; import com.google.android.gms.common.internal.ValidateAccountRequest; +import org.microg.gms.base.core.BuildConfig; import org.microg.gms.common.GmsService; import java.util.EnumSet; @@ -56,24 +59,37 @@ public abstract class AbstractGmsServiceBroker extends IGmsServiceBroker.Stub { @Override public void getAddressService(IGmsCallbacks callback, int versionCode, String packageName) throws RemoteException { - callGetService(callback, versionCode, packageName); + callGetService(GmsService.ADDRESS, callback, versionCode, packageName); } - private void callGetService(IGmsCallbacks callback, int gmsVersion, + private void callGetService(GmsService service, IGmsCallbacks callback, int gmsVersion, String packageName) throws RemoteException { - callGetService(GmsService.ADDRESS, callback, gmsVersion, packageName, null); + callGetService(service, callback, gmsVersion, packageName, null); } - private void callGetService(GmsService service, IGmsCallbacks callback, int gmsVersion, String packageName, Bundle extras) throws RemoteException { + private void callGetService(GmsService service, IGmsCallbacks callback, int gmsVersion, + String packageName, Bundle extras) throws RemoteException { + callGetService(service, callback, gmsVersion, packageName, extras, null, null); + } + + private void callGetService(GmsService service, IGmsCallbacks callback, int gmsVersion, String packageName, Bundle extras, String accountName, String[] scopes) throws RemoteException { GetServiceRequest request = new GetServiceRequest(service.SERVICE_ID); request.gmsVersion = gmsVersion; request.packageName = packageName; request.extras = extras; - request.account = null; - request.scopes = null; + request.account = accountName == null ? null : new Account(accountName, BuildConfig.BASE_PACKAGE_NAME); + request.scopes = scopes == null ? null : scopesFromStringArray(scopes); getService(callback, request); } + private Scope[] scopesFromStringArray(String[] arr) { + Scope[] scopes = new Scope[arr.length]; + for (int i = 0; i < arr.length; i++) { + scopes[i] = new Scope(arr[i]); + } + return scopes; + } + @Override public void getService(IGmsCallbacks callback, GetServiceRequest request) throws RemoteException { GmsService gmsService = GmsService.byServiceId(request.serviceId); @@ -88,7 +104,7 @@ public abstract class AbstractGmsServiceBroker extends IGmsServiceBroker.Stub { public abstract void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException; @Override - public void validateAccount(IGmsCallbacks callback, ValidateAccountRequest request) { + public void validateAccount(IGmsCallbacks callback, ValidateAccountRequest request) throws RemoteException { throw new IllegalArgumentException("ValidateAccountRequest not supported"); } diff --git a/play-services-base-core/src/main/java/org/microg/gms/BaseService.java b/play-services-base-core/src/main/java/org/microg/gms/BaseService.java index 38545ff0..3941e508 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/BaseService.java +++ b/play-services-base-core/src/main/java/org/microg/gms/BaseService.java @@ -21,7 +21,6 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Log; -import androidx.annotation.NonNull; import androidx.lifecycle.LifecycleService; import com.google.android.gms.common.internal.GetServiceRequest; @@ -59,7 +58,7 @@ public abstract class BaseService extends LifecycleService { } @Override - public IBinder onBind(@NonNull Intent intent) { + public IBinder onBind(Intent intent) { super.onBind(intent); Log.d(TAG, "onBind: " + intent); return broker.asBinder(); diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java b/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java index d4ab5940..55bffb3c 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java @@ -16,6 +16,8 @@ package org.microg.gms.common; +import android.annotation.SuppressLint; + import android.app.ActivityManager; import android.content.Context; import android.content.pm.ConfigurationInfo; @@ -26,6 +28,8 @@ import android.opengl.GLES10; import android.os.Build; import android.util.DisplayMetrics; +import org.microg.gms.profile.ProfileManager; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -56,6 +60,7 @@ public class DeviceConfiguration { public int widthPixels; public DeviceConfiguration(Context context) { + ProfileManager.ensureInitialized(context); ConfigurationInfo configurationInfo = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getDeviceConfigurationInfo(); touchScreen = configurationInfo.reqTouchScreen; keyboardType = configurationInfo.reqKeyboardType; @@ -78,29 +83,35 @@ public class DeviceConfiguration { } Collections.sort(sharedLibraries); availableFeatures = new ArrayList<>(); - if (packageManager.getSystemAvailableFeatures() != null) { - for (FeatureInfo featureInfo : packageManager.getSystemAvailableFeatures()) { - if (featureInfo != null && featureInfo.name != null) availableFeatures.add(featureInfo.name); - } + packageManager.getSystemAvailableFeatures(); + for (FeatureInfo featureInfo : packageManager.getSystemAvailableFeatures()) { + if (featureInfo != null && featureInfo.name != null) + availableFeatures.add(featureInfo.name); } Collections.sort(availableFeatures); this.nativePlatforms = getNativePlatforms(); widthPixels = displayMetrics.widthPixels; heightPixels = displayMetrics.heightPixels; - locales = new ArrayList<>(Arrays.asList(context.getAssets().getLocales())); - for (int i = 0; i < locales.size(); i++) { - locales.set(i, locales.get(i).replace("-", "_")); - } - Collections.sort(locales); + locales = getLocales(context); Set glExtensions = new HashSet<>(); addEglExtensions(glExtensions); this.glExtensions = new ArrayList<>(glExtensions); Collections.sort(this.glExtensions); } - @SuppressWarnings({"deprecation", "InlinedApi"}) + @SuppressLint("GetLocales") + private static List getLocales(Context context) { + List locales = new ArrayList(); + locales.addAll(Arrays.asList(context.getAssets().getLocales())); + for (int i = 0; i < locales.size(); i++) { + locales.set(i, locales.get(i).replace("-", "_")); + } + Collections.sort(locales); + return locales; + } + + @SuppressWarnings({"InlinedApi"}) private static List getNativePlatforms() { - List nativePlatforms; return Arrays.asList(Build.SUPPORTED_ABIS); } diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/ForegroundServiceContext.java b/play-services-base-core/src/main/java/org/microg/gms/common/ForegroundServiceContext.java index c7a2ff52..417ad6f7 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/ForegroundServiceContext.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/ForegroundServiceContext.java @@ -1,5 +1,7 @@ package org.microg.gms.common; +import static android.os.Build.VERSION.SDK_INT; + import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationChannel; @@ -11,7 +13,6 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.os.PowerManager; import android.provider.Settings; import android.util.Log; @@ -33,7 +34,7 @@ public class ForegroundServiceContext extends ContextWrapper { if (!isIgnoringBatteryOptimizations()) { Log.d(TAG, "Starting in foreground mode."); service.putExtra(EXTRA_FOREGROUND, true); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (SDK_INT >= 26) { return super.startForegroundService(service); } else { return super.startService(service); @@ -75,7 +76,7 @@ public class ForegroundServiceContext extends ContextWrapper { String serviceName = getServiceName(service); Log.d(tag, "Started " + serviceName + " in foreground mode."); try { - Notification notification = buildForegroundNotification(service, serviceName); + Notification notification = buildForegroundNotification(service); service.startForeground(serviceName.hashCode(), notification); Log.d(tag, "Notification: " + notification); } catch (Exception e) { @@ -85,9 +86,9 @@ public class ForegroundServiceContext extends ContextWrapper { } @SuppressLint("BatteryLife") - private static Notification buildForegroundNotification(Context context, String serviceName) { + private static Notification buildForegroundNotification(Context context) { Intent notificationIntent = new Intent(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (SDK_INT >= 31) { notificationIntent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); notificationIntent.setData(Uri.parse("package:" + context.getPackageName())); } else { @@ -97,8 +98,8 @@ public class ForegroundServiceContext extends ContextWrapper { PendingIntent notificationPendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, - 0); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + PendingIntent.FLAG_IMMUTABLE); + if (SDK_INT >= 26) { NotificationChannel Channel = new NotificationChannel("foreground-service", context.getResources().getString(R.string.notification_service_name), NotificationManager.IMPORTANCE_LOW); diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java index 787d2e11..03fd8b93 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java @@ -16,6 +16,10 @@ package org.microg.gms.common; +import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; +import static org.microg.gms.common.Constants.GMS_PACKAGE_SIGNATURE_SHA1; + +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.Application; import android.app.PendingIntent; @@ -28,6 +32,8 @@ import android.util.Log; import androidx.annotation.Nullable; +import org.microg.gms.base.core.BuildConfig; + import java.lang.reflect.Method; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -35,29 +41,25 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; - -import static android.os.Build.VERSION.SDK_INT; -import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; -import static org.microg.gms.common.Constants.GMS_PACKAGE_SIGNATURE_SHA1; -import static org.microg.gms.common.Constants.GMS_SECONDARY_PACKAGE_SIGNATURE_SHA1; +import java.util.Objects; public class PackageUtils { private static final String GOOGLE_PLATFORM_KEY = GMS_PACKAGE_SIGNATURE_SHA1; - private static final String GOOGLE_PLATFORM_KEY_2 = GMS_SECONDARY_PACKAGE_SIGNATURE_SHA1; - private static final String GOOGLE_APP_KEY = "24bb24c05e47e0aefa68a58a766179d9b613a600"; - private static final String[] GOOGLE_PRIMARY_KEYS = {GOOGLE_PLATFORM_KEY, GOOGLE_PLATFORM_KEY_2, GOOGLE_APP_KEY}; + private static final String GOOGLE_LEGACY_KEY = "58e1c4133f7441ec3d2c270270a14802da47ba0e"; // Seems to be no longer used. + private static final String[] GOOGLE_PRIMARY_KEYS = {GOOGLE_PLATFORM_KEY, "24bb24c05e47e0aefa68a58a766179d9b613a600", "afb0fed5eeaebdd86f56a97742f4b6b33ef59875", "61226bdb57cc32c8a2a9ef71f7bc9548e95dcc0b", "3a82b5ee26bc46bf68113d920e610cd090198d4a"}; - private static final Map KNOWN_GOOGLE_PACKAGES; + public static final Map KNOWN_GOOGLE_PACKAGES; static { KNOWN_GOOGLE_PACKAGES = new HashMap<>(); + KNOWN_GOOGLE_PACKAGES.put("com.google.android.apps.youtube", "24bb24c05e47e0aefa68a58a766179d9b613a600"); KNOWN_GOOGLE_PACKAGES.put("com.google.android.apps.youtube.music", "afb0fed5eeaebdd86f56a97742f4b6b33ef59875"); - KNOWN_GOOGLE_PACKAGES.put("com.google.android.youtube", "24bb24c05e47e0aefa68a58a766179d9b613a600"); } public static boolean isGooglePackage(Context context, String packageName) { String signatureDigest = firstSignatureDigest(context, packageName); + packageName = PackageSpoofUtils.spoofPackageName(context.getPackageManager(), packageName); return isGooglePackage(packageName, signatureDigest); } @@ -74,7 +76,7 @@ public class PackageUtils { if (signatureDigest == null) return false; if (Arrays.asList(GOOGLE_PRIMARY_KEYS).contains(signatureDigest)) return true; if (!KNOWN_GOOGLE_PACKAGES.containsKey(packageName)) return false; - return KNOWN_GOOGLE_PACKAGES.get(packageName).equals(signatureDigest); + return Objects.equals(KNOWN_GOOGLE_PACKAGES.get(packageName), signatureDigest); } public static void assertExtendedAccess(Context context) { @@ -84,14 +86,14 @@ public class PackageUtils { public static boolean callerHasExtendedAccess(Context context) { String[] packagesForUid = context.getPackageManager().getPackagesForUid(Binder.getCallingUid()); - if (packagesForUid != null && packagesForUid.length != 0) { + if (packagesForUid != null) { for (String packageName : packagesForUid) { packageName = PackageSpoofUtils.spoofPackageName(context.getPackageManager(), packageName); if (isGooglePackage(context, packageName) || GMS_PACKAGE_NAME.equals(packageName)) return true; } } - return context.checkCallingPermission("org.mgoogle.gms.EXTENDED_ACCESS") == PackageManager.PERMISSION_GRANTED; + return context.checkCallingPermission(BuildConfig.BASE_PACKAGE_NAME + ".gms.EXTENDED_ACCESS") == PackageManager.PERMISSION_GRANTED; } public static void checkPackageUid(Context context, String packageName, int callingUid) { @@ -114,16 +116,16 @@ public class PackageUtils { if (packageName.equals("com.google.android.youtube.tv") || packageName.contains("youtube.tv")) { return "61226bdb57cc32c8a2a9ef71f7bc9548e95dcc0b"; } - if (packageName.equals("com.google.android.youtube") || packageName.contains("youtube")) { + if (packageName.equals("com.google.android.apps.photos") || packageName.contains("apps.photos") || packageName.equals("com.google.android.youtube") || packageName.contains("youtube")) { return "24bb24c05e47e0aefa68a58a766179d9b613a600"; } final PackageInfo info; try { info = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); } catch (PackageManager.NameNotFoundException e) { - return null; + return KNOWN_GOOGLE_PACKAGES.get(packageName); } - if (info != null && info.signatures != null && info.signatures.length > 0) { + if (info != null && info.signatures != null) { for (Signature sig : info.signatures) { String digest = sha1sum(sig.toByteArray()); if (digest != null) { @@ -148,7 +150,7 @@ public class PackageUtils { } catch (PackageManager.NameNotFoundException e) { return null; } - if (info != null && info.signatures != null && info.signatures.length > 0) { + if (info != null && info.signatures != null) { for (Signature sig : info.signatures) { byte[] digest = sha1bytes(sig.toByteArray()); if (digest != null) { @@ -179,10 +181,7 @@ public class PackageUtils { } catch (final NoSuchAlgorithmException e) { return null; } - if (md != null) { - return md.digest(bytes); - } - return null; + return md.digest(bytes); } @Nullable @@ -221,7 +220,7 @@ public class PackageUtils { if (suggestedCallerPid > 0 && suggestedCallerPid != callingPid) { throw new SecurityException("suggested PID [" + suggestedCallerPid + "] and real calling PID [" + callingPid + "] mismatch!"); } - return getAndCheckPackage(context, suggestedPackageName, callingUid, Binder.getCallingPid()); + return getAndCheckPackage(context, suggestedPackageName, callingUid, callingPid); } @Nullable @@ -253,7 +252,6 @@ public class PackageUtils { } @Nullable - @Deprecated public static String packageFromProcessId(Context context, int pid) { ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (manager == null) return null; @@ -278,7 +276,6 @@ public class PackageUtils { return null; } - @SuppressWarnings("deprecation") public static String packageFromPendingIntent(PendingIntent pi) { if (pi == null) return null; return pi.getCreatorPackage(); @@ -288,8 +285,10 @@ public class PackageUtils { if (android.os.Build.VERSION.SDK_INT >= 28) return Application.getProcessName(); try { + @SuppressLint("PrivateApi") Class activityThread = Class.forName("android.app.ActivityThread"); String methodName = "currentProcessName"; + @SuppressLint("DiscouragedPrivateApi") Method getProcessName = activityThread.getDeclaredMethod(methodName); return (String) getProcessName.invoke(null); } catch (Exception e) { @@ -334,17 +333,12 @@ public class PackageUtils { } catch (final NoSuchAlgorithmException e) { return null; } - if (md != null) { - bytes = md.digest(bytes); - if (bytes != null) { - StringBuilder sb = new StringBuilder(2 * bytes.length); - for (byte b : bytes) { - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } + bytes = md.digest(bytes); + StringBuilder sb = new StringBuilder(2 * bytes.length); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); } - return null; + return sb.toString(); } public static int versionCode(Context context, String packageName) { diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/common/PackageSpoofUtils.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/common/PackageSpoofUtils.kt index 94ec088d..7aec1077 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/common/PackageSpoofUtils.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/common/PackageSpoofUtils.kt @@ -4,15 +4,17 @@ import android.content.pm.PackageManager import android.os.Bundle import android.util.Log +import org.microg.gms.base.core.BuildConfig + /** * utilities to spoof package information */ internal object PackageSpoofUtils { private const val TAG = "SpoofUtils" private const val META_SPOOF_PACKAGE_NAME = - "com.mgoogle.android.gms.SPOOFED_PACKAGE_NAME" + "${BuildConfig.BASE_PACKAGE_NAME}.android.gms.SPOOFED_PACKAGE_NAME" private const val META_SPOOF_PACKAGE_SIGNATURE = - "com.mgoogle.android.gms.SPOOFED_PACKAGE_SIGNATURE" + "${BuildConfig.BASE_PACKAGE_NAME}.android.gms.SPOOFED_PACKAGE_SIGNATURE" private val spoofedPackageNameCache = HashMap() private val spoofedPackageSignatureCache = HashMap() @@ -139,6 +141,7 @@ internal object PackageSpoofUtils { * @param packageName name of the package to get metadata of * @return package metadata bundle */ + @Suppress("DEPRECATION") private fun getPackageMetadata(packageManager: PackageManager, packageName: String): Bundle? { return try { // PackageManager.getPackageInfo() has been deprecated in targetSdkVersion 30+ diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt index c0398c2b..725e0d6a 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt @@ -5,6 +5,8 @@ package org.microg.gms.profile +import android.annotation.TargetApi + object Build { @JvmField var BOARD: String? = null @@ -87,6 +89,9 @@ object Build { @JvmField var SECURITY_PATCH: String? = null + + @JvmField + var DEVICE_INITIAL_SDK_INT: Int = 0 } fun generateWebViewUserAgentString(original: String): String { diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt index 0cbc69b8..76286eab 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt @@ -10,8 +10,8 @@ import android.content.Context import android.content.res.XmlResourceParser import android.util.Log import org.microg.gms.utils.FileXmlResourceParser -import org.microg.mgms.settings.SettingsContract -import org.microg.mgms.settings.SettingsContract.Profile +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.Profile import org.xmlpull.v1.XmlPullParser import java.io.File import java.util.* @@ -32,11 +32,7 @@ object ProfileManager { private fun getSystemProfileFile(): File = File("/system/etc/microg_device_profile.xml") private fun getProfileResId(context: Context, profile: String) = context.resources.getIdentifier("${context.packageName}:xml/profile_$profile".lowercase(Locale.US), null, null) - fun getConfiguredProfile(context: Context): String = Profile.getContentUri(context)?.let { - SettingsContract.getSettings(context, - it, arrayOf(Profile.PROFILE)) { it.getString(0) } - } - ?: PROFILE_AUTO + fun getConfiguredProfile(context: Context): String = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.PROFILE)) { it.getString(0) } ?: PROFILE_AUTO fun getAutoProfile(context: Context): String { if (hasProfile(context, PROFILE_SYSTEM) && isAutoProfile(context, PROFILE_SYSTEM)) return PROFILE_SYSTEM @@ -72,43 +68,60 @@ object ProfileManager { PROFILE_AUTO -> false PROFILE_REAL -> false PROFILE_NATIVE -> true - else -> getProfileXml(context, profile)?.use { - var next = it.next() - while (next != XmlPullParser.END_DOCUMENT) { - when (next) { - XmlPullParser.START_TAG -> when (it.name) { - "profile" -> { - return@use it.getAttributeBooleanValue(null, "auto", false) + else -> { + val parser = getProfileXml(context, profile) + if (parser != null) { + try { + var next = parser.next() + while (next != XmlPullParser.END_DOCUMENT) { + when (next) { + XmlPullParser.START_TAG -> when (parser.name) { + "profile" -> { + return@runCatching parser.getAttributeBooleanValue(null, "auto", false) + } + } } + next = parser.next() } + } finally { + parser.close() } - next = it.next() + false + } else { + false } - } == true + } } }.getOrDefault(false) + fun getActiveProfileData(context: Context): Map = + getProfileData(context, getProfile(context), getRealData()) + private fun getProfileData(context: Context, profile: String, realData: Map): Map { try { if (profile in listOf(PROFILE_REAL, PROFILE_NATIVE)) return realData - val profileResId = getProfileResId(context, profile) - if (profileResId == 0) return realData + if (profile != PROFILE_USER && getProfileResId(context, profile) == 0) return realData val resultData = mutableMapOf() resultData.putAll(realData) - getProfileXml(context, profile)?.use { - var next = it.next() - while (next != XmlPullParser.END_DOCUMENT) { - when (next) { - XmlPullParser.START_TAG -> when (it.name) { - "data" -> { - val key = it.getAttributeValue(null, "key") - val value = it.getAttributeValue(null, "value") - resultData[key] = value - Log.d(TAG, "Overwrite from profile: $key = $value") + val parser = getProfileXml(context, profile) + if (parser != null) { + try { + var next = parser.next() + while (next != XmlPullParser.END_DOCUMENT) { + when (next) { + XmlPullParser.START_TAG -> when (parser.name) { + "data" -> { + val key = parser.getAttributeValue(null, "key") + val value = parser.getAttributeValue(null, "value") + resultData[key] = value + Log.d(TAG, "Overwrite from profile: $key = $value") + } } } + next = parser.next() } - next = it.next() + } finally { + parser.close() } } return resultData @@ -119,14 +132,8 @@ object ProfileManager { } private fun getProfile(context: Context) = getConfiguredProfile(context).let { if (it != PROFILE_AUTO) it else getAutoProfile(context) } - private fun getSerialFromSettings(context: Context): String? = - Profile.getContentUri(context)?.let { - SettingsContract.getSettings(context, - it, arrayOf(Profile.SERIAL)) { it.getString(0) } - } - private fun saveSerial(context: Context, serial: String) = - Profile.getContentUri(context) - ?.let { SettingsContract.setSettings(context, it) { put(Profile.SERIAL, serial) } } + private fun getSerialFromSettings(context: Context): String? = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.SERIAL)) { it.getString(0) } + private fun saveSerial(context: Context, serial: String) = SettingsContract.setSettings(context, Profile.getContentUri(context)) { put(Profile.SERIAL, serial) } private fun randomSerial(template: String, prefixLength: Int = (template.length / 2).coerceAtMost(6)): String { val serial = StringBuilder() @@ -162,15 +169,20 @@ object ProfileManager { // From profile try { - getProfileXml(context, profile)?.use { - var next = it.next() - while (next != XmlPullParser.END_DOCUMENT) { - when (next) { - XmlPullParser.START_TAG -> when (it.name) { - "serial" -> return it.getAttributeValue(null, "template") + val parser = getProfileXml(context, profile) + if (parser != null) { + try { + var next = parser.next() + while (next != XmlPullParser.END_DOCUMENT) { + when (next) { + XmlPullParser.START_TAG -> when (parser.name) { + "serial" -> return parser.getAttributeValue(null, "template") + } } + next = parser.next() } - next = it.next() + } finally { + parser.close() } } } catch (e: Exception) { @@ -193,6 +205,7 @@ object ProfileManager { return serial } + @SuppressLint("BlockedPrivateApi") private fun getRealData(): Map = mutableMapOf( "Build.BOARD" to android.os.Build.BOARD, "Build.BOOTLOADER" to android.os.Build.BOOTLOADER, @@ -226,9 +239,15 @@ object ProfileManager { if (android.os.Build.VERSION.SDK_INT >= 23) { put("Build.VERSION.SECURITY_PATCH", android.os.Build.VERSION.SECURITY_PATCH) } + try { + val field = android.os.Build.VERSION::class.java.getDeclaredField("DEVICE_INITIAL_SDK_INT") + field.isAccessible = true + put("Build.VERSION.DEVICE_INITIAL_SDK_INT", field.getInt(null).toString()) + } catch (ignored: Exception) { + } } - private fun applyProfileData(profileData: Map) { + fun applyProfileData(profileData: Map) { fun applyStringField(key: String, valueSetter: (String) -> Unit) = profileData[key]?.let { valueSetter(it) } fun applyIntField(key: String, valueSetter: (Int) -> Unit) = profileData[key]?.toIntOrNull()?.let { valueSetter(it) } fun applyLongField(key: String, valueSetter: (Long) -> Unit) = profileData[key]?.toLongOrNull()?.let { valueSetter(it) } @@ -258,6 +277,7 @@ object ProfileManager { applyStringField("Build.VERSION.RELEASE") { Build.VERSION.RELEASE = it } applyStringField("Build.VERSION.SDK") { Build.VERSION.SDK = it } applyIntField("Build.VERSION.SDK_INT") { Build.VERSION.SDK_INT = it } + applyIntField("Build.VERSION.DEVICE_INITIAL_SDK_INT") { Build.VERSION.DEVICE_INITIAL_SDK_INT = it } if (android.os.Build.VERSION.SDK_INT >= 21) { Build.SUPPORTED_ABIS = profileData["Build.SUPPORTED_ABIS"]?.split(",")?.toTypedArray() ?: emptyArray() } else { @@ -285,30 +305,35 @@ object ProfileManager { fun getProfileName(context: Context, profile: String): String? = getProfileName { getProfileXml(context, profile) } - private fun getProfileName(parserCreator: () -> XmlResourceParser?): String? = parserCreator()?.use { - var next = it.next() - while (next != XmlPullParser.END_DOCUMENT) { - when (next) { - XmlPullParser.START_TAG -> when (it.name) { - "profile" -> { - return@use it.getAttributeValue(null, "name") + private fun getProfileName(parserCreator: () -> XmlResourceParser?): String? { + val parser = parserCreator() + if (parser != null) { + try { + var next = parser.next() + while (next != XmlPullParser.END_DOCUMENT) { + when (next) { + XmlPullParser.START_TAG -> when (parser.name) { + "profile" -> { + return parser.getAttributeValue(null, "name") + } + } } + next = parser.next() } + } finally { + parser.close() } - next = it.next() } - null + return null } fun setProfile(context: Context, profile: String?) { val changed = getProfile(context) != profile val newProfile = profile ?: PROFILE_AUTO val newSerial = if (changed) getSerial(context, newProfile, true) else getSerial(context) - Profile.getContentUri(context)?.let { - SettingsContract.setSettings(context, it) { - put(Profile.PROFILE, newProfile) - if (changed) put(Profile.SERIAL, newSerial) - } + SettingsContract.setSettings(context, Profile.getContentUri(context)) { + put(Profile.PROFILE, newProfile) + if (changed) put(Profile.SERIAL, newSerial) } if (changed && activeProfile != null) applyProfile(context, newProfile, newSerial) } @@ -317,7 +342,7 @@ object ProfileManager { val profileName = getProfileName { FileXmlResourceParser(file) } ?: return false try { Log.d(TAG, "Importing user profile '$profileName'") - file.copyTo(getUserProfileFile(context)) + file.copyTo(getUserProfileFile(context), overwrite = true) if (activeProfile == PROFILE_USER) applyProfile(context, PROFILE_USER) return true } catch (e: Exception) { diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileProvider.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileProvider.kt new file mode 100644 index 00000000..53e4149f --- /dev/null +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileProvider.kt @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2023 e Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.profile + +import android.content.ContentProvider +import android.content.ContentValues +import android.database.Cursor +import android.database.MatrixCursor +import android.net.Uri +import org.microg.gms.settings.SettingsContract + +class ProfileProvider : ContentProvider() { + + val COLUMN_ID = "profile_id" + val COLUMN_VALUE = "profile_value" + + override fun onCreate(): Boolean { + ProfileManager.ensureInitialized(context!!) + return true + } + + override fun query( + uri: Uri, + projection: Array?, + selection: String?, + selectionArgs: Array?, + sortOrder: String? + ): Cursor = + MatrixCursor(arrayOf(COLUMN_ID, COLUMN_VALUE)).apply { + ProfileManager.getActiveProfileData(context!!).entries + .forEach { + addRow(arrayOf(it.key, it.value)) + } + } + + override fun getType(uri: Uri): String { + return "vnd.android.cursor.item/vnd.${SettingsContract.getAuthority(context!!)}.${uri.path}" + } + + override fun insert(uri: Uri, values: ContentValues?): Nothing = throw UnsupportedOperationException() + + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Nothing = + throw UnsupportedOperationException() + + override fun update( + uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array? + ): Nothing = throw UnsupportedOperationException() + +} diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/MetaDataPreferences.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/MetaDataPreferences.kt new file mode 100644 index 00000000..1e21f6d9 --- /dev/null +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/MetaDataPreferences.kt @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.settings + +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.PackageManager.GET_META_DATA +import android.os.Bundle + +class MetaDataPreferences(private val context: Context, private val prefix: String = "") : SharedPreferences { + private val metaData by lazy { + runCatching { context.packageManager.getApplicationInfo(context.packageName, GET_META_DATA) }.getOrNull()?.metaData ?: Bundle.EMPTY + } + + override fun getAll(): Map = metaData.keySet().filter { it.startsWith(prefix) }.associate { it.substring(prefix.length) to metaData.get(it) } + + override fun getString(key: String, defValue: String?): String? = metaData.getString(prefix + key, defValue) + + override fun getStringSet(key: String, defValues: Set?): Set? = metaData.getStringArray(prefix + key)?.toSet() ?: defValues + + override fun getInt(key: String?, defValue: Int): Int = metaData.getInt(prefix + key, defValue) + + override fun getLong(key: String?, defValue: Long): Long = metaData.getLong(prefix + key, defValue) + + override fun getFloat(key: String?, defValue: Float): Float = metaData.getFloat(prefix + key, defValue) + + override fun getBoolean(key: String?, defValue: Boolean): Boolean = metaData.getBoolean(prefix + key, defValue) + + override fun contains(key: String?): Boolean = metaData.containsKey(prefix + key) + + override fun edit(): SharedPreferences.Editor { + throw UnsupportedOperationException() + } + + override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) { + throw UnsupportedOperationException() + } + + override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) { + throw UnsupportedOperationException() + } +} \ No newline at end of file diff --git a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsContract.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt similarity index 80% rename from play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsContract.kt rename to play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt index 4f930962..8851d1a1 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsContract.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt @@ -1,4 +1,4 @@ -package org.microg.mgms.settings +package org.microg.gms.settings import android.content.ContentValues import android.content.Context @@ -12,7 +12,8 @@ object SettingsContract { object CheckIn { private const val id = "check-in" - fun getContentUri(context: Context): Uri? = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" const val ENABLED = "checkin_enable_service" const val ANDROID_ID = "androidId" @@ -21,7 +22,6 @@ object SettingsContract { const val SECURITY_TOKEN = "securityToken" const val VERSION_INFO = "versionInfo" const val DEVICE_DATA_VERSION_INFO = "deviceDataVersionInfo" - const val BRAND_SPOOF = "brandSpoof" const val HIDE_LAUNCHER_ICON = "hideLauncherIcon" @@ -43,7 +43,8 @@ object SettingsContract { object Gcm { private const val id = "gcm" - fun getContentUri(context: Context): Uri? = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" const val FULL_LOG = "gcm_full_log" const val LAST_PERSISTENT_ID = "gcm_last_persistent_id" @@ -74,20 +75,24 @@ object SettingsContract { object Auth { private const val id = "auth" - fun getContentUri(context: Context): Uri? = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" const val TRUST_GOOGLE = "auth_manager_trust_google" const val VISIBLE = "auth_manager_visible" + const val INCLUDE_ANDROID_ID = "auth_include_android_id" val PROJECTION = arrayOf( TRUST_GOOGLE, VISIBLE, + INCLUDE_ANDROID_ID, ) } object Profile { private const val id = "profile" - fun getContentUri(context: Context): Uri? = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" const val PROFILE = "device_profile" const val SERIAL = "device_profile_serial" diff --git a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt similarity index 86% rename from play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt rename to play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt index ba7e2e25..39dc00e1 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt @@ -4,9 +4,8 @@ */ @file:Suppress("DEPRECATION") -package org.microg.mgms.settings +package org.microg.gms.settings -import android.annotation.SuppressLint import android.content.ContentProvider import android.content.ContentValues import android.content.Context @@ -17,22 +16,20 @@ import android.database.MatrixCursor import android.net.Uri import android.preference.PreferenceManager import org.microg.gms.common.PackageUtils.warnIfNotMainProcess -import org.microg.mgms.settings.SettingsContract.Auth -import org.microg.mgms.settings.SettingsContract.CheckIn -import org.microg.mgms.settings.SettingsContract.Gcm -import org.microg.mgms.settings.SettingsContract.Profile -import org.microg.mgms.settings.SettingsContract.getAuthority +import org.microg.gms.settings.SettingsContract.Auth +import org.microg.gms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract.Gcm +import org.microg.gms.settings.SettingsContract.Profile +import org.microg.gms.settings.SettingsContract.getAuthority import java.io.File +private const val SETTINGS_PREFIX = "org.microg.gms.settings." + /** * All settings access should go through this [ContentProvider], * because it provides safe access from different processes which normal [SharedPreferences] don't. */ -@SuppressLint("DiscouragedPrivateApi") class SettingsProvider : ContentProvider() { - companion object { - const val INTERVAL = 1 * 60 * 1000 // 1 minute - } private val preferences: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(context) @@ -52,6 +49,10 @@ class SettingsProvider : ContentProvider() { } } + private val metaDataPreferences: SharedPreferences by lazy { + MetaDataPreferences(context!!, SETTINGS_PREFIX) + } + override fun onCreate(): Boolean { return true } @@ -99,7 +100,7 @@ class SettingsProvider : ContentProvider() { CheckIn.VERSION_INFO -> checkInPrefs.getString(key, "") ?: "" CheckIn.DEVICE_DATA_VERSION_INFO -> checkInPrefs.getString(key, "") ?: "" CheckIn.BRAND_SPOOF -> getSettingsBoolean(key, false) - CheckIn.HIDE_LAUNCHER_ICON -> getSettingsBoolean(key, false) + CheckIn.HIDE_LAUNCHER_ICON -> getSettingsBoolean(key, true) else -> throw IllegalArgumentException() } } @@ -167,9 +168,9 @@ class SettingsProvider : ContentProvider() { Gcm.NETWORK_ROAMING -> Integer.parseInt(preferences.getString(key, "0") ?: "0") Gcm.NETWORK_OTHER -> Integer.parseInt(preferences.getString(key, "0") ?: "0") - Gcm.LEARNT_MOBILE -> preferences.getInt(key, INTERVAL) - Gcm.LEARNT_WIFI -> preferences.getInt(key, INTERVAL) - Gcm.LEARNT_OTHER -> preferences.getInt(key, INTERVAL) + Gcm.LEARNT_MOBILE -> preferences.getInt(key, 300000) + Gcm.LEARNT_WIFI -> preferences.getInt(key, 300000) + Gcm.LEARNT_OTHER -> preferences.getInt(key, 300000) else -> throw IllegalArgumentException("Unknown key: $key") } @@ -204,6 +205,7 @@ class SettingsProvider : ContentProvider() { when (key) { Auth.TRUST_GOOGLE -> getSettingsBoolean(key, true) Auth.VISIBLE -> getSettingsBoolean(key, false) + Auth.INCLUDE_ANDROID_ID -> getSettingsBoolean(key, true) else -> throw IllegalArgumentException("Unknown key: $key") } } @@ -215,6 +217,7 @@ class SettingsProvider : ContentProvider() { when (key) { Auth.TRUST_GOOGLE -> editor.putBoolean(key, value as Boolean) Auth.VISIBLE -> editor.putBoolean(key, value as Boolean) + Auth.INCLUDE_ANDROID_ID -> editor.putBoolean(key, value as Boolean) else -> throw IllegalArgumentException("Unknown key: $key") } } @@ -269,12 +272,16 @@ class SettingsProvider : ContentProvider() { * @return the current setting as [Int], because [ContentProvider] does not support [Boolean]. */ private fun getSettingsBoolean(key: String, def: Boolean): Int { - return listOf(preferences, systemDefaultPreferences).getBooleanAsInt(key, def) + return listOf(preferences, systemDefaultPreferences, metaDataPreferences).getBooleanAsInt(key, def) } - private fun getSettingsString(key: String, def: String? = null): String? = listOf(preferences, systemDefaultPreferences).getString(key, def) + private fun getSettingsString(key: String, def: String? = null): String? = listOf(preferences, systemDefaultPreferences, metaDataPreferences).getString(key, def) + private fun getSettingsInt(key: String, def: Int): Int = listOf(preferences, systemDefaultPreferences, metaDataPreferences).getInt(key, def) + private fun getSettingsLong(key: String, def: Long): Long = listOf(preferences, systemDefaultPreferences, metaDataPreferences).getLong(key, def) private fun List.getString(key: String, def: String?): String? = foldRight(def) { preferences, defValue -> preferences?.getString(key, defValue) ?: defValue } + private fun List.getInt(key: String, def: Int): Int = foldRight(def) { preferences, defValue -> preferences?.getInt(key, defValue) ?: defValue } + private fun List.getLong(key: String, def: Long): Long = foldRight(def) { preferences, defValue -> preferences?.getLong(key, defValue) ?: defValue } private fun List.getBoolean(key: String, def: Boolean): Boolean = foldRight(def) { preferences, defValue -> preferences?.getBoolean(key, defValue) ?: defValue } private fun List.getBooleanAsInt(key: String, def: Boolean): Int = if (getBoolean(key, def)) 1 else 0 } diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/utils/BinderUtils.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/BinderUtils.kt index 79e8ff8f..226bb431 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/utils/BinderUtils.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/BinderUtils.kt @@ -12,13 +12,13 @@ import android.util.Log private const val TAG = "BinderUtils" -fun IBinder.warnOnTransactionIssues(code: Int, reply: Parcel?, flags: Int, base: () -> Boolean): Boolean { +fun IBinder.warnOnTransactionIssues(code: Int, reply: Parcel?, flags: Int, tag: String = TAG, base: () -> Boolean): Boolean { if (base.invoke()) { if ((flags and Binder.FLAG_ONEWAY) > 0 && (reply?.dataSize() ?: 0) > 0) { - Log.w(TAG, "Method $code in $interfaceDescriptor is oneway, but returned data") + Log.w(tag, "Method $code in $interfaceDescriptor is oneway, but returned data") } return true } - Log.w(TAG, "Unknown method $code in $interfaceDescriptor, skipping") + Log.w(tag, "Unknown method $code in $interfaceDescriptor, skipping") return (flags and Binder.FLAG_ONEWAY) > 0 // Don't return false on oneway transaction to suppress warning } diff --git a/play-services-base-core/src/main/res/layout/preference_app_heading.xml b/play-services-base-core/src/main/res/layout/preference_app_heading.xml new file mode 100644 index 00000000..cb7e4edf --- /dev/null +++ b/play-services-base-core/src/main/res/layout/preference_app_heading.xml @@ -0,0 +1,43 @@ + + + + + + + \ No newline at end of file diff --git a/play-services-base-core/src/main/res/layout/preference_switch_bar.xml b/play-services-base-core/src/main/res/layout/preference_switch_bar.xml new file mode 100644 index 00000000..9818305b --- /dev/null +++ b/play-services-base-core/src/main/res/layout/preference_switch_bar.xml @@ -0,0 +1,42 @@ + + + + + + + + \ No newline at end of file diff --git a/play-services-base-core/src/main/res/values-ja/strings.xml b/play-services-base-core/src/main/res/values-ja/strings.xml index 97f279c1..a603746e 100644 --- a/play-services-base-core/src/main/res/values-ja/strings.xml +++ b/play-services-base-core/src/main/res/values-ja/strings.xml @@ -4,8 +4,8 @@ ~ SPDX-License-Identifier: Apache-2.0 --> - 電池の最適化 の警告 + バッテリー節約の警告 通知を展開する - Vanced microGをバックグラウンドで保持 - ここをタップして、Vanced microGの[電池の最適化]を無効にし、デバイスを再起動して通知を削除してください。 + microGをバックグラウンドで保持 + こちらをタップして、microGのバッテリー最適化を無効にし、デバイスを再起動して通知を削除する diff --git a/play-services-base/build.gradle b/play-services-base/build.gradle index 7c7057d4..e1cff12a 100644 --- a/play-services-base/build.gradle +++ b/play-services-base/build.gradle @@ -26,8 +26,8 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = 17 + targetCompatibility = 17 } namespace 'org.microg.gms.base' } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/GoogleApiAvailability.java b/play-services-base/src/main/java/com/google/android/gms/common/GoogleApiAvailability.java index 61ee1ec0..74f857a3 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/GoogleApiAvailability.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/GoogleApiAvailability.java @@ -16,25 +16,65 @@ package com.google.android.gms.common; -import android.app.Dialog; +import static com.google.android.gms.common.ConnectionResult.INTERNAL_ERROR; +import static com.google.android.gms.common.ConnectionResult.INVALID_ACCOUNT; +import static com.google.android.gms.common.ConnectionResult.NETWORK_ERROR; +import static com.google.android.gms.common.ConnectionResult.RESOLUTION_REQUIRED; +import static com.google.android.gms.common.ConnectionResult.SERVICE_DISABLED; +import static com.google.android.gms.common.ConnectionResult.SERVICE_INVALID; +import static com.google.android.gms.common.ConnectionResult.SERVICE_MISSING; +import static com.google.android.gms.common.ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED; +import static com.google.android.gms.common.ConnectionResult.SIGN_IN_REQUIRED; +import static com.google.android.gms.common.ConnectionResult.SUCCESS; +import android.app.Activity; +import android.app.Dialog; +import android.app.PendingIntent; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.Log; + +import androidx.fragment.app.FragmentActivity; + +import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.Tasks; + +import org.microg.gms.common.Constants; import org.microg.gms.common.PublicApi; @PublicApi public class GoogleApiAvailability { + private static final String TAG = "GmsApiAvailability"; + + /** + * Package name for Google Play services. + */ + public static final String GOOGLE_PLAY_SERVICES_PACKAGE = Constants.GMS_PACKAGE_NAME; + + /** + * Google Play services client library version (declared in library's AndroidManifest.xml android:versionCode). + */ + public static final int GOOGLE_PLAY_SERVICES_VERSION_CODE = Constants.GMS_VERSION_CODE; + + private static GoogleApiAvailability instance; private GoogleApiAvailability() { } - private static final class InstanceHolder { - static final GoogleApiAvailability instance = new GoogleApiAvailability(); - } - /** * Returns the singleton instance of GoogleApiAvailability. */ public static GoogleApiAvailability getInstance() { - return InstanceHolder.instance; + if (instance == null) { + synchronized (GoogleApiAvailability.class) { + if (instance == null) { + instance = new GoogleApiAvailability(); + } + } + } + return instance; } @@ -44,10 +84,219 @@ public class GoogleApiAvailability { * to the Play Store if Google Play services is out of date or missing, or to system settings * if Google Play services is disabled on the device. * + * @param activity parent activity for creating the dialog, also used for identifying language to display dialog in. + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param requestCode The requestCode given when calling startActivityForResult. */ - public Dialog getErrorDialog() { + public Dialog getErrorDialog(Activity activity, int errorCode, int requestCode) { + return getErrorDialog(activity, errorCode, requestCode, null); + } + + /** + * Returns a dialog to address the provided errorCode. The returned dialog displays a localized + * message about the error and upon user confirmation (by tapping on dialog) will direct them + * to the Play Store if Google Play services is out of date or missing, or to system settings + * if Google Play services is disabled on the device. + * + * @param activity parent activity for creating the dialog, also used for identifying language to display dialog in. + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param requestCode The requestCode given when calling startActivityForResult. + * @param cancelListener The {@link DialogInterface.OnCancelListener} to invoke if the dialog is canceled. + */ + public Dialog getErrorDialog(Activity activity, int errorCode, int requestCode, DialogInterface.OnCancelListener cancelListener) { // TODO return null; } + /** + * Returns a PendingIntent to address the provided connection failure. + *

+ * If {@link ConnectionResult#hasResolution()} is true, then {@link ConnectionResult#getResolution()} + * will be returned. Otherwise, the returned PendingIntent will direct the user to either the + * Play Store if Google Play services is out of date or missing, or system settings if Google + * Play services is disabled on the device. + * + * @param context parent context for creating the PendingIntent. + * @param result the connection failure. If successful or the error is not resolvable by the user, null is returned. + */ + public PendingIntent getErrorResolutionPendingIntent(Context context, ConnectionResult result) { + if (result.hasResolution()) { + return result.getResolution(); + } + return getErrorResolutionPendingIntent(context, result.getErrorCode(), 0); + } + + /** + * Returns a PendingIntent to address the provided errorCode. It will direct the user to either + * the Play Store if Google Play services is out of date or missing, or system settings if + * Google Play services is disabled on the device. + * + * @param context parent context for creating the PendingIntent. + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param requestCode The requestCode given when calling startActivityForResult. + */ + public PendingIntent getErrorResolutionPendingIntent(Context context, int errorCode, int requestCode) { + // TODO + return null; + } + + /** + * Returns a human-readable string of the error code returned from {@link #isGooglePlayServicesAvailable(Context)}. + */ + public final String getErrorString(int errorCode) { + return ConnectionResult.getStatusString(errorCode); + } + + /** + * Verifies that Google Play services is installed and enabled on this device, and that the + * version installed on this device is no older than the one required by this client. + * + * @return status code indicating whether there was an error. Can be one of following in + * {@link ConnectionResult}: SUCCESS, SERVICE_MISSING, SERVICE_UPDATING, + * SERVICE_VERSION_UPDATE_REQUIRED, SERVICE_DISABLED, SERVICE_INVALID + */ + public int isGooglePlayServicesAvailable(Context context) { + Log.d(TAG, "As we can't know right now if the later desired feature is available, " + + "we just pretend it to be."); + return SUCCESS; + } + + /** + * Verifies that Google Play services is installed and enabled on this device, and that the version installed on + * this device is no older than the one required by this client or the version is not older than the one specified + * in minApkVersion. + * + * @return status code indicating whether there was an error. Can be one of following in + * {@link ConnectionResult}: SUCCESS, SERVICE_MISSING, SERVICE_UPDATING, + * SERVICE_VERSION_UPDATE_REQUIRED, SERVICE_DISABLED, SERVICE_INVALID + */ + public int isGooglePlayServicesAvailable(Context context, int minApkVersion) { + return isGooglePlayServicesAvailable(context); + } + + /** + * Determines whether an error can be resolved via user action. If true, proceed by calling + * {@link #getErrorDialog(Activity, int, int)} and showing the dialog. + * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)}, or + * returned to your application via {@link OnConnectionFailedListener#onConnectionFailed(ConnectionResult)} + * @return true if the error is resolvable with {@link #getErrorDialog(Activity, int, int)} + */ + public final boolean isUserResolvableError(int errorCode) { + switch (errorCode) { + case SERVICE_MISSING: + case SERVICE_VERSION_UPDATE_REQUIRED: + case SERVICE_DISABLED: + case SERVICE_INVALID: + return true; + case SIGN_IN_REQUIRED: + case INVALID_ACCOUNT: + case RESOLUTION_REQUIRED: + case NETWORK_ERROR: + case INTERNAL_ERROR: + default: + return false; + } + } + + /** + * Attempts to make Google Play services available on this device. If Play Services is already + * available, the returned {@link Task} may complete immediately. + *

+ * If it is necessary to display UI in order to complete this request (e.g. sending the user + * to the Google Play store) the passed {@link Activity} will be used to display this UI. + *

+ * It is recommended to call this method from {@link Activity#onCreate(Bundle)}. + * If the passed {@link Activity} completes before the returned {@link Task} completes, the + * Task will fail with a {@link java.util.concurrent.CancellationException}. + *

+ * This method must be called from the main thread. + * + * @return A {@link Task}. If this Task completes without throwing an exception, Play Services + * is available on this device. + */ + public Task makeGooglePlayServicesAvailable(Activity activity) { + int status = isGooglePlayServicesAvailable(activity); + if (status == SUCCESS) { + return Tasks.forResult(null); + } + // TODO + return Tasks.forResult(null); + } + + /** + * Displays a DialogFragment for an error code returned by {@link #isGooglePlayServicesAvailable(Context)}. + * + * @param activity parent activity for creating the dialog, also used for identifying language to display dialog in. + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param requestCode The requestCode given when calling startActivityForResult. + * @return true if the dialog is shown, false otherwise + * @throws RuntimeException if API level is below 11 and activity is not a {@link FragmentActivity}. + * @see ErrorDialogFragment + * @see SupportErrorDialogFragmet + */ + public boolean showErrorDialogFragment(Activity activity, int errorCode, int requestCode) { + return showErrorDialogFragment(activity, errorCode, requestCode, null); + } + + /** + * Displays a DialogFragment for an error code returned by {@link #isGooglePlayServicesAvailable(Context)}. + * + * @param activity parent activity for creating the dialog, also used for identifying language to display dialog in. + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param requestCode The requestCode given when calling startActivityForResult. + * @param cancelListener The {@link DialogInterface.OnCancelListener} to invoke if the dialog is canceled. + * @return true if the dialog is shown, false otherwise + * @throws RuntimeException if API level is below 11 and activity is not a {@link FragmentActivity}. + */ + public boolean showErrorDialogFragment(Activity activity, int errorCode, int requestCode, DialogInterface.OnCancelListener cancelListener) { + Dialog dialog = getErrorDialog(activity, errorCode, requestCode, cancelListener); + if (dialog == null) { + return false; + } else { + // TODO + return false; + } + } + + /** + * Displays a notification for an error code returned from + * {@link #isGooglePlayServicesAvailable(Context)}, if it is resolvable by the user. + *

+ * This method is similar to {@link #getErrorDialog(int, android.app.Activity, int)}, but is + * provided for background tasks that cannot or should not display dialogs. + * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param context used for identifying language to display dialog in as well as accessing the + * {@link android.app.NotificationManager}. + */ + public void showErrorNotification(Context context, int errorCode) { + if (errorCode == RESOLUTION_REQUIRED) { + Log.e(TAG, "showErrorNotification(context, errorCode) is called for RESOLUTION_REQUIRED when showErrorNotification(context, result) should be called"); + } + + if (isUserResolvableError(errorCode)) { + GooglePlayServicesUtil.showErrorNotification(errorCode, context); + } + } + + /** + * Displays a notification for a connection failure, if it is resolvable by the user. + * + * @param context The calling context used to display the notification. + * @param result The connection failure. If successful or the error is not resolvable by the + * user, no notification is shown. + */ + public void showErrorNotification(Context context, ConnectionResult result) { + PendingIntent pendingIntent = getErrorResolutionPendingIntent(context, result); + if (pendingIntent != null) { + // TODO + } + } } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesClient.java b/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesClient.java index 0941e410..a5642fdd 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesClient.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesClient.java @@ -26,6 +26,20 @@ public interface GooglePlayServicesClient { boolean isConnected(); + boolean isConnecting(); + + void registerConnectionCallbacks(ConnectionCallbacks listener); + + boolean isConnectionCallbacksRegistered(ConnectionCallbacks listener); + + void unregisterConnectionCallbacks(ConnectionCallbacks listener); + + void registerConnectionFailedListener(OnConnectionFailedListener listener); + + boolean isConnectionFailedListenerRegistered(OnConnectionFailedListener listener); + + void unregisterConnectionFailedListener(OnConnectionFailedListener listener); + @Deprecated interface OnConnectionFailedListener { diff --git a/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesUtil.java b/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesUtil.java index d5399280..4850b816 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesUtil.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesUtil.java @@ -16,8 +16,14 @@ package com.google.android.gms.common; +import android.app.Activity; import android.app.Dialog; +import android.app.Fragment; import android.app.PendingIntent; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.PackageManager; +import android.content.res.Resources; import android.util.Log; import org.microg.gms.common.Constants; @@ -34,6 +40,8 @@ import org.microg.gms.common.PublicApi; public class GooglePlayServicesUtil { private static final String TAG = "GooglePlayServicesUtil"; + public static final String GMS_ERROR_DIALOG = "GooglePlayServicesErrorDialog"; + /** * Package name for Google Play services. */ @@ -46,16 +54,45 @@ public class GooglePlayServicesUtil { @Deprecated public static final int GOOGLE_PLAY_SERVICES_VERSION_CODE = Constants.GMS_VERSION_CODE; + /** + * Package name for Google Play Store. + */ + public static final String GOOGLE_PLAY_STORE_PACKAGE = "com.android.vending"; + /** * Returns a dialog to address the provided errorCode. The returned dialog displays a localized * message about the error and upon user confirmation (by tapping on dialog) will direct them * to the Play Store if Google Play services is out of date or missing, or to system settings * if Google Play services is disabled on the device. * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param activity parent activity for creating the dialog, also used for identifying + * language to display dialog in. + * @param requestCode The requestCode given when calling startActivityForResult. */ @Deprecated - public static Dialog getErrorDialog() { - return GoogleApiAvailability.getInstance().getErrorDialog(); + public static Dialog getErrorDialog(int errorCode, Activity activity, int requestCode) { + return getErrorDialog(errorCode, activity, requestCode, null); + } + + /** + * Returns a dialog to address the provided errorCode. The returned dialog displays a localized + * message about the error and upon user confirmation (by tapping on dialog) will direct them + * to the Play Store if Google Play services is out of date or missing, or to system settings + * if Google Play services is disabled on the device. + * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param activity parent activity for creating the dialog, also used for identifying + * language to display dialog in. + * @param requestCode The requestCode given when calling startActivityForResult. + * @param cancelListener The {@link DialogInterface.OnCancelListener} to invoke if the dialog + * is canceled. + */ + @Deprecated + public static Dialog getErrorDialog(int errorCode, Activity activity, int requestCode, DialogInterface.OnCancelListener cancelListener) { + return GoogleApiAvailability.getInstance().getErrorDialog(activity, errorCode, requestCode, cancelListener); } /** @@ -63,17 +100,22 @@ public class GooglePlayServicesUtil { * following places to either the Play Store if Google Play services is out of date or missing, * or system settings if Google Play services is disabled on the device. * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param activity parent context for creating the PendingIntent. + * @param requestCode The requestCode given when calling startActivityForResult. */ @Deprecated - public static PendingIntent getErrorPendingIntent() { + public static PendingIntent getErrorPendingIntent(int errorCode, Activity activity, + int requestCode) { return null; // TODO } /** - * Returns a human-readable string of the error code returned from {@link #isGooglePlayServicesAvailable()}. + * Returns a human-readable string of the error code returned from {@link #isGooglePlayServicesAvailable(Context)}. */ @Deprecated - public static String getErrorString() { + public static String getErrorString(int errorCode) { return null; // TODO } @@ -82,7 +124,27 @@ public class GooglePlayServicesUtil { * application, or null if Google Play services is not available on this device. */ @Deprecated - public static String getOpenSourceSoftwareLicenseInfo() { + public static String getOpenSourceSoftwareLicenseInfo(Context context) { + return null; // TODO + } + + /** + * This gets the Context object of the Buddy APK. This loads the Buddy APK code from the Buddy + * APK into memory. This returned context can be used to create classes and obtain resources + * defined in the Buddy APK. + * + * @return The Context object of the Buddy APK or null if the Buddy APK is not installed on the device. + */ + public static Context getRemoteContext(Context context) { + return null; // TODO + } + + /** + * This gets the Resources object of the Buddy APK. + * + * @return The Resources object of the Buddy APK or null if the Buddy APK is not installed on the device. + */ + public static Resources getRemoteResources(Context context) { return null; // TODO } @@ -95,41 +157,79 @@ public class GooglePlayServicesUtil { * SERVICE_DISABLED, SERVICE_INVALID */ @Deprecated - public static int isGooglePlayServicesAvailable() { + public static int isGooglePlayServicesAvailable(Context context) { Log.d(TAG, "As we can't know right now if the later desired feature is available, " + "we just pretend it to be."); return ConnectionResult.SUCCESS; } @Deprecated - public static boolean isGoogleSignedUid() { + public static boolean isGoogleSignedUid(PackageManager packageManager, int uid) { return false; // TODO } /** * Determines whether an error is user-recoverable. If true, proceed by calling - * {@link #getErrorDialog()} and showing the dialog. + * {@link #getErrorDialog(int, Activity, int)} and showing the dialog. * - * @return true if the error is recoverable with {@link #getErrorDialog()} + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)}, or + * returned to your application via {@link com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener#onConnectionFailed(ConnectionResult)} + * @return true if the error is recoverable with {@link #getErrorDialog(int, Activity, int)} */ @Deprecated - public static boolean isUserRecoverableError() { - return false; // TODO - } - - @Deprecated - public static boolean showErrorDialogFragment() { + public static boolean isUserRecoverableError(int errorCode) { return false; // TODO } /** - * Displays a notification relevant to the provided error code. This method is similar to - * {@link #getErrorDialog()}, but is provided for background - * tasks that cannot or shouldn't display dialogs. + * Display a DialogFragment for an error code returned by {@link #isGooglePlayServicesAvailable(Context)}. * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param activity parent activity for creating the dialog, also used for identifying + * language to display dialog in. + * @param requestCode The requestCode given when calling startActivityForResult. + * @return true if the dialog is shown, false otherwise + * @throws RuntimeException if API level is below 11 and activity is not a {@link android.support.v4.app.FragmentActivity}. */ @Deprecated - public static void showErrorNotification() { + public static boolean showErrorDialogFragment(int errorCode, Activity activity, int requestCode) { + return showErrorDialogFragment(errorCode, activity, requestCode, null); + } + + @Deprecated + public static boolean showErrorDialogFragment(int errorCode, Activity activity, Fragment fragment, int requestCode, DialogInterface.OnCancelListener cancelListener) { + return false; // TODO + } + + /** + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param activity parent activity for creating the dialog, also used for identifying + * language to display dialog in. + * @param requestCode The requestCode given when calling startActivityForResult. + * @param cancelListener The {@link DialogInterface.OnCancelListener} to invoke if the dialog + * is canceled. + * @return true if the dialog is shown, false otherwise. + * @throws RuntimeException if API level is below 11 and activity is not a {@link android.support.v4.app.FragmentActivity}. + */ + @Deprecated + public static boolean showErrorDialogFragment(int errorCode, Activity activity, int requestCode, DialogInterface.OnCancelListener cancelListener) { + return showErrorDialogFragment(errorCode, activity, null, requestCode, cancelListener); + } + + /** + * Displays a notification relevant to the provided error code. This method is similar to + * {@link #getErrorDialog(int, android.app.Activity, int)}, but is provided for background + * tasks that cannot or shouldn't display dialogs. + * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param context used for identifying language to display dialog in as well as accessing the + * {@link android.app.NotificationManager}. + */ + @Deprecated + public static void showErrorNotification(int errorCode, Context context) { // TODO } } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/AccountInfo.java b/play-services-base/src/main/java/com/google/android/gms/common/api/AccountInfo.java index fd400c45..30d6a169 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/AccountInfo.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/AccountInfo.java @@ -20,5 +20,5 @@ import org.microg.safeparcel.AutoSafeParcelable; @Deprecated public class AccountInfo extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(AccountInfo.class); + public static final Creator CREATOR = new AutoCreator(AccountInfo.class); } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/Api.java b/play-services-base/src/main/java/com/google/android/gms/common/api/Api.java index 6b7f15e3..fd91a375 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/Api.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/Api.java @@ -16,6 +16,11 @@ package com.google.android.gms.common.api; +import android.accounts.Account; + +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; + +import org.microg.gms.common.Hide; import org.microg.gms.common.PublicApi; import org.microg.gms.common.api.ApiClientBuilder; @@ -65,6 +70,45 @@ public final class Api { interface NotRequiredOptions extends ApiOptions { } + /** + * {@link ApiOptions} implementation for {@link Api}s that do not take any options. + */ + @PublicApi + final class NoOptions implements NotRequiredOptions { + } + + /** + * Base interface for {@link ApiOptions} that are optional. + */ + @PublicApi + interface Optional extends HasOptions, NotRequiredOptions { + } + + /** + * An interface for {@link ApiOptions} that include an account. + */ + @PublicApi + interface HasAccountOptions extends HasOptions, NotRequiredOptions { + Account getAccount(); + } + + /** + * An interface for {@link ApiOptions} that includes a {@link GoogleSignInAccount} + */ + @PublicApi + interface HasGoogleSignInAccountOptions extends HasOptions { + GoogleSignInAccount getGoogleSignInAccount(); + } } + @Hide + public interface Client { + void connect(); + + void disconnect(); + + boolean isConnected(); + + boolean isConnecting(); + } } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApi.java b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApi.java index f0adb967..4a33737c 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApi.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApi.java @@ -7,22 +7,51 @@ package com.google.android.gms.common.api; import android.content.Context; +import com.google.android.gms.common.api.internal.ApiKey; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.TaskCompletionSource; + +import org.microg.gms.common.Hide; import org.microg.gms.common.PublicApi; import org.microg.gms.common.api.GoogleApiManager; +import org.microg.gms.common.api.PendingGoogleApiCall; @PublicApi -public abstract class GoogleApi implements HasApiKey { - @PublicApi(exclude = true) +public abstract class GoogleApi implements HasApiKey { + private GoogleApiManager manager; + @Hide public Api api; + @Hide + public O options; @PublicApi(exclude = true) protected GoogleApi(Context context, Api api) { this.api = api; - GoogleApiManager.getInstance(); + this.manager = GoogleApiManager.getInstance(context); + } + + @PublicApi(exclude = true) + protected GoogleApi(Context context, Api api, O options) { + this.api = api; + this.manager = GoogleApiManager.getInstance(context); + this.options = options; + } + + @PublicApi(exclude = true) + protected Task scheduleTask(PendingGoogleApiCall apiCall) { + TaskCompletionSource completionSource = new TaskCompletionSource<>(); + manager.scheduleTask(this, apiCall, completionSource); + return completionSource.getTask(); + } + + @Override + @PublicApi(exclude = true) + public ApiKey getApiKey() { + return null; } @PublicApi(exclude = true) public O getOptions() { - return null; + return options; } } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiActivity.java b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiActivity.java index 8a6b0690..b47a2d47 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiActivity.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiActivity.java @@ -1,6 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + package com.google.android.gms.common.api; import android.app.Activity; +import android.content.DialogInterface; -public class GoogleApiActivity extends Activity { +import org.microg.gms.common.PublicApi; + +/** + * Helper activity used by Google Play services APIs to display resolutions for connection errors. + */ +@PublicApi +public class GoogleApiActivity extends Activity implements DialogInterface.OnCancelListener { + @Override + @PublicApi(exclude = true) + public void onCancel(DialogInterface dialog) { + + } } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java index 0e20b6d5..62e52ba3 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java @@ -19,10 +19,15 @@ package com.google.android.gms.common.api; import android.app.Activity; import android.content.Context; import android.os.Bundle; +import android.os.Handler; import android.os.Looper; +import android.view.View; + +import androidx.fragment.app.FragmentActivity; import com.google.android.gms.common.ConnectionResult; +import org.microg.gms.auth.AuthConstants; import org.microg.gms.common.PublicApi; import org.microg.gms.common.api.ApiClientSettings; import org.microg.gms.common.api.GoogleApiClientImpl; @@ -31,6 +36,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; /** * The main entry point for Google Play services integration. @@ -53,6 +59,32 @@ import java.util.Set; @PublicApi @Deprecated public interface GoogleApiClient { + /** + * Connects the client to Google Play services. Blocks until the connection either succeeds or + * fails. This is not allowed on the UI thread. + * + * @return the result of the connection + */ + ConnectionResult blockingConnect(); + + /** + * Connects the client to Google Play services. Blocks until the connection is set or failed or + * has timed out. This is not allowed on the UI thread. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the {@code timeout} argument + * @return the result of the connection + */ + ConnectionResult blockingConnect(long timeout, TimeUnit unit); + + /** + * Clears the account selected by the user and reconnects the client asking the user to pick an + * account again if {@link Builder#useDefaultAccount()} was set. + * + * @return the pending result is fired once the default account has been cleared, but before + * the client is reconnected - for that {@link ConnectionCallbacks} can be used. + */ + PendingResult clearDefaultAccountAndReconnect(); /** * Connects the client to Google Play services. This method returns immediately, and connects @@ -111,7 +143,23 @@ public interface GoogleApiClient { * @see #registerConnectionFailedListener(OnConnectionFailedListener) * @see #unregisterConnectionFailedListener(OnConnectionFailedListener) */ - boolean isConnectionFailedListenerRegistered(OnConnectionFailedListener listener); + public boolean isConnectionFailedListenerRegistered(OnConnectionFailedListener listener); + + /** + * Closes the current connection to Google Play services and creates a new connection. + *

+ * This method closes the current connection then returns immediately and reconnects to the + * service in the background. + *

+ * After calling this method, your application will receive + * {@link ConnectionCallbacks#onConnected(Bundle)} if the connection is successful, or + * {@link OnConnectionFailedListener#onConnectionFailed(ConnectionResult)} if the connection + * failed. + * + * @see #connect() + * @see #disconnect() + */ + void reconnect(); /** * Registers a listener to receive connection events from this {@link GoogleApiClient}. If the @@ -150,7 +198,20 @@ public interface GoogleApiClient { * @param listener the listener where the results of the asynchronous {@link #connect()} call * are delivered. */ - void registerConnectionFailedListener(OnConnectionFailedListener listener); + public void registerConnectionFailedListener(OnConnectionFailedListener listener); + + /** + * Disconnects the client and stops automatic lifecycle management. Use this before creating a + * new client (which might be necessary when switching accounts, changing the set of used APIs + * etc.). + *

+ * This method must be called from the main thread. + * + * @param lifecycleActivity the activity managing the client's lifecycle. + * @throws IllegalStateException if called from outside of the main thread. + * @see Builder#enableAutoManage(FragmentActivity, int, OnConnectionFailedListener) + */ + void stopAutoManager(FragmentActivity lifecycleActivity) throws IllegalStateException; /** * Removes a connection listener from this {@link GoogleApiClient}. Note that removing a @@ -163,19 +224,73 @@ public interface GoogleApiClient { */ void unregisterConnectionCallbacks(ConnectionCallbacks listener); + /** + * Removes a connection failed listener from the {@link GoogleApiClient}. Note that removing a + * listener does not generate any callbacks. + *

+ * If the specified listener is not currently registered to receive connection failed events, + * this method will have no effect. + * + * @param listener the listener to unregister. + */ + void unregisterConnectionFailedListener(OnConnectionFailedListener listener); + + /** + * Builder to configure a {@link GoogleApiClient}. + */ @PublicApi class Builder { private final Context context; - private final Map apis = new HashMap<>(); - private final Set connectionCallbacks = new HashSet<>(); - private final Set connectionFailedListeners = new HashSet<>(); - private final Looper looper; + private final Map apis = new HashMap(); + private final Set connectionCallbacks = new HashSet(); + private final Set connectionFailedListeners = new HashSet(); + private final Set scopes = new HashSet(); + private String accountName; + private int clientId = -1; + private FragmentActivity fragmentActivity; + private Looper looper; + private int gravityForPopups; + private OnConnectionFailedListener unresolvedConnectionFailedListener; + private View viewForPopups; + /** + * Builder to help construct the {@link GoogleApiClient} object. + * + * @param context The context to use for the connection. + */ public Builder(Context context) { this.context = context; this.looper = context.getMainLooper(); } + /** + * Builder to help construct the {@link GoogleApiClient} object. + * + * @param context The context to use for the connection. + * @param connectedListener The listener where the results of the asynchronous + * {@link #connect()} call are delivered. + * @param connectionFailedListener The listener which will be notified if the connection + * attempt fails. + */ + public Builder(Context context, ConnectionCallbacks connectedListener, + OnConnectionFailedListener connectionFailedListener) { + this(context); + addConnectionCallbacks(connectedListener); + addOnConnectionFailedListener(connectionFailedListener); + } + + /** + * Specify which Apis are requested by your app. See {@link Api} for more information. + * + * @param api The Api requested by your app. + * @param options Any additional parameters required for the specific AP + * @see Api + */ + public Builder addApi(Api api, O options) { + apis.put(api, options); + return this; + } + /** * Specify which Apis are requested by your app. See {@link Api} for more information. * @@ -187,20 +302,136 @@ public interface GoogleApiClient { return this; } + /** + * Registers a listener to receive connection events from this {@link GoogleApiClient}. + * Applications should balance calls to this method with calls to + * {@link #unregisterConnectionCallbacks(ConnectionCallbacks)} to avoid + * leaking resources. + *

+ * If the specified listener is already registered to receive connection events, this + * method will not add a duplicate entry for the same listener. + *

+ * Note that the order of messages received here may not be stable, so clients should not + * rely on the order that multiple listeners receive events in. + * + * @param listener the listener where the results of the asynchronous {@link #connect()} + * call are delivered. + */ + public Builder addConnectionCallbacks(ConnectionCallbacks listener) { + connectionCallbacks.add(listener); + return this; + } + + /** + * Adds a listener to register to receive connection failed events from this + * {@link GoogleApiClient}. Applications should balance calls to this method with calls to + * {@link #unregisterConnectionFailedListener(OnConnectionFailedListener)} to avoid + * leaking resources. + *

+ * If the specified listener is already registered to receive connection failed events, + * this method will not add a duplicate entry for the same listener. + *

+ * Note that the order of messages received here may not be stable, so clients should not + * rely on the order that multiple listeners receive events in. + * + * @param listener the listener where the results of the asynchronous {@link #connect()} + * call are delivered. + */ + public Builder addOnConnectionFailedListener(OnConnectionFailedListener listener) { + connectionFailedListeners.add(listener); + return this; + } + + /** + * Specify the OAuth 2.0 scopes requested by your app. See + * {@link com.google.android.gms.common.Scopes} for more information. + * + * @param scope The OAuth 2.0 scopes requested by your app. + * @see com.google.android.gms.common.Scopes + */ + public Builder addScope(Scope scope) { + scopes.add(scope.getScopeUri()); + return this; + } + /** * Builds a new {@link GoogleApiClient} object for communicating with the Google APIs. * * @return The {@link GoogleApiClient} object. */ public GoogleApiClient build() { - int clientId = -1; - return new GoogleApiClientImpl(context, looper, getClientSettings(), apis, connectionCallbacks, connectionFailedListeners); + return new GoogleApiClientImpl(context, looper, getClientSettings(), apis, connectionCallbacks, connectionFailedListeners, clientId); } private ApiClientSettings getClientSettings() { - return null; + ApiClientSettings clientSettings = new ApiClientSettings(); + clientSettings.accountName = accountName; + clientSettings.scopes = new HashSet<>(scopes); + clientSettings.gravityForPopups = gravityForPopups; + clientSettings.viewForPopups = viewForPopups; + return clientSettings; } + public Builder enableAutoManage(FragmentActivity fragmentActivity, int cliendId, + OnConnectionFailedListener unresolvedConnectionFailedListener) + throws NullPointerException, IllegalArgumentException, IllegalStateException { + this.fragmentActivity = fragmentActivity; + this.clientId = cliendId; + this.unresolvedConnectionFailedListener = unresolvedConnectionFailedListener; + return this; + } + + /** + * Specify an account name on the device that should be used. If this is never called, the + * client will use the current default account for Google Play services for this + * application. + * + * @param accountName The account name on the device that should be used by + * {@link GoogleApiClient}. + */ + public Builder setAccountName(String accountName) { + this.accountName = accountName; + return this; + } + + /** + * Specifies the part of the screen at which games service popups (for example, + * "welcome back" or "achievement unlocked" popups) will be displayed using gravity. + * + * @param gravityForPopups The gravity which controls the placement of games service popups. + */ + public Builder setGravityForPopups(int gravityForPopups) { + this.gravityForPopups = gravityForPopups; + return this; + } + + /** + * Sets a {@link Handler} to indicate which thread to use when invoking callbacks. Will not + * be used directly to handle callbacks. If this is not called then the application's main + * thread will be used. + */ + public Builder setHandler(Handler handler) { + this.looper = handler.getLooper(); + return this; + } + + /** + * Sets the {@link View} to use as a content view for popups. + * + * @param viewForPopups The view to use as a content view for popups. View cannot be null. + */ + public Builder setViewForPopups(View viewForPopups) { + this.viewForPopups = viewForPopups; + return this; + } + + /** + * Specify that the default account should be used when connecting to services. + */ + public Builder useDefaultAccount() { + this.accountName = AuthConstants.DEFAULT_ACCOUNT; + return this; + } } /** @@ -210,8 +441,23 @@ public interface GoogleApiClient { @PublicApi @Deprecated interface ConnectionCallbacks extends org.microg.gms.common.api.ConnectionCallbacks { + /** + * A suspension cause informing that the service has been killed. + */ + int CAUSE_SERVICE_DISCONNECTED = 1; + /** + * A suspension cause informing you that a peer device connection was lost. + */ + int CAUSE_NETWORK_LOST = 2; + + void onConnected(Bundle connectionHint); } + /** + * Provides callbacks for scenarios that result in a failed attempt to connect the client to + * the service. See {@link ConnectionResult} for a list of error codes and suggestions for + * resolution. + */ @PublicApi @Deprecated interface OnConnectionFailedListener extends org.microg.gms.common.api.OnConnectionFailedListener { diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/HasApiKey.java b/play-services-base/src/main/java/com/google/android/gms/common/api/HasApiKey.java index 642a636d..e208b4d8 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/HasApiKey.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/HasApiKey.java @@ -5,5 +5,8 @@ package com.google.android.gms.common.api; -public interface HasApiKey { +import com.google.android.gms.common.api.internal.ApiKey; + +public interface HasApiKey { + ApiKey getApiKey(); } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/PendingResult.java b/play-services-base/src/main/java/com/google/android/gms/common/api/PendingResult.java index e5f92160..ec162920 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/PendingResult.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/PendingResult.java @@ -16,5 +16,44 @@ package com.google.android.gms.common.api; -public interface PendingResult { +import java.util.concurrent.TimeUnit; + +/** + * Represents a pending result from calling an API method in Google Play services. The final result + * object from a PendingResult is of type R, which can be retrieved in one of two ways. + *

+ *

+ * After the result has been retrieved using {@link #await()} or delivered to the result callback, + * it is an error to attempt to retrieve the result again. It is the responsibility of the caller + * or callback receiver to release any resources associated with the returned result. Some result + * types may implement {@link Releasable}, in which case {@link Releasable#release()} should be + * used to free the associated resources. + *

+ * TODO: Docs + */ +public interface PendingResult { + /** + * Blocks until the task is completed. This is not allowed on the UI thread. The returned + * result object can have an additional failure mode of INTERRUPTED. + */ + public R await(); + + /** + * Blocks until the task is completed or has timed out waiting for the result. This is not + * allowed on the UI thread. The returned result object can have an additional failure mode + * of either INTERRUPTED or TIMEOUT. + */ + public R await(long time, TimeUnit unit); + + public void cancel(); + + public boolean isCanceled(); + + public void setResultCallback(ResultCallback callback, long time, TimeUnit unit); + + public void setResultCallback(ResultCallback callback); } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/internal/ApiKey.java b/play-services-base/src/main/java/com/google/android/gms/common/api/internal/ApiKey.java index b679b2b9..5a42d8fd 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/internal/ApiKey.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/internal/ApiKey.java @@ -5,8 +5,8 @@ package com.google.android.gms.common.api.internal; -public class ApiKey { +import com.google.android.gms.common.api.Api; - public ApiKey() { - } +public class ApiKey { + private Api api; } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/data/DataBuffer.java b/play-services-base/src/main/java/com/google/android/gms/common/data/DataBuffer.java index 907aee2d..49c13a99 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/data/DataBuffer.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/data/DataBuffer.java @@ -16,8 +16,6 @@ package com.google.android.gms.common.data; -import androidx.annotation.NonNull; - import com.google.android.gms.common.api.Releasable; import org.microg.gms.common.PublicApi; @@ -30,8 +28,11 @@ import java.util.Iterator; @PublicApi public abstract class DataBuffer implements Releasable, Iterable { + private final DataHolder dataHolder; + @PublicApi(exclude = true) - public DataBuffer() { + public DataBuffer(DataHolder dataHolder) { + this.dataHolder = dataHolder; } /** @@ -42,6 +43,20 @@ public abstract class DataBuffer implements Releasable, Iterable { release(); } + /** + * Get the item at the specified position. Note that the objects returned from subsequent + * invocations of this method for the same position may not be identical objects, but will be + * equal in value. + * + * @param position The position of the item to retrieve. + * @return the item at {@code position} in this buffer. + */ + public abstract T get(int position); + + public int getCount() { + return dataHolder == null ? 0 : dataHolder.getCount(); + } + /** * @deprecated {@link #release()} is idempotent, and so is safe to call multiple times */ @@ -50,7 +65,6 @@ public abstract class DataBuffer implements Releasable, Iterable { return false; } - @NonNull @Override public Iterator iterator() { return null; @@ -64,4 +78,12 @@ public abstract class DataBuffer implements Releasable, Iterable { } + /** + * In order to use this one should correctly override setDataRow(int) in his DataBufferRef + * implementation. Be careful: there will be single DataBufferRef while iterating. + * If you are not sure - DO NOT USE this iterator. + */ + public Iterator singleRefIterator() { + return null; + } } diff --git a/play-services-base/src/main/java/org/microg/gms/common/DummyApiClient.java b/play-services-base/src/main/java/org/microg/gms/common/DummyApiClient.java new file mode 100644 index 00000000..c527866f --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/DummyApiClient.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.gms.common; + +import com.google.android.gms.common.api.Api; + +public class DummyApiClient implements Api.Client { + private boolean connected = false; + + @Override + public void connect() { + connected = true; + } + + @Override + public void disconnect() { + connected = false; + } + + @Override + public boolean isConnected() { + return connected; + } + + @Override + public boolean isConnecting() { + return false; + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionCallbacks.java b/play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionCallbacks.java new file mode 100644 index 00000000..fdffdac7 --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionCallbacks.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.gms.common; + +import android.os.Bundle; + +import com.google.android.gms.common.GooglePlayServicesClient; +import com.google.android.gms.common.api.GoogleApiClient; + +public final class ForwardConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks { + private final GooglePlayServicesClient.ConnectionCallbacks callbacks; + + public ForwardConnectionCallbacks(GooglePlayServicesClient.ConnectionCallbacks callbacks) { + this.callbacks = callbacks; + } + + @Override + public boolean equals(Object o) { + return o instanceof ForwardConnectionCallbacks && + callbacks.equals(((ForwardConnectionCallbacks) o).callbacks); + } + + @Override + public int hashCode() { + return callbacks.hashCode(); + } + + @Override + public void onConnected(Bundle connectionHint) { + callbacks.onConnected(connectionHint); + } + + @Override + public void onConnectionSuspended(int cause) { + callbacks.onDisconnected(); + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionFailedListener.java b/play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionFailedListener.java new file mode 100644 index 00000000..1d1f4434 --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionFailedListener.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.gms.common; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesClient; +import com.google.android.gms.common.api.GoogleApiClient; + +public final class ForwardConnectionFailedListener + implements GoogleApiClient.OnConnectionFailedListener { + private final GooglePlayServicesClient.OnConnectionFailedListener listener; + + public ForwardConnectionFailedListener( + GooglePlayServicesClient.OnConnectionFailedListener listener) { + this.listener = listener; + } + + @Override + public boolean equals(Object o) { + return o instanceof ForwardConnectionFailedListener && + listener.equals(((ForwardConnectionFailedListener) o).listener); + } + + @Override + public int hashCode() { + return listener.hashCode(); + } + + @Override + public void onConnectionFailed(ConnectionResult result) { + listener.onConnectionFailed(result); + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java b/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java new file mode 100644 index 00000000..e5b71717 --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.gms.common; + +import android.accounts.Account; +import android.content.ComponentName; +import android.content.Context; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.IInterface; +import android.os.RemoteException; +import android.util.Log; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.api.CommonStatusCodes; +import com.google.android.gms.common.internal.ConnectionInfo; +import com.google.android.gms.common.internal.GetServiceRequest; +import com.google.android.gms.common.internal.IGmsCallbacks; +import com.google.android.gms.common.internal.IGmsServiceBroker; + +import org.microg.gms.common.api.ConnectionCallbacks; +import org.microg.gms.common.api.OnConnectionFailedListener; + +public abstract class GmsClient implements Api.Client { + private static final String TAG = "GmsClient"; + + private final Context context; + protected final ConnectionCallbacks callbacks; + protected final OnConnectionFailedListener connectionFailedListener; + protected ConnectionState state = ConnectionState.NOT_CONNECTED; + private ServiceConnection serviceConnection; + private I serviceInterface; + private final String actionString; + + protected boolean requireMicrog; + protected String packageName; + + protected int serviceId = -1; + protected Account account = null; + protected Bundle extras = new Bundle(); + + public GmsClient(Context context, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener, String actionString) { + this.context = context; + this.callbacks = callbacks; + this.connectionFailedListener = connectionFailedListener; + this.actionString = actionString; + this.requireMicrog = false; + this.packageName = context.getPackageName(); + } + + protected void onConnectedToBroker(IGmsServiceBroker broker, GmsCallbacks callbacks) throws RemoteException { + if (serviceId == -1) { + throw new IllegalStateException("Service ID not set in constructor and onConnectedToBroker not implemented"); + } + GetServiceRequest request = new GetServiceRequest(serviceId); + request.packageName = packageName; + request.account = account; + request.extras = extras; + broker.getService(callbacks, request); + } + + protected abstract I interfaceFromBinder(IBinder binder); + + @Override + public synchronized void connect() { + Log.d(TAG, "connect()"); + if (state == ConnectionState.CONNECTED || state == ConnectionState.CONNECTING) { + Log.d(TAG, "Already connected/connecting - nothing to do"); + } + state = ConnectionState.CONNECTING; + if (serviceConnection != null) { + MultiConnectionKeeper.getInstance(context).unbind(actionString, serviceConnection); + } + serviceConnection = new GmsServiceConnection(); + if (!MultiConnectionKeeper.getInstance(context).bind(actionString, serviceConnection, requireMicrog)) { + state = ConnectionState.ERROR; + handleConnectionFailed(); + } + } + + public void handleConnectionFailed() { + connectionFailedListener.onConnectionFailed(new ConnectionResult(ConnectionResult.API_UNAVAILABLE, null)); + } + + @Override + public synchronized void disconnect() { + Log.d(TAG, "disconnect()"); + if (state == ConnectionState.DISCONNECTING) return; + if (state == ConnectionState.CONNECTING) { + state = ConnectionState.DISCONNECTING; + return; + } + serviceInterface = null; + if (serviceConnection != null) { + MultiConnectionKeeper.getInstance(context).unbind(actionString, serviceConnection); + serviceConnection = null; + } + state = ConnectionState.NOT_CONNECTED; + } + + @Override + public synchronized boolean isConnected() { + return (state == ConnectionState.CONNECTED && serviceInterface != null && serviceInterface.asBinder().isBinderAlive() ) || state == ConnectionState.PSEUDO_CONNECTED; + } + + @Override + public synchronized boolean isConnecting() { + return state == ConnectionState.CONNECTING; + } + + public synchronized boolean hasError() { + return state == ConnectionState.ERROR; + } + + public Context getContext() { + return context; + } + + public synchronized I getServiceInterface() { + if (isConnecting()) { + // TODO: wait for connection to be established and return afterwards. + throw new IllegalStateException("Waiting for connection"); + } else if (!isConnected()) { + throw new IllegalStateException("interface only available once connected!"); + } + return serviceInterface; + } + + protected enum ConnectionState { + NOT_CONNECTED, CONNECTING, CONNECTED, DISCONNECTING, ERROR, PSEUDO_CONNECTED + } + + private class GmsServiceConnection implements ServiceConnection { + + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + try { + Log.d(TAG, "ServiceConnection : onServiceConnected(" + componentName + ")"); + onConnectedToBroker(IGmsServiceBroker.Stub.asInterface(iBinder), new GmsCallbacks()); + } catch (RemoteException e) { + disconnect(); + } + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + synchronized (GmsClient.this) { + state = ConnectionState.NOT_CONNECTED; + } + } + } + + public class GmsCallbacks extends IGmsCallbacks.Stub { + + @Override + public void onPostInitComplete(int statusCode, IBinder binder, Bundle params) + throws RemoteException { + if (statusCode != CommonStatusCodes.SUCCESS) { + state = ConnectionState.CONNECTED; + disconnect(); + connectionFailedListener.onConnectionFailed(new ConnectionResult(statusCode)); + } else { + synchronized (GmsClient.this) { + if (state == ConnectionState.DISCONNECTING) { + state = ConnectionState.CONNECTED; + disconnect(); + return; + } + state = ConnectionState.CONNECTED; + serviceInterface = interfaceFromBinder(binder); + } + Log.d(TAG, "GmsCallbacks : onPostInitComplete(" + serviceInterface + ")"); + callbacks.onConnected(params); + } + } + + @Override + public void onAccountValidationComplete(int statusCode, Bundle params) throws RemoteException { + Log.d(TAG, "GmsCallbacks : onAccountValidationComplete"); + } + + @Override + public void onPostInitCompleteWithConnectionInfo(int statusCode, IBinder binder, ConnectionInfo info) throws RemoteException { + onPostInitComplete(statusCode, binder, info == null ? null : info.params); + } + } + +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/GmsConnector.java b/play-services-base/src/main/java/org/microg/gms/common/GmsConnector.java new file mode 100644 index 00000000..b77c6d2c --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/GmsConnector.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.gms.common; + +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; + +import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.PendingResult; +import com.google.android.gms.common.api.Result; + +import org.microg.gms.common.api.AbstractPendingResult; +import org.microg.gms.common.api.GoogleApiClientImpl; + +public class GmsConnector { + private static final String TAG = "GmsConnector"; + + private final GoogleApiClientImpl apiClient; + private final Api api; + private final Callback callback; + + public GmsConnector(GoogleApiClient apiClient, Api api, Callback callback) { + this.apiClient = (GoogleApiClientImpl) apiClient; + this.api = api; + this.callback = callback; + } + + public static PendingResult call(GoogleApiClient client, Api api, GmsConnector.Callback callback) { + return new GmsConnector(client, api, callback).connect(); + } + + public AbstractPendingResult connect() { + Log.d(TAG, "connect()"); + apiClient.incrementUsageCounter(); + apiClient.getApiConnection(api); + Looper looper = apiClient.getLooper(); + final AbstractPendingResult result = new AbstractPendingResult(looper); + Message msg = new Message(); + msg.obj = result; + new Handler(looper).sendMessage(msg); + return result; + } + + public interface Callback { + void onClientAvailable(C client, ResultProvider resultProvider) throws RemoteException; + + interface ResultProvider { + void onResultAvailable(R result); + } + } + + private class Handler extends android.os.Handler { + private Handler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + Log.d(TAG, "Handler : handleMessage"); + final AbstractPendingResult result = (AbstractPendingResult) msg.obj; + try { + C connection = (C) apiClient.getApiConnection(api); + callback.onClientAvailable(connection, new GmsConnector.Callback.ResultProvider() { + @Override + public void onResultAvailable(R realResult) { + result.deliverResult(realResult); + apiClient.decrementUsageCounter(); + } + }); + } catch (RemoteException ignored) { + + } + } + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/MultiConnectionKeeper.java b/play-services-base/src/main/java/org/microg/gms/common/MultiConnectionKeeper.java new file mode 100644 index 00000000..616224bf --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/MultiConnectionKeeper.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.gms.common; + +import android.annotation.SuppressLint; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; +import android.content.pm.ResolveInfo; +import android.os.IBinder; +import android.util.Log; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; + +public class MultiConnectionKeeper { + private static final String TAG = "GmsMultiConKeeper"; + + private static MultiConnectionKeeper INSTANCE; + + private final Context context; + private final Map connections = new HashMap(); + + public MultiConnectionKeeper(Context context) { + this.context = context; + } + + public synchronized static MultiConnectionKeeper getInstance(Context context) { + if (INSTANCE == null) + INSTANCE = new MultiConnectionKeeper(context.getApplicationContext()); + return INSTANCE; + } + + public synchronized boolean bind(String action, ServiceConnection connection) { + return bind(action, connection, false); + } + + public synchronized boolean bind(String action, ServiceConnection connection, boolean requireMicrog) { + Connection con = connections.get(action); + Log.d(TAG, "bind(" + action + ", " + connection + ", " + requireMicrog + ") has=" + (con != null)); + if (con != null) { + if (!con.forwardsConnection(connection)) { + con.addConnectionForward(connection); + if (!con.isBound()) + con.bind(); + } + } else { + con = new Connection(action, requireMicrog); + con.addConnectionForward(connection); + con.bind(); + connections.put(action, con); + } + Log.d(TAG, "bind() : bound=" + con.isBound()); + return con.isBound(); + } + + public synchronized void unbind(String action, ServiceConnection connection) { + Log.d(TAG, "unbind(" + action + ", " + connection + ")"); + Connection con = connections.get(action); + if (con != null) { + con.removeConnectionForward(connection); + if (con.isBound()) { + if (!con.hasForwards()) { + con.unbind(); + connections.remove(action); + } else { + Log.d(TAG, "Not unbinding for " + connection + ": has pending other bindings on action " + action); + } + } + } + } + + public class Connection { + private final String actionString; + private final boolean requireMicrog; + private final Set connectionForwards = new HashSet(); + private boolean bound = false; + private boolean connected = false; + private IBinder binder; + private ComponentName component; + private ServiceConnection serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + Log.d(TAG, "Connection(" + actionString + ") : ServiceConnection : " + + "onServiceConnected(" + componentName + ")"); + binder = iBinder; + component = componentName; + for (ServiceConnection connection : connectionForwards) { + connection.onServiceConnected(componentName, iBinder); + } + connected = true; + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + Log.d(TAG, "Connection(" + actionString + ") : ServiceConnection : " + + "onServiceDisconnected(" + componentName + ")"); + binder = null; + component = componentName; + for (ServiceConnection connection : connectionForwards) { + connection.onServiceDisconnected(componentName); + } + connected = false; + bound = false; + } + }; + + public Connection(String actionString) { + this(actionString, false); + } + + public Connection(String actionString, boolean requireMicrog) { + this.actionString = actionString; + this.requireMicrog = requireMicrog; + } + + @SuppressLint("InlinedApi") + public void bind() { + Log.d(TAG, "Connection(" + actionString + ") : bind()"); + Intent gmsIntent = new Intent(actionString).setPackage(GMS_PACKAGE_NAME); + Intent selfIntent = new Intent(actionString).setPackage(context.getPackageName()); + Intent intent; + ResolveInfo resolveInfo; + if ((resolveInfo = context.getPackageManager().resolveService(gmsIntent, 0)) == null) { + Log.w(TAG, "No GMS service found for " + actionString); + if (context.getPackageManager().resolveService(selfIntent, 0) != null) { + Log.d(TAG, "Found service for " + actionString + " in self package, using it instead"); + intent = selfIntent; + } else { + return; + } + } else if (requireMicrog && !isMicrog(resolveInfo)) { + Log.w(TAG, "GMS service found for " + actionString + " but looks not like microG"); + if (context.getPackageManager().resolveService(selfIntent, 0) != null) { + Log.d(TAG, "Found service for " + actionString + " in self package, using it instead"); + intent = selfIntent; + } else { + intent = gmsIntent; + } + } else { + intent = gmsIntent; + } + int flags = Context.BIND_AUTO_CREATE | Context.BIND_DEBUG_UNBIND | Context.BIND_ADJUST_WITH_ACTIVITY; + bound = context.bindService(intent, serviceConnection, flags); + Log.d(TAG, "Connection(" + actionString + ") : bind() : bindService=" + bound); + if (!bound) { + context.unbindService(serviceConnection); + } + } + + public boolean isMicrog(ResolveInfo resolveInfo) { + if (resolveInfo == null || resolveInfo.serviceInfo == null) return false; + if (resolveInfo.serviceInfo.name.startsWith("org.microg.")) return true; + try { + PermissionInfo info = context.getPackageManager().getPermissionInfo("org.microg.gms.EXTENDED_ACCESS", 0); + return info.packageName.equals(resolveInfo.serviceInfo.packageName); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + public boolean isBound() { + return bound; + } + + public IBinder getBinder() { + return binder; + } + + public void unbind() { + Log.d(TAG, "Connection(" + actionString + ") : unbind()"); + try { + context.unbindService(serviceConnection); + } catch (IllegalArgumentException e) { // not bound (whatever reason) + Log.w(TAG, e); + } + bound = false; + } + + public void addConnectionForward(ServiceConnection connection) { + connectionForwards.add(connection); + if (connected) { + connection.onServiceConnected(component, binder); + } + } + + public void removeConnectionForward(ServiceConnection connection) { + connectionForwards.remove(connection); + if (connected) { + connection.onServiceDisconnected(component); + } + } + + public boolean forwardsConnection(ServiceConnection connection) { + return connectionForwards.contains(connection); + } + + public boolean hasForwards() { + return !connectionForwards.isEmpty(); + } + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPendingResult.java b/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPendingResult.java index cea4229b..15aac9e3 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPendingResult.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPendingResult.java @@ -19,11 +19,93 @@ package org.microg.gms.common.api; import android.os.Looper; import com.google.android.gms.common.api.PendingResult; +import com.google.android.gms.common.api.Result; +import com.google.android.gms.common.api.ResultCallback; -public class AbstractPendingResult implements PendingResult { +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class AbstractPendingResult implements PendingResult { + private final Object lock = new Object(); + private final CountDownLatch countDownLatch = new CountDownLatch(1); + private final ResultCallbackHandler handler; + private boolean canceled; + private R result; + private ResultCallback resultCallback; public AbstractPendingResult(Looper looper) { - new ResultCallbackHandler<>(looper); + handler = new ResultCallbackHandler(looper); } + private R getResult() { + synchronized (lock) { + return result; + } + } + + @Override + public R await() { + try { + countDownLatch.await(); + } catch (InterruptedException ignored) { + } + return getResult(); + } + + @Override + public R await(long time, TimeUnit unit) { + try { + countDownLatch.await(time, unit); + } catch (InterruptedException ignored) { + } + return getResult(); + } + + @Override + public void cancel() { + // TODO + } + + @Override + public boolean isCanceled() { + synchronized (lock) { + return canceled; + } + } + + public boolean isReady() { + return this.countDownLatch.getCount() == 0L; + } + + @Override + public void setResultCallback(ResultCallback callback, long time, TimeUnit unit) { + synchronized (lock) { + if (!isCanceled()) { + if (isReady()) { + handler.sendResultCallback(callback, getResult()); + } else { + handler.sendTimeoutResultCallback(this, unit.toMillis(time)); + } + } + } + } + + @Override + public void setResultCallback(ResultCallback callback) { + synchronized (lock) { + if (!isCanceled()) { + if (isReady()) { + handler.sendResultCallback(callback, getResult()); + } else { + resultCallback = callback; + } + } + } + } + + public void deliverResult(R result) { + this.result = result; + countDownLatch.countDown(); + + } } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPlayServicesClient.java b/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPlayServicesClient.java new file mode 100644 index 00000000..eb9d66bd --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPlayServicesClient.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.gms.common.api; + +import android.util.Log; + +import com.google.android.gms.common.GooglePlayServicesClient; +import com.google.android.gms.common.api.GoogleApiClient; + +import org.microg.gms.common.ForwardConnectionCallbacks; +import org.microg.gms.common.ForwardConnectionFailedListener; + +public class AbstractPlayServicesClient implements GooglePlayServicesClient { + private static final String TAG = "GmsPlayServicesClient"; + + protected final GoogleApiClient googleApiClient; + + public AbstractPlayServicesClient(GoogleApiClient googleApiClient) { + this.googleApiClient = googleApiClient; + } + + public void assertConnected() { + if (!isConnected()) throw new IllegalStateException("Not connected!"); + } + + @Override + public void connect() { + Log.d(TAG, "connect()"); + googleApiClient.connect(); + } + + @Override + public void disconnect() { + Log.d(TAG, "disconnect()"); + //TODO googleApiClient.disconnect(); + } + + @Override + public boolean isConnected() { + return googleApiClient.isConnected(); + } + + @Override + public boolean isConnecting() { + return googleApiClient.isConnecting(); + } + + @Override + public void registerConnectionCallbacks(final ConnectionCallbacks listener) { + googleApiClient.registerConnectionCallbacks(new ForwardConnectionCallbacks(listener)); + } + + @Override + public boolean isConnectionCallbacksRegistered(ConnectionCallbacks listener) { + return googleApiClient + .isConnectionCallbacksRegistered(new ForwardConnectionCallbacks(listener)); + } + + @Override + public void unregisterConnectionCallbacks( + ConnectionCallbacks listener) { + googleApiClient.unregisterConnectionCallbacks(new ForwardConnectionCallbacks(listener)); + } + + @Override + public void registerConnectionFailedListener( + OnConnectionFailedListener listener) { + googleApiClient.registerConnectionFailedListener( + new ForwardConnectionFailedListener(listener)); + } + + @Override + public boolean isConnectionFailedListenerRegistered( + OnConnectionFailedListener listener) { + return googleApiClient.isConnectionFailedListenerRegistered( + new ForwardConnectionFailedListener(listener)); + } + + @Override + public void unregisterConnectionFailedListener( + OnConnectionFailedListener listener) { + googleApiClient.unregisterConnectionFailedListener( + new ForwardConnectionFailedListener(listener)); + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientBuilder.java b/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientBuilder.java index b5aa386f..18336fa5 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientBuilder.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientBuilder.java @@ -22,5 +22,5 @@ import android.os.Looper; import com.google.android.gms.common.api.Api; public interface ApiClientBuilder { - ApiClient build(O options, Context context, Looper looper, ApiClientSettings clientSettings, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener); + Api.Client build(O options, Context context, Looper looper, ApiClientSettings clientSettings, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener); } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientSettings.java b/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientSettings.java index d1e314a7..af67e799 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientSettings.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientSettings.java @@ -5,5 +5,15 @@ package org.microg.gms.common.api; +import android.view.View; + +import java.util.Set; + public class ApiClientSettings { + public String accountName; + public String packageName; + public Integer sessionId; + public Set scopes; + public int gravityForPopups; + public View viewForPopups; } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiClientImpl.java b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiClientImpl.java index 371a6555..2dffa374 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiClientImpl.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiClientImpl.java @@ -19,59 +19,117 @@ package org.microg.gms.common.api; import android.content.Context; import android.os.Bundle; import android.os.Looper; + +import androidx.fragment.app.FragmentActivity; + import android.util.Log; +import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.PendingResult; +import com.google.android.gms.common.api.Status; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; public class GoogleApiClientImpl implements GoogleApiClient { private static final String TAG = "GmsApiClientImpl"; - private final Map apiConnections = new HashMap<>(); - private final Set connectionCallbacks = new HashSet<>(); - private final Set connectionFailedListeners = new HashSet<>(); + private final Context context; + private final Looper looper; + private final ApiClientSettings clientSettings; + private final Map apis = new HashMap(); + private final Map apiConnections = new HashMap(); + private final Set connectionCallbacks = new HashSet(); + private final Set connectionFailedListeners = new HashSet(); + private final int clientId; + private final ConnectionCallbacks baseConnectionCallbacks = new ConnectionCallbacks() { + @Override + public void onConnected(Bundle connectionHint) { + Log.d(TAG, "ConnectionCallbacks : onConnected()"); + for (ConnectionCallbacks callback : connectionCallbacks) { + callback.onConnected(connectionHint); + } + } + + @Override + public void onConnectionSuspended(int cause) { + Log.d(TAG, "ConnectionCallbacks : onConnectionSuspended()"); + for (ConnectionCallbacks callback : connectionCallbacks) { + callback.onConnectionSuspended(cause); + } + } + }; + private final OnConnectionFailedListener baseConnectionFailedListener = new + OnConnectionFailedListener() { + @Override + public void onConnectionFailed(ConnectionResult result) { + Log.d(TAG, "OnConnectionFailedListener : onConnectionFailed()"); + for (OnConnectionFailedListener listener : connectionFailedListeners) { + listener.onConnectionFailed(result); + } + } + }; + private int usageCounter = 0; private boolean shouldDisconnect = false; public GoogleApiClientImpl(Context context, Looper looper, ApiClientSettings clientSettings, Map apis, Set connectionCallbacks, - Set connectionFailedListeners) { + Set connectionFailedListeners, int clientId) { + this.context = context; + this.looper = looper; + this.clientSettings = clientSettings; + this.apis.putAll(apis); this.connectionCallbacks.addAll(connectionCallbacks); this.connectionFailedListeners.addAll(connectionFailedListeners); + this.clientId = clientId; + + if (this.clientSettings.sessionId == null) { + this.clientSettings.sessionId = hashCode(); + } for (Api api : apis.keySet()) { - ConnectionCallbacks baseConnectionCallbacks = new ConnectionCallbacks() { - @Override - public void onConnected(Bundle connectionHint) { - Log.d(TAG, "ConnectionCallbacks : onConnected()"); - for (ConnectionCallbacks callback : connectionCallbacks) { - callback.onConnected(connectionHint); - } - } - - @Override - public void onConnectionSuspended(int cause) { - Log.d(TAG, "ConnectionCallbacks : onConnectionSuspended()"); - for (ConnectionCallbacks callback : connectionCallbacks) { - callback.onConnectionSuspended(cause); - } - } - }; - OnConnectionFailedListener baseConnectionFailedListener = result -> { - Log.d(TAG, "OnConnectionFailedListener : onConnectionFailed()"); - for (OnConnectionFailedListener listener : connectionFailedListeners) { - listener.onConnectionFailed(result); - } - }; apiConnections.put(api, api.getBuilder().build(apis.get(api), context, looper, clientSettings, baseConnectionCallbacks, baseConnectionFailedListener)); } } + public synchronized void incrementUsageCounter() { + usageCounter++; + } + + public synchronized void decrementUsageCounter() { + usageCounter--; + if (shouldDisconnect) disconnect(); + } + + public Looper getLooper() { + return looper; + } + + public Api.Client getApiConnection(Api api) { + return apiConnections.get(api); + } + + @Override + public ConnectionResult blockingConnect() { + return null; + } + + @Override + public ConnectionResult blockingConnect(long timeout, TimeUnit unit) { + return null; + } + + @Override + public PendingResult clearDefaultAccountAndReconnect() { + return null; + } + @Override public synchronized void connect() { Log.d(TAG, "connect()"); @@ -83,7 +141,7 @@ public class GoogleApiClientImpl implements GoogleApiClient { Log.d(TAG, "Already connected/connecting, nothing to do"); return; } - for (ApiClient connection : apiConnections.values()) { + for (Api.Client connection : apiConnections.values()) { if (!connection.isConnected()) { connection.connect(); } @@ -92,17 +150,21 @@ public class GoogleApiClientImpl implements GoogleApiClient { @Override public synchronized void disconnect() { - Log.d(TAG, "disconnect()"); - for (ApiClient connection : apiConnections.values()) { - if (connection.isConnected()) { - connection.disconnect(); + if (usageCounter > 0) { + shouldDisconnect = true; + } else { + Log.d(TAG, "disconnect()"); + for (Api.Client connection : apiConnections.values()) { + if (connection.isConnected()) { + connection.disconnect(); + } } } } @Override public synchronized boolean isConnected() { - for (ApiClient connection : apiConnections.values()) { + for (Api.Client connection : apiConnections.values()) { if (!connection.isConnected()) return false; } return true; @@ -110,7 +172,7 @@ public class GoogleApiClientImpl implements GoogleApiClient { @Override public synchronized boolean isConnecting() { - for (ApiClient connection : apiConnections.values()) { + for (Api.Client connection : apiConnections.values()) { if (connection.isConnecting()) return true; } return false; @@ -127,6 +189,13 @@ public class GoogleApiClientImpl implements GoogleApiClient { return connectionFailedListeners.contains(listener); } + @Override + public synchronized void reconnect() { + Log.d(TAG, "reconnect()"); + disconnect(); + connect(); + } + @Override public void registerConnectionCallbacks(ConnectionCallbacks listener) { connectionCallbacks.add(listener); @@ -137,9 +206,18 @@ public class GoogleApiClientImpl implements GoogleApiClient { connectionFailedListeners.add(listener); } + @Override + public void stopAutoManager(FragmentActivity lifecycleActivity) throws IllegalStateException { + + } + @Override public void unregisterConnectionCallbacks(ConnectionCallbacks listener) { connectionCallbacks.remove(listener); } + @Override + public void unregisterConnectionFailedListener(OnConnectionFailedListener listener) { + connectionFailedListeners.remove(listener); + } } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java index a5fa2545..5cf7933e 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java @@ -5,17 +5,185 @@ package org.microg.gms.common.api; -import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Bundle; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.api.GoogleApi; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.TaskCompletionSource; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class GoogleApiManager { - @SuppressLint("StaticFieldLeak") private static GoogleApiManager instance; + private Context context; + private Map clientMap = new HashMap<>(); + private Map>> waitingApiCallMap = new HashMap<>(); - private GoogleApiManager() { + private GoogleApiManager(Context context) { + this.context = context; } - public synchronized static void getInstance() { - if (instance == null) instance = new GoogleApiManager(); + public synchronized static GoogleApiManager getInstance(Context context) { + if (instance == null) instance = new GoogleApiManager(context); + return instance; } + private synchronized A clientForApi(GoogleApi api) { + ApiInstance apiInstance = new ApiInstance(api); + if (clientMap.containsKey(apiInstance)) { + return (A) clientMap.get(apiInstance); + } else { + Api.Client client = api.api.getBuilder().build(api.getOptions(), context, context.getMainLooper(), null, new ConnectionCallback(apiInstance), new ConnectionFailedListener(apiInstance)); + clientMap.put(apiInstance, client); + waitingApiCallMap.put(apiInstance, new ArrayList<>()); + return (A) client; + } + } + + public synchronized void scheduleTask(GoogleApi api, PendingGoogleApiCall apiCall, TaskCompletionSource completionSource) { + A client = clientForApi(api); + boolean connecting = client.isConnecting(); + boolean connected = client.isConnected(); + if (connected) { + try { + apiCall.execute(client, completionSource); + } catch (Exception e) { + completionSource.setException(e); + } + } else { + waitingApiCallMap.get(new ApiInstance(api)).add(new WaitingApiCall((PendingGoogleApiCall) apiCall, completionSource)); + if (!connecting) { + client.connect(); + } + } + } + + private synchronized void onInstanceConnected(ApiInstance apiInstance, Bundle connectionHint) { + List> waitingApiCalls = waitingApiCallMap.get(apiInstance); + for (WaitingApiCall waitingApiCall : waitingApiCalls) { + try { + waitingApiCall.execute(clientMap.get(apiInstance)); + } catch (Exception e) { + waitingApiCall.failed(e); + } + } + waitingApiCalls.clear(); + } + + private synchronized void onInstanceSuspended(ApiInstance apiInstance, int cause) { + + } + + private synchronized void onInstanceFailed(ApiInstance apiInstance, ConnectionResult result) { + List> waitingApiCalls = waitingApiCallMap.get(apiInstance); + for (WaitingApiCall waitingApiCall : waitingApiCalls) { + waitingApiCall.failed(new RuntimeException(result.getErrorMessage())); + } + waitingApiCalls.clear(); + } + + private class ConnectionCallback implements ConnectionCallbacks { + private ApiInstance apiInstance; + + public ConnectionCallback(ApiInstance apiInstance) { + this.apiInstance = apiInstance; + } + + @Override + public void onConnected(Bundle connectionHint) { + onInstanceConnected(apiInstance, connectionHint); + } + + @Override + public void onConnectionSuspended(int cause) { + onInstanceSuspended(apiInstance, cause); + } + } + + private class ConnectionFailedListener implements OnConnectionFailedListener { + private ApiInstance apiInstance; + + public ConnectionFailedListener(ApiInstance apiInstance) { + this.apiInstance = apiInstance; + } + + @Override + public void onConnectionFailed(ConnectionResult result) { + onInstanceFailed(apiInstance, result); + } + } + + private static class WaitingApiCall { + private PendingGoogleApiCall apiCall; + private TaskCompletionSource completionSource; + + public WaitingApiCall(PendingGoogleApiCall apiCall, TaskCompletionSource completionSource) { + this.apiCall = apiCall; + this.completionSource = completionSource; + } + + public void execute(Api.Client client) throws Exception { + apiCall.execute(client, completionSource); + } + + public void failed(Exception e) { + completionSource.setException(e); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + WaitingApiCall that = (WaitingApiCall) o; + + if (apiCall != null ? !apiCall.equals(that.apiCall) : that.apiCall != null) return false; + return completionSource != null ? completionSource.equals(that.completionSource) : that.completionSource == null; + } + + @Override + public int hashCode() { + int result = apiCall != null ? apiCall.hashCode() : 0; + result = 31 * result + (completionSource != null ? completionSource.hashCode() : 0); + return result; + } + } + + private static class ApiInstance { + private Class apiClass; + private Api.ApiOptions apiOptions; + + public ApiInstance(Class apiClass, Api.ApiOptions apiOptions) { + this.apiClass = apiClass; + this.apiOptions = apiOptions; + } + + public ApiInstance(GoogleApi api) { + this(api.getClass(), api.getOptions()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ApiInstance that = (ApiInstance) o; + + if (apiClass != null ? !apiClass.equals(that.apiClass) : that.apiClass != null) return false; + return apiOptions != null ? apiOptions.equals(that.apiOptions) : that.apiOptions == null; + } + + @Override + public int hashCode() { + int result = apiClass != null ? apiClass.hashCode() : 0; + result = 31 * result + (apiOptions != null ? apiOptions.hashCode() : 0); + return result; + } + } } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/InstantPendingResult.java b/play-services-base/src/main/java/org/microg/gms/common/api/InstantPendingResult.java new file mode 100644 index 00000000..ba75725c --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/api/InstantPendingResult.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.gms.common.api; + +import com.google.android.gms.common.api.PendingResult; +import com.google.android.gms.common.api.Result; +import com.google.android.gms.common.api.ResultCallback; + +import java.util.concurrent.TimeUnit; + +public class InstantPendingResult implements PendingResult { + R value; + + public InstantPendingResult(R value) { + this.value = value; + } + + @Override + public R await() { + return value; + } + + @Override + public R await(long time, TimeUnit unit) { + return value; + } + + @Override + public void cancel() { + + } + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public void setResultCallback(ResultCallback callback, long time, TimeUnit unit) { + callback.onResult(value); + } + + @Override + public void setResultCallback(ResultCallback callback) { + callback.onResult(value); + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/PendingGoogleApiCall.java b/play-services-base/src/main/java/org/microg/gms/common/api/PendingGoogleApiCall.java new file mode 100644 index 00000000..578d02b9 --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/api/PendingGoogleApiCall.java @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.common.api; + +import com.google.android.gms.common.api.Api; +import com.google.android.gms.tasks.TaskCompletionSource; + +public interface PendingGoogleApiCall { + void execute(A client, TaskCompletionSource completionSource) throws Exception; +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/ResultCallbackHandler.java b/play-services-base/src/main/java/org/microg/gms/common/api/ResultCallbackHandler.java index a737a076..c19c849c 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/ResultCallbackHandler.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/ResultCallbackHandler.java @@ -47,6 +47,17 @@ class ResultCallbackHandler extends Handler { } } + public void sendResultCallback(ResultCallback callback, R result) { + Message message = new Message(); + message.what = CALLBACK_ON_COMPLETE; + message.obj = new OnCompleteObject(callback, result); + sendMessage(message); + } + + public void sendTimeoutResultCallback(AbstractPendingResult pendingResult, long millis) { + + } + public static class OnCompleteObject { public ResultCallback callback; public R result; diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/ReturningGoogleApiCall.java b/play-services-base/src/main/java/org/microg/gms/common/api/ReturningGoogleApiCall.java new file mode 100644 index 00000000..f836b9d4 --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/api/ReturningGoogleApiCall.java @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.common.api; + +import com.google.android.gms.common.api.Api; +import com.google.android.gms.tasks.TaskCompletionSource; + +public interface ReturningGoogleApiCall extends PendingGoogleApiCall { + R execute(A client) throws Exception; + + @Override + default void execute(A client, TaskCompletionSource completionSource) { + try { + completionSource.setResult(execute(client)); + } catch (Exception e) { + completionSource.setException(e); + } + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/VoidReturningGoogleApiCall.java b/play-services-base/src/main/java/org/microg/gms/common/api/VoidReturningGoogleApiCall.java new file mode 100644 index 00000000..ee8cbf66 --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/api/VoidReturningGoogleApiCall.java @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.common.api; + +import com.google.android.gms.common.api.Api; +import com.google.android.gms.tasks.TaskCompletionSource; + +public interface VoidReturningGoogleApiCall extends PendingGoogleApiCall{ + void execute(A client) throws Exception; + + @Override + default void execute(A client, TaskCompletionSource completionSource) { + try { + execute(client); + completionSource.setResult(null); + } catch (Exception e) { + completionSource.setException(e); + } + } +} diff --git a/play-services-basement/build.gradle b/play-services-basement/build.gradle index 8971fb98..9884e3c9 100644 --- a/play-services-basement/build.gradle +++ b/play-services-basement/build.gradle @@ -18,12 +18,12 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' dependencies { - api "org.microg:safe-parcel:1.7.0" + api "org.microg:safe-parcel:$safeParcelVersion" // Dependencies from play-services-basement:18.0.0 api "androidx.collection:collection:$collectionVersion" - api "androidx.core:core-ktx:$coreVersion" - api "androidx.fragment:fragment-ktx:$fragmentVersion" + api "androidx.core:core:$coreVersion" + api "androidx.fragment:fragment:$fragmentVersion" } android { @@ -39,6 +39,7 @@ android { minSdkVersion androidMinSdk targetSdkVersion androidTargetSdk buildConfigField "int", "VERSION_CODE", "$appVersionCode" + buildConfigField "String", "BASE_PACKAGE_NAME", "\"$basePackageName\"" } buildFeatures { diff --git a/play-services-basement/src/main/AndroidManifest.xml b/play-services-basement/src/main/AndroidManifest.xml new file mode 100644 index 00000000..359a9b71 --- /dev/null +++ b/play-services-basement/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/ConnectionResult.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/ConnectionResult.aidl new file mode 100644 index 00000000..e46e6d5d --- /dev/null +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/ConnectionResult.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.common; + +parcelable ConnectionResult; \ No newline at end of file diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupQuery.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupQuery.aidl new file mode 100644 index 00000000..fab15aa1 --- /dev/null +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupQuery.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.common; + +parcelable GoogleCertificatesLookupQuery; \ No newline at end of file diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupResponse.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupResponse.aidl new file mode 100644 index 00000000..e9b4bff3 --- /dev/null +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupResponse.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.common; + +parcelable GoogleCertificatesLookupResponse; \ No newline at end of file diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesQuery.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesQuery.aidl new file mode 100644 index 00000000..e6aedfef --- /dev/null +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesQuery.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.common; + +parcelable GoogleCertificatesQuery; \ No newline at end of file diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ConnectionInfo.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ConnectionInfo.aidl new file mode 100644 index 00000000..b393f11d --- /dev/null +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ConnectionInfo.aidl @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal; +parcelable ConnectionInfo; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/ICertData.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ICertData.aidl similarity index 100% rename from play-services-api/src/main/aidl/com/google/android/gms/common/internal/ICertData.aidl rename to play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ICertData.aidl diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGmsCallbacks.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGmsCallbacks.aidl index b0210281..8fe63347 100644 --- a/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGmsCallbacks.aidl +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGmsCallbacks.aidl @@ -1,7 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + package com.google.android.gms.common.internal; import android.os.Bundle; +import com.google.android.gms.common.internal.ConnectionInfo; interface IGmsCallbacks { void onPostInitComplete(int statusCode, IBinder binder, in Bundle params); + void onAccountValidationComplete(int statusCode, in Bundle params); + void onPostInitCompleteWithConnectionInfo(int statusCode, IBinder binder, in ConnectionInfo info); } diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl new file mode 100644 index 00000000..6d54ad26 --- /dev/null +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl @@ -0,0 +1,18 @@ +package com.google.android.gms.common.internal; + +import com.google.android.gms.common.GoogleCertificatesLookupQuery; +import com.google.android.gms.common.GoogleCertificatesLookupResponse; +import com.google.android.gms.common.GoogleCertificatesQuery; +import com.google.android.gms.dynamic.IObjectWrapper; + +interface IGoogleCertificatesApi { + IObjectWrapper getGoogleCertificates() = 0; + IObjectWrapper getGoogleReleaseCertificates() = 1; + boolean isGoogleReleaseSigned(String packageName, IObjectWrapper certData) = 2; + boolean isGoogleSigned(String packageName, IObjectWrapper certData) = 3; + boolean isGoogleOrPlatformSigned(in GoogleCertificatesQuery query, IObjectWrapper packageManager) = 4; + GoogleCertificatesLookupResponse isPackageGoogleOrPlatformSigned(in GoogleCertificatesLookupQuery query) = 5; + boolean isPackageGoogleOrPlatformSignedAvailable() = 6; + GoogleCertificatesLookupResponse queryPackageSigned(in GoogleCertificatesLookupQuery query) = 7; + boolean isFineGrainedPackageVerificationAvailable() = 8; +} \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/ISignInButtonCreator.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ISignInButtonCreator.aidl similarity index 100% rename from play-services-api/src/main/aidl/com/google/android/gms/common/internal/ISignInButtonCreator.aidl rename to play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ISignInButtonCreator.aidl diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/ConnectionResult.java b/play-services-basement/src/main/java/com/google/android/gms/common/ConnectionResult.java index 23ebfb81..f3272de8 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/ConnectionResult.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/ConnectionResult.java @@ -18,6 +18,7 @@ package com.google.android.gms.common; import android.app.Activity; import android.app.PendingIntent; +import android.content.Intent; import android.content.IntentSender; import android.text.TextUtils; @@ -238,6 +239,35 @@ public class ConnectionResult { } } + /** + * Indicates the type of error that interrupted connection. + * + * @return the error code, or {@link #SUCCESS} if no error occurred. + */ + public int getErrorCode() { + return statusCode; + } + + /** + * Returns an error message for connection result. + * + * @return the message + */ + public String getErrorMessage() { + return message; + } + + /** + * A pending intent to resolve the connection failure. This intent can be started with + * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} to + * present UI to solve the issue. + * + * @return The pending intent to resolve the connection failure. + */ + public PendingIntent getResolution() { + return pendingIntent; + } + @Override public int hashCode() { return Arrays.hashCode(new Object[]{statusCode, pendingIntent, message}); @@ -253,6 +283,15 @@ public class ConnectionResult { return statusCode != 0 && pendingIntent != null; } + /** + * Returns {@code true} if the connection was successful. + * + * @return {@code true} if the connection was successful, {@code false} if there was an error. + */ + public boolean isSuccess() { + return statusCode == 0; + } + /** * Resolves an error by starting any intents requiring user interaction. See * {@link #SIGN_IN_REQUIRED}, and {@link #RESOLUTION_REQUIRED}. diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupQuery.java b/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupQuery.java new file mode 100644 index 00000000..ab8da97a --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupQuery.java @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common; + +import android.content.Context; +import android.os.Parcel; +import androidx.annotation.NonNull; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.dynamic.IObjectWrapper; +import com.google.android.gms.dynamic.ObjectWrapper; + +@SafeParcelable.Class +public class GoogleCertificatesLookupQuery extends AbstractSafeParcelable { + @Field(value = 1, getterName = "getCallingPackage") + String callingPackage; + @Field(2) + boolean allowTestKeys; + @Field(3) + boolean ignoreTestKeysOverride; + @Field(4) + IObjectWrapper contextWrapper; + private Context context; + @Field(5) + boolean isChimeraPackage; + @Field(6) + boolean includeHashesInErrorMessage; + + public String getCallingPackage() { + return callingPackage; + } + + public Context getContext() { + if (context == null && contextWrapper != null) { + context = ObjectWrapper.unwrapTyped(contextWrapper, Context.class); + } + return context; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GoogleCertificatesLookupQuery.class); +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupResponse.java b/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupResponse.java new file mode 100644 index 00000000..90e0ee3a --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupResponse.java @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common; + +import android.os.Parcel; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class GoogleCertificatesLookupResponse extends AbstractSafeParcelable { + @Field(1) + public final boolean result; + @Field(2) + public final String errorMessage; + @Field(3) + public final int statusValue; + @Field(4) + public final int firstPartyStatusValue; + + @Constructor + public GoogleCertificatesLookupResponse(@Param(1) boolean result, @Param(2) String errorMessage, @Param(3) int statusValue, @Param(4) int firstPartyStatusValue) { + this.result = result; + this.errorMessage = errorMessage; + this.statusValue = statusValue; + this.firstPartyStatusValue = firstPartyStatusValue; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + CREATOR.writeToParcel(this, out, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GoogleCertificatesLookupResponse.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/common/internal/GoogleCertificatesQuery.java b/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesQuery.java similarity index 53% rename from play-services-api/src/main/java/com/google/android/gms/common/internal/GoogleCertificatesQuery.java rename to play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesQuery.java index 02dcc3e9..dce40a90 100644 --- a/play-services-api/src/main/java/com/google/android/gms/common/internal/GoogleCertificatesQuery.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesQuery.java @@ -1,47 +1,38 @@ /* - * Copyright (C) 2019 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. + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ -package com.google.android.gms.common.internal; +package com.google.android.gms.common; import android.os.IBinder; +import android.os.Parcel; import android.os.RemoteException; - +import androidx.annotation.NonNull; +import com.google.android.gms.common.internal.CertData; +import com.google.android.gms.common.internal.ICertData; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.dynamic.ObjectWrapper; +import org.microg.gms.common.Hide; -import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParceled; - -public class GoogleCertificatesQuery extends AutoSafeParcelable { - @SafeParceled(1) - private final String packageName; - @SafeParceled(2) - private final IBinder certDataBinder; +@Hide +@SafeParcelable.Class +public class GoogleCertificatesQuery extends AbstractSafeParcelable { + @Field(value = 1, getterName = "getCallingPackage") + String callingPackage; + @Field(2) + IBinder certDataBinder; private CertData certData; - @SafeParceled(3) - private final boolean allowNonRelease; + @Field(3) + boolean allowTestKeys; + @Field(4) + boolean ignoreTestKeysOverride; - public GoogleCertificatesQuery(String packageName, IBinder certDataBinder, boolean allowNonRelease) { - this.packageName = packageName; - this.certDataBinder = certDataBinder; - this.allowNonRelease = allowNonRelease; - } - - public String getPackageName() { - return packageName; + public String getCallingPackage() { + return callingPackage; } public CertData getCertData() { @@ -77,5 +68,10 @@ public class GoogleCertificatesQuery extends AutoSafeParcelable { return certData; } - public static final Creator CREATOR = new AutoCreator(GoogleCertificatesQuery.class); + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GoogleCertificatesQuery.class); } diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/Scopes.java b/play-services-basement/src/main/java/com/google/android/gms/common/Scopes.java new file mode 100644 index 00000000..520bef02 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/Scopes.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.gms.common; + +public class Scopes { + public static final String PROFILE = "profile"; + public static final String APP_STATE = "https://www.googleapis.com/auth/appstate"; +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/api/CommonStatusCodes.java b/play-services-basement/src/main/java/com/google/android/gms/common/api/CommonStatusCodes.java index 984c6fce..6a2cbb51 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/api/CommonStatusCodes.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/api/CommonStatusCodes.java @@ -16,10 +16,13 @@ package com.google.android.gms.common.api; +import androidx.annotation.NonNull; + import org.microg.gms.common.PublicApi; @PublicApi public class CommonStatusCodes { + public static final int SUCCESS_CACHE = -1; public static final int SUCCESS = 0; @Deprecated public static final int SERVICE_MISSING = 1; @@ -28,10 +31,61 @@ public class CommonStatusCodes { @Deprecated public static final int SERVICE_DISABLED = 3; public static final int SIGN_IN_REQUIRED = 4; + public static final int INVALID_ACCOUNT = 5; public static final int RESOLUTION_REQUIRED = 6; + public static final int NETWORK_ERROR = 7; public static final int INTERNAL_ERROR = 8; + public static final int SERVICE_INVALID = 9; + public static final int DEVELOPER_ERROR = 10; + public static final int LICENSE_CHECK_FAILED = 11; public static final int ERROR = 13; public static final int INTERRUPTED = 14; + public static final int TIMEOUT = 15; public static final int CANCELED = 16; + public static final int API_NOT_CONNECTED = 17; + public static final int DEAD_CLIENT = 18; + @NonNull + public static String getStatusCodeString(int statusCode) { + switch (statusCode) { + case SUCCESS_CACHE: + return "SUCCESS_CACHE"; + case SUCCESS: + return "SUCCESS"; + case SERVICE_VERSION_UPDATE_REQUIRED: + return "SERVICE_VERSION_UPDATE_REQUIRED"; + case SERVICE_DISABLED: + return "SERVICE_DISABLED"; + case SIGN_IN_REQUIRED: + return "SIGN_IN_REQUIRED"; + case INVALID_ACCOUNT: + return "INVALID_ACCOUNT"; + case RESOLUTION_REQUIRED: + return "RESOLUTION_REQUIRED"; + case NETWORK_ERROR: + return "NETWORK_ERROR"; + case INTERNAL_ERROR: + return "INTERNAL_ERROR"; + case SERVICE_INVALID: + return "SERVICE_INVALID"; + case DEVELOPER_ERROR: + return "DEVELOPER_ERROR"; + case LICENSE_CHECK_FAILED: + return "LICENSE_CHECK_FAILED"; + case ERROR: + return "ERROR"; + case INTERRUPTED: + return "INTERRUPTED"; + case TIMEOUT: + return "TIMEOUT"; + case CANCELED: + return "CANCELED"; + case API_NOT_CONNECTED: + return "API_NOT_CONNECTED"; + case DEAD_CLIENT: + return "DEAD_CLIENT"; + default: + return "unknown status code: " + statusCode; + } + } } diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/api/Scope.java b/play-services-basement/src/main/java/com/google/android/gms/common/api/Scope.java index a90803e7..60bc1fb9 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/api/Scope.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/api/Scope.java @@ -16,8 +16,6 @@ package com.google.android.gms.common.api; -import androidx.annotation.NonNull; - import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; @@ -46,22 +44,20 @@ public class Scope extends AutoSafeParcelable { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Scope)) return false; - assert scopeUri != null; - return scopeUri.equals(((Scope) o).scopeUri); + return this == o || o instanceof Scope && scopeUri.equals(((Scope) o).scopeUri); + } + + public String getScopeUri() { + return scopeUri; } @Override public int hashCode() { - assert scopeUri != null; return scopeUri.hashCode(); } - @NonNull @Override public String toString() { - assert scopeUri != null; return scopeUri; } diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/api/Status.java b/play-services-basement/src/main/java/com/google/android/gms/common/api/Status.java index 433ef043..7b936e67 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/api/Status.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/api/Status.java @@ -16,7 +16,11 @@ package com.google.android.gms.common.api; +import android.app.Activity; import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentSender; +import android.content.IntentSender.SendIntentException; import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; @@ -40,8 +44,16 @@ public final class Status extends AutoSafeParcelable implements Result { @SafeParceled(1) private final int statusCode; + @SafeParceled(2) + private final String statusMessage; + + @SafeParceled(3) + private final PendingIntent resolution; + private Status() { statusCode = 0; + statusMessage = null; + resolution = null; } /** @@ -72,6 +84,19 @@ public final class Status extends AutoSafeParcelable implements Result { */ public Status(int statusCode, String statusMessage, PendingIntent resolution) { this.statusCode = statusCode; + this.statusMessage = statusMessage; + this.resolution = resolution; + } + + /** + * A pending intent to resolve the failure. This intent can be started with + * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} to + * present UI to solve the issue. + * + * @return The pending intent to resolve the failure. + */ + public PendingIntent getResolution() { + return resolution; } /** @@ -86,6 +111,30 @@ public final class Status extends AutoSafeParcelable implements Result { return this; } + /** + * Indicates the status of the operation. + * + * @return Status code resulting from the operation. The value is one of the constants in + * {@link CommonStatusCodes} or specific to the APIs added to the GoogleApiClient. + */ + public int getStatusCode() { + return statusCode; + } + + public String getStatusMessage() { + return statusMessage; + } + + /** + * Returns true if calling {@link #startResolutionForResult(Activity, int)} will start any + * intents requiring user interaction. + * + * @return true if there is a resolution that can be started. + */ + public boolean hasResolution() { + return resolution != null; + } + /** * Returns true if the operation was canceled. */ @@ -93,5 +142,39 @@ public final class Status extends AutoSafeParcelable implements Result { return statusCode == CommonStatusCodes.CANCELED; } + /** + * Returns true if the operation was interrupted. + */ + public boolean isInterrupted() { + return statusCode == CommonStatusCodes.INTERRUPTED; + } + + /** + * Returns true if the operation was successful. + * + * @return true if the operation was successful, false if there was an error. + */ + public boolean isSuccess() { + return statusCode <= 0; + } + + /** + * Resolves an error by starting any intents requiring user interaction. See + * {@link CommonStatusCodes#SIGN_IN_REQUIRED}, and {@link CommonStatusCodes#RESOLUTION_REQUIRED}. + * + * @param activity An Activity context to use to resolve the issue. The activity's + * onActivityResult method will be invoked after the user is done. If the + * resultCode is {@link Activity#RESULT_OK}, the application should try to + * connect again. + * @param requestCode The request code to pass to onActivityResult. + * @throws SendIntentException If the resolution intent has been canceled or is no longer able + * to execute the request. + */ + public void startResolutionForResult(Activity activity, int requestCode) throws SendIntentException { + if (hasResolution()) { + activity.startIntentSenderForResult(resolution.getIntentSender(), requestCode, null, 0, 0, 0); + } + } + public static final Creator CREATOR = new AutoCreator<>(Status.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/common/internal/CertData.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/CertData.java similarity index 93% rename from play-services-api/src/main/java/com/google/android/gms/common/internal/CertData.java rename to play-services-basement/src/main/java/com/google/android/gms/common/internal/CertData.java index 62b1abfd..826d1c37 100644 --- a/play-services-api/src/main/java/com/google/android/gms/common/internal/CertData.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/CertData.java @@ -55,12 +55,12 @@ public class CertData extends ICertData.Stub { } @Override - public IObjectWrapper getWrappedBytes() { + public IObjectWrapper getWrappedBytes() throws RemoteException { return ObjectWrapper.wrap(getBytes()); } @Override - public int remoteHashCode() { + public int remoteHashCode() throws RemoteException { return hashCode(); } } diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/ConnectionInfo.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/ConnectionInfo.java new file mode 100644 index 00000000..19326d08 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/ConnectionInfo.java @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal; + +import android.os.Bundle; +import android.os.Parcel; +import androidx.annotation.NonNull; +import com.google.android.gms.common.Feature; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class ConnectionInfo extends AbstractSafeParcelable { + @Field(1) + public Bundle params; + @Field(2) + public Feature[] features; + @Field(3) + public int unknown3; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(ConnectionInfo.class); +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java index a203a99f..9df8735c 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java @@ -31,7 +31,7 @@ import java.util.Arrays; public class GetServiceRequest extends AutoSafeParcelable { @Field(1) - private final int versionCode = 4; + private final int versionCode = 6; @Field(2) public final int serviceId; @Field(3) @@ -47,6 +47,7 @@ public class GetServiceRequest extends AutoSafeParcelable { @Field(8) public Account account; @Field(9) + @Deprecated private long field9; @Field(10) public Feature[] defaultFeatures; @@ -59,7 +60,7 @@ public class GetServiceRequest extends AutoSafeParcelable { @Field(14) private boolean field14; @Field(15) - private String field15; + private String attributionTag; private GetServiceRequest() { serviceId = -1; diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/ValidateAccountRequest.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/ValidateAccountRequest.java index 75e59d95..1cf0ecc1 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/internal/ValidateAccountRequest.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/ValidateAccountRequest.java @@ -16,8 +16,18 @@ package com.google.android.gms.common.internal; -import org.microg.safeparcel.AutoSafeParcelable; +import android.os.Parcel; +import androidx.annotation.NonNull; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; -public class ValidateAccountRequest extends AutoSafeParcelable { - public static Creator CREATOR = new AutoCreator<>(ValidateAccountRequest.class); +@SafeParcelable.Class +public class ValidateAccountRequest extends AbstractSafeParcelable { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = findCreator(ValidateAccountRequest.class); } diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/AbstractSafeParcelable.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/AbstractSafeParcelable.java new file mode 100644 index 00000000..ec7d84d5 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/AbstractSafeParcelable.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +public abstract class AbstractSafeParcelable implements SafeParcelable { + + @SuppressWarnings("unchecked") + public static SafeParcelableCreatorAndWriter findCreator(java.lang.Class tClass) { + String creatorClassName = tClass.getName() + "$000Creator"; + try { + return (SafeParcelableCreatorAndWriter) java.lang.Class.forName(creatorClassName).newInstance(); + } catch (Exception e) { + throw new RuntimeException("No Creator found for " + tClass.getName(), e); + } + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelReader.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelReader.java new file mode 100644 index 00000000..226f50e8 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelReader.java @@ -0,0 +1,328 @@ +/* + * SPDX-FileCopyrightText: 2015, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.HashMap; + +@SuppressWarnings("MagicNumber") +public final class SafeParcelReader { + + private SafeParcelReader() { + } + + @Deprecated + public static int halfOf(int i) { + return i & 0xFFFF; + } + + public static int getFieldId(int header) { + return header & 0xFFFF; + } + + @Deprecated + public static int readSingleInt(Parcel parcel) { + return parcel.readInt(); + } + + public static int readHeader(Parcel parcel) { + return parcel.readInt(); + } + + private static int readSize(Parcel parcel, int header) { + if ((header & 0xFFFF0000) != 0xFFFF0000) + return header >> 16 & 0xFFFF; + return parcel.readInt(); + } + + private static void readExpectedSize(Parcel parcel, int header, int expectedSize) { + int i = readSize(parcel, header); + if (i != expectedSize) + throw new ReadException("Expected size " + expectedSize + " got " + i + " (0x" + Integer.toHexString(i) + ")", parcel); + } + + @Deprecated + public static int readStart(Parcel parcel) { + return readObjectHeader(parcel); + } + + public static int readObjectHeader(Parcel parcel) { + int header = readHeader(parcel); + int size = readSize(parcel, header); + int start = parcel.dataPosition(); + if (getFieldId(header) != SafeParcelable.SAFE_PARCEL_OBJECT_MAGIC) + throw new ReadException("Expected object header. Got 0x" + Integer.toHexString(header), parcel); + int end = start + size; + if ((end < start) || (end > parcel.dataSize())) + throw new ReadException("Size read is invalid start=" + start + " end=" + end, parcel); + return end; + } + + public static int readInt(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return parcel.readInt(); + } + + public static byte readByte(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return (byte) parcel.readInt(); + } + + public static short readShort(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return (short) parcel.readInt(); + } + + public static boolean readBool(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return parcel.readInt() != 0; + } + + public static long readLong(Parcel parcel, int header) { + readExpectedSize(parcel, header, 8); + return parcel.readLong(); + } + + public static float readFloat(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return parcel.readFloat(); + } + + public static double readDouble(Parcel parcel, int header) { + readExpectedSize(parcel, header, 8); + return parcel.readDouble(); + } + + public static String readString(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + String string = parcel.readString(); + parcel.setDataPosition(start + size); + return string; + } + + public static IBinder readBinder(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + IBinder binder = parcel.readStrongBinder(); + parcel.setDataPosition(start + size); + return binder; + } + + public static T readParcelable(Parcel parcel, int header, Parcelable.Creator creator) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + T t = creator.createFromParcel(parcel); + parcel.setDataPosition(start + size); + return t; + } + + public static ArrayList readList(Parcel parcel, int header, ClassLoader classLoader) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + ArrayList list = parcel.readArrayList(classLoader); + parcel.setDataPosition(start + size); + return list; + } + + public static HashMap readMap(Parcel parcel, int header, ClassLoader classLoader) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + HashMap map = parcel.readHashMap(classLoader); + parcel.setDataPosition(start + size); + return map; + } + + public static ArrayList readParcelableList(Parcel parcel, int header, Parcelable.Creator creator) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + ArrayList list = parcel.createTypedArrayList(creator); + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readStringList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + ArrayList list = parcel.createStringArrayList(); + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readIntegerList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readInt()); + } + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readLongList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readLong()); + } + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readFloatList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readFloat()); + } + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readDoubleList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readDouble()); + } + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readBooleanList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readInt() != 0); + } + parcel.setDataPosition(start + size); + return list; + } + + public static T[] readParcelableArray(Parcel parcel, int header, Parcelable.Creator creator) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + T[] arr = parcel.createTypedArray(creator); + parcel.setDataPosition(start + size); + return arr; + } + + public static String[] readStringArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + String[] arr = parcel.createStringArray(); + parcel.setDataPosition(start + size); + return arr; + } + + public static byte[] readByteArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + byte[] arr = parcel.createByteArray(); + parcel.setDataPosition(start + size); + return arr; + } + + public static byte[][] readByteArrayArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + byte[][] arr = new byte[length][]; + for (int i = 0; i < length; i++) { + arr[i] = parcel.createByteArray(); + } + parcel.setDataPosition(start + size); + return arr; + } + + public static float[] readFloatArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + float[] arr = parcel.createFloatArray(); + parcel.setDataPosition(start + size); + return arr; + } + + public static int[] readIntArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int[] arr = parcel.createIntArray(); + parcel.setDataPosition(start + size); + return arr; + } + + public static Bundle readBundle(Parcel parcel, int header, ClassLoader classLoader) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + Bundle bundle = parcel.readBundle(classLoader); + parcel.setDataPosition(start + size); + return bundle; + } + + public static void skip(Parcel parcel, int header) { + int size = readSize(parcel, header); + parcel.setDataPosition(parcel.dataPosition() + size); + } + + public static class ReadException extends RuntimeException { + public ReadException(String message, Parcel parcel) { + super(message); + } + } +} \ No newline at end of file diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelWriter.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelWriter.java new file mode 100644 index 00000000..b2f96116 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelWriter.java @@ -0,0 +1,384 @@ +/* + * SPDX-FileCopyrightText: 2015, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; +import java.util.Map; + +@SuppressWarnings("MagicNumber") +public final class SafeParcelWriter { + + private SafeParcelWriter() { + } + + private static void writeHeader(Parcel parcel, int fieldId, int size) { + if (size >= 0xFFFF) { + parcel.writeInt(0xFFFF0000 | fieldId); + parcel.writeInt(size); + } else { + parcel.writeInt(size << 16 | fieldId); + } + } + + @Deprecated + public static int writeStart(Parcel parcel) { + return writeObjectHeader(parcel); + } + + public static int writeObjectHeader(Parcel parcel) { + writeHeader(parcel, SafeParcelable.SAFE_PARCEL_OBJECT_MAGIC, 0xFFFF); + return parcel.dataPosition(); + } + + private static int writeObjectHeader(Parcel parcel, int fieldId) { + writeHeader(parcel, fieldId, 0xFFFF); + return parcel.dataPosition(); + } + + @Deprecated + public static void writeEnd(Parcel parcel, int start) { + finishObjectHeader(parcel, start); + } + + public static void finishObjectHeader(Parcel parcel, int start) { + int end = parcel.dataPosition(); + int length = end - start; + parcel.setDataPosition(start - 4); + parcel.writeInt(length); + parcel.setDataPosition(end); + } + + public static void write(Parcel parcel, int fieldId, Boolean val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeInt(val ? 1 : 0); + } + + public static void write(Parcel parcel, int fieldId, Byte val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeInt(val); + } + + public static void write(Parcel parcel, int fieldId, Short val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeInt(val); + } + + public static void write(Parcel parcel, int fieldId, Integer val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeInt(val); + } + + public static void write(Parcel parcel, int fieldId, Long val) { + if (val == null) return; + writeHeader(parcel, fieldId, 8); + parcel.writeLong(val); + } + + public static void write(Parcel parcel, int fieldId, Float val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeFloat(val); + } + + public static void write(Parcel parcel, int fieldId, Double val) { + if (val == null) return; + writeHeader(parcel, fieldId, 8); + parcel.writeDouble(val); + } + + public static void write(Parcel parcel, int fieldId, String val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeString(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, Parcelable val, int flags, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + val.writeToParcel(parcel, flags); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, Bundle val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeBundle(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, byte[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeByteArray(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, byte[][] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.length); + for (byte[] arr : val) { + parcel.writeByteArray(arr); + } + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, float[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeFloatArray(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, int[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeIntArray(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, String[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeStringArray(val); + finishObjectHeader(parcel, start); + } + } + + public static void writeStringList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeStringList(val); + finishObjectHeader(parcel, start); + } + } + + public static void writeIntegerList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Integer i : val) { + parcel.writeInt(i); + } + finishObjectHeader(parcel, start); + } + } + + public static void writeLongList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Long l : val) { + parcel.writeLong(l); + } + finishObjectHeader(parcel, start); + } + } + + public static void writeFloatList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Float f : val) { + parcel.writeFloat(f); + } + finishObjectHeader(parcel, start); + } + } + + public static void writeDoubleList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Double d : val) { + parcel.writeDouble(d); + } + finishObjectHeader(parcel, start); + } + } + + public static void writeBooleanList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Boolean b : val) { + parcel.writeInt(b ? 1 : 0); + } + finishObjectHeader(parcel, start); + } + } + + private static void writeArrayPart(Parcel parcel, T val, int flags) { + int before = parcel.dataPosition(); + parcel.writeInt(1); + int start = parcel.dataPosition(); + val.writeToParcel(parcel, flags); + int end = parcel.dataPosition(); + parcel.setDataPosition(before); + parcel.writeInt(end - start); + parcel.setDataPosition(end); + } + + public static void write(Parcel parcel, int fieldId, T[] val, int flags, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.length); + for (T t : val) { + if (t == null) { + parcel.writeInt(0); + } else { + writeArrayPart(parcel, t, flags); + } + } + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, List val, int flags, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (T t : val) { + if (t == null) { + parcel.writeInt(0); + } else { + writeArrayPart(parcel, t, flags); + } + } + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, Parcel val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.appendFrom(val, 0, val.dataSize()); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeList(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, Map val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeMap(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, IBinder val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeStrongBinder(val); + finishObjectHeader(parcel, start); + } + } + +} \ No newline at end of file diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelable.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelable.java new file mode 100644 index 00000000..0d8d1cc0 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelable.java @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Parcelable; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public interface SafeParcelable extends Parcelable { + int SAFE_PARCEL_OBJECT_MAGIC = 0x4F45; + + @Target(ElementType.TYPE) + @interface Class { + } + + @Target(ElementType.CONSTRUCTOR) + @interface Constructor { + + } + + @Target(ElementType.PARAMETER) + @interface Param { + int value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface Field { + int value(); + + boolean mayNull() default false; + + java.lang.Class subClass() default SafeParcelable.class; + + boolean useValueParcel() default false; + + boolean useDirectList() default false; + + long versionCode() default -1; + + String defaultValue() default ""; + + String type() default ""; + + String getterName() default ""; + + String getter() default ""; + } +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableCreatorAndWriter.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableCreatorAndWriter.java new file mode 100644 index 00000000..6afaa9c6 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableCreatorAndWriter.java @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Parcel; +import android.os.Parcelable; + +public interface SafeParcelableCreatorAndWriter extends Parcelable.Creator { + void writeToParcel(T object, Parcel parcel, int flags); +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableSerializer.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableSerializer.java new file mode 100644 index 00000000..66b8b4d5 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableSerializer.java @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Parcel; +import android.os.Parcelable; + +public class SafeParcelableSerializer { + public static T deserializeFromBytes(byte[] bytes, Parcelable.Creator tCreator) { + if (bytes == null) return null; + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(bytes, 0, bytes.length); + parcel.setDataPosition(0); + T parcelable = tCreator.createFromParcel(parcel); + parcel.recycle(); + return parcelable; + } + + public static byte[] serializeToBytes(T parcelable) { + if (parcelable == null) return null; + Parcel parcel = Parcel.obtain(); + parcelable.writeToParcel(parcel, 0); + byte[] bytes = parcel.marshall(); + parcel.recycle(); + return bytes; + } +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/dynamic/ObjectWrapper.java b/play-services-basement/src/main/java/com/google/android/gms/dynamic/ObjectWrapper.java index 9158c941..4bc96034 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/dynamic/ObjectWrapper.java +++ b/play-services-basement/src/main/java/com/google/android/gms/dynamic/ObjectWrapper.java @@ -18,6 +18,7 @@ package com.google.android.gms.dynamic; import android.os.IBinder; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.lang.reflect.Field; @@ -34,37 +35,13 @@ public class ObjectWrapper extends IObjectWrapper.Stub { if (obj == null) { return null; } + if (obj instanceof ObjectWrapper) { return ((ObjectWrapper) obj).t; } + IBinder binder = obj.asBinder(); - Field[] fields = binder.getClass().getDeclaredFields(); - - if (fields.length < 1) { - throw new IllegalArgumentException("No fields were found"); - } - - // Ignore synthetic field(s) from JaCoCo or elsewhere - // https://www.jacoco.org/jacoco/trunk/doc/faq.html - - @Nullable - Field field = null; - - for (Field currentField : fields) { - if (currentField.isSynthetic()) { - continue; - } - - if (field == null) { - field = currentField; - } else { - throw new IllegalArgumentException("Too many non-synthetic fields were found"); - } - } - - if (field == null) { - throw new IllegalArgumentException("No non-synthetic fields were found"); - } + Field field = getField(binder); if (!field.isAccessible()) { field.setAccessible(true); @@ -85,6 +62,35 @@ public class ObjectWrapper extends IObjectWrapper.Stub { } } + @NonNull + private static Field getField(IBinder binder) { + Field[] fields = binder.getClass().getDeclaredFields(); + + if (fields.length < 1) { + throw new IllegalArgumentException("No fields were found"); + } + + @Nullable + Field field = null; + + for (Field currentField : fields) { + if (currentField.isSynthetic()) { + continue; + } + + if (field == null) { + field = currentField; + } else { + throw new IllegalArgumentException("Too many non-synthetic fields were found"); + } + } + + if (field == null) { + throw new IllegalArgumentException("No non-synthetic fields were found"); + } + return field; + } + @Nullable public static T unwrapTyped(IObjectWrapper obj, Class clazz) { try { @@ -97,4 +103,4 @@ public class ObjectWrapper extends IObjectWrapper.Stub { public static ObjectWrapper wrap(T t) { return new ObjectWrapper<>(t); } -} \ No newline at end of file +} diff --git a/play-services-basement/src/main/java/org/microg/gms/auth/AuthConstants.java b/play-services-basement/src/main/java/org/microg/gms/auth/AuthConstants.java index 774f5756..6cac0d45 100644 --- a/play-services-basement/src/main/java/org/microg/gms/auth/AuthConstants.java +++ b/play-services-basement/src/main/java/org/microg/gms/auth/AuthConstants.java @@ -16,11 +16,14 @@ package org.microg.gms.auth; +import org.microg.gms.basement.BuildConfig; + public class AuthConstants { + public static final String DEFAULT_ACCOUNT = "<>"; public static final String SCOPE_GET_ACCOUNT_ID = "^^_account_id_^^"; public static final String PROVIDER_METHOD_GET_ACCOUNTS = "get_accounts"; public static final String PROVIDER_METHOD_CLEAR_PASSWORD = "clear_password"; public static final String PROVIDER_EXTRA_CLEAR_PASSWORD = "clear_password"; public static final String PROVIDER_EXTRA_ACCOUNTS = "accounts"; - public static final String DEFAULT_ACCOUNT_TYPE = "com.mgoogle"; + public static final String DEFAULT_ACCOUNT_TYPE = BuildConfig.BASE_PACKAGE_NAME; } diff --git a/play-services-basement/src/main/java/org/microg/gms/common/Constants.java b/play-services-basement/src/main/java/org/microg/gms/common/Constants.java index 077bdbde..a905f2bc 100644 --- a/play-services-basement/src/main/java/org/microg/gms/common/Constants.java +++ b/play-services-basement/src/main/java/org/microg/gms/common/Constants.java @@ -21,9 +21,8 @@ import org.microg.gms.basement.BuildConfig; public class Constants { public static final int GMS_VERSION_CODE = (BuildConfig.VERSION_CODE / 1000) * 1000; public static final String GMS_PACKAGE_NAME = "com.google.android.gms"; + public static final String GSF_PACKAGE_NAME = BuildConfig.BASE_PACKAGE_NAME + ".android.gsf"; public static final String GMS_PACKAGE_SIGNATURE_SHA1 = "38918a453d07199354f8b19af05ec6562ced5788"; - public static final String GMS_SECONDARY_PACKAGE_SIGNATURE_SHA1 = "bd32424203e0fb25f36b57e5aa356f9bdd1da998"; - @Deprecated public static final int MAX_REFERENCE_VERSION = GMS_VERSION_CODE; } diff --git a/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java b/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java index 739bc259..efdbb1d2 100644 --- a/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java +++ b/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java @@ -19,20 +19,161 @@ package org.microg.gms.common; public enum GmsService { UNKNOWN(-2), ANY(-1), + GAMES(1, "com.google.android.gms.games.service.START", "com.google.android.gms.games.internal.connect.service.START"), + PLUS(2, "com.google.android.gms.plus.service.START", "com.google.android.gms.plus.service.internal.START"), + PANORAMA(3, "com.google.android.gms.panorama.service.START"), + WALLET(4, "com.google.android.gms.wallet.service.BIND"), PEOPLE(5, "com.google.android.gms.people.service.START"), + LOCATION(6), + APPSTATE(7, "com.google.android.gms.appstate.service.START"), + ADREQUEST(8, "com.google.android.gms.ads.service.START"), ACCOUNT(9, "com.google.android.gms.accounts.ACCOUNT_SERVICE"), + CAST(10, "com.google.android.gms.cast.service.BIND_CAST_DEVICE_CONTROLLER_SERVICE"), + DRIVE(11, "com.google.android.gms.drive.ApiService.START"), ADDRESS(12, "com.google.android.gms.identity.service.BIND"), + CAR(13, "com.google.android.gms.car.service.START"), + WEARABLE(14, "com.google.android.gms.wearable.BIND"), AUTH_PROXY(16, "com.google.android.gms.auth.service.START"), + FITNESS(17, "com.google.android.gms.fitness.GoogleFitnessService.START"), + REMINDERS(18, "com.google.android.gms.reminders.service.START"), + LIGHTWEIGHT_INDEX(19, "com.google.android.gms.icing.LIGHTWEIGHT_INDEX_SERVICE"), + DEVICE_CONNECTION(20, "com.google.android.gms.deviceconnection.service.START"), INDEX(21, "com.google.android.gms.icing.INDEX_SERVICE"), + LOCATION_REPORTING(22, "com.google.android.gms.location.reporting.service.START", "com.google.android.location.reporting.service.START"), + LOCATION_MANAGER(23, "com.google.android.location.internal.GoogleLocationManagerService.START"), + PLAY_LOG(24, "com.google.android.gms.playlog.service.START"), + DROIDGUARD(25, "com.google.android.gms.droidguard.service.START"), + LOCKBOX(26, "com.google.android.gms.lockbox.service.START"), + CAST_MIRRORING(27, "com.google.android.gms.cast_mirroring.service.START"), + NETWORK_QUALITY(28, "com.google.android.gms.mdm.services.START"), + FEEDBACK(29, "com.google.android.gms.feedback.internal.IFeedbackService"), + SEARCH_ADMINISTRATION(30), + PHOTO_AUTO_BACKUP(31, "com.google.android.gms.photos.autobackup.service.START"), + SEARCH_QUERIES(32), + SEARCH_GLOBAL(33), + UDC(35, "com.google.android.gms.udc.service.START"), + SEARCH_CORPORA(36), + DEVICE_MANAGER(37, "com.google.android.gms.mdm.services.DeviceManagerApiService.START"), + PSEUDONYMOUS_ID(38, "com.google.android.gms.pseudonymous.service.START"), COMMON(39, "com.google.android.gms.common.service.START"), + CLEARCUT_LOGGER(40, "com.google.android.gms.clearcut.service.START"), + USAGE_REPORTING(41, "com.google.android.gms.usagereporting.service.START"), + KIDS(42, "com.google.android.gms.kids.service.START"), + DOWNLOAD(43, "com.google.android.gms.common.download.START"), SIGN_IN(44, "com.google.android.gms.signin.service.START"), + SAFETY_NET_CLIENT(45, "com.google.android.gms.safetynet.service.START"), + GSERVICES(46, "com.google.android.gms.ads.gservice.START"), + CONTEXT_MANAGER(47, "com.google.android.contextmanager.service.ContextManagerService.START"), + AUDIO_MODEM(48, "com.google.android.gms.audiomodem.service.AudioModemService.START"), + NEARBY_SHARING(49, "com.google.android.gms.nearby.sharing.service.NearbySharingService.START"), + LIGHTWEIGHT_NETWORK_QUALITY(50, "com.google.android.gms.herrevad.services.LightweightNetworkQualityAndroidService.START"), + PHENOTYPE(51, "com.google.android.gms.phenotype.service.START"), + VOICE_UNLOCK(52, "com.google.android.gms.speech.service.START"), + NEARBY_CONNECTIONS(54, "com.google.android.gms.nearby.connection.service.START"), + FITNESS_SENSORS(55, "com.google.android.gms.fitness.SensorsApi"), + FITNESS_RECORDING(56, "com.google.android.gms.fitness.RecordingApi"), + FITNESS_HISTORY(57, "com.google.android.gms.fitness.HistoryApi"), + FITNESS_SESSIONS(58, "com.google.android.gms.fitness.SessionsApi"), + FITNESS_BLE(59, "com.google.android.gms.fitness.BleApi"), + FITNESS_CONFIG(60, "com.google.android.gms.fitness.ConfigApi"), + FITNESS_INTERNAL(61, "com.google.android.gms.fitness.InternalApi"), + NEARBY_MESSAGES(62, "com.google.android.gms.nearby.messages.service.NearbyMessagesService.START"), + HELP(63, "com.google.android.gms.googlehelp.service.GoogleHelpService.START"), + CONFIG(64, "com.google.android.gms.config.START"), + GEODATA(65, "com.google.android.gms.location.places.GeoDataApi"), + SEARCH_IME(66), + PLACE_DETECTION(67, "com.google.android.gms.location.places.PlaceDetectionApi"), CREDENTIALS(68, "com.google.android.gms.auth.api.credentials.service.START"), + NEARBY_BOOTSTRAP(69, "com.google.android.gms.nearby.bootstrap.service.NearbyBootstrapService.START"), + PLUS_INTERNAL(70), + SOURCE_DEVICE(75, "com.google.android.gms.smartdevice.d2d.SourceDeviceService.START"), + TARGET_DEVICE(76, "com.google.android.gms.smartdevice.d2d.TargetDeviceService.START"), + APP_INVITE(77, "com.google.android.gms.appinvite.service.START"), + TAP_AND_PAY(79, "com.google.android.gms.tapandpay.service.BIND"), + CHROME_SYNC(80, "com.google.android.gms.chromesync.service.START"), + ACCOUNTS(81, "com.google.android.gms.smartdevice.setup.accounts.AccountsService.START"), + CAST_REMOTE_DISPLAY(83, "com.google.android.gms.cast.remote_display.service.START"), + TRUST_AGENT(85, "com.google.android.gms.trustagent.StateApi.START"), + AUTH_SIGN_IN(91, "com.google.android.gms.auth.api.signin.service.START"), + MEASUREMENT(93, "com.google.android.gms.measurement.START"), + FREIGHTER(98, "com.google.android.gms.freighter.service.START"), + GUNS(110, "com.google.android.gms.notifications.service.START"), + BLE(111, "com.google.android.gms.beacon.internal.IBleService.START"), + FIREBASE_AUTH(112, "com.google.firebase.auth.api.gms.service.START"), + APP_INDEXING(113), + GASS(116, "com.google.android.gms.gass.START"), + WORK_ACCOUNT(120), + INSTANT_APPS(121, "com.google.android.gms.instantapps.START"), + CAST_FIRSTPATY(122, "com.google.android.gms.cast.firstparty.START"), + AD_CACHE(123, "com.google.android.gms.ads.service.CACHE"), + SMS_RETRIEVER(126, "com.google.android.gms.auth.api.phone.service.SmsRetrieverApiService.START"), + CRYPT_AUTH(129, "com.google.android.gms.auth.cryptauth.cryptauthservice.START"), + DYNAMIC_LINKS(131, "com.google.firebase.dynamiclinks.service.START"), + FONTS(132, "com.google.android.gms.fonts.service.START"), + ROMANESCO(135, "com.google.android.gms.romanesco.service.START"), + TRAINER(139, "com.google.android.gms.learning.trainer.START"), + FIDO2_REGULAR(148, "com.google.android.gms.fido.fido2.regular.START"), + FIDO2_PRIVILEGED(149, "com.google.android.gms.fido.fido2.privileged.START"), + DATA_DOWNLOAD(152, "com.google.android.mdd.service.START"), + ACCOUNT_DATA(153, "com.google.android.gms.auth.account.data.service.START"), + CONSTELLATION(155, "com.google.android.gms.constellation.service.START"), + AUDIT(154, "com.google.android.gms.audit.service.START"), + SYSTEM_UPDATE(157, "com.google.android.gms.update.START_API_SERVICE"), + MOBSTORE(160, "com.google.android.mobstore.service.START"), + USER_LOCATION(163, "com.google.android.gms.userlocation.service.START"), + AD_HTTP(166, "com.google.android.gms.ads.service.HTTP"), + LANGUAGE_PROFILE(167, "com.google.android.gms.languageprofile.service.START"), + MDNS(168, "com.google.android.gms.mdns.service.START"), + FIDO2_ZEROPARTY(180, "com.google.android.gms.fido.fido2.zeroparty.START"), + G1_RESTORE(181, "com.google.android.gms.backup.G1_RESTORE"), + G1_BACKUP(182, "com.google.android.gms.backup.G1_BACKUP"), + OSS_LICENSES(185, "com.google.android.gms.oss.licenses.service.START"), + PAYSE(188, "com.google.android.gms.payse.service.BIND"), + RCS(189, "com.google.android.gms.rcs.START"), + CARRIER_AUTH(191, "com.google.android.gms.carrierauth.service.START"), + SYSTEM_UPDATE_SINGLE_UESR(192, "com.google.android.gms.update.START_SINGLE_USER_API_SERVICE"), + APP_USAGE(193, "com.google.android.gms.appusage.service.START"), + NEARBY_SHARING_2(194, "com.google.android.gms.nearby.sharing.START_SERVICE"), + AD_CONSENT_LOOKUP(195, "com.google.android.gms.ads.service.CONSENT_LOOKUP"), + CREDENTIAL_MANAGER(196, "com.google.android.gms.credential.manager.service.firstparty.START"), + PHONE_INTERNAL(197, "com.google.android.gms.auth.api.phone.service.InternalService.START"), + PAY(198, "com.google.android.gms.pay.service.BIND"), + ASTERISM(199, "com.google.android.gms.asterism.service.START"), + MODULE_RESTORE(201, "com.google.android.gms.backup.GMS_MODULE_RESTORE"), + FACS_CACHE(202, "com.google.android.gms.facs.cache.service.START"), + RECAPTCHA(205, "com.google.android.gms.recaptcha.service.START"), + CONTACT_SYNC(208, "com.google.android.gms.people.contactssync.service.START"), IDENTITY_SIGN_IN(212, "com.google.android.gms.auth.api.identity.service.signin.START"), + CREDENTIAL_STORE(214, "com.google.android.gms.fido.credentialstore.internal_service.START"), + MDI_SYNC(215, "com.google.android.gms.mdisync.service.START"), + EVENT_ATTESTATION(216, "com.google.android.gms.ads.identifier.service.EVENT_ATTESTATION"), + SCHEDULER(218, "com.google.android.gms.scheduler.ACTION_PROXY_SCHEDULE"), + AUTHORIZATION(219, "com.google.android.gms.auth.api.identity.service.authorization.START"), + FACS_SYNC(220, "com.google.android.gms.facs.internal.service.START"), + AUTH_CONFIG_SYNC(221, "com.google.android.gms.auth.config.service.START"), + CREDENTIAL_SAVING(223, "com.google.android.gms.auth.api.identity.service.credentialsaving.START"), + GOOGLE_AUTH(224, "com.google.android.gms.auth.account.authapi.START"), + ENTERPRISE_LOADER(225, "com.google.android.gms.enterprise.loader.service.START"), + THUNDERBIRD(226, "com.google.android.gms.thunderbird.service.START"), + NEARBY_EXPOSURE(236, "com.google.android.gms.nearby.exposurenotification.START"), + GMS_COMPLIANCE(257, "com.google.android.gms.gmscompliance.service.START"), + FIDO_SOURCE_DEVICE(262, "com.google.android.gms.fido.sourcedevice.service.START"), + FAST_PAIR(265, "com.google.android.gms.nearby.fastpair.START"), + MATCHSTICK_LIGHTER(268, "com.google.android.gms.matchstick.lighter.service.START"), + FIDO_TARGET_DEVICE_INTERNAL(269, "com.google.android.gms.fido.targetdevice.internal_service.START"), + TELEMETRY(270, "com.google.android.gms.common.telemetry.service.START"), + SECOND_DEVICE_AUTH(275, "com.google.android.gms.setup.auth.SecondDeviceAuth.START"), + LOCATION_SHARING_REPORTER(277, "com.google.android.gms.locationsharingreporter.service.START"), + OCR(279, "com.google.android.gms.ocr.service.START"), + POTOKENS(285, "com.google.android.gms.potokens.service.START"), + OCR_INTERNAL(281, "com.google.android.gms.ocr.service.internal.START"), + IN_APP_REACH(315, "com.google.android.gms.inappreach.service.START"), + APP_ERRORS(334, "com.google.android.gms.apperrors.service.START_APP_ERROR"), ; - public final int SERVICE_ID; - public final String ACTION; - public final String[] SECONDARY_ACTIONS; + public int SERVICE_ID; + public String ACTION; + public String[] SECONDARY_ACTIONS; GmsService(int serviceId, String... actions) { this.SERVICE_ID = serviceId; @@ -40,6 +181,11 @@ public enum GmsService { this.SECONDARY_ACTIONS = actions; } + public interface ADVERTISING_ID { + // Has no service id + String ACTION = "com.google.android.gms.ads.identifier.service.START"; + } + public static GmsService byServiceId(int serviceId) { for (GmsService service : values()) { if (service.SERVICE_ID == serviceId) return service; @@ -47,6 +193,15 @@ public enum GmsService { return UNKNOWN; } + public static GmsService byAction(String action) { + for (GmsService service : values()) { + for (String serviceAction : service.SECONDARY_ACTIONS) { + if (serviceAction.equals(action)) return service; + } + } + return UNKNOWN; + } + public static String nameFromServiceId(int serviceId) { return byServiceId(serviceId).toString(serviceId); } @@ -55,4 +210,4 @@ public enum GmsService { if (this != UNKNOWN) return toString(); return "UNKNOWN(" + serviceId + ")"; } -} \ No newline at end of file +} diff --git a/play-services-basement/src/main/java/org/microg/gms/common/Hide.java b/play-services-basement/src/main/java/org/microg/gms/common/Hide.java new file mode 100644 index 00000000..e15414b5 --- /dev/null +++ b/play-services-basement/src/main/java/org/microg/gms/common/Hide.java @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.common; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Hide the class, method or field from the public API. + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface Hide { +} diff --git a/play-services-basement/src/main/java/org/microg/gms/common/PublicApi.java b/play-services-basement/src/main/java/org/microg/gms/common/PublicApi.java index 92663979..a7fb9b44 100644 --- a/play-services-basement/src/main/java/org/microg/gms/common/PublicApi.java +++ b/play-services-basement/src/main/java/org/microg/gms/common/PublicApi.java @@ -43,6 +43,8 @@ public @interface PublicApi { * marked as public api. * * @return true if the method or field is not part of the public api + * @deprecated use {@link Hide} instead */ + @Deprecated boolean exclude() default false; } diff --git a/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java b/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java index d96fe90a..00117d4e 100644 --- a/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java +++ b/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java @@ -16,29 +16,80 @@ package org.microg.gms.gcm; +import org.microg.gms.basement.BuildConfig; + public final class GcmConstants { - public static final String ACTION_C2DM_RECEIVE = "com.mgoogle.android.c2dm.intent.RECEIVE"; - public static final String ACTION_C2DM_REGISTER = "com.mgoogle.android.c2dm.intent.REGISTER"; - public static final String ACTION_C2DM_REGISTRATION = "com.mgoogle.android.c2dm.intent.REGISTRATION"; - public static final String ACTION_C2DM_UNREGISTER = "com.mgoogle.android.c2dm.intent.UNREGISTER"; + public static final String ACTION_C2DM_RECEIVE = BuildConfig.BASE_PACKAGE_NAME + ".android.c2dm.intent.RECEIVE"; + public static final String ACTION_C2DM_REGISTER = BuildConfig.BASE_PACKAGE_NAME + ".android.c2dm.intent.REGISTER"; + public static final String ACTION_C2DM_REGISTRATION = BuildConfig.BASE_PACKAGE_NAME + ".android.c2dm.intent.REGISTRATION"; + public static final String ACTION_C2DM_UNREGISTER = BuildConfig.BASE_PACKAGE_NAME + ".android.c2dm.intent.UNREGISTER"; + public static final String ACTION_GCM_SEND = BuildConfig.BASE_PACKAGE_NAME + ".android.gcm.intent.SEND"; + public static final String ACTION_NOTIFICATION_OPEN = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.gcm.NOTIFICATION_OPEN"; + public static final String ACTION_NOTIFICATION_DISMISS = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.gcm.NOTIFICATION_DISMISS"; + public static final String ACTION_SCHEDULE = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.gcm.ACTION_SCHEDULE"; + public static final String ACTION_TASK_READY = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.gcm.ACTION_TASK_READY"; + public static final String ACTION_TASK_INITIALZE = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.gcm.SERVICE_ACTION_INITIALIZE"; + public static final String ACTION_INSTANCE_ID = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.iid.InstanceID"; public static final String EXTRA_APP = "app"; public static final String EXTRA_APP_OVERRIDE = "org.microg.gms.gcm.APP_OVERRIDE"; + public static final String EXTRA_APP_ID = "appid"; + public static final String EXTRA_APP_VERSION_CODE = "app_ver"; public static final String EXTRA_APP_VERSION_NAME = "app_ver_name"; + public static final String EXTRA_CLIENT_VERSION = "cliv"; + public static final String EXTRA_COMPONENT = "component"; public static final String EXTRA_COLLAPSE_KEY = "collapse_key"; + public static final String EXTRA_DELAY = "google.delay"; public static final String EXTRA_DELETE = "delete"; public static final String EXTRA_ERROR = "error"; public static final String EXTRA_FROM = "from"; + public static final String EXTRA_GSF_INTENT = "GSF"; + public static final String EXTRA_GMS_VERSION = "gmsv"; + public static final String EXTRA_IS_MESSENGER2 = "messenger2"; public static final String EXTRA_KID = "kid"; public static final String EXTRA_MESSENGER = "google.messenger"; + public static final String EXTRA_MESSAGE_TYPE = "message_type"; public static final String EXTRA_MESSAGE_ID = "google.message_id"; + public static final String EXTRA_OS_VERSION = "osv"; + public static final String EXTRA_PENDING_INTENT = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.gcm.PENDING_INTENT"; + public static final String EXTRA_PUBLIC_KEY = "pub2"; + public static final String EXTRA_RAWDATA = "rawData"; + public static final String EXTRA_RAWDATA_BASE64 = "gcm.rawData64"; public static final String EXTRA_REGISTRATION_ID = "registration_id"; public static final String EXTRA_RETRY_AFTER = "Retry-After"; + public static final String EXTRA_SCHEDULER_ACTION = "scheduler_action"; + public static final String EXTRA_SCOPE = "scope"; public static final String EXTRA_SENDER = "sender"; + public static final String EXTRA_SENDER_LEGACY = "legacy.sender"; public static final String EXTRA_SEND_TO = "google.to"; public static final String EXTRA_SEND_FROM = "google.from"; + public static final String EXTRA_SIGNATURE = "sig"; + public static final String EXTRA_SUBSCIPTION = "subscription"; + public static final String EXTRA_SUBTYPE = "subtype"; + public static final String EXTRA_USE_GSF = "useGsf"; + public static final String EXTRA_TAG = "tag"; + public static final String EXTRA_TOPIC = "gcm.topic"; public static final String EXTRA_TTL = "google.ttl"; public static final String EXTRA_UNREGISTERED = "unregistered"; + public static final String MESSAGE_TYPE_GCM = "gcm"; + public static final String MESSAGE_TYPE_DELETED_MESSAGE = "deleted_message"; + public static final String MESSAGE_TYPE_SEND_ERROR = "send_error"; + public static final String MESSAGE_TYPE_SEND_EVENT = "send_event"; + + public static final String SCHEDULER_ACTION_CANCEL = "CANCEL_TASK"; + public static final String SCHEDULER_ACTION_CANCEL_ALL = "CANCEL_ALL"; + public static final String SCHEDULER_ACTION_SCHEDULE = "SCHEDULE_TASK"; + + public static final String PERMISSION_GTALK = BuildConfig.BASE_PACKAGE_NAME + ".android.gtalkservice.permission.GTALK_SERVICE"; + public static final String PERMISSION_NETWORK_TASK = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.permission.BIND_NETWORK_TASK_SERVICE"; + public static final String PERMISSION_RECEIVE = BuildConfig.BASE_PACKAGE_NAME + ".android.c2dm.permission.RECEIVE"; + public static final String PERMISSION_SEND = BuildConfig.BASE_PACKAGE_NAME + ".android.c2dm.permission.SEND"; + public static final String ERROR_SERVICE_NOT_AVAILABLE = "SERVICE_NOT_AVAILABLE"; + + public static final String INSTANCE_ID_SCOPE_GCM = "GCM"; + + public static final String GCMID_INSTANCE_ID = "google.com/iid"; + public static final String GCMID_REFRESH = "gcm.googleapis.com/refresh"; } diff --git a/play-services-basement/src/main/kotlin/com/google/android/gms/dynamic/ObjectWrapper.kt b/play-services-basement/src/main/kotlin/com/google/android/gms/dynamic/ObjectWrapper.kt index 27996b8f..06c131ab 100644 --- a/play-services-basement/src/main/kotlin/com/google/android/gms/dynamic/ObjectWrapper.kt +++ b/play-services-basement/src/main/kotlin/com/google/android/gms/dynamic/ObjectWrapper.kt @@ -14,5 +14,9 @@ * limitations under the License. */ -package com.google.android.gms.dynamic +package org.microg.gms.kotlin +import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.dynamic.ObjectWrapper + +inline fun IObjectWrapper?.unwrap(): T? = ObjectWrapper.unwrapTyped(this, T::class.java) diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 2449e81f..22e0b42c 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -23,6 +23,8 @@ dependencies { implementation "de.hdodenhof:circleimageview:$circleimageviewVersion" implementation "androidx.webkit:webkit:$webkitVersion" + implementation project(':fake-signature') + implementation project(':play-services-base-core') implementation project(':play-services-core-proto') implementation project(':play-services-core:microg-ui-tools') // deprecated @@ -47,21 +49,41 @@ dependencies { } android { + namespace "${basePackageName}.android.gms" + compileSdkVersion androidCompileSdk buildToolsVersion androidBuildVersionTools defaultConfig { versionName version versionCode appVersionCode - resConfigs 'xxhdpi' + resourceConfigurations += ['xxhdpi'] minSdkVersion androidMinSdk targetSdkVersion androidTargetSdk + buildConfigField "String", "BASE_PACKAGE_NAME", "\"$basePackageName\"" + + multiDexEnabled true + multiDexKeepProguard file('multidex-keep.pro') + ndk { abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" } + buildConfigField "String", "APPLICATION_NAMESPACE", "\"${android.namespace}\"" + buildConfigField "String", "BASE_PACKAGE_NAME", "\"$basePackageName\"" + manifestPlaceholders = [ + basePackageName: "$basePackageName" + ] + + // provide package name to xml resources + resValue "string", "var_authenticator_account_type", "$basePackageName" + resValue "string", "var_contact_syncadapter_account_type", "$basePackageName" + resValue "string", "var_pref_auth_action_security_settings", "${basePackageName}.android.gms.accountsettings.SECURITY_SETTINGS" + resValue "string", "var_pref_auth_action_privacy_settings", "${basePackageName}.android.gms.accountsettings.PRIVACY_SETTINGS" + resValue "string", "var_pref_auth_action_preferences_settings", "${basePackageName}.android.gms.accountsettings.ACCOUNT_PREFERENCES_SETTINGS" + resValue "string", "var_pref_start_gms_package", "${basePackageName}.android.gms" } buildFeatures { @@ -75,6 +97,9 @@ android { } } + lintOptions { + disable 'MissingTranslation', 'GetLocales', 'BatteryLife', 'ImpliedQuantity', 'MissingQuantity', 'InvalidWakeLockTag', 'Range' + } buildTypes { release { @@ -89,17 +114,11 @@ android { } packagingOptions { + exclude 'META-INF/ASL2.0' jniLibs { - useLegacyPackaging false - } - resources { - excludes += ['META-INF/ASL2.0'] + useLegacyPackaging true } } - lint { - disable 'MissingTranslation', 'BatteryLife', 'ImpliedQuantity', 'MissingQuantity', 'InvalidWakeLockTag', 'Range' - } - namespace 'com.mgoogle.android.gms' } if (file('user.gradle').exists()) { diff --git a/play-services-core/microg-ui-tools/build.gradle b/play-services-core/microg-ui-tools/build.gradle index d9a5c2bb..260aa6a5 100644 --- a/play-services-core/microg-ui-tools/build.gradle +++ b/play-services-core/microg-ui-tools/build.gradle @@ -23,17 +23,20 @@ android { defaultConfig { minSdkVersion androidMinSdk targetSdkVersion androidTargetSdk + + buildConfigField "String", "GMS_APPLICATION_NAMESPACE", "\"$applicationNamespace\"" } compileOptions { - sourceCompatibility = 11 - targetCompatibility = 11 + sourceCompatibility = 17 + targetCompatibility = 17 } - lint { - disable 'MissingTranslation' + + lintOptions { + // TODO: Remove MissingTranslation once we have stable strings and proper translations. + disable 'MissingTranslation', 'GetLocales' } namespace 'org.microg.tools.ui' - } dependencies { diff --git a/play-services-core/microg-ui-tools/src/main/AndroidManifest.xml b/play-services-core/microg-ui-tools/src/main/AndroidManifest.xml new file mode 100644 index 00000000..359a9b71 --- /dev/null +++ b/play-services-core/microg-ui-tools/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java index b77ea4ce..bb065aca 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java @@ -29,7 +29,6 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; @@ -65,7 +64,7 @@ public abstract class AbstractAboutFragment extends Fragment { } protected String getAppName() { - return getAppName(requireContext()); + return getAppName(getContext()); } public static String getLibVersion(String packageName) { @@ -83,7 +82,7 @@ public abstract class AbstractAboutFragment extends Fragment { } protected String getSelfVersion() { - return getSelfVersion(requireContext()); + return getSelfVersion(getContext()); } protected String getSummary() { @@ -94,7 +93,7 @@ public abstract class AbstractAboutFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View aboutRoot = inflater.inflate(R.layout.about_root, container, false); - ((ImageView) aboutRoot.findViewById(android.R.id.icon)).setImageDrawable(getIcon(requireContext())); + ((ImageView) aboutRoot.findViewById(android.R.id.icon)).setImageDrawable(getIcon(getContext())); ((TextView) aboutRoot.findViewById(android.R.id.title)).setText(getAppName()); ((TextView) aboutRoot.findViewById(R.id.about_version)).setText(getString(R.string.about_version_str, getSelfVersion())); String summary = getSummary(); @@ -137,7 +136,6 @@ public abstract class AbstractAboutFragment extends Fragment { this.copyright = copyright; } - @NonNull @Override public String toString() { return name + ", " + copyright; diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractDashboardActivity.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractDashboardActivity.java index 85bc123f..30bf98c8 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractDashboardActivity.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractDashboardActivity.java @@ -66,6 +66,22 @@ public abstract class AbstractDashboardActivity extends AppCompatActivity { resetConditionViews(); } + protected void addAllConditions(Condition[] conditions) { + for (Condition condition : conditions) { + addCondition(condition); + } + } + + protected void addCondition(Condition condition) { + conditions.add(condition); + if (conditionContainer == null) return; + if (condition.isEvaluated()) { + addConditionToView(condition); + } else { + evaluateConditionAsync(condition); + } + } + private synchronized void addConditionToView(Condition condition) { for (int i = 0; i < conditionContainer.getChildCount(); i++) { if (conditionContainer.getChildAt(i).getTag() == condition) return; @@ -73,6 +89,11 @@ public abstract class AbstractDashboardActivity extends AppCompatActivity { conditionContainer.addView(condition.createView(this, conditionContainer)); } + protected void clearConditions() { + conditions.clear(); + resetConditionViews(); + } + protected Fragment getFragment() { if (preferencesResource == 0) { throw new IllegalStateException("Neither preferencesResource given, nor overriden getFragment()"); diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java index db1da7b2..2b0542a5 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java @@ -3,15 +3,15 @@ package org.microg.tools.ui; import android.os.Bundle; import android.view.MenuItem; import android.view.ViewGroup; - +import androidx.appcompat.widget.Toolbar; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; - -import java.util.Objects; +import androidx.fragment.app.FragmentTransaction; public abstract class AbstractSettingsActivity extends AppCompatActivity { protected boolean showHomeAsUp = false; protected int preferencesResource = 0; + private ViewGroup customBarContainer; protected int customBarLayout = 0; protected SwitchBar switchBar; @@ -19,13 +19,14 @@ public abstract class AbstractSettingsActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.settings_activity); + setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); if (showHomeAsUp) { - Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); } switchBar = (SwitchBar) findViewById(R.id.switch_bar); - ViewGroup customBarContainer = (ViewGroup) findViewById(R.id.custom_bar); + customBarContainer = (ViewGroup) findViewById(R.id.custom_bar); if (customBarLayout != 0) { customBarContainer.addView(getLayoutInflater().inflate(customBarLayout, customBarContainer, false)); } @@ -35,6 +36,26 @@ public abstract class AbstractSettingsActivity extends AppCompatActivity { .commit(); } + public void setCustomBarLayout(int layout) { + customBarLayout = layout; + if (customBarContainer != null) { + customBarContainer.removeAllViews(); + customBarContainer.addView(getLayoutInflater().inflate(customBarLayout, customBarContainer, false)); + } + } + + public SwitchBar getSwitchBar() { + return switchBar; + } + + public void replaceFragment(Fragment fragment) { + getSupportFragmentManager().beginTransaction() + .addToBackStack("root") + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) + .replace(R.id.content_wrapper, fragment) + .commit(); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsFragment.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsFragment.java index 78ac70b0..0f1967a5 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsFragment.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsFragment.java @@ -16,7 +16,6 @@ package org.microg.tools.ui; -import androidx.annotation.NonNull; import androidx.fragment.app.DialogFragment; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; @@ -27,11 +26,10 @@ public abstract class AbstractSettingsFragment extends PreferenceFragmentCompat private static final String DIALOG_FRAGMENT_TAG = "androidx.preference.PreferenceFragment.DIALOG"; @Override - public void onDisplayPreferenceDialog(@NonNull Preference preference) { + public void onDisplayPreferenceDialog(Preference preference) { if (preference instanceof DialogPreference) { DialogFragment f = DialogPreference.DialogPreferenceCompatDialogFragment.newInstance(preference.getKey()); f.setTargetFragment(this, 0); - assert getFragmentManager() != null; f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); } else { super.onDisplayPreferenceDialog(preference); diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/Condition.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/Condition.java index c94b3bd6..5adeeb88 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/Condition.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/Condition.java @@ -192,7 +192,6 @@ public class Condition { public synchronized void evaluate(Context context) { active = evaluation == null || evaluation.isActive(context); - assert evaluation != null; evaluatedPlurals = evaluation.getPluralsCount(); evaluated = true; evaluating = false; @@ -223,40 +222,29 @@ public class Condition { @StringRes private int titleRes; @PluralsRes - private final int titlePluralsRes; + private int titlePluralsRes; private CharSequence title; @StringRes private int summaryRes; @PluralsRes - private final int summaryPluralsRes; + private int summaryPluralsRes; private CharSequence summary; @StringRes - private final int firstActionTextRes; + private int firstActionTextRes; @PluralsRes - private final int firstActionPluralsRes; - private final CharSequence firstActionText; - private final View.OnClickListener firstActionListener; + private int firstActionPluralsRes; + private CharSequence firstActionText; + private View.OnClickListener firstActionListener; @StringRes - private final int secondActionTextRes; + private int secondActionTextRes; @PluralsRes - private final int secondActionPluralsRes; - private final CharSequence secondActionText; - private final View.OnClickListener secondActionListener; - private final Evaluation evaluation; + private int secondActionPluralsRes; + private CharSequence secondActionText; + private View.OnClickListener secondActionListener; + private Evaluation evaluation; - public Builder(int titlePluralsRes, int summaryPluralsRes, int firstActionTextRes, int firstActionPluralsRes, CharSequence firstActionText, View.OnClickListener firstActionListener, int secondActionTextRes, int secondActionPluralsRes, CharSequence secondActionText, View.OnClickListener secondActionListener, Evaluation evaluation) { - this.titlePluralsRes = titlePluralsRes; - this.summaryPluralsRes = summaryPluralsRes; - this.firstActionTextRes = firstActionTextRes; - this.firstActionPluralsRes = firstActionPluralsRes; - this.firstActionText = firstActionText; - this.firstActionListener = firstActionListener; - this.secondActionTextRes = secondActionTextRes; - this.secondActionPluralsRes = secondActionPluralsRes; - this.secondActionText = secondActionText; - this.secondActionListener = secondActionListener; - this.evaluation = evaluation; + public Builder() { } public Builder icon(Drawable val) { @@ -279,6 +267,11 @@ public class Condition { return this; } + public Builder titlePlurals(@PluralsRes int val) { + titlePluralsRes = val; + return this; + } + public Builder summary(CharSequence val) { summary = val; return this; @@ -289,6 +282,52 @@ public class Condition { return this; } + public Builder summaryPlurals(@PluralsRes int val) { + summaryPluralsRes = val; + return this; + } + + public Builder firstAction(CharSequence text, View.OnClickListener listener) { + firstActionText = text; + firstActionListener = listener; + return this; + } + + public Builder firstAction(@StringRes int val, View.OnClickListener listener) { + firstActionTextRes = val; + firstActionListener = listener; + return this; + } + + public Builder firstActionPlurals(@PluralsRes int val, View.OnClickListener listener) { + firstActionPluralsRes = val; + firstActionListener = listener; + return this; + } + + public Builder secondAction(CharSequence text, View.OnClickListener listener) { + secondActionText = text; + secondActionListener = listener; + return this; + } + + public Builder secondAction(@StringRes int val, View.OnClickListener listener) { + secondActionTextRes = val; + secondActionListener = listener; + return this; + } + + public Builder secondActionPlurals(@PluralsRes int val, View.OnClickListener listener) { + secondActionPluralsRes = val; + secondActionListener = listener; + return this; + } + + public Builder evaluation(Evaluation evaluation) { + this.evaluation = evaluation; + return this; + } + public Condition build() { return new Condition(this); } diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/DialogPreference.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/DialogPreference.java index b5335089..af07a691 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/DialogPreference.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/DialogPreference.java @@ -24,7 +24,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; -import androidx.annotation.NonNull; import androidx.fragment.app.DialogFragment; import androidx.preference.Preference; import androidx.preference.PreferenceDialogFragmentCompat; @@ -60,21 +59,22 @@ public class DialogPreference extends androidx.preference.DialogPreference imple * Called when the dialog is dismissed and should be used to save data to * the {@link SharedPreferences}. * + * @param positiveResult Whether the positive button was clicked (true), or + * the negative button was clicked or the dialog was canceled (false). */ - protected void onDialogClosed() { + protected void onDialogClosed(boolean positiveResult) { } @Override - public boolean onPreferenceDisplayDialog(@NonNull PreferenceFragmentCompat caller, @NonNull Preference pref) { + public boolean onPreferenceDisplayDialog(PreferenceFragmentCompat caller, Preference pref) { DialogPreferenceCompatDialogFragment fragment = new DialogPreferenceCompatDialogFragment(); fragment.setTargetFragment(caller, 0); - assert caller.getFragmentManager() != null; fragment.show(caller.getFragmentManager(), DIALOG_FRAGMENT_TAG); return true; } @Override - public void onBindViewHolder(@NonNull PreferenceViewHolder view) { + public void onBindViewHolder(PreferenceViewHolder view) { super.onBindViewHolder(view); ViewGroup.LayoutParams layoutParams = view.findViewById(R.id.icon_frame).getLayoutParams(); @@ -88,7 +88,7 @@ public class DialogPreference extends androidx.preference.DialogPreference imple public static class DialogPreferenceCompatDialogFragment extends PreferenceDialogFragmentCompat { @Override - protected View onCreateDialogView(@NonNull Context context) { + protected View onCreateDialogView(Context context) { if (getPreference() instanceof DialogPreference) { View view = ((DialogPreference) getPreference()).onCreateDialogView(); if (view != null) return view; @@ -99,7 +99,7 @@ public class DialogPreference extends androidx.preference.DialogPreference imple @Override public void onDialogClosed(boolean positiveResult) { if (getPreference() instanceof DialogPreference) { - ((DialogPreference) getPreference()).onDialogClosed(); + ((DialogPreference) getPreference()).onDialogClosed(positiveResult); } } diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/LongTextPreference.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/LongTextPreference.java new file mode 100644 index 00000000..4c6edb8b --- /dev/null +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/LongTextPreference.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.tools.ui; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.TextView; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +public class LongTextPreference extends Preference { + + public LongTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public LongTextPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public LongTextPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LongTextPreference(Context context) { + super(context); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + TextView view = (TextView) holder.findViewById(android.R.id.summary); + if (view != null) { + view.setMaxLines(Integer.MAX_VALUE); + } + } +} diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/RadioButtonPreference.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/RadioButtonPreference.java new file mode 100644 index 00000000..e45d3812 --- /dev/null +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/RadioButtonPreference.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.tools.ui; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.AttributeSet; + +import androidx.core.content.res.TypedArrayUtils; +import androidx.preference.CheckBoxPreference; + +public class RadioButtonPreference extends CheckBoxPreference { + + public RadioButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public RadioButtonPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setWidgetLayoutResource(R.layout.preference_widget_radiobutton); + } + + @SuppressLint("RestrictedApi") + public RadioButtonPreference(Context context, AttributeSet attrs) { + this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.checkBoxPreferenceStyle, + android.R.attr.checkBoxPreferenceStyle)); + } + + public RadioButtonPreference(Context context) { + this(context, null); + } +} diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java index 0a30f683..b9144222 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java @@ -116,12 +116,39 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC mSwitch.setChecked(checked); } + public void setCheckedInternal(boolean checked) { + setTextViewLabel(checked); + mSwitch.setCheckedInternal(checked); + } + + public boolean isChecked() { + return mSwitch.isChecked(); + } + public void setEnabled(boolean enabled) { super.setEnabled(enabled); mTextView.setEnabled(enabled); mSwitch.setEnabled(enabled); } + public final ToggleSwitch getSwitch() { + return mSwitch; + } + + public void show() { + if (!isShowing()) { + setVisibility(View.VISIBLE); + mSwitch.setOnCheckedChangeListener(this); + } + } + + public void hide() { + if (isShowing()) { + setVisibility(View.GONE); + mSwitch.setOnCheckedChangeListener(null); + } + } + public boolean isShowing() { return (getVisibility() == View.VISIBLE); } @@ -151,6 +178,13 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC mSwitchChangeListeners.add(listener); } + public void removeOnSwitchChangeListener(OnSwitchChangeListener listener) { + if (!mSwitchChangeListeners.contains(listener)) { + throw new IllegalStateException("Cannot remove OnSwitchChangeListener"); + } + mSwitchChangeListeners.remove(listener); + } + static class SavedState extends BaseSavedState { boolean checked; boolean visible; diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/ToggleSwitch.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/ToggleSwitch.java index 3a3a7bb3..6530319e 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/ToggleSwitch.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/ToggleSwitch.java @@ -44,6 +44,10 @@ public class ToggleSwitch extends SwitchMaterial { super(context, attrs, defStyleAttr); } + public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) { + mOnBeforeListener = listener; + } + @Override public void setChecked(boolean checked) { if (mOnBeforeListener != null diff --git a/play-services-core/microg-ui-tools/src/main/res/layout/settings_activity.xml b/play-services-core/microg-ui-tools/src/main/res/layout/settings_activity.xml index a664de26..e5f71828 100644 --- a/play-services-core/microg-ui-tools/src/main/res/layout/settings_activity.xml +++ b/play-services-core/microg-ui-tools/src/main/res/layout/settings_activity.xml @@ -19,6 +19,14 @@ android:layout_height="match_parent" android:orientation="vertical"> + + + diff --git a/play-services-core/multidex-keep.pro b/play-services-core/multidex-keep.pro new file mode 100644 index 00000000..0cd8699e --- /dev/null +++ b/play-services-core/multidex-keep.pro @@ -0,0 +1,9 @@ +# Make sure maps is in the primary dex file +-keep class com.google.android.gms.maps.** { *; } +-keep class org.microg.gms.maps.** { *; } +-keep class com.mapbox.** { *; } +-keep class org.oscim.** { *; } + +# Keep Conscrypt in the primary dex file otherwise it will error out on legacy Android versions +-keep class com.google.android.gms.common.security.ProviderInstallerImpl { *; } +-keep class com.google.android.gms.org.conscrypt.** { *; } diff --git a/play-services-core/proguard-rules.pro b/play-services-core/proguard-rules.pro index 9b421ddd..923a7661 100644 --- a/play-services-core/proguard-rules.pro +++ b/play-services-core/proguard-rules.pro @@ -21,10 +21,13 @@ # Keep dynamically loaded GMS classes -keep public class com.google.android.gms.common.security.ProviderInstallerImpl { public *; } +-keep public class com.google.android.gms.dynamic.IObjectWrapper { public *; } +-keep public class com.google.android.gms.dynamite.descriptors.** { public *; } # Keep AutoSafeParcelables -keep public class * extends org.microg.safeparcel.AutoSafeParcelable { @org.microg.safeparcel.SafeParceled *; + (...); } # Keep form data diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index fd9bd743..5a42409c 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -18,39 +18,45 @@ xmlns:tools="http://schemas.android.com/tools"> + - - + + @@ -59,15 +65,17 @@ + - - - + + + - + @@ -86,32 +94,28 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:multiArch="true" - android:theme="@style/AppTheme" + android:theme="@style/Theme.App" android:networkSecurityConfig="@xml/network_security_config" tools:targetApi="r"> - - - + + android:exported="true" /> @@ -135,15 +139,16 @@ - + + @@ -151,11 +156,11 @@ + android:exported="true" + android:process=":persistent"> - - + + @@ -163,10 +168,10 @@ + android:exported="true" + android:process=":persistent"> - + @@ -176,8 +181,8 @@ + android:exported="true" + android:process=":persistent"> @@ -189,8 +194,8 @@ + android:exported="true" + android:process=":persistent"> @@ -213,8 +218,8 @@ + android:exported="true" + android:process=":persistent"> @@ -232,7 +237,7 @@ - + + android:process=":ui" + android:theme="@style/Theme.LoginBlue.Base"> @@ -257,7 +263,7 @@ @@ -279,13 +285,13 @@ + android:exported="true" + android:process=":ui"> @@ -315,14 +321,14 @@ + android:theme="@style/AppTheme"> @@ -333,7 +339,7 @@ @@ -347,13 +353,13 @@ + android:theme="@style/AppTheme"> @@ -369,6 +375,15 @@ android:resource="@string/gms_settings_summary" /> + + + + + + + android:theme="@style/Theme.LoginBlue.Base"> + - + + + + + + + + + = 29) { + if (SDK_INT >= 29) { return context.getOpPackageName(); } Context applicationContext = context.getApplicationContext(); 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 9c96646b..9274f439 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 @@ -17,6 +17,7 @@ package org.microg.gms.auth; import static android.accounts.AccountManager.VISIBILITY_VISIBLE; +import static android.os.Build.VERSION.SDK_INT; import android.Manifest; import android.accounts.Account; @@ -27,7 +28,6 @@ import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Binder; -import android.os.Build; import android.os.Bundle; import android.util.Log; @@ -36,6 +36,7 @@ import androidx.annotation.Nullable; import org.microg.gms.common.PackageUtils; import java.util.Arrays; +import java.util.Objects; import static org.microg.gms.auth.AuthConstants.DEFAULT_ACCOUNT_TYPE; import static org.microg.gms.auth.AuthConstants.PROVIDER_EXTRA_ACCOUNTS; @@ -54,11 +55,10 @@ public class AccountContentProvider extends ContentProvider { @Nullable @Override public Bundle call(String method, String arg, Bundle extras) { - String suggestedPackageName; - suggestedPackageName = getCallingPackage(); + String suggestedPackageName = getCallingPackage(); String packageName = PackageUtils.getAndCheckCallingPackage(getContext(), suggestedPackageName); Log.d(TAG, "Call " + method + " from " + packageName + " with arg " + arg); - if (!PackageUtils.callerHasExtendedAccess(getContext())) { + if (!PackageUtils.callerHasExtendedAccess(Objects.requireNonNull(getContext()))) { String[] packagesForUid = getContext().getPackageManager().getPackagesForUid(Binder.getCallingUid()); if (packagesForUid != null && packagesForUid.length != 0) Log.w(TAG, "Not granting extended access to " + Arrays.toString(packagesForUid) @@ -72,10 +72,10 @@ public class AccountContentProvider extends ContentProvider { if (arg != null && (arg.equals(DEFAULT_ACCOUNT_TYPE) || arg.startsWith(DEFAULT_ACCOUNT_TYPE + "."))) { AccountManager am = AccountManager.get(getContext()); accounts = am.getAccountsByTypeForPackage(arg, packageName); - if (accounts == null || accounts.length == 0) { + if (accounts.length == 0) { accounts = am.getAccountsByType(arg); } - if (Build.VERSION.SDK_INT >= 26 && accounts != null && arg.equals(DEFAULT_ACCOUNT_TYPE)) { + if (SDK_INT >= 26 && arg.equals(DEFAULT_ACCOUNT_TYPE)) { for (Account account : accounts) { if (am.getAccountVisibility(account, packageName) == AccountManager.VISIBILITY_UNDEFINED) { Log.d(TAG, "Make account " + account + " visible to " + packageName); 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 73adeabf..57b6a516 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 @@ -18,24 +18,25 @@ package org.microg.gms.auth; import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; +import static android.os.Build.VERSION.SDK_INT; import static org.microg.gms.auth.AuthPrefs.isTrustGooglePermitted; import android.accounts.Account; import android.accounts.AccountManager; import android.content.Context; import android.content.pm.PackageManager; -import android.os.Build; import android.util.Log; +import org.microg.gms.base.core.BuildConfig; import org.microg.gms.common.PackageUtils; -import org.microg.mgms.settings.SettingsContract; +import org.microg.gms.settings.SettingsContract; import java.io.IOException; public class AuthManager { private static final String TAG = "GmsAuthManager"; - public static final String PERMISSION_TREE_BASE = "com.mgoogle.android.googleapps.permission.GOOGLE_AUTH."; + public static final String PERMISSION_TREE_BASE = BuildConfig.BASE_PACKAGE_NAME + ".android.googleapps.permission.GOOGLE_AUTH."; public static final String PREF_AUTH_VISIBLE = SettingsContract.Auth.VISIBLE; public static final int ONE_HOUR_IN_SECONDS = 60 * 60; @@ -104,7 +105,7 @@ public class AuthManager { public void setPermitted(boolean value) { setUserData(buildPermKey(), value ? "1" : "0"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && value && packageName != null) { + if (SDK_INT >= 26 && value && packageName != null) { // Make account persistently visible as we already granted access accountManager.setAccountVisibility(getAccount(), packageName, AccountManager.VISIBILITY_VISIBLE); } @@ -169,7 +170,7 @@ public class AuthManager { public void setAuthToken(String service, String auth) { getAccountManager().setAuthToken(getAccount(), buildTokenKey(service), auth); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && packageName != null && auth != null) { + if (SDK_INT >= 26 && packageName != null && auth != null) { // Make account persistently visible as we already granted access accountManager.setAccountVisibility(getAccount(), packageName, AccountManager.VISIBILITY_VISIBLE); } diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java index 5ddf9d28..7f57a370 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java @@ -16,28 +16,29 @@ package org.microg.gms.auth; -import static android.accounts.AccountManager.KEY_ACCOUNTS; -import static android.accounts.AccountManager.KEY_ACCOUNT_NAME; -import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE; -import static android.accounts.AccountManager.KEY_AUTHTOKEN; -import static android.accounts.AccountManager.KEY_CALLER_PID; - import android.accounts.Account; import android.accounts.AccountManager; import android.annotation.SuppressLint; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Parcel; import android.os.RemoteException; +import android.util.Base64; import android.util.Log; +import androidx.core.app.NotificationCompat; + import com.google.android.auth.IAuthManagerService; +import com.mgoogle.android.gms.R; import com.google.android.gms.auth.AccountChangeEventsRequest; import com.google.android.gms.auth.AccountChangeEventsResponse; import com.google.android.gms.auth.GetHubTokenInternalResponse; import com.google.android.gms.auth.GetHubTokenRequest; -import com.google.android.gms.auth.HasCababilitiesRequest; +import com.google.android.gms.auth.HasCapabilitiesRequest; import com.google.android.gms.auth.TokenData; import com.google.android.gms.common.api.Scope; @@ -45,9 +46,13 @@ import org.microg.gms.common.PackageUtils; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; +import static android.accounts.AccountManager.*; +import static android.os.Build.VERSION.SDK_INT; + public class AuthManagerServiceImpl extends IAuthManagerService.Stub { private static final String TAG = "GmsAuthManagerSvc"; @@ -62,8 +67,11 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { public static final String KEY_REQUEST_VISIBLE_ACTIVITIES = "request_visible_actions"; public static final String KEY_SUPPRESS_PROGRESS_SCREEN = "suppressProgressScreen"; public static final String KEY_SYNC_EXTRAS = "sync_extras"; + public static final String KEY_DELEGATION_TYPE = "delegation_type"; + public static final String KEY_DELEGATEE_USER_ID = "delegatee_user_id"; public static final String KEY_ERROR = "Error"; + public static final String KEY_USER_RECOVERY_INTENT = "userRecoveryIntent"; private final Context context; @@ -107,7 +115,8 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { packageName = PackageUtils.getAndCheckCallingPackage(context, packageName, extras.getInt(KEY_CALLER_UID, 0), extras.getInt(KEY_CALLER_PID, 0)); boolean notify = extras.getBoolean(KEY_HANDLE_NOTIFICATION, false); - Log.d(TAG, "getToken: account:" + account.name + " scope:" + scope + " extras:" + extras + ", notify: " + notify); + if (!AuthConstants.SCOPE_GET_ACCOUNT_ID.equals(scope)) + Log.d(TAG, "getToken: account:" + account.name + " scope:" + scope + " extras:" + extras + ", notify: " + notify); /* * TODO: This scope seems to be invalid (according to https://developers.google.com/oauthplayground/), @@ -169,18 +178,29 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { } @Override - public Bundle requestGoogleAccountsAccess(String packageName) { - Log.w(TAG, "Not implemented: requestGoogleAccountsAccess(" + packageName + ")"); + public Bundle requestGoogleAccountsAccess(String packageName) throws RemoteException { + PackageUtils.assertExtendedAccess(context); + if (SDK_INT >= 26) { + for (Account account : get(context).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE)) { + AccountManager.get(context).setAccountVisibility(account, packageName, VISIBILITY_VISIBLE); + } + Bundle res = new Bundle(); + res.putString("Error", "Ok"); + return res; + } else { + Log.w(TAG, "Not implemented: requestGoogleAccountsAccess(" + packageName + ")"); + } return null; } @Override - public int hasCapabilities(HasCababilitiesRequest request) { + public int hasCapabilities(HasCapabilitiesRequest request) throws RemoteException { + Log.w(TAG, "Not implemented: hasCapabilities(" + request.account + ", " + Arrays.toString(request.capabilities) + ")"); return 1; } @Override - public GetHubTokenInternalResponse getHubToken(GetHubTokenRequest request, Bundle extras) { + public GetHubTokenInternalResponse getHubToken(GetHubTokenRequest request, Bundle extras) throws RemoteException { Log.w(TAG, "Not implemented: getHubToken()"); return null; } @@ -194,7 +214,11 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { Log.d(TAG, "clearToken: token:" + token + " extras:" + extras); AccountManager.get(context).invalidateAuthToken(AuthConstants.DEFAULT_ACCOUNT_TYPE, token); - return null; + + Bundle res = new Bundle(); + res.putString("Error", "Ok"); + res.putBoolean("booleanResult", true); + return res; } @Override diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java index 55ff536b..e1b4749a 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java @@ -113,7 +113,9 @@ public class AuthRequest extends HttpFormClient.Request { public AuthRequest fromContext(Context context) { build(context); locale(Utils.getLocale(context)); - androidIdHex = Long.toHexString(LastCheckinInfo.read(context).getAndroidId()); + if (AuthPrefs.shouldIncludeAndroidId(context)) { + androidIdHex = Long.toHexString(LastCheckinInfo.read(context).getAndroidId()); + } return this; } diff --git a/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java b/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java index b4bea3f8..de9832e8 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java @@ -45,7 +45,7 @@ public abstract class AssistantActivity extends Activity { @SuppressLint("WrongViewCast") private void formatTitle() { if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { - double widthPixels = getResources().getDisplayMetrics().widthPixels; + double widthPixels = (double) (getResources().getDisplayMetrics().widthPixels); findViewById(R.id.title_container).getLayoutParams().height = (int) (dpToPx(TITLE_MIN_HEIGHT) + (TITLE_WIDTH_FACTOR * widthPixels)); } else { 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 96d12f57..f52b93c4 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 @@ -25,9 +25,9 @@ import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT; import static org.microg.gms.auth.AuthPrefs.isAuthVisible; -import static org.microg.gms.checkin.CheckinPrefs.hideLauncherIcon; -import static org.microg.gms.checkin.CheckinPrefs.isSpoofingEnabled; -import static org.microg.gms.checkin.CheckinPrefs.setSpoofingEnabled; +import static org.microg.gms.checkin.CheckinPreferences.hideLauncherIcon; +import static org.microg.gms.checkin.CheckinPreferences.isSpoofingEnabled; +import static org.microg.gms.checkin.CheckinPreferences.setSpoofingEnabled; import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; import static org.microg.gms.common.Constants.GMS_VERSION_CODE; @@ -161,8 +161,10 @@ public class LoginActivity extends AssistantActivity { super.onHuaweiButtonClicked(); state++; if (state == 1) { - hideLauncherIcon(this, false); - UtilsKt.hideIcon(this, false); + if (SDK_INT >= 23) { + hideLauncherIcon(this, false); + UtilsKt.hideIcon(this, false); + } if (!isSpoofingEnabled(this)) { LastCheckinInfo.clear(this); setSpoofingEnabled(this, true); @@ -199,12 +201,22 @@ public class LoginActivity extends AssistantActivity { authContent.addView(loading); setMessage(R.string.auth_connecting); CookieManager.getInstance().setAcceptCookie(true); - CookieManager.getInstance().removeAllCookies(value -> start()); + if (SDK_INT >= 21) { + CookieManager.getInstance().removeAllCookies(value -> start()); + } else { + //noinspection deprecation + CookieManager.getInstance().removeAllCookie(); + start(); + } } private static WebView createWebView(Context context) { WebView webView = new WebView(context); - webView.setVisibility(INVISIBLE); + if (SDK_INT < 21) { + webView.setVisibility(VISIBLE); + } else { + webView.setVisibility(INVISIBLE); + } webView.setLayoutParams(new RelativeLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); webView.setBackgroundColor(Color.TRANSPARENT); @@ -271,6 +283,10 @@ public class LoginActivity extends AssistantActivity { webView.loadUrl(buildUrl(tmpl, Utils.getLocale(this))); } + protected void runScript(String js) { + runOnUiThread(() -> webView.loadUrl("javascript:" + js)); + } + private void closeWeb(boolean programmaticAuth) { setMessage(R.string.auth_finalize); runOnUiThread(() -> webView.setVisibility(INVISIBLE)); @@ -406,12 +422,32 @@ public class LoginActivity extends AssistantActivity { Log.d(TAG, "JSBridge: addAccount"); } + @JavascriptInterface + public final void attemptLogin(String accountName, String password) { + Log.d(TAG, "JSBridge: attemptLogin"); + } + + @JavascriptInterface + public void backupSyncOptIn(String accountName) { + Log.d(TAG, "JSBridge: backupSyncOptIn"); + } + + @JavascriptInterface + public void clearOldLoginAttempts() { + Log.d(TAG, "JSBridge: clearOldLoginAttempts"); + } + @JavascriptInterface public final void closeView() { Log.d(TAG, "JSBridge: closeView"); closeWeb(false); } + @JavascriptInterface + public void fetchIIDToken(String entity) { + Log.d(TAG, "JSBridge: fetchIIDToken"); + } + @JavascriptInterface public final String fetchVerifiedPhoneNumber() { Log.d(TAG, "JSBridge: fetchVerifiedPhoneNumber"); @@ -446,12 +482,17 @@ public class LoginActivity extends AssistantActivity { @JavascriptInterface public final int getAuthModuleVersionCode() { - return 1; + return GMS_VERSION_CODE; } @JavascriptInterface public final int getBuildVersionSdk() { - return SDK_INT; + return Build.VERSION.SDK_INT; + } + + @JavascriptInterface + public int getDeviceContactsCount() { + return -1; } @JavascriptInterface @@ -525,15 +566,40 @@ public class LoginActivity extends AssistantActivity { } @JavascriptInterface - public final void setAccountIdentifier(String accountIdentifier) { + public final void setAccountIdentifier(String accountName) { Log.d(TAG, "JSBridge: setAccountIdentifier"); } + @JavascriptInterface + public void setAllActionsEnabled(boolean z) { + Log.d(TAG, "JSBridge: setAllActionsEnabled"); + } + @JavascriptInterface public final void setNewAccountCreated() { Log.d(TAG, "JSBridge: setNewAccountCreated"); } + @JavascriptInterface + public void setPrimaryActionEnabled(boolean z) { + Log.d(TAG, "JSBridge: setPrimaryActionEnabled"); + } + + @JavascriptInterface + public void setPrimaryActionLabel(String str, int i) { + Log.d(TAG, "JSBridge: setPrimaryActionLabel: " + str); + } + + @JavascriptInterface + public void setSecondaryActionEnabled(boolean z) { + Log.d(TAG, "JSBridge: setSecondaryActionEnabled"); + } + + @JavascriptInterface + public void setSecondaryActionLabel(String str, int i) { + Log.d(TAG, "JSBridge: setSecondaryActionLabel: " + str); + } + @JavascriptInterface public final void showKeyboard() { inputMethodManager.showSoftInput(webView, SHOW_IMPLICIT); 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 89ee692f..fa4eb2bc 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 @@ -104,7 +104,7 @@ class AccountAuthenticator extends AbstractAccountAuthenticator { } } catch (Exception e) { Log.w(TAG, e); - return null; + throw new NetworkErrorException(e); } return null; } diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java index 98598a09..df7c245b 100755 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java @@ -80,8 +80,7 @@ public class CheckinClient { DeviceIdentifier deviceIdent, PhoneInfo phoneInfo, LastCheckinInfo checkinInfo, Locale locale, List accounts, Boolean brandSpoof) { - CheckinRequest.Builder builder; - builder = new CheckinRequest.Builder() + CheckinRequest.Builder builder = new CheckinRequest.Builder() .accountCookie(new ArrayList<>()) .androidId(checkinInfo.getAndroidId()) .checkin(new CheckinRequest.Checkin.Builder() @@ -137,7 +136,7 @@ public class CheckinClient { .loggingId(new Random().nextLong()) // TODO: static .meid(deviceIdent.meid) .otaCert(Collections.singletonList("71Q6Rn2DDZl1zPDVaaeEHItd")) - .serial(Build.SERIAL) + .serial(Build.SERIAL != null && !Build.SERIAL.isEmpty() ? Build.SERIAL : null) .timeZone(TimeZone.getDefault().getID()) .userName((String) TODO) .userSerialNumber((Integer) TODO) 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 355800d1..b9432ac6 100755 --- 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 @@ -16,7 +16,7 @@ package org.microg.gms.checkin; -import static org.microg.gms.checkin.CheckinPrefs.isSpoofingEnabled; +import static org.microg.gms.checkin.CheckinPreferences.isSpoofingEnabled; import android.accounts.Account; import android.accounts.AccountManager; @@ -43,7 +43,7 @@ public class CheckinManager { LastCheckinInfo info = LastCheckinInfo.read(context); if (!force && info.getLastCheckin() > System.currentTimeMillis() - MIN_CHECKIN_INTERVAL) return null; - if (!CheckinPrefs.isEnabled(context)) + if (!CheckinPreferences.isEnabled(context)) return null; List accounts = new ArrayList<>(); AccountManager accountManager = AccountManager.get(context); @@ -71,7 +71,8 @@ public class CheckinManager { ContentResolver resolver = context.getContentResolver(); for (CheckinResponse.GservicesSetting setting : response.setting) { - GServices.setString(resolver, setting.name.utf8(), setting.value_.utf8()); + if (setting.name != null && setting.value_ != null) + GServices.setString(resolver, setting.name.utf8(), setting.value_.utf8()); } return info; 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 bd5bfabb..3f53ebcd 100755 --- 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 @@ -80,7 +80,7 @@ public class CheckinService extends IntentService { protected void onHandleIntent(Intent intent) { try { ForegroundServiceContext.completeForegroundService(this, intent, TAG); - if (CheckinPrefs.isEnabled(this)) { + if (CheckinPreferences.isEnabled(this)) { LastCheckinInfo info = CheckinManager.checkin(this, intent.getBooleanExtra(EXTRA_FORCE_CHECKIN, false)); if (info != null) { Log.d(TAG, "Checked in as " + Long.toHexString(info.getAndroidId())); @@ -124,7 +124,7 @@ public class CheckinService extends IntentService { static void schedule(Context context) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - PendingIntent pendingIntent = PendingIntent.getService(context, TriggerReceiver.class.getName().hashCode(), new Intent(context, TriggerReceiver.class), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pendingIntent = PendingIntent.getService(context, TriggerReceiver.class.getName().hashCode(), new Intent(context, TriggerReceiver.class), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); alarmManager.set(AlarmManager.RTC, Math.max(LastCheckinInfo.read(context).getLastCheckin() + REGULAR_CHECKIN_INTERVAL, System.currentTimeMillis() + BACKUP_CHECKIN_DELAY), pendingIntent); } } diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java b/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java index 4b6551bd..e41a954b 100755 --- a/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java @@ -18,7 +18,6 @@ package org.microg.gms.checkin; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; -import static android.os.Build.VERSION.SDK_INT; import static org.microg.gms.checkin.CheckinService.EXTRA_FORCE_CHECKIN; import static org.microg.gms.checkin.CheckinService.REGULAR_CHECKIN_INTERVAL; @@ -42,7 +41,7 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { try { boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction()); - if (CheckinPrefs.isEnabled(context) || force) { + if (CheckinPreferences.isEnabled(context) || force) { if (LastCheckinInfo.read(context).getLastCheckin() > System.currentTimeMillis() - REGULAR_CHECKIN_INTERVAL && !force) { CheckinService.schedule(context); return; @@ -54,7 +53,7 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { Intent subIntent = new Intent(context, CheckinService.class); subIntent.putExtra(EXTRA_FORCE_CHECKIN, force); startWakefulService(new ForegroundServiceContext(context), subIntent); - } else if (SDK_INT >= 23) { + } else { // no network, register a network callback to retry when we have internet NetworkRequest networkRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_INTERNET) 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 index d9881dd8..d19ce6ea 100644 --- 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 @@ -10,7 +10,6 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import android.os.Build; import android.text.TextUtils; import java.util.ArrayList; @@ -55,9 +54,7 @@ public class GcmDatabase extends SQLiteOpenHelper { public GcmDatabase(Context context) { super(context, DB_NAME, null, DB_VERSION); this.context = context; - if (Build.VERSION.SDK_INT >= 16) { - this.setWriteAheadLoggingEnabled(true); - } + this.setWriteAheadLoggingEnabled(true); } public static class App { 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 a21d9137..343d1721 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 @@ -19,8 +19,42 @@ package org.microg.gms.gcm; import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.os.Build.VERSION.SDK_INT; import static org.microg.gms.common.PackageUtils.warnIfNotPersistentProcess; -import static org.microg.gms.gcm.GcmConstants.*; -import static org.microg.gms.gcm.McsConstants.*; +import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_RECEIVE; +import static org.microg.gms.gcm.GcmConstants.EXTRA_APP; +import static org.microg.gms.gcm.GcmConstants.EXTRA_APP_OVERRIDE; +import static org.microg.gms.gcm.GcmConstants.EXTRA_COLLAPSE_KEY; +import static org.microg.gms.gcm.GcmConstants.EXTRA_FROM; +import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSAGE_ID; +import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSENGER; +import static org.microg.gms.gcm.GcmConstants.EXTRA_RAWDATA; +import static org.microg.gms.gcm.GcmConstants.EXTRA_RAWDATA_BASE64; +import static org.microg.gms.gcm.GcmConstants.EXTRA_REGISTRATION_ID; +import static org.microg.gms.gcm.GcmConstants.EXTRA_SEND_FROM; +import static org.microg.gms.gcm.GcmConstants.EXTRA_SEND_TO; +import static org.microg.gms.gcm.GcmConstants.EXTRA_TTL; +import static org.microg.gms.gcm.McsConstants.ACTION_ACK; +import static org.microg.gms.gcm.McsConstants.ACTION_CONNECT; +import static org.microg.gms.gcm.McsConstants.ACTION_HEARTBEAT; +import static org.microg.gms.gcm.McsConstants.ACTION_RECONNECT; +import static org.microg.gms.gcm.McsConstants.ACTION_SEND; +import static org.microg.gms.gcm.McsConstants.EXTRA_REASON; +import static org.microg.gms.gcm.McsConstants.MCS_CLOSE_TAG; +import static org.microg.gms.gcm.McsConstants.MCS_DATA_MESSAGE_STANZA_TAG; +import static org.microg.gms.gcm.McsConstants.MCS_HEARTBEAT_ACK_TAG; +import static org.microg.gms.gcm.McsConstants.MCS_HEARTBEAT_PING_TAG; +import static org.microg.gms.gcm.McsConstants.MCS_IQ_STANZA_TAG; +import static org.microg.gms.gcm.McsConstants.MCS_LOGIN_REQUEST_TAG; +import static org.microg.gms.gcm.McsConstants.MCS_LOGIN_RESPONSE_TAG; +import static org.microg.gms.gcm.McsConstants.MSG_ACK; +import static org.microg.gms.gcm.McsConstants.MSG_CONNECT; +import static org.microg.gms.gcm.McsConstants.MSG_HEARTBEAT; +import static org.microg.gms.gcm.McsConstants.MSG_INPUT; +import static org.microg.gms.gcm.McsConstants.MSG_INPUT_ERROR; +import static org.microg.gms.gcm.McsConstants.MSG_OUTPUT; +import static org.microg.gms.gcm.McsConstants.MSG_OUTPUT_DONE; +import static org.microg.gms.gcm.McsConstants.MSG_OUTPUT_ERROR; +import static org.microg.gms.gcm.McsConstants.MSG_OUTPUT_READY; +import static org.microg.gms.gcm.McsConstants.MSG_TEARDOWN; import android.annotation.SuppressLint; import android.app.AlarmManager; @@ -34,15 +68,16 @@ import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.Messenger; import android.os.Parcelable; import android.os.PowerManager; import android.os.SystemClock; import android.os.UserHandle; +import android.util.Base64; import android.util.Log; import androidx.annotation.Nullable; @@ -66,7 +101,6 @@ import org.microg.gms.gcm.mcs.IqStanza; import org.microg.gms.gcm.mcs.LoginRequest; import org.microg.gms.gcm.mcs.LoginResponse; import org.microg.gms.gcm.mcs.Setting; -import org.microg.mgms.settings.SettingsProvider; import java.io.Closeable; import java.lang.reflect.Field; @@ -137,10 +171,10 @@ public class McsService extends Service implements Handler.Callback { @Nullable private Method addPowerSaveTempWhitelistAppMethod; @Nullable - @RequiresApi(Build.VERSION_CODES.S) + @RequiresApi(31) private Object powerExemptionManager; @Nullable - @RequiresApi(Build.VERSION_CODES.S) + @RequiresApi(31) private Method addToTemporaryAllowListMethod; private class HandlerThread extends Thread { @@ -155,7 +189,7 @@ public class McsService extends Service implements Handler.Callback { wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "mcs"); wakeLock.setReferenceCounted(false); synchronized (McsService.class) { - rootHandler = new Handler(Looper.myLooper(), McsService.this); + rootHandler = new Handler(Objects.requireNonNull(Looper.myLooper()), McsService.this); if (connectIntent != null) { rootHandler.sendMessage(rootHandler.obtainMessage(MSG_CONNECT, connectIntent)); WakefulBroadcastReceiver.completeWakefulIntent(connectIntent); @@ -166,7 +200,7 @@ public class McsService extends Service implements Handler.Callback { } private static void logd(Context context, String msg) { - if (context == null || Objects.requireNonNull(GcmPrefs.get(context)).isGcmLogEnabled()) Log.d(TAG, msg); + if (context == null || GcmPrefs.get(context).isGcmLogEnabled()) Log.d(TAG, msg); } @Override @@ -175,12 +209,12 @@ public class McsService extends Service implements Handler.Callback { super.onCreate(); TriggerReceiver.register(this); database = new GcmDatabase(this); - heartbeatIntent = PendingIntent.getService(this, 0, new Intent(ACTION_HEARTBEAT, null, this, McsService.class), 0); + heartbeatIntent = PendingIntent.getService(this, 0, new Intent(ACTION_HEARTBEAT, null, this, McsService.class), PendingIntent.FLAG_IMMUTABLE); alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); powerManager = (PowerManager) getSystemService(POWER_SERVICE); if (checkSelfPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") == PackageManager.PERMISSION_GRANTED) { try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (SDK_INT >= 31) { Class powerExemptionManagerClass = Class.forName("android.os.PowerExemptionManager"); powerExemptionManager = getSystemService(powerExemptionManagerClass); addToTemporaryAllowListMethod = @@ -198,7 +232,6 @@ public class McsService extends Service implements Handler.Callback { deviceIdleController = Class.forName("android.os.IDeviceIdleController$Stub") .getMethod("asInterface", IBinder.class).invoke(null, binder); getUserIdMethod = UserHandle.class.getMethod("getUserId", int.class); - assert deviceIdleController != null; addPowerSaveTempWhitelistAppMethod = deviceIdleController.getClass() .getMethod("addPowerSaveTempWhitelistApp", String.class, long.class, int.class, String.class); } @@ -241,7 +274,7 @@ public class McsService extends Service implements Handler.Callback { return false; } // consider connection to be dead if we did not receive an ack within 90s to our ping - int heartbeatMs = Objects.requireNonNull(GcmPrefs.get(context)).getHeartbeatMsFor(activeNetworkPref); + int heartbeatMs = GcmPrefs.get(context).getHeartbeatMsFor(activeNetworkPref); // if disabled for active network, heartbeatMs will be -1 if (heartbeatMs < 0) { closeAll(); @@ -251,7 +284,7 @@ public class McsService extends Service implements Handler.Callback { long timeSinceLastPing = SystemClock.elapsedRealtime() - lastHeartbeatPingElapsedRealtime; if (noAckReceived && timeSinceLastPing > HEARTBEAT_ACK_AFTER_PING_TIMEOUT_MS) { logd(null, "No heartbeat for " + timeSinceLastPing / 1000 + "s, connection assumed to be dead after 90s"); - Objects.requireNonNull(GcmPrefs.get(context)).learnTimeout(context, activeNetworkPref); + GcmPrefs.get(context).learnTimeout(context, activeNetworkPref); return false; } } @@ -267,26 +300,25 @@ public class McsService extends Service implements Handler.Callback { AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); long delay = getCurrentDelay(); logd(context, "Scheduling reconnect in " + delay / 1000 + " seconds..."); - PendingIntent pi = PendingIntent.getBroadcast(context, 1, new Intent(ACTION_RECONNECT, null, context, TriggerReceiver.class), 0); + PendingIntent pi = PendingIntent.getBroadcast(context, 1, new Intent(ACTION_RECONNECT, null, context, TriggerReceiver.class), PendingIntent.FLAG_IMMUTABLE); alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delay, pi); } public void scheduleHeartbeat(Context context) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); - int heartbeatMs = Objects.requireNonNull(GcmPrefs.get(this)).getHeartbeatMsFor(activeNetworkPref); + int heartbeatMs = GcmPrefs.get(this).getHeartbeatMsFor(activeNetworkPref); if (heartbeatMs < 0) { closeAll(); } logd(context, "Scheduling heartbeat in " + heartbeatMs / 1000 + " seconds..."); - // This is supposed to work even when running in idle and without battery optimization disabled alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, heartbeatIntent); } public synchronized static long getCurrentDelay() { long delay = currentDelay == 0 ? 5000 : currentDelay; - if (currentDelay < SettingsProvider.INTERVAL) currentDelay += 10000; - if (currentDelay >= SettingsProvider.INTERVAL && currentDelay < 600000) currentDelay += SettingsProvider.INTERVAL; + if (currentDelay < 60000) currentDelay += 10000; + if (currentDelay >= 60000 && currentDelay < 600000) currentDelay += 60000; return delay; } @@ -326,6 +358,7 @@ public class McsService extends Service implements Handler.Callback { String messageId = intent.getStringExtra(EXTRA_MESSAGE_ID); String collapseKey = intent.getStringExtra(EXTRA_COLLAPSE_KEY); + Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER); intent.removeExtra(EXTRA_MESSENGER); Parcelable app = intent.getParcelableExtra(EXTRA_APP); @@ -343,8 +376,16 @@ public class McsService extends Service implements Handler.Callback { } intent.removeExtra(EXTRA_APP); + int ttl; try { - intent.hasExtra(EXTRA_TTL); + if (intent.hasExtra(EXTRA_TTL)) { + ttl = Integer.parseInt(intent.getStringExtra(EXTRA_TTL)); + if (ttl < 0 || ttl > maxTtl) { + ttl = maxTtl; + } + } else { + ttl = maxTtl; + } } catch (NumberFormatException e) { // TODO: error TtlUnsupported Log.w(TAG, e); @@ -410,7 +451,7 @@ public class McsService extends Service implements Handler.Callback { } } - private void connect(int port) { + private boolean connect(int port) { try { wasTornDown = false; @@ -433,8 +474,10 @@ public class McsService extends Service implements Handler.Callback { Log.w(TAG, "Exception while connecting to " + SERVICE_HOST + ":" + port, e); rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, e)); closeAll(); + return false; } + return true; } private synchronized void connect() { @@ -442,8 +485,8 @@ public class McsService extends Service implements Handler.Callback { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo(); - activeNetworkPref = Objects.requireNonNull(GcmPrefs.get(this)).getNetworkPrefForInfo(activeNetworkInfo); - if (!Objects.requireNonNull(GcmPrefs.get(this)).isEnabledFor(activeNetworkInfo)) { + activeNetworkPref = GcmPrefs.get(this).getNetworkPrefForInfo(activeNetworkInfo); + if (!GcmPrefs.get(this).isEnabledFor(activeNetworkInfo)) { if (activeNetworkInfo != null) { logd(this, "Don't connect, because disabled for " + activeNetworkInfo.getTypeName()); } else { @@ -503,14 +546,13 @@ public class McsService extends Service implements Handler.Callback { } private void handleHeartbeatAck(HeartbeatAck ack) { - Objects.requireNonNull(GcmPrefs.get(this)).learnReached(this, activeNetworkPref, SystemClock.elapsedRealtime() - lastIncomingNetworkRealtime); + GcmPrefs.get(this).learnReached(this, activeNetworkPref, SystemClock.elapsedRealtime() - lastIncomingNetworkRealtime); lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime(); wakeLock.release(); } private LoginRequest buildLoginRequest() { LastCheckinInfo info = LastCheckinInfo.read(this); - assert info != null; return new LoginRequest.Builder() .adaptive_heartbeat(false) .auth_service(LoginRequest.AuthService.ANDROID_ID) @@ -523,7 +565,7 @@ public class McsService extends Service implements Handler.Callback { .user(Long.toString(info.getAndroidId())) .use_rmq2(true) .setting(Collections.singletonList(new Setting.Builder().name("new_vc").value_("1").build())) - .received_persistent_id(Objects.requireNonNull(GcmPrefs.get(this)).getLastPersistedIds()) + .received_persistent_id(GcmPrefs.get(this).getLastPersistedIds()) .build(); } @@ -536,15 +578,17 @@ public class McsService extends Service implements Handler.Callback { intent.setAction(ACTION_C2DM_RECEIVE); intent.putExtra(EXTRA_FROM, msg.from); intent.putExtra(EXTRA_MESSAGE_ID, msg.id); - if (msg.persistent_id != null) { - intent.putExtra(EXTRA_MESSAGE_ID, msg.persistent_id); + if (msg.persistent_id != null) intent.putExtra(EXTRA_MESSAGE_ID, msg.persistent_id); + if (msg.token != null) intent.putExtra(EXTRA_COLLAPSE_KEY, msg.token); + if (msg.raw_data != null) { + intent.putExtra(EXTRA_RAWDATA_BASE64, Base64.encodeToString(msg.raw_data.toByteArray(), Base64.DEFAULT)); + intent.putExtra(EXTRA_RAWDATA, msg.raw_data.toByteArray()); } if (app.wakeForDelivery) { intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); } else { intent.addFlags(Intent.FLAG_EXCLUDE_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_); } @@ -568,7 +612,7 @@ public class McsService extends Service implements Handler.Callback { sendOrderedBroadcast(intent, null); } else { List infos = getPackageManager().queryBroadcastReceivers(intent, PackageManager.GET_RESOLVED_FILTER); - if (infos == null || infos.isEmpty()) { + if (infos.isEmpty()) { logd(this, "No target for message, wut?"); } else { for (ResolveInfo resolveInfo : infos) { @@ -590,7 +634,7 @@ public class McsService extends Service implements Handler.Callback { } private void addPowerSaveTempWhitelistApp(String packageName) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (SDK_INT >= 31) { try { if (addToTemporaryAllowListMethod != null && powerExemptionManager != null) { logd(this, "Adding app " + packageName + " to the temp allowlist"); @@ -658,7 +702,7 @@ public class McsService extends Service implements Handler.Callback { if (msg.obj instanceof SocketException) { SocketException e = (SocketException) msg.obj; if ("Connection reset".equals(e.getMessage())) { - Objects.requireNonNull(GcmPrefs.get(this)).learnTimeout(this, activeNetworkPref); + GcmPrefs.get(this).learnTimeout(this, activeNetworkPref); } } rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, msg.obj)); diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java index 1dcfec44..bc93194e 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java @@ -147,7 +147,7 @@ public class PushRegisterManager { resultBundle.putString(EXTRA_REGISTRATION_ID, attachRequestId(response.token, requestId)); } } else { - if (!request.app.equals(response.deleted) && !request.app.equals(response.token)) { + if (!request.app.equals(response.deleted) && !request.app.equals(response.token) && !request.sender.equals(response.token)) { database.noteAppRegistrationError(request.app, response.responseText); resultBundle.putString(EXTRA_ERROR, attachRequestId(ERROR_SERVICE_NOT_AVAILABLE, requestId)); } else { diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java b/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java index dbc21088..80c7fd83 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java @@ -123,7 +123,7 @@ public class RegisterRequest extends HttpFormClient.Request { public RegisterRequest extraParams(Bundle extraBundle) { for (String key : extraBundle.keySet()) { - if (!key.equals(GcmConstants.EXTRA_SENDER) && !key.equals(GcmConstants.EXTRA_DELETE)) { + if (!key.equals(GcmConstants.EXTRA_SENDER) && !key.equals(GcmConstants.EXTRA_DELETE) && !key.equals(GcmConstants.EXTRA_APP)) { extraParam(key, extraBundle.getString(key)); } } 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 245d5c2a..83d182aa 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 @@ -17,7 +17,6 @@ package org.microg.gms.gcm; import static android.os.Build.VERSION.SDK_INT; -import static android.os.Build.VERSION_CODES.N; import static org.microg.gms.gcm.McsConstants.ACTION_CONNECT; import static org.microg.gms.gcm.McsConstants.ACTION_HEARTBEAT; import static org.microg.gms.gcm.McsConstants.EXTRA_REASON; @@ -31,7 +30,7 @@ import android.util.Log; import androidx.legacy.content.WakefulBroadcastReceiver; -import org.microg.gms.checkin.CheckinPrefs; +import org.microg.gms.checkin.CheckinPreferences; import org.microg.gms.checkin.LastCheckinInfo; import org.microg.gms.common.ForegroundServiceContext; @@ -44,7 +43,7 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { * "Project Svelte" is just there to f**k things up... */ public synchronized static void register(Context context) { - if (SDK_INT >= N && !registered) { + if (SDK_INT >= 24 && !registered) { IntentFilter intentFilter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"); context.getApplicationContext().registerReceiver(new TriggerReceiver(), intentFilter); registered = true; @@ -68,7 +67,7 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { if (LastCheckinInfo.read(context).getAndroidId() == 0) { Log.d(TAG, "Ignoring " + intent + ": need to checkin first."); - if (CheckinPrefs.isEnabled(context)) { + if (CheckinPreferences.isEnabled(context)) { // Do a check-in if we are not actually checked in, // but should be, e.g. cleared app data Log.d(TAG, "Requesting check-in..."); diff --git a/play-services-core/src/main/java/org/microg/gms/gservices/GServices.java b/play-services-core/src/main/java/org/microg/gms/gservices/GServices.java index 79543f47..5c811f28 100644 --- a/play-services-core/src/main/java/org/microg/gms/gservices/GServices.java +++ b/play-services-core/src/main/java/org/microg/gms/gservices/GServices.java @@ -21,10 +21,12 @@ import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; +import org.microg.gms.base.core.BuildConfig; + public class GServices { - public static final Uri CONTENT_URI = Uri.parse("content://com.mgoogle.android.gsf.gservices"); - public static final Uri MAIN_URI = Uri.parse("content://com.mgoogle.android.gsf.gservices/main"); - public static final Uri OVERRIDE_URI = Uri.parse("content://com.mgoogle.android.gsf.gservices/override"); + public static final Uri CONTENT_URI = Uri.parse("content://" + BuildConfig.BASE_PACKAGE_NAME + ".android.gsf.gservices"); + public static final Uri MAIN_URI = Uri.parse("content://" + BuildConfig.BASE_PACKAGE_NAME + ".android.gsf.gservices/main"); + public static final Uri OVERRIDE_URI = Uri.parse("content://" + BuildConfig.BASE_PACKAGE_NAME + ".android.gsf.gservices/override"); public static int setString(ContentResolver resolver, String key, String value) { ContentValues values = new ContentValues(); diff --git a/play-services-core/src/main/java/org/microg/gms/gservices/GServicesProvider.java b/play-services-core/src/main/java/org/microg/gms/gservices/GServicesProvider.java index 2a95655b..3d3a8189 100644 --- a/play-services-core/src/main/java/org/microg/gms/gservices/GServicesProvider.java +++ b/play-services-core/src/main/java/org/microg/gms/gservices/GServicesProvider.java @@ -29,6 +29,8 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; +import org.microg.gms.base.core.BuildConfig; + /** * Originally found in Google Services Framework (com.google.android.gsf), this provides a generic * key-value store, that is written by the checkin service and read from various Google Apps. @@ -37,7 +39,7 @@ import java.util.Set; * if certain "experiments" are enabled on the device. */ public class GServicesProvider extends ContentProvider { - public static final Uri CONTENT_URI = Uri.parse("content://com.mgoogle.android.gsf.gservices/"); + public static final Uri CONTENT_URI = Uri.parse("content://" + BuildConfig.BASE_PACKAGE_NAME + ".android.gsf.gservices/"); public static final Uri MAIN_URI = Uri.withAppendedPath(CONTENT_URI, "main"); public static final Uri OVERRIDE_URI = Uri.withAppendedPath(CONTENT_URI, "override"); public static final Uri PREFIX_URI = Uri.withAppendedPath(CONTENT_URI, "prefix"); diff --git a/play-services-core/src/main/java/org/microg/mgms/settings/GoogleSettingsProvider.java b/play-services-core/src/main/java/org/microg/gms/settings/GoogleSettingsProvider.java similarity index 97% rename from play-services-core/src/main/java/org/microg/mgms/settings/GoogleSettingsProvider.java rename to play-services-core/src/main/java/org/microg/gms/settings/GoogleSettingsProvider.java index 3ab7f51c..da3e5094 100644 --- a/play-services-core/src/main/java/org/microg/mgms/settings/GoogleSettingsProvider.java +++ b/play-services-core/src/main/java/org/microg/gms/settings/GoogleSettingsProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.microg.mgms.settings; +package org.microg.gms.settings; import android.content.ContentProvider; import android.content.ContentValues; 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/LegacyAccountSettingsActivity.java old mode 100755 new mode 100644 similarity index 91% rename from play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java rename to play-services-core/src/main/java/org/microg/gms/ui/LegacyAccountSettingsActivity.java index fa099d49..fc015f5e --- a/play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/LegacyAccountSettingsActivity.java @@ -18,7 +18,6 @@ package org.microg.gms.ui; import android.accounts.Account; import android.accounts.AccountManager; -import android.os.Build; import android.os.Bundle; import androidx.annotation.Nullable; @@ -34,9 +33,10 @@ import org.microg.tools.ui.ResourceSettingsFragment; import static android.accounts.AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE; import static android.accounts.AccountManager.VISIBILITY_NOT_VISIBLE; import static android.accounts.AccountManager.VISIBILITY_VISIBLE; +import static android.os.Build.VERSION.SDK_INT; import static org.microg.gms.auth.AuthManager.PREF_AUTH_VISIBLE; -public class AccountSettingsActivity extends AbstractSettingsActivity { +public class LegacyAccountSettingsActivity extends AbstractSettingsActivity { @Override protected Fragment getFragment() { @@ -45,7 +45,7 @@ public class AccountSettingsActivity extends AbstractSettingsActivity { public static class AccountSettingsFragment extends ResourceSettingsFragment { public AccountSettingsFragment() { - preferencesResource = R.xml.preferences_account; + preferencesResource = R.xml.preferences_google_more; } @Override @@ -53,7 +53,7 @@ public class AccountSettingsActivity extends AbstractSettingsActivity { super.onCreatePreferences(savedInstanceState, rootKey); Preference pref = findPreference(PREF_AUTH_VISIBLE); if (pref != null) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + if (SDK_INT < 26) { pref.setVisible(false); } else { pref.setOnPreferenceChangeListener((preference, newValue) -> { 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/MainSettingsActivity.java old mode 100755 new mode 100644 similarity index 60% rename from play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java rename to play-services-core/src/main/java/org/microg/gms/ui/MainSettingsActivity.java index 32230055..b65b3806 --- a/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/MainSettingsActivity.java @@ -1,6 +1,6 @@ package org.microg.gms.ui; -import android.os.Build; +import android.content.Intent; import android.os.Bundle; import androidx.annotation.Nullable; @@ -10,25 +10,33 @@ import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.ui.AppBarConfiguration; import androidx.navigation.ui.NavigationUI; -import com.google.android.material.color.DynamicColors; import com.mgoogle.android.gms.R; +import org.microg.gms.ui.settings.SettingsProvider; -import java.util.Objects; +import static org.microg.gms.ui.settings.SettingsProviderKt.getAllSettingsProviders; -public class SettingsActivity extends AppCompatActivity { +public class MainSettingsActivity extends AppCompatActivity { private AppBarConfiguration appBarConfiguration; private NavController getNavController() { - return ((NavHostFragment) Objects.requireNonNull(getSupportFragmentManager().findFragmentById(R.id.navhost))).getNavController(); + return ((NavHostFragment)getSupportFragmentManager().findFragmentById(R.id.navhost)).getNavController(); } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.settings_root_activity); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - DynamicColors.applyToActivityIfAvailable(this); + + Intent intent = getIntent(); + for (SettingsProvider settingsProvider : getAllSettingsProviders(this)) { + settingsProvider.preProcessSettingsIntent(intent); } + + setContentView(R.layout.settings_root_activity); + + for (SettingsProvider settingsProvider : getAllSettingsProviders(this)) { + settingsProvider.extendNavigation(getNavController()); + } + appBarConfiguration = new AppBarConfiguration.Builder(getNavController().getGraph()).build(); NavigationUI.setupActionBarWithNavController(this, getNavController(), appBarConfiguration); } @@ -37,4 +45,4 @@ public class SettingsActivity extends AppCompatActivity { public boolean onSupportNavigateUp() { return NavigationUI.navigateUp(getNavController(), appBarConfiguration) || super.onSupportNavigateUp(); } -} +} \ No newline at end of file diff --git a/play-services-core/src/main/java/org/microg/tools/AccountPickerActivity.java b/play-services-core/src/main/java/org/microg/tools/AccountPickerActivity.java index 4c596f32..69d87394 100755 --- a/play-services-core/src/main/java/org/microg/tools/AccountPickerActivity.java +++ b/play-services-core/src/main/java/org/microg/tools/AccountPickerActivity.java @@ -16,16 +16,15 @@ package org.microg.tools; -import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; +import androidx.appcompat.app.AppCompatActivity; /** * This is just an activity that forwards to the systems native account picker */ -public class AccountPickerActivity extends Activity { - private static final int REQUEST_CODE = AccountPickerActivity.class.hashCode(); +public class AccountPickerActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { @@ -36,16 +35,8 @@ public class AccountPickerActivity extends Activity { ComponentName.unflattenFromString("android/.accounts.ChooseTypeAndAccountActivity"); intent.setClassName(componentName.getPackageName(), componentName.getClassName()); intent.putExtras(extras); - startActivityForResult(intent, REQUEST_CODE); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == REQUEST_CODE) { - setResult(resultCode, data); - finish(); - } else { - super.onActivityResult(requestCode, resultCode, data); - } + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + startActivity(intent); + finish(); } } diff --git a/play-services-core/src/main/kotlin/com/google/android/gms/common/GoogleCertificatesImpl.kt b/play-services-core/src/main/kotlin/com/google/android/gms/common/GoogleCertificatesImpl.kt new file mode 100644 index 00000000..e1fb7a21 --- /dev/null +++ b/play-services-core/src/main/kotlin/com/google/android/gms/common/GoogleCertificatesImpl.kt @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package com.google.android.gms.common + +import android.content.pm.PackageManager +import android.os.IBinder +import android.os.Parcel +import android.util.Log +import androidx.annotation.Keep +import com.google.android.gms.common.internal.CertData +import com.google.android.gms.common.internal.IGoogleCertificatesApi +import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.dynamic.ObjectWrapper +import org.microg.gms.common.PackageUtils +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "GoogleCertificates" + +@Keep +class GoogleCertificatesImpl : IGoogleCertificatesApi.Stub() { + private val googleCertificates = emptySet() + private val googleReleaseCertificates = emptySet() + + override fun getGoogleCertificates(): IObjectWrapper { + Log.d(TAG, "unimplemented Method: getGoogleCertificates") + return ObjectWrapper.wrap(googleCertificates.toTypedArray()) + } + + override fun getGoogleReleaseCertificates(): IObjectWrapper { + Log.d(TAG, "unimplemented Method: getGoogleReleaseCertificates") + return ObjectWrapper.wrap(googleReleaseCertificates.toTypedArray()) + } + + override fun isGoogleReleaseSigned(packageName: String, certData: IObjectWrapper): Boolean { + return PackageUtils.isGooglePackage(packageName, ObjectWrapper.unwrapTyped(certData, ByteArray::class.java)) + } + + override fun isGoogleSigned(packageName: String, certData: IObjectWrapper): Boolean { + return PackageUtils.isGooglePackage(packageName, ObjectWrapper.unwrapTyped(certData, ByteArray::class.java)) + } + + override fun isGoogleOrPlatformSigned(query: GoogleCertificatesQuery, packageManager: IObjectWrapper): Boolean { + val pm = ObjectWrapper.unwrapTyped(packageManager, PackageManager::class.java) + return if (query == null || query.callingPackage == null) { + false + } else if (query.getCertData() == null) { + if (pm == null) false else PackageUtils.isGooglePackage(pm, query.callingPackage) + } else { + PackageUtils.isGooglePackage(query.callingPackage, query.getCertData().bytes) + } + } + + override fun isPackageGoogleOrPlatformSigned(query: GoogleCertificatesLookupQuery): GoogleCertificatesLookupResponse { + return certificateLookup(query, true) + } + + override fun isPackageGoogleOrPlatformSignedAvailable(): Boolean { + return true + } + + override fun queryPackageSigned(query: GoogleCertificatesLookupQuery): GoogleCertificatesLookupResponse { + if (!isFineGrainedPackageVerificationAvailable) throw IllegalStateException("API unavailable") + return certificateLookup(query, false) + } + + override fun isFineGrainedPackageVerificationAvailable(): Boolean { + return true + } + + private fun certificateLookup(query: GoogleCertificatesLookupQuery, allowPlatform: Boolean): GoogleCertificatesLookupResponse { + val context = query.context + ?: return GoogleCertificatesLookupResponse(false, "context is null", 5, 1) + val packageManager = context.packageManager + ?: return GoogleCertificatesLookupResponse(false, "context has no package manager", 5, 1) + val callingPackage = query.callingPackage + ?: return GoogleCertificatesLookupResponse(false, "callingPackage is null", 5, 1) + val signatureDigest = PackageUtils.firstSignatureDigest(packageManager, callingPackage) + ?: return GoogleCertificatesLookupResponse(false, "callingPackage not found", 4, 1) + return if (PackageUtils.isGooglePackage(callingPackage, signatureDigest)) { + GoogleCertificatesLookupResponse(true, null, 1, 3) + } else { + GoogleCertificatesLookupResponse(false, "not allowed", 2, 1) + } + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/LoaderActivity.kt b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/LoaderActivity.kt new file mode 100644 index 00000000..c1a4a2da --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/LoaderActivity.kt @@ -0,0 +1,165 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.accountsettings.ui + +import android.accounts.AccountManager +import android.content.Intent +import android.content.Intent.ACTION_VIEW +import android.content.Intent.CATEGORY_BROWSABLE +import android.content.Intent.FLAG_ACTIVITY_FORWARD_RESULT +import android.net.Uri +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import org.microg.gms.auth.AuthConstants.DEFAULT_ACCOUNT_TYPE +import org.microg.gms.common.PackageUtils +import org.microg.tools.AccountPickerActivity + +private const val TAG = "AccountSettingsLoader" + +private val ALLOWED_FALLBACK_PREFIXES = setOf("https://myaccount.google.com/", "https://takeout.google.com/") +private val BROWSABLE_SCREEN_IDS = setOf(1, 200, 400, 502, 527, 10003, 10050, 12700, 12701) +private val ACCOUNT_CHOOSER_URI = Uri.parse("https://accounts.google.com/AccountChooser") + +private const val QUERY_PARAM_CONTINUE = "continue" +private const val QUERY_PARAM_LANG = "hl" +private const val QUERY_PARAM_EMAIL = "Email" + +private const val EXTRA_ALLOWABLE_ACCOUNT_TYPES = "allowableAccountTypes" + +private const val REQUEST_ACCOUNT_PICKER = 1 + +@Suppress("DEPRECATION") +class LoaderActivity : AppCompatActivity() { + private var canAskForAccount = false + + private fun launchFallback() { + val fallbackUrl = intent?.getStringExtra(EXTRA_FALLBACK_URL) + + if (fallbackUrl == null) { + Log.d(TAG, "No fallback") + finishResult(RESULT_CANCELED) + } else if (fallbackUrl in ALLOWED_FALLBACK_PREFIXES) { + // TODO: Error screen? + Log.d(TAG, "Illegal fallback url") + finishResult(RESULT_CANCELED) + } else { + val fallbackAuth = intent?.getBooleanExtra(EXTRA_FALLBACK_AUTH, false) ?: false + val uri = if (fallbackAuth) { + val builder = ACCOUNT_CHOOSER_URI.buildUpon().appendQueryParameter(QUERY_PARAM_CONTINUE, fallbackUrl) + val accountName = intent?.getStringExtra(EXTRA_ACCOUNT_NAME) + if (!accountName.isNullOrBlank()) { + builder.appendQueryParameter(QUERY_PARAM_EMAIL, accountName) + } + val lang = Uri.parse(fallbackUrl).getQueryParameter(QUERY_PARAM_LANG) + if (lang != null) { + builder.appendQueryParameter(QUERY_PARAM_LANG, lang) + } + builder.build() + } else { + Uri.parse(fallbackUrl) + } + Log.d(TAG, "Opening fallback $fallbackUrl") + val intent = Intent(ACTION_VIEW, uri).apply { addCategory(CATEGORY_BROWSABLE) } + startActivity(intent) + finishResult(RESULT_OK) + } + } + + private fun launchMain() { + val requestedAccountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME) + val ignoreAccount = intent?.getBooleanExtra(EXTRA_IGNORE_ACCOUNT, false) ?: false + val accounts = AccountManager.get(this).getAccountsByType(DEFAULT_ACCOUNT_TYPE) + val account = if (requestedAccountName != null) { + val account = accounts.find { it.name == requestedAccountName } + if (account == null) { + // TODO: Error screen? + Log.d(TAG, "Account not found: $requestedAccountName") + return finishResult(RESULT_CANCELED) + } + account + } else if (accounts.isEmpty()) { + if (intent?.getStringExtra(EXTRA_FALLBACK_URL) != null) { + return launchFallback() + } else { + // TODO: Error screen? + Log.d(TAG, "No account configured") + return finishResult(RESULT_CANCELED) + } + } else if (accounts.size > 1) { + if (canAskForAccount) { + val intent = Intent(this, AccountPickerActivity::class.java) + intent.putExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES, arrayOf(DEFAULT_ACCOUNT_TYPE)) + startActivityForResult(intent, REQUEST_ACCOUNT_PICKER) + canAskForAccount = false + return + } else { + return finishResult(RESULT_CANCELED) + } + } else { + accounts.first() + } + + val intent = Intent(this, MainActivity::class.java).apply { + action = intent.action + if (intent.hasExtra(EXTRA_THEME_CHOICE)) putExtra(EXTRA_THEME_CHOICE, intent.getIntExtra(EXTRA_THEME_CHOICE, 0)) + putExtra(EXTRA_ACCOUNT_NAME, account.name) + if (ignoreAccount) putExtra(EXTRA_IGNORE_ACCOUNT, true) + putExtra(EXTRA_SCREEN_ID, intent.getIntExtra(EXTRA_SCREEN_ID, 1)) + for (it in intent.extras?.keySet().orEmpty()) { + if (it.startsWith(EXTRA_SCREEN_OPTIONS_PREFIX)) putExtra(it, intent.getStringExtra(it)) + } + putExtra(EXTRA_CALLING_PACKAGE_NAME, callingActivity?.packageName) + if (intent.action != ACTION_BROWSE_SETTINGS) { + addFlags(FLAG_ACTIVITY_FORWARD_RESULT) + } + } + + startActivity(intent) + + if (!isFinishing && !isChangingConfigurations) { + finishResult(RESULT_OK) + } + } + + @Deprecated("Deprecated in Java") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == REQUEST_ACCOUNT_PICKER) { + if (resultCode == RESULT_OK && data?.hasExtra(AccountManager.KEY_ACCOUNT_NAME) == true) { + intent.putExtra(EXTRA_ACCOUNT_NAME, data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)) + launchMain() + } else { + finishResult(resultCode) + } + } else { + super.onActivityResult(requestCode, resultCode, data) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + canAskForAccount = true + val extras = intent?.extras?.also { it.keySet() } + Log.d(TAG, "Invoked with ${intent.action} and extras $extras") + + super.onCreate(savedInstanceState) + + val isMainAllowed = if (intent == null || intent.action != ACTION_BROWSE_SETTINGS) + PackageUtils.isGooglePackage(this, callingActivity?.packageName) + else + extras?.getInt(EXTRA_SCREEN_ID, -1) in BROWSABLE_SCREEN_IDS + + if (!isMainAllowed) { + launchFallback() + } else if (!isFinishing && !isChangingConfigurations){ + launchMain() + } + } + + private fun finishResult(resultCode: Int) { + setResult(resultCode) + finish() + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/MainActivity.kt b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/MainActivity.kt new file mode 100644 index 00000000..441a2e3b --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/MainActivity.kt @@ -0,0 +1,185 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.accountsettings.ui + +import android.accounts.Account +import android.accounts.AccountManager +import android.os.Bundle +import android.util.Log +import android.view.View +import android.webkit.WebView +import android.widget.ProgressBar +import android.widget.RelativeLayout +import android.widget.RelativeLayout.LayoutParams.MATCH_PARENT +import android.widget.RelativeLayout.LayoutParams.WRAP_CONTENT +import androidx.appcompat.app.AppCompatActivity +import org.microg.gms.auth.AuthConstants +import org.microg.gms.common.Constants + +private const val TAG = "AccountSettings" + +// TODO: There likely is some API to figure those out... +private val SCREEN_ID_TO_URL = hashMapOf( + 1 to "https://myaccount.google.com", + 200 to "https://myaccount.google.com/privacycheckup", + 203 to "https://myaccount.google.com/email", + 204 to "https://myaccount.google.com/phone", + 205 to "https://myaccount.google.com/birthday", + 206 to "https://myaccount.google.com/gender", + 210 to "https://myaccount.google.com/locationsharing", + 214 to "https://myaccount.google.com/dashboard", + 215 to "https://takeout.google.com", + 216 to "https://myaccount.google.com/inactive", + 219 to "https://myactivity.google.com/myactivity", + 220 to "https://www.google.com/maps/timeline", + 224 to "https://myactivity.google.com/activitycontrols?settings=search", + 227 to "https://myactivity.google.com/activitycontrols?settings=location", + 231 to "https://myactivity.google.com/activitycontrols?settings=youtube", + 235 to "https://myactivity.google.com/activitycontrols/youtube", + 238 to "https://www.google.com/setting/search/privateresults/", + 241 to "https://myaccount.google.com/communication-preferences", + 300 to "https://myaccount.google.com/language", + 301 to "https://drive.google.com/settings/storage", + 302 to "https://myaccount.google.com/deleteservices", + 303 to "https://myaccount.google.com/deleteaccount", + 307 to "https://payments.google.com/payments/home", + 308 to "https://myaccount.google.com/subscriptions", + 309 to "https://myaccount.google.com/purchases", + 310 to "https://myaccount.google.com/reservations", + 312 to "https://myaccount.google.com/accessibility", + 313 to "https://myaccount.google.com/inputtools", + 400 to "https://myaccount.google.com/security-checkup/", + 401 to "https://myaccount.google.com/signinoptions/password", + 403 to "https://myaccount.google.com/signinoptions/two-step-verification", + 406 to "https://myaccount.google.com/signinoptions/rescuephone", + 407 to "https://myaccount.google.com/recovery/email", + 409 to "https://myaccount.google.com/notifications", + 410 to "https://myaccount.google.com/device-activity", + 417 to "https://myaccount.google.com/find-your-phone", + 425 to "https://myaccount.google.com/account-enhanced-safe-browsing", + 426 to "https://myaccount.google.com/two-step-verification/authenticator", + 427 to "https://myaccount.google.com/two-step-verification/backup-codes", + 429 to "https://myaccount.google.com/two-step-verification/security-keys", + 430 to "https://myaccount.google.com/two-step-verification/prompt", + 431 to "https://myaccount.google.com/connections", + 432 to "https://myaccount.google.com/two-step-verification/phone-numbers", + 433 to "https://myaccount.google.com/signinoptions/passkeys", + 437 to "https://myaccount.google.com/signinoptions/passwordoptional", + 500 to "https://policies.google.com/privacy", + 503 to "https://policies.google.com/terms", + 519 to "https://myaccount.google.com/yourdata/maps", + 520 to "https://myaccount.google.com/yourdata/search", + 530 to "https://fit.google.com/privacy/settings", + 547 to "https://myactivity.google.com/product/search", + 562 to "https://myaccount.google.com/yourdata/youtube", + 10003 to "https://myaccount.google.com/personal-info", + 10004 to "https://myaccount.google.com/data-and-privacy", + 10005 to "https://myaccount.google.com/people-and-sharing", + 10006 to "https://myaccount.google.com/security", + 10007 to "https://myaccount.google.com/payments-and-subscriptions", + 10015 to "https://support.google.com/accounts", + 10050 to "https://myaccount.google.com/profile", + 10090 to "https://myaccount.google.com/profile/name", + 10704 to "https://www.google.com/account/about", + 10706 to "https://myaccount.google.com/profile/profiles-summary", + 10728 to "https://myaccount.google.com/data-and-privacy/how-data-improves-experience", + 10729 to "https://myaccount.google.com/data-and-privacy/data-visibility", + 10759 to "https://myaccount.google.com/address/home", + 10760 to "https://myaccount.google.com/address/work", +) + +private val ALLOWED_WEB_PREFIXES = setOf( + "https://accounts.google.com/", + "https://myaccount.google.com/", + "https://one.google.com/", + "https://myactivity.google.com/", + "https://timeline.google.com/", + "https://takeout.google.com/", + "https://www.google.com/maps/timeline", + "https://www.google.com/setting/", + "https://drive.google.com/settings/", + "https://drive.google.com/accounts/", + "https://drive.google.com/u/1/settings/", + "https://payments.google.com/", + "https://policies.google.com/", + "https://fit.google.com/privacy/settings", + "https://maps.google.com/maps/timeline" +) + +private val ACTION_TO_SCREEN_ID = hashMapOf( + ACTION_MY_ACCOUNT to 1, + ACTION_ACCOUNT_PREFERENCES_SETTINGS to 1, + ACTION_SECURITY_SETTINGS to 10006, + ACTION_PRIVACY_SETTINGS to 10004, +) + +@Suppress("DEPRECATION") +class MainActivity : AppCompatActivity() { + private lateinit var webView: WebView + + private fun getSelectedAccountName(): String? = null + + override fun onCreate(savedInstanceState: Bundle?) { + val extras = intent?.extras?.also { it.keySet() } + Log.d(TAG, "Invoked with ${intent.action} and extras $extras") + super.onCreate(savedInstanceState) + + val screenId = intent?.getIntExtra(EXTRA_SCREEN_ID, -1).takeIf { it != -1 } ?: ACTION_TO_SCREEN_ID[intent.action] ?: 1 + val screenOptions = intent.extras?.keySet().orEmpty() + .filter { it.startsWith(EXTRA_SCREEN_OPTIONS_PREFIX) } + .map { it.substring(EXTRA_SCREEN_OPTIONS_PREFIX.length) to intent.getStringExtra(it) } + .toMap() + + val callingPackage = intent?.getStringExtra(EXTRA_CALLING_PACKAGE_NAME) ?: callingActivity?.packageName ?: Constants.GMS_PACKAGE_NAME + + val ignoreAccount = intent?.getBooleanExtra(EXTRA_IGNORE_ACCOUNT, false) ?: false + val accountName = if (ignoreAccount) null else { + val accounts = AccountManager.get(this).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE) + val accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME) ?: intent.getParcelableExtra("account")?.name ?: getSelectedAccountName() + accounts.find { it.name.equals(accountName) }?.name + } + + if (accountName == null) { + Log.w(TAG, "No account, going without!") + } + + if (screenId in SCREEN_ID_TO_URL) { + val layout = RelativeLayout(this) + layout.addView(ProgressBar(this).apply { + layoutParams = RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { + addRule(RelativeLayout.CENTER_HORIZONTAL) + addRule(RelativeLayout.CENTER_VERTICAL) + } + isIndeterminate = true + }) + webView = WebView(this).apply { + layoutParams = RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + visibility = View.INVISIBLE + } + layout.addView(webView) + setContentView(layout) + WebViewHelper(this, webView, ALLOWED_WEB_PREFIXES).openWebView(SCREEN_ID_TO_URL[screenId], accountName) + setResult(RESULT_OK) + } else { + Log.w(TAG, "Unknown screen id, can't open corresponding web page") + setResult(RESULT_CANCELED) + finish() + } + } + + override fun onDestroy() { + super.onDestroy() + } + + @Deprecated("Deprecated in Java") + override fun onBackPressed() { + if (this::webView.isInitialized && webView.canGoBack()) { + webView.goBack() + } else { + super.onBackPressed() + } + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/WebViewHelper.kt b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/WebViewHelper.kt new file mode 100644 index 00000000..7ce98e9c --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/WebViewHelper.kt @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.accountsettings.ui + +import android.content.Intent +import android.content.Intent.URI_INTENT_SCHEME +import android.net.Uri +import android.util.Log +import android.view.View +import android.webkit.WebResourceRequest +import android.webkit.WebSettings +import android.webkit.WebView +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import androidx.webkit.WebResourceErrorCompat +import androidx.webkit.WebViewClientCompat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.microg.gms.auth.AuthManager +import org.microg.gms.common.Constants.GMS_PACKAGE_NAME +import org.microg.gms.common.PackageUtils +import java.net.URLEncoder +import java.util.* + +private const val TAG = "AccountSettingsWebView" + +class WebViewHelper(private val activity: AppCompatActivity, private val webView: WebView, private val allowedPrefixes: Set = emptySet()) { + fun openWebView(url: String?, accountName: String?) { + prepareWebViewSettings(webView.settings) + webView.webViewClient = object : WebViewClientCompat() { + override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceErrorCompat) { + Log.w(TAG, "Error loading: $error") + } + + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + webView.visibility = View.VISIBLE + } + + @Deprecated("Deprecated in Java") + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + Log.d(TAG, "Navigating to $url") + if (url.startsWith("intent:")) { + try { + val intent = Intent.parseUri(url, URI_INTENT_SCHEME) + if (intent.`package` == GMS_PACKAGE_NAME || PackageUtils.isGooglePackage(activity, intent.`package`)) { + // Only allow to start Google packages + activity.startActivity(intent) + } else { + Log.w(TAG, "Ignoring request to start non-Google app") + } + } catch (e: Exception) { + Log.w(TAG, "Error invoking intent", e) + } + return false + } + if (allowedPrefixes.isNotEmpty() && allowedPrefixes.none { url.startsWith(it) }) { + try { + activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply { addCategory(Intent.CATEGORY_BROWSABLE) }) + } catch (e: Exception) { + Log.w(TAG, "Error forwarding to browser", e) + } + return true + } + return false + } + } + + val urlWithLanguage: String? = addLanguageParam(url) + if (accountName != null) { + activity.lifecycleScope.launch { + withContext(Dispatchers.IO) { + openWebWithAccount(accountName, urlWithLanguage) + } + } + } else { + loadWebViewUrl(urlWithLanguage) + } + } + + private fun loadWebViewUrl(url: String?) { + if (url != null) { + webView.loadUrl(url) + } else { + activity.finish() + } + } + + private fun addLanguageParam(url: String?): String? { + val language = Locale.getDefault().language + return if (language.isNotEmpty()) { + Uri.parse(url).buildUpon().appendQueryParameter("hl", language).toString() + } else { + url + } + } + + private fun openWebWithAccount(accountName: String, url: String?) { + try { + val service = "weblogin:continue=" + URLEncoder.encode(url, "utf-8") + val authManager = AuthManager(activity, accountName, GMS_PACKAGE_NAME, service) + val authUrl = authManager.requestAuth(false)?.auth + if (authUrl?.contains("WILL_NOT_SIGN_IN") == true) { + throw RuntimeException("Would not sign in") + } + Log.d(TAG, "Opening $authUrl") + webView.post { + loadWebViewUrl(authUrl) + } + } catch (e: Exception) { + Log.w(TAG, "Failed to get weblogin auth.", e) + activity.finish() + } + } + + private fun prepareWebViewSettings(settings: WebSettings) { + settings.javaScriptEnabled = true + settings.setSupportMultipleWindows(false) + settings.allowFileAccess = false + settings.databaseEnabled = false + settings.setNeedInitialFocus(false) + settings.useWideViewPort = false + settings.setSupportZoom(false) + settings.javaScriptCanOpenWindowsAutomatically = false + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/extensions.kt b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/extensions.kt new file mode 100644 index 00000000..5ca88fe7 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/extensions.kt @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.accountsettings.ui + +const val ACTION_BROWSE_SETTINGS = "com.google.android.gms.accountsettings.action.BROWSE_SETTINGS" +const val ACTION_MY_ACCOUNT = "com.google.android.gms.accountsettings.MY_ACCOUNT" +const val ACTION_ACCOUNT_PREFERENCES_SETTINGS = "com.google.android.gms.accountsettings.ACCOUNT_PREFERENCES_SETTINGS" +const val ACTION_PRIVACY_SETTINGS = "com.google.android.gms.accountsettings.PRIVACY_SETTINGS" +const val ACTION_SECURITY_SETTINGS = "com.google.android.gms.accountsettings.SECURITY_SETTINGS" + +const val EXTRA_CALLING_PACKAGE_NAME = "extra.callingPackageName" +const val EXTRA_IGNORE_ACCOUNT = "extra.ignoreAccount" +const val EXTRA_ACCOUNT_NAME = "extra.accountName" +const val EXTRA_SCREEN_ID = "extra.screenId" +const val EXTRA_SCREEN_OPTIONS_PREFIX = "extra.screen." +const val EXTRA_FALLBACK_URL = "extra.fallbackUrl" +const val EXTRA_FALLBACK_AUTH = "extra.fallbackAuth" +const val EXTRA_THEME_CHOICE = "extra.themeChoice" + +const val OPTION_SCREEN_FLAVOR = "screenFlavor" \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt index a86db122..ba5d2b2e 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt @@ -1,26 +1,29 @@ package org.microg.gms.auth import android.content.Context -import org.microg.mgms.settings.SettingsContract -import org.microg.mgms.settings.SettingsContract.Auth +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.Auth object AuthPrefs { @JvmStatic - fun isTrustGooglePermitted(context: Context): Boolean? { - return Auth.getContentUri(context)?.let { - SettingsContract.getSettings(context, it, arrayOf(Auth.TRUST_GOOGLE)) { c -> - c.getInt(0) != 0 - } + fun isTrustGooglePermitted(context: Context): Boolean { + return SettingsContract.getSettings(context, Auth.getContentUri(context), arrayOf(Auth.TRUST_GOOGLE)) { c -> + c.getInt(0) != 0 } } @JvmStatic - fun isAuthVisible(context: Context): Boolean? { - return Auth.getContentUri(context)?.let { - SettingsContract.getSettings(context, it, arrayOf(Auth.VISIBLE)) { c -> - c.getInt(0) != 0 - } + fun isAuthVisible(context: Context): Boolean { + return SettingsContract.getSettings(context, Auth.getContentUri(context), arrayOf(Auth.VISIBLE)) { c -> + c.getInt(0) != 0 + } + } + + @JvmStatic + fun shouldIncludeAndroidId(context: Context): Boolean { + return SettingsContract.getSettings(context, Auth.getContentUri(context), arrayOf(Auth.INCLUDE_ANDROID_ID)) { c -> + c.getInt(0) != 0 } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt index 3120a555..c99b3cc3 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt @@ -7,6 +7,7 @@ package org.microg.gms.auth.appcert import android.content.Context import android.database.Cursor +import android.os.SystemClock import android.util.Base64 import android.util.Log import com.android.volley.NetworkResponse @@ -28,8 +29,8 @@ import org.microg.gms.gcm.RegisterRequest import org.microg.gms.gcm.completeRegisterRequest import org.microg.gms.profile.Build import org.microg.gms.profile.ProfileManager -import org.microg.mgms.settings.SettingsContract.CheckIn -import org.microg.mgms.settings.SettingsContract.getSettings +import org.microg.gms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract.getSettings import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec import kotlin.random.Random @@ -62,7 +63,7 @@ class AppCertManager(private val context: Context) { Log.w(TAG, "DeviceKeys for app certifications are experimental") deviceKeyCacheTime = currentTime val lastCheckinInfo = LastCheckinInfo.read(context) - val androidId = lastCheckinInfo?.androidId + val androidId = lastCheckinInfo.androidId val sessionId = Random.nextLong() val token = completeRegisterRequest(context, GcmDatabase(context), RegisterRequest().build(context) .checkin(lastCheckinInfo) @@ -75,7 +76,7 @@ class AppCertManager(private val context: Context) { .extraParam("scope", REGISTER_SCOPE)) .getString(GcmConstants.EXTRA_REGISTRATION_ID) val request = DeviceKeyRequest( - androidId = lastCheckinInfo?.androidId, + androidId = lastCheckinInfo.androidId, sessionId = sessionId, versionInfo = DeviceKeyRequest.VersionInfo(Build.VERSION.SDK_INT, BuildConfig.VERSION_CODE), token = token @@ -96,7 +97,11 @@ class AppCertManager(private val context: Context) { } override fun deliverError(error: VolleyError) { - Log.d(TAG, "Error: ${Base64.encodeToString(error.networkResponse.data, 2)}") + if (error.networkResponse != null) { + Log.d(TAG, "Error: ${Base64.encodeToString(error.networkResponse.data, 2)}") + } else { + Log.d(TAG, "Error: ${error.message}") + } deviceKeyCacheTime = 0 deferredResponse.complete(null) } @@ -107,10 +112,10 @@ class AppCertManager(private val context: Context) { override fun getHeaders(): Map { return mapOf( - "User-Agent" to "GoogleAuth/1.4 (${Build.DEVICE} ${Build.ID}); gzip", - "content-type" to "application/octet-stream", - "app" to "com.google.android.gms", - "device" to androidId!!.toString(16) + "User-Agent" to "GoogleAuth/1.4 (${Build.DEVICE} ${Build.ID}); gzip", + "content-type" to "application/octet-stream", + "app" to "com.google.android.gms", + "device" to androidId.toString(16) ) } }) @@ -149,14 +154,12 @@ class AppCertManager(private val context: Context) { ) } else { Log.d(TAG, "Using fallback spatula header based on Android ID") - val androidId = CheckIn.getContentUri(context)?.let { - getSettings(context, - it, arrayOf(CheckIn.ANDROID_ID, CheckIn.SECURITY_TOKEN)) { cursor: Cursor -> cursor.getLong(0) } - } + val androidId = getSettings(context, CheckIn.getContentUri(context), arrayOf(CheckIn.ANDROID_ID)) { cursor: Cursor -> cursor.getLong(0) } SpatulaHeaderProto( packageInfo = SpatulaHeaderProto.PackageInfo(packageName, packageCertificateHash), deviceId = androidId ) + return null // TODO } Log.d(TAG, "Spatula Header: $proto") return Base64.encodeToString(proto.encode(), Base64.NO_WRAP) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertService.kt index e73017e2..9b14af49 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertService.kt @@ -38,5 +38,5 @@ class AppCertServiceImpl(private val context: Context) : IAppCertService.Stub() return runBlocking { manager.getSpatulaHeader(packageName) } } - override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt index adc0370b..4e933426 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt @@ -52,5 +52,5 @@ class CredentialsServiceImpl : ICredentialsService.Stub() { callbacks.onStatus(Status.SUCCESS) } - override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt index e1cedd59..ede33d26 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt @@ -59,5 +59,5 @@ class AuthServiceImpl(private val context: Context, private val lifecycle: Lifec override fun getLifecycle(): Lifecycle = lifecycle - override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPreferences.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPreferences.kt new file mode 100644 index 00000000..eb78d498 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPreferences.kt @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.checkin + +import android.content.Context +import android.content.Intent +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract.setSettings + +object CheckinPreferences { + + @JvmStatic + fun isEnabled(context: Context): Boolean { + val projection = arrayOf(CheckIn.ENABLED) + return SettingsContract.getSettings( + context, + CheckIn.getContentUri(context), + projection + ) { c -> + c.getInt(0) != 0 + } + } + + @JvmStatic + fun isSpoofingEnabled(context: Context): Boolean { + val projection = arrayOf(CheckIn.BRAND_SPOOF) + return SettingsContract.getSettings( + context, + CheckIn.getContentUri(context), + projection + ) { c -> + c.getInt(0) != 0 + } + } + + @JvmStatic + fun setSpoofingEnabled(context: Context, enabled: Boolean) { + setSettings(context, CheckIn.getContentUri(context)) { + put(CheckIn.BRAND_SPOOF, enabled) + } + } + + @JvmStatic + fun hideLauncherIcon(context: Context, enabled: Boolean) { + setSettings(context, CheckIn.getContentUri(context)) { + put(CheckIn.HIDE_LAUNCHER_ICON, enabled) + } + } + + @JvmStatic + fun setEnabled(context: Context, enabled: Boolean) { + SettingsContract.setSettings(context, CheckIn.getContentUri(context)) { + put(CheckIn.ENABLED, enabled) + } + if (enabled) { + context.sendOrderedBroadcast(Intent(context, TriggerReceiver::class.java), null) + } + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt deleted file mode 100644 index 019ddfa9..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ -package org.microg.gms.checkin - -import android.content.Context -import org.microg.mgms.settings.SettingsContract -import org.microg.mgms.settings.SettingsContract.CheckIn -import org.microg.mgms.settings.SettingsContract.setSettings - -object CheckinPrefs { - - @JvmStatic - fun isEnabled(context: Context): Boolean? { - val projection = arrayOf(CheckIn.ENABLED) - return CheckIn.getContentUri(context)?.let { - SettingsContract.getSettings(context, it, projection) { c -> - c.getInt(0) != 0 - } - } - } - - @JvmStatic - fun isSpoofingEnabled(context: Context): Boolean? { - val projection = arrayOf(CheckIn.BRAND_SPOOF) - return CheckIn.getContentUri(context)?.let { - SettingsContract.getSettings(context, it, projection) { c -> - c.getInt(0) != 0 - } - } - } - - @JvmStatic - fun setSpoofingEnabled(context: Context, enabled: Boolean) { - CheckIn.getContentUri(context)?.let { - setSettings(context, it) { - put(CheckIn.BRAND_SPOOF, enabled) - } - } - } - - @JvmStatic - fun hideLauncherIcon(context: Context, enabled: Boolean) { - CheckIn.getContentUri(context)?.let { - setSettings(context, it) { - put(CheckIn.HIDE_LAUNCHER_ICON, enabled) - } - } - } - -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt index e3e26f68..ab2dc042 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt @@ -16,8 +16,8 @@ package org.microg.gms.checkin import android.content.Context -import org.microg.mgms.settings.SettingsContract -import org.microg.mgms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.CheckIn data class LastCheckinInfo( val lastCheckin: Long, @@ -39,7 +39,7 @@ data class LastCheckinInfo( companion object { @JvmStatic - fun read(context: Context): LastCheckinInfo? { + fun read(context: Context): LastCheckinInfo { val projection = arrayOf( CheckIn.ANDROID_ID, CheckIn.DIGEST, @@ -48,43 +48,35 @@ data class LastCheckinInfo( CheckIn.VERSION_INFO, CheckIn.DEVICE_DATA_VERSION_INFO, ) - return CheckIn.getContentUri(context)?.let { - SettingsContract.getSettings(context, it, projection) { c -> - LastCheckinInfo( - androidId = c.getLong(0), - digest = c.getString(1), - lastCheckin = c.getLong(2), - securityToken = c.getLong(3), - versionInfo = c.getString(4), - deviceDataVersionInfo = c.getString(5), - ) - } + return SettingsContract.getSettings(context, CheckIn.getContentUri(context), projection) { c -> + LastCheckinInfo( + androidId = c.getLong(0), + digest = c.getString(1), + lastCheckin = c.getLong(2), + securityToken = c.getLong(3), + versionInfo = c.getString(4), + deviceDataVersionInfo = c.getString(5), + ) } } @JvmStatic - fun clear(context: Context) = - CheckIn.getContentUri(context)?.let { - SettingsContract.setSettings(context, it) { - put(CheckIn.ANDROID_ID, 0L) - put(CheckIn.DIGEST, CheckIn.INITIAL_DIGEST) - put(CheckIn.LAST_CHECK_IN, 0L) - put(CheckIn.SECURITY_TOKEN, 0L) - put(CheckIn.VERSION_INFO, "") - put(CheckIn.DEVICE_DATA_VERSION_INFO, "") - } - } + fun clear(context: Context) = SettingsContract.setSettings(context, CheckIn.getContentUri(context)) { + put(CheckIn.ANDROID_ID, 0L) + put(CheckIn.DIGEST, CheckIn.INITIAL_DIGEST) + put(CheckIn.LAST_CHECK_IN, 0L) + put(CheckIn.SECURITY_TOKEN, 0L) + put(CheckIn.VERSION_INFO, "") + put(CheckIn.DEVICE_DATA_VERSION_INFO, "") + } } - fun write(context: Context) = - CheckIn.getContentUri(context)?.let { - SettingsContract.setSettings(context, it) { - put(CheckIn.ANDROID_ID, androidId) - put(CheckIn.DIGEST, digest) - put(CheckIn.LAST_CHECK_IN, lastCheckin) - put(CheckIn.SECURITY_TOKEN, securityToken) - put(CheckIn.VERSION_INFO, versionInfo) - put(CheckIn.DEVICE_DATA_VERSION_INFO, deviceDataVersionInfo) - } - } + fun write(context: Context) = SettingsContract.setSettings(context, CheckIn.getContentUri(context)) { + put(CheckIn.ANDROID_ID, androidId) + put(CheckIn.DIGEST, digest) + put(CheckIn.LAST_CHECK_IN, lastCheckin) + put(CheckIn.SECURITY_TOKEN, securityToken) + put(CheckIn.VERSION_INFO, versionInfo) + put(CheckIn.DEVICE_DATA_VERSION_INFO, deviceDataVersionInfo) + } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt index 67084e03..d42c9e03 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt @@ -9,9 +9,9 @@ import android.content.Context import android.content.Intent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.microg.mgms.settings.SettingsContract.CheckIn -import org.microg.mgms.settings.SettingsContract.getSettings -import org.microg.mgms.settings.SettingsContract.setSettings +import org.microg.gms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract.getSettings +import org.microg.gms.settings.SettingsContract.setSettings import java.io.Serializable data class ServiceInfo(val configuration: ServiceConfiguration, val lastCheckin: Long, val androidId: Long) : Serializable @@ -20,26 +20,22 @@ data class ServiceConfiguration(val enabled: Boolean) : Serializable suspend fun getCheckinServiceInfo(context: Context): ServiceInfo = withContext(Dispatchers.IO) { val projection = arrayOf(CheckIn.ENABLED, CheckIn.LAST_CHECK_IN, CheckIn.ANDROID_ID) - CheckIn.getContentUri(context)?.let { - getSettings(context, it, projection) { c -> - ServiceInfo( - configuration = ServiceConfiguration(c.getInt(0) != 0), - lastCheckin = c.getLong(1), - androidId = c.getLong(2), - ) - } - }!! + getSettings(context, CheckIn.getContentUri(context), projection) { c -> + ServiceInfo( + configuration = ServiceConfiguration(c.getInt(0) != 0), + lastCheckin = c.getLong(1), + androidId = c.getLong(2), + ) + } } suspend fun setCheckinServiceConfiguration(context: Context, configuration: ServiceConfiguration) = withContext(Dispatchers.IO) { val serviceInfo = getCheckinServiceInfo(context) if (serviceInfo.configuration == configuration) return@withContext // enabled state is not already set, setting it now - CheckIn.getContentUri(context)?.let { - setSettings(context, it) { + setSettings(context, CheckIn.getContentUri(context)) { put(CheckIn.ENABLED, configuration.enabled) } - } if (configuration.enabled) { context.sendOrderedBroadcast(Intent(context, TriggerReceiver::class.java), null) } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt index 77cf34b0..c506ed62 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt @@ -8,10 +8,11 @@ import android.net.ConnectivityManager import android.net.NetworkInfo import android.util.Log import org.microg.gms.gcm.TriggerReceiver.FORCE_TRY_RECONNECT -import org.microg.mgms.settings.SettingsContract -import org.microg.mgms.settings.SettingsContract.Gcm -import org.microg.mgms.settings.SettingsContract.setSettings -import org.microg.mgms.settings.SettingsProvider +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.Gcm +import org.microg.gms.settings.SettingsContract.setSettings +import kotlin.math.max +import kotlin.math.min data class GcmPrefs( val isGcmLogEnabled: Boolean, @@ -37,49 +38,57 @@ data class GcmPrefs( const val PREF_NETWORK_ROAMING = Gcm.NETWORK_ROAMING const val PREF_NETWORK_OTHER = Gcm.NETWORK_OTHER + private const val MIN_INTERVAL = 5 * 60 * 1000 // 5 minutes + private const val MAX_INTERVAL = 15 * 60 * 1000 // 15 minutes + @JvmStatic - fun get(context: Context): GcmPrefs? { - return Gcm.getContentUri(context)?.let { - SettingsContract.getSettings(context, it, Gcm.PROJECTION) { c -> - GcmPrefs( - isGcmLogEnabled = c.getInt(0) != 0, - lastPersistedId = c.getString(1), - gcmEnabled = c.getInt(2) != 0, - networkMobile = c.getInt(3), - networkWifi = c.getInt(4), - networkRoaming = c.getInt(5), - networkOther = c.getInt(6), - learntMobileInterval = c.getInt(7), - learntWifiInterval = c.getInt(8), - learntOtherInterval = c.getInt(9), - ) - } + fun get(context: Context): GcmPrefs { + return SettingsContract.getSettings(context, Gcm.getContentUri(context), Gcm.PROJECTION) { c -> + GcmPrefs( + isGcmLogEnabled = c.getInt(0) != 0, + lastPersistedId = c.getString(1), + gcmEnabled = c.getInt(2) != 0, + networkMobile = c.getInt(3), + networkWifi = c.getInt(4), + networkRoaming = c.getInt(5), + networkOther = c.getInt(6), + learntMobileInterval = c.getInt(7), + learntWifiInterval = c.getInt(8), + learntOtherInterval = c.getInt(9), + ) } } fun write(context: Context, config: ServiceConfiguration) { - Gcm.getContentUri(context)?.let { - setSettings(context, it) { - put(Gcm.ENABLE_GCM, config.enabled) - put(Gcm.NETWORK_MOBILE, config.mobile) - put(Gcm.NETWORK_WIFI, config.wifi) - put(Gcm.NETWORK_ROAMING, config.roaming) - put(Gcm.NETWORK_OTHER, config.other) - } + val gcmPrefs = get(context) + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.ENABLE_GCM, config.enabled) + put(Gcm.NETWORK_MOBILE, config.mobile) + put(Gcm.NETWORK_WIFI, config.wifi) + put(Gcm.NETWORK_ROAMING, config.roaming) + put(Gcm.NETWORK_OTHER, config.other) } - get(context)?.setEnabled(context, config.enabled) + gcmPrefs.setEnabled(context, config.enabled) } + fun setEnabled(context: Context, enabled: Boolean) { + val prefs = get(context) + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.ENABLE_GCM, enabled) + } + prefs.setEnabled(context, enabled) + } + + @JvmStatic fun clearLastPersistedId(context: Context) { - Gcm.getContentUri(context)?.let { - setSettings(context, it) { - put(Gcm.LAST_PERSISTENT_ID, "") - } + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.LAST_PERSISTENT_ID, "") } } } + /** * Call this whenever the enabled state of GCM has changed. */ @@ -93,6 +102,7 @@ data class GcmPrefs( } } + @Suppress("DEPRECATION") fun getNetworkPrefForInfo(info: NetworkInfo?): String { if (info == null) return PREF_NETWORK_OTHER return if (info.isRoaming) PREF_NETWORK_ROAMING else when (info.type) { @@ -102,42 +112,37 @@ data class GcmPrefs( } } + @Suppress("DEPRECATION") fun getHeartbeatMsFor(info: NetworkInfo?): Int { return getHeartbeatMsFor(getNetworkPrefForInfo(info)) } fun getHeartbeatMsFor(pref: String): Int { return if (PREF_NETWORK_ROAMING == pref) { - if (networkRoaming != 0) networkRoaming * SettingsProvider.INTERVAL else learntMobileInterval + if (networkRoaming != 0) networkRoaming * 60000 else learntMobileInterval } else if (PREF_NETWORK_MOBILE == pref) { - if (networkMobile != 0) networkMobile * SettingsProvider.INTERVAL else learntMobileInterval + if (networkMobile != 0) networkMobile * 60000 else learntMobileInterval } else if (PREF_NETWORK_WIFI == pref) { - if (networkWifi != 0) networkWifi * SettingsProvider.INTERVAL else learntWifiInterval + if (networkWifi != 0) networkWifi * 60000 else learntWifiInterval } else { - if (networkOther != 0) networkOther * SettingsProvider.INTERVAL else learntOtherInterval + if (networkOther != 0) networkOther * 60000 else learntOtherInterval } } fun learnTimeout(context: Context, pref: String) { Log.d("GmsGcmPrefs", "learnTimeout: $pref") when (pref) { - PREF_NETWORK_MOBILE, PREF_NETWORK_ROAMING -> Gcm.getContentUri(context)?.let { - setSettings(context, it) { - val newInterval = (learntMobileInterval * 0.95).toInt() - put(Gcm.LEARNT_MOBILE, newInterval) - } + PREF_NETWORK_MOBILE, PREF_NETWORK_ROAMING -> setSettings(context, Gcm.getContentUri(context)) { + val newInterval = (learntMobileInterval * 0.95).toInt() + put(Gcm.LEARNT_MOBILE, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) } - PREF_NETWORK_WIFI -> Gcm.getContentUri(context)?.let { - setSettings(context, it) { - val newInterval = (learntMobileInterval * 0.95).toInt() - put(Gcm.LEARNT_WIFI, newInterval) - } + PREF_NETWORK_WIFI -> setSettings(context, Gcm.getContentUri(context)) { + val newInterval = (learntWifiInterval * 0.95).toInt() + put(Gcm.LEARNT_WIFI, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) } - else -> Gcm.getContentUri(context)?.let { - setSettings(context, it) { - val newInterval = (learntMobileInterval * 0.95).toInt() - put(Gcm.LEARNT_OTHER, newInterval) - } + else -> setSettings(context, Gcm.getContentUri(context)) { + val newInterval = (learntOtherInterval * 0.95).toInt() + put(Gcm.LEARNT_OTHER, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) } } } @@ -147,45 +152,40 @@ data class GcmPrefs( when (pref) { PREF_NETWORK_MOBILE, PREF_NETWORK_ROAMING -> { if (time > learntMobileInterval / 4 * 3) { - Gcm.getContentUri(context)?.let { - setSettings(context, it) { - put(Gcm.LEARNT_MOBILE, SettingsProvider.INTERVAL) - } + val newInterval = (learntMobileInterval * 1.02).toInt() + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.LEARNT_MOBILE, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) } } } PREF_NETWORK_WIFI -> { if (time > learntWifiInterval / 4 * 3) { - Gcm.getContentUri(context)?.let { - setSettings(context, it) { - put(Gcm.LEARNT_WIFI, SettingsProvider.INTERVAL) - } + val newInterval = (learntWifiInterval * 1.02).toInt() + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.LEARNT_WIFI, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) } } } else -> { if (time > learntOtherInterval / 4 * 3) { - Gcm.getContentUri(context)?.let { - setSettings(context, it) { - put(Gcm.LEARNT_OTHER, SettingsProvider.INTERVAL) - } + val newInterval = (learntOtherInterval * 1.02).toInt() + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.LEARNT_OTHER, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) } } } } } + @Suppress("DEPRECATION") fun isEnabledFor(info: NetworkInfo?): Boolean { return isEnabled && info != null && getHeartbeatMsFor(info) >= 0 } fun extendLastPersistedId(context: Context, id: String) { val newId = if (lastPersistedId.isNullOrEmpty()) id else "$lastPersistedId|$id" - Gcm.getContentUri(context)?.let { - setSettings(context, it) { - put(Gcm.LAST_PERSISTENT_ID, newId) - } + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.LAST_PERSISTENT_ID, newId) } } - } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt index 4b7b44bc..8d3b9a78 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt @@ -6,7 +6,6 @@ package org.microg.gms.gcm -import android.annotation.SuppressLint import android.app.PendingIntent import android.content.Context import android.content.Intent @@ -17,7 +16,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope -import org.microg.gms.checkin.CheckinPrefs +import org.microg.gms.checkin.CheckinPreferences import org.microg.gms.checkin.CheckinService import org.microg.gms.checkin.LastCheckinInfo import org.microg.gms.common.ForegroundServiceContext @@ -30,32 +29,30 @@ import kotlin.coroutines.suspendCoroutine private const val TAG = "GmsGcmRegister" private suspend fun ensureCheckinIsUpToDate(context: Context) { - if (!CheckinPrefs.isEnabled(context)!!) throw RuntimeException("Checkin disabled") - val lastCheckin = LastCheckinInfo.read(context)?.lastCheckin - if (lastCheckin != null) { - if (lastCheckin < System.currentTimeMillis() - CheckinService.MAX_VALID_CHECKIN_AGE) { - val resultData: Bundle = suspendCoroutine { continuation -> - val intent = Intent(context, CheckinService::class.java) - val continued = AtomicBoolean(false) - intent.putExtra(CheckinService.EXTRA_RESULT_RECEIVER, object : ResultReceiver(null) { - override fun onReceiveResult(resultCode: Int, resultData: Bundle?) { - if (continued.compareAndSet(false, true)) continuation.resume(resultData ?: Bundle.EMPTY) - } - }) - ForegroundServiceContext(context).startService(intent) - Handler().postDelayed({ - if (continued.compareAndSet(false, true)) continuation.resume(Bundle.EMPTY) - }, 10000L) - } - if (resultData.getLong(CheckinService.EXTRA_NEW_CHECKIN_TIME, 0L) + lastCheckin == 0L) { - throw RuntimeException("No checkin available") - } + if (!CheckinPreferences.isEnabled(context)) throw RuntimeException("Checkin disabled") + val lastCheckin = LastCheckinInfo.read(context).lastCheckin + if (lastCheckin < System.currentTimeMillis() - CheckinService.MAX_VALID_CHECKIN_AGE) { + val resultData: Bundle = suspendCoroutine { continuation -> + val intent = Intent(context, CheckinService::class.java) + val continued = AtomicBoolean(false) + intent.putExtra(CheckinService.EXTRA_RESULT_RECEIVER, object : ResultReceiver(null) { + override fun onReceiveResult(resultCode: Int, resultData: Bundle?) { + if (continued.compareAndSet(false, true)) continuation.resume(resultData ?: Bundle.EMPTY) + } + }) + ForegroundServiceContext(context).startService(intent) + Handler().postDelayed({ + if (continued.compareAndSet(false, true)) continuation.resume(Bundle.EMPTY) + }, 10000L) + } + if (resultData.getLong(CheckinService.EXTRA_NEW_CHECKIN_TIME, 0L) + lastCheckin == 0L) { + throw RuntimeException("No checkin available") } } } private fun ensureAppRegistrationAllowed(context: Context, database: GcmDatabase, packageName: String) { - if (!GcmPrefs.get(context)?.isEnabled!!) throw RuntimeException("GCM disabled") + if (!GcmPrefs.get(context).isEnabled) throw RuntimeException("GCM disabled") val app = database.getApp(packageName) if (app?.allowRegister == false) { throw RuntimeException("Push permission not granted to $packageName") @@ -245,11 +242,10 @@ internal class PushRegisterHandler(private val context: Context, private val dat } private val selfAuthIntent: PendingIntent - @SuppressLint("UnspecifiedImmutableFlag") get() { val intent = Intent() intent.setPackage("com.google.example.invalidpackage") - return PendingIntent.getBroadcast(context, 0, intent, 0) + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) } override fun handleMessage(msg: Message) { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt index 1fd34bac..be9c0f67 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt @@ -36,16 +36,14 @@ class ServiceInfoReceiver : BroadcastReceiver() { context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { setPackage(context.packageName) val prefs = GcmPrefs.get(context) - val info = prefs?.let { - ServiceInfo( - configuration = it.toConfiguration(), + val info = ServiceInfo( + configuration = prefs.toConfiguration(), connected = McsService.isConnected(context), startTimestamp = McsService.getStartTimestamp(), learntMobileInterval = prefs.learntMobileInterval, learntWifiInterval = prefs.learntWifiInterval, learntOtherInterval = prefs.learntOtherInterval - ) - } + ) putExtra(EXTRA_SERVICE_INFO, info) }, null) } catch (e: Exception) { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/AppHeadingPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppHeadingPreference.kt new file mode 100644 index 00000000..c3cf6185 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppHeadingPreference.kt @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.ui +import android.content.Context +import android.content.Intent +import android.content.pm.ApplicationInfo +import android.net.Uri +import android.provider.Settings +import android.util.AttributeSet +import android.util.Log +import android.view.View +import android.widget.ImageView +import androidx.appcompat.content.res.AppCompatResources +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import org.microg.gms.base.core.R +class AppHeadingPreference : AppPreference, Preference.OnPreferenceClickListener { + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context) : super(context) + init { + layoutResource = R.layout.preference_app_heading + onPreferenceClickListener = this + } + override fun onPreferenceClick(preference: Preference): Boolean { + if (packageName != null) { + val intent = Intent() + intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + val uri: Uri = Uri.fromParts("package", packageName, null) + intent.data = uri + try { + context.startActivity(intent) + } catch (e: Exception) { + Log.w(TAG, "Failed to launch app", e) + } + return true + } else { + return false + } + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt index ef30f876..1570c691 100755 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt @@ -11,10 +11,10 @@ import android.util.DisplayMetrics import android.widget.ImageView import androidx.preference.Preference import androidx.preference.PreferenceViewHolder +import android.content.pm.ApplicationInfo -class AppIconPreference : Preference { - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super( - context, attrs, defStyleAttr, defStyleRes) +class AppIconPreference : AppPreference { + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context) : super(context) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/AppPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppPreference.kt new file mode 100644 index 00000000..27626231 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppPreference.kt @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.ui +import android.content.Context +import android.content.pm.ApplicationInfo +import android.util.AttributeSet +import androidx.appcompat.content.res.AppCompatResources +import androidx.preference.Preference +abstract class AppPreference : Preference { + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context) : super(context) + init { + isPersistent = false + } + private var packageNameField: String? = null + var applicationInfo: ApplicationInfo? + get() = context.packageManager.getApplicationInfoIfExists(packageNameField) + set(value) { + if (value == null && packageNameField != null) { + title = null + icon = null + } else if (value != null) { + val pm = context.packageManager + title = value.loadLabel(pm) ?: value.packageName + icon = value.loadIcon(pm) ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) + } + packageNameField = value?.packageName + } + var packageName: String? + get() = packageNameField + set(value) { + if (value == null && packageNameField != null) { + title = null + icon = null + } else if (value != null) { + val pm = context.packageManager + val applicationInfo = pm.getApplicationInfoIfExists(value) + title = applicationInfo?.loadLabel(pm)?.toString() ?: value + icon = applicationInfo?.loadIcon(pm) ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) + } + packageNameField = value + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt index 17887575..8c668529 100755 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt @@ -5,51 +5,163 @@ package org.microg.gms.ui + +import android.annotation.SuppressLint +import android.net.Uri import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment +import android.os.Handler +import android.text.format.DateUtils +import android.util.Log +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.lifecycle.lifecycleScope +import androidx.preference.ListPreference +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat import com.mgoogle.android.gms.R -import com.mgoogle.android.gms.databinding.DeviceRegistrationFragmentBinding -import org.microg.gms.checkin.ServiceInfo +import org.microg.gms.checkin.CheckinPreferences import org.microg.gms.checkin.getCheckinServiceInfo -import org.microg.gms.checkin.setCheckinServiceConfiguration +import org.microg.gms.profile.ProfileManager +import org.microg.gms.profile.ProfileManager.PROFILE_AUTO +import org.microg.gms.profile.ProfileManager.PROFILE_NATIVE +import org.microg.gms.profile.ProfileManager.PROFILE_REAL +import org.microg.gms.profile.ProfileManager.PROFILE_SYSTEM +import org.microg.gms.profile.ProfileManager.PROFILE_USER +import java.io.File +import java.io.FileOutputStream -class DeviceRegistrationFragment : Fragment(R.layout.device_registration_fragment) { - private lateinit var binding: DeviceRegistrationFragmentBinding +class DeviceRegistrationFragment : PreferenceFragmentCompat() { + private lateinit var switchBarPreference: SwitchBarPreference + private lateinit var deviceProfile: ListPreference + private lateinit var importProfile: Preference + private lateinit var serial: Preference + private lateinit var statusCategory: PreferenceCategory + private lateinit var status: Preference + private lateinit var androidId: Preference + private val handler = Handler() + private val updateRunnable = Runnable { updateStatus() } + private lateinit var profileFileImport: ActivityResultLauncher - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - binding = DeviceRegistrationFragmentBinding.inflate(inflater, container, false) - binding.switchBarCallback = object : PreferenceSwitchBarCallback { - override fun onChecked(newStatus: Boolean) { - setEnabled(newStatus) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + profileFileImport = registerForActivityResult(ActivityResultContracts.GetContent(), this::onFileSelected) + } + private fun onFileSelected(uri: Uri?) { + if (uri == null) return + try { + val context = requireContext() + val file = File.createTempFile("profile_", ".xml", context.cacheDir) + context.contentResolver.openInputStream(uri)?.use { inputStream -> + FileOutputStream(file).use { inputStream.copyTo(it) } + } + val success = ProfileManager.importUserProfile(context, file) + file.delete() + if (success && ProfileManager.isAutoProfile(context, PROFILE_USER)) { + ProfileManager.setProfile(context, PROFILE_USER) + } + updateStatus() + } catch (e: Exception) { + Log.w(TAG, e) + } + } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences_device_registration) + } + @SuppressLint("RestrictedApi") + override fun onBindPreferences() { + switchBarPreference = preferenceScreen.findPreference("pref_checkin_enabled") ?: switchBarPreference + deviceProfile = preferenceScreen.findPreference("pref_device_profile") ?: deviceProfile + importProfile = preferenceScreen.findPreference("pref_device_profile_import") ?: importProfile + serial = preferenceScreen.findPreference("pref_device_serial") ?: serial + statusCategory = preferenceScreen.findPreference("prefcat_device_registration_status") ?: statusCategory + status = preferenceScreen.findPreference("pref_device_registration_status") ?: status + androidId = preferenceScreen.findPreference("pref_device_registration_android_id") ?: androidId + deviceProfile.setOnPreferenceChangeListener { _, newValue -> + ProfileManager.setProfile(requireContext(), newValue as String? ?: PROFILE_AUTO) + updateStatus() + true + } + importProfile.setOnPreferenceClickListener { + profileFileImport.launch("text/xml") + true + } + switchBarPreference.setOnPreferenceChangeListener { _, newValue -> + val newStatus = newValue as Boolean + CheckinPreferences.setEnabled(requireContext(), newStatus) + true + } + } + private fun configureProfilePreference() { + val context = requireContext() + val configuredProfile = ProfileManager.getConfiguredProfile(context) + val autoProfile = ProfileManager.getAutoProfile(context) + val autoProfileName = when (autoProfile) { + PROFILE_NATIVE -> getString(R.string.profile_name_native) + PROFILE_REAL -> getString(R.string.profile_name_real) + else -> ProfileManager.getProfileName(context, autoProfile) + } + val profiles = + mutableListOf(PROFILE_AUTO, PROFILE_NATIVE, PROFILE_REAL) + val profileNames = mutableListOf(getString(R.string.profile_name_auto, autoProfileName), getString(R.string.profile_name_native), getString(R.string.profile_name_real)) + if (ProfileManager.hasProfile(context, PROFILE_SYSTEM)) { + profiles.add(PROFILE_SYSTEM) + profileNames.add(getString(R.string.profile_name_system, ProfileManager.getProfileName(context, PROFILE_SYSTEM))) + } + if (ProfileManager.hasProfile(context, PROFILE_USER)) { + profiles.add(PROFILE_USER) + profileNames.add(getString(R.string.profile_name_user, ProfileManager.getProfileName(context, PROFILE_USER))) + } + for (profile in R.xml::class.java.declaredFields.map { it.name } + .filter { it.startsWith("profile_") } + .map { it.substring(8) } + .sorted()) { + val profileName = ProfileManager.getProfileName(context, profile) + if (profileName != null) { + profiles.add(profile) + profileNames.add(profileName) } } - return binding.root - } - - fun setEnabled(newStatus: Boolean) { - val appContext = requireContext().applicationContext - lifecycleScope.launchWhenResumed { - val info = getCheckinServiceInfo(appContext) - val newConfiguration = info.configuration.copy(enabled = newStatus) - setCheckinServiceConfiguration(appContext, newConfiguration) - displayServiceInfo(info.copy(configuration = newConfiguration)) - } - } - - private fun displayServiceInfo(serviceInfo: ServiceInfo) { - binding.checkinEnabled = serviceInfo.configuration.enabled + deviceProfile.entryValues = profiles.toTypedArray() + deviceProfile.entries = profileNames.toTypedArray() + deviceProfile.value = configuredProfile + deviceProfile.summary = + profiles.indexOf(configuredProfile).takeIf { it >= 0 }?.let { profileNames[it] } ?: "Unknown" } override fun onResume() { super.onResume() + switchBarPreference.isChecked = CheckinPreferences.isEnabled(requireContext()) + updateStatus() + } + override fun onPause() { + super.onPause() + handler.removeCallbacks(updateRunnable) + } + private fun updateStatus() { + handler.removeCallbacks(updateRunnable) + handler.postDelayed(updateRunnable, UPDATE_INTERVAL) val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - displayServiceInfo(getCheckinServiceInfo(appContext)) + configureProfilePreference() + serial.summary = ProfileManager.getSerial(appContext) + val serviceInfo = getCheckinServiceInfo(appContext) + statusCategory.isVisible = serviceInfo.configuration.enabled + if (serviceInfo.lastCheckin > 0) { + status.summary = getString( + R.string.checkin_last_registration, + DateUtils.getRelativeTimeSpanString(serviceInfo.lastCheckin, System.currentTimeMillis(), 0) + ) + androidId.isVisible = true + androidId.summary = serviceInfo.androidId.toString(16) + } else { + status.summary = getString(R.string.checkin_not_registered) + androidId.isVisible = false + } } } + companion object { + private const val UPDATE_INTERVAL = 1000L + } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt deleted file mode 100755 index a0b4ad13..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt +++ /dev/null @@ -1,164 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.ui - -import android.annotation.SuppressLint -import android.net.Uri -import android.os.Bundle -import android.os.Handler -import android.text.format.DateUtils -import android.util.Log -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts -import androidx.lifecycle.lifecycleScope -import androidx.preference.ListPreference -import androidx.preference.Preference -import androidx.preference.PreferenceCategory -import androidx.preference.PreferenceFragmentCompat -import com.mgoogle.android.gms.R -import org.microg.gms.checkin.getCheckinServiceInfo -import org.microg.gms.profile.ProfileManager -import org.microg.gms.profile.ProfileManager.PROFILE_AUTO -import org.microg.gms.profile.ProfileManager.PROFILE_NATIVE -import org.microg.gms.profile.ProfileManager.PROFILE_REAL -import org.microg.gms.profile.ProfileManager.PROFILE_SYSTEM -import org.microg.gms.profile.ProfileManager.PROFILE_USER -import java.io.File -import java.io.FileOutputStream - -@Suppress("Warnings") -class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() { - private lateinit var deviceProfile: ListPreference - private lateinit var importProfile: Preference - private lateinit var serial: Preference - private lateinit var statusCategory: PreferenceCategory - private lateinit var status: Preference - private lateinit var androidId: Preference - private val handler = Handler() - private val updateRunnable = Runnable { updateStatus() } - private lateinit var profileFileImport: ActivityResultLauncher - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - profileFileImport = registerForActivityResult(ActivityResultContracts.GetContent(), this::onFileSelected) - } - - private fun onFileSelected(uri: Uri?) { - if (uri == null) return - try { - val context = requireContext() - val file = File.createTempFile("profile_", ".xml", context.cacheDir) - context.contentResolver.openInputStream(uri)?.use { inputStream -> - FileOutputStream(file).use { inputStream.copyTo(it) } - } - val success = ProfileManager.importUserProfile(context, file) - file.delete() - if (success && ProfileManager.isAutoProfile(context, PROFILE_USER)) { - ProfileManager.setProfile(context, PROFILE_USER) - } - updateStatus() - } catch (e: Exception) { - Log.w(TAG, e) - } - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.preferences_device_registration) - } - - @SuppressLint("RestrictedApi") - override fun onBindPreferences() { - deviceProfile = preferenceScreen.findPreference("pref_device_profile") ?: deviceProfile - importProfile = preferenceScreen.findPreference("pref_device_profile_import") ?: importProfile - serial = preferenceScreen.findPreference("pref_device_serial") ?: serial - statusCategory = preferenceScreen.findPreference("prefcat_device_registration_status") ?: statusCategory - status = preferenceScreen.findPreference("pref_device_registration_status") ?: status - androidId = preferenceScreen.findPreference("pref_device_registration_android_id") ?: androidId - - deviceProfile.setOnPreferenceChangeListener { _, newValue -> - ProfileManager.setProfile(requireContext(), newValue as String? ?: PROFILE_AUTO) - updateStatus() - true - } - importProfile.setOnPreferenceClickListener { - profileFileImport.launch("text/xml") - true - } - } - - private fun configureProfilePreference() { - val context = requireContext() - val configuredProfile = ProfileManager.getConfiguredProfile(context) - val autoProfileName = when (val autoProfile = ProfileManager.getAutoProfile(context)) { - PROFILE_NATIVE -> getString(R.string.profile_name_native) - PROFILE_REAL -> getString(R.string.profile_name_real) - else -> ProfileManager.getProfileName(context, autoProfile) - } - val profiles = - mutableListOf(PROFILE_AUTO, PROFILE_NATIVE, PROFILE_REAL) - val profileNames = mutableListOf(getString(R.string.profile_name_auto, autoProfileName), getString(R.string.profile_name_native), getString(R.string.profile_name_real)) - if (ProfileManager.hasProfile(context, PROFILE_SYSTEM)) { - profiles.add(PROFILE_SYSTEM) - profileNames.add(getString(R.string.profile_name_system, ProfileManager.getProfileName(context, PROFILE_SYSTEM))) - } - if (ProfileManager.hasProfile(context, PROFILE_USER)) { - profiles.add(PROFILE_USER) - profileNames.add(getString(R.string.profile_name_user, ProfileManager.getProfileName(context, PROFILE_USER))) - } - for (profile in R.xml::class.java.declaredFields.map { it.name } - .filter { it.startsWith("profile_") } - .map { it.substring(8) } - .sorted()) { - val profileName = ProfileManager.getProfileName(context, profile) - if (profileName != null) { - profiles.add(profile) - profileNames.add(profileName) - } - } - deviceProfile.entryValues = profiles.toTypedArray() - deviceProfile.entries = profileNames.toTypedArray() - deviceProfile.value = configuredProfile - deviceProfile.summary = - profiles.indexOf(configuredProfile).takeIf { it >= 0 }?.let { profileNames[it] } ?: "Unknown" - } - - override fun onResume() { - super.onResume() - updateStatus() - } - - override fun onPause() { - super.onPause() - handler.removeCallbacks(updateRunnable) - } - - private fun updateStatus() { - handler.removeCallbacks(updateRunnable) - handler.postDelayed(updateRunnable, UPDATE_INTERVAL) - val appContext = requireContext().applicationContext - lifecycleScope.launchWhenResumed { - configureProfilePreference() - serial.summary = ProfileManager.getSerial(appContext) - val serviceInfo = getCheckinServiceInfo(appContext) - statusCategory.isVisible = serviceInfo.configuration.enabled - if (serviceInfo.lastCheckin > 0) { - status.summary = getString( - R.string.checkin_last_registration, - DateUtils.getRelativeTimeSpanString(serviceInfo.lastCheckin, System.currentTimeMillis(), 0) - ) - androidId.isVisible = true - androidId.summary = serviceInfo.androidId.toString(16) - } else { - status.summary = getString(R.string.checkin_not_registered) - androidId.isVisible = false - } - } - } - - companion object { - private const val UPDATE_INTERVAL = 1000L - } -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PreferenceSwitchBarCallback.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PreferenceSwitchBarCallback.kt deleted file mode 100755 index b66f1119..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PreferenceSwitchBarCallback.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.ui - -interface PreferenceSwitchBarCallback { - fun onChecked(newStatus: Boolean) -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt index d3cef96f..55b730f6 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt @@ -14,7 +14,7 @@ import com.mgoogle.android.gms.R import org.microg.gms.gcm.GcmPrefs import org.microg.gms.gcm.getGcmServiceInfo import org.microg.gms.gcm.setGcmServiceConfiguration -import org.microg.mgms.settings.SettingsProvider +import org.microg.gms.settings.SettingsProvider class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { private lateinit var networkMobile: ListPreference @@ -37,7 +37,7 @@ class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { (newValue as? String)?.toIntOrNull()?.let { - setGcmServiceConfiguration(requireContext(), getGcmServiceInfo(appContext).configuration.copy(mobile = it)) + setGcmServiceConfiguration(appContext, getGcmServiceInfo(appContext).configuration.copy(mobile = it)) } updateContent() } @@ -47,7 +47,7 @@ class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { (newValue as? String)?.toIntOrNull()?.let { - setGcmServiceConfiguration(appContext, getGcmServiceInfo(requireContext()).configuration.copy(wifi = it)) + setGcmServiceConfiguration(appContext, getGcmServiceInfo(appContext).configuration.copy(wifi = it)) } updateContent() } @@ -57,7 +57,7 @@ class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { (newValue as? String)?.toIntOrNull()?.let { - setGcmServiceConfiguration(appContext, getGcmServiceInfo(requireContext()).configuration.copy(roaming = it)) + setGcmServiceConfiguration(appContext, getGcmServiceInfo(appContext).configuration.copy(roaming = it)) } updateContent() } @@ -67,7 +67,7 @@ class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { (newValue as? String)?.toIntOrNull()?.let { - setGcmServiceConfiguration(appContext, getGcmServiceInfo(requireContext()).configuration.copy(other = it)) + setGcmServiceConfiguration(appContext, getGcmServiceInfo(appContext).configuration.copy(other = it)) } updateContent() } @@ -98,13 +98,13 @@ class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { private fun getSummaryString(value: Int, learnt: Int): String = when (value) { -1 -> getString(R.string.service_status_disabled_short) 0 -> getString(R.string.service_status_enabled_short) + " / " + getString(R.string.gcm_status_pref_default) + ": " + getHeartbeatString(learnt) - else -> getString(R.string.service_status_enabled_short) + " / " + getString(R.string.gcm_status_pref_manual) + ": " + getHeartbeatString(value * SettingsProvider.INTERVAL) + else -> getString(R.string.service_status_enabled_short) + " / " + getString(R.string.gcm_status_pref_manual) + ": " + getHeartbeatString(value * 60000) } private fun getHeartbeatString(heartbeatMs: Int): String { return if (heartbeatMs < 120000) { (heartbeatMs / 1000).toString() + " " + getString(R.string.gcm_status_pref_sec) - } else (heartbeatMs / SettingsProvider.INTERVAL).toString() + " " + getString(R.string.gcm_status_pref_min) + } else (heartbeatMs / 60000).toString() + " " + getString(R.string.gcm_status_pref_min) } companion object { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt index da3210e4..392e14d6 100755 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt @@ -56,16 +56,12 @@ class PushNotificationAllAppsFragment : PreferenceFragmentCompat() { lifecycleScope.launchWhenResumed { val apps = withContext(Dispatchers.IO) { val res = database.appList.map { app -> - app to context.packageManager.getApplicationInfoIfExists(app.packageName) - }.map { (app, applicationInfo) -> val pref = AppIconPreference(context) - pref.title = applicationInfo?.loadLabel(context.packageManager) ?: app.packageName + pref.packageName = app.packageName pref.summary = when { app.lastMessageTimestamp > 0 -> getString(R.string.gcm_last_message_at, DateUtils.getRelativeTimeSpanString(app.lastMessageTimestamp)) else -> null } - pref.icon = applicationInfo?.loadIcon(context.packageManager) - ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) pref.onPreferenceClickListener = Preference.OnPreferenceClickListener { findNavController().navigate(requireContext(), R.id.openGcmAppDetailsFromAll, bundleOf( "package" to app.packageName diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt index f83a5e5d..780e85e1 100755 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt @@ -5,58 +5,135 @@ package org.microg.gms.ui -import android.content.Intent -import android.net.Uri +import android.annotation.SuppressLint import android.os.Bundle -import android.provider.Settings -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.content.res.AppCompatResources -import androidx.fragment.app.Fragment +import android.text.format.DateUtils +import androidx.appcompat.app.AlertDialog import androidx.lifecycle.lifecycleScope +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.TwoStatePreference import com.mgoogle.android.gms.R -import com.mgoogle.android.gms.databinding.PushNotificationAppFragmentBinding +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.microg.gms.gcm.GcmDatabase +import org.microg.gms.gcm.PushRegisterManager +class PushNotificationAppFragment : PreferenceFragmentCompat() { + private lateinit var appHeadingPreference: AppHeadingPreference + private lateinit var wakeForDelivery: TwoStatePreference + private lateinit var allowRegister: TwoStatePreference + private lateinit var status: Preference + private lateinit var unregister: Preference + private lateinit var unregisterCat: PreferenceCategory -class PushNotificationAppFragment : Fragment(R.layout.push_notification_fragment) { - lateinit var binding: PushNotificationAppFragmentBinding - val packageName: String? + private lateinit var database: GcmDatabase + private val packageName: String? get() = arguments?.getString("package") - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - binding = PushNotificationAppFragmentBinding.inflate(inflater, container, false) - binding.callbacks = object : PushNotificationAppFragmentCallbacks { - override fun onAppClicked() { - val intent = Intent() - intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS - val uri: Uri = Uri.fromParts("package", packageName, null) - intent.data = uri - try { - context!!.startActivity(intent) - } catch (e: Exception) { - Log.w(TAG, "Failed to launch app", e) + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences_push_notifications_app) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + database = GcmDatabase(context) + } + + @SuppressLint("RestrictedApi") + override fun onBindPreferences() { + appHeadingPreference = preferenceScreen.findPreference("pref_push_app_heading") ?: appHeadingPreference + wakeForDelivery = preferenceScreen.findPreference("pref_push_app_wake_for_delivery") ?: wakeForDelivery + allowRegister = preferenceScreen.findPreference("pref_push_app_allow_register") ?: allowRegister + unregister = preferenceScreen.findPreference("pref_push_app_unregister") ?: unregister + unregisterCat = preferenceScreen.findPreference("prefcat_push_app_unregister") ?: unregisterCat + status = preferenceScreen.findPreference("pref_push_app_status") ?: status + wakeForDelivery.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + database.setAppWakeForDelivery(packageName, newValue as Boolean) + database.close() + true + } + allowRegister.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + val enabled = newValue as? Boolean ?: return@OnPreferenceChangeListener false + if (!enabled) { + val registrations = packageName?.let { database.getRegistrationsByApp(it) } ?: emptyList() + if (registrations.isNotEmpty()) { + showUnregisterConfirm(R.string.gcm_unregister_after_deny_message) } } + database.setAppAllowRegister(packageName, enabled) + database.close() + true + } + unregister.onPreferenceClickListener = Preference.OnPreferenceClickListener { + showUnregisterConfirm(R.string.gcm_unregister_confirm_message) + true + } + } + + + private fun showUnregisterConfirm(unregisterConfirmDesc: Int) { + val pm = requireContext().packageManager + val applicationInfo = pm.getApplicationInfoIfExists(packageName) + AlertDialog.Builder(requireContext()) + .setTitle(getString(R.string.gcm_unregister_confirm_title, applicationInfo?.loadLabel(pm) + ?: packageName)) + .setMessage(unregisterConfirmDesc) + .setPositiveButton(android.R.string.yes) { _, _ -> unregister() } + .setNegativeButton(android.R.string.no) { _, _ -> }.show() + } + + private fun unregister() { + lifecycleScope.launchWhenResumed { + withContext(Dispatchers.IO) { + for (registration in database.getRegistrationsByApp(packageName)) { + PushRegisterManager.unregister(context, registration.packageName, registration.signature, null, null) + } + } + updateDetails() } - childFragmentManager.findFragmentById(R.id.sub_preferences)?.arguments = arguments - return binding.root } override fun onResume() { super.onResume() - val context = requireContext() + updateDetails() + } + + private fun updateDetails() { lifecycleScope.launchWhenResumed { - val pm = context.packageManager - val applicationInfo = pm.getApplicationInfoIfExists(packageName) - binding.appName = applicationInfo?.loadLabel(pm)?.toString() ?: packageName - binding.appIcon = applicationInfo?.loadIcon(pm) - ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) + appHeadingPreference.packageName = packageName + val app = packageName?.let { database.getApp(it) } + wakeForDelivery.isChecked = app?.wakeForDelivery ?: true + allowRegister.isChecked = app?.allowRegister ?: true + val registrations = packageName?.let { database.getRegistrationsByApp(it) } ?: emptyList() + unregisterCat.isVisible = registrations.isNotEmpty() + + val sb = StringBuilder() + if ((app?.totalMessageCount ?: 0L) == 0L) { + sb.append(getString(R.string.gcm_no_message_yet)) + } else { + sb.append(getString(R.string.gcm_messages_counter, app?.totalMessageCount, app?.totalMessageBytes)) + if (app?.lastMessageTimestamp != 0L) { + sb.append("\n").append(getString(R.string.gcm_last_message_at, DateUtils.getRelativeDateTimeString(context, app?.lastMessageTimestamp ?: 0L, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME))) + } + } + for (registration in registrations) { + sb.append("\n") + if (registration.timestamp == 0L) { + sb.append(getString(R.string.gcm_registered)) + } else { + sb.append(getString(R.string.gcm_registered_since, DateUtils.getRelativeDateTimeString(context, registration.timestamp, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME))) + } + } + status.summary = sb.toString() + + database.close() } } -} -interface PushNotificationAppFragmentCallbacks { - fun onAppClicked() -} + override fun onPause() { + super.onPause() + database.close() + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppPreferencesFragment.kt deleted file mode 100755 index 7106e506..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppPreferencesFragment.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ -@file:Suppress("DEPRECATION") - -package org.microg.gms.ui - -import android.annotation.SuppressLint -import android.os.Bundle -import android.text.format.DateUtils -import androidx.appcompat.app.AlertDialog -import androidx.lifecycle.lifecycleScope -import androidx.preference.Preference -import androidx.preference.PreferenceCategory -import androidx.preference.PreferenceFragmentCompat -import androidx.preference.TwoStatePreference -import com.mgoogle.android.gms.R -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.microg.gms.gcm.GcmDatabase -import org.microg.gms.gcm.PushRegisterManager - -class PushNotificationAppPreferencesFragment : PreferenceFragmentCompat() { - private lateinit var wakeForDelivery: TwoStatePreference - private lateinit var allowRegister: TwoStatePreference - private lateinit var status: Preference - private lateinit var unregister: Preference - private lateinit var unregisterCat: PreferenceCategory - - private lateinit var database: GcmDatabase - private val packageName: String? - get() = arguments?.getString("package") - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.preferences_push_notifications_app) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - database = GcmDatabase(context) - } - - @SuppressLint("RestrictedApi") - override fun onBindPreferences() { - wakeForDelivery = preferenceScreen.findPreference("pref_push_app_wake_for_delivery") ?: wakeForDelivery - allowRegister = preferenceScreen.findPreference("pref_push_app_allow_register") ?: allowRegister - unregister = preferenceScreen.findPreference("pref_push_app_unregister") ?: unregister - unregisterCat = preferenceScreen.findPreference("prefcat_push_app_unregister") ?: unregisterCat - status = preferenceScreen.findPreference("pref_push_app_status") ?: status - wakeForDelivery.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - database.setAppWakeForDelivery(packageName, newValue as Boolean) - database.close() - true - } - allowRegister.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - val enabled = newValue as? Boolean ?: return@OnPreferenceChangeListener false - if (!enabled) { - val registrations = packageName?.let { database.getRegistrationsByApp(it) } ?: emptyList() - if (registrations.isNotEmpty()) { - showUnregisterConfirm(R.string.gcm_unregister_after_deny_message) - } - } - database.setAppAllowRegister(packageName, enabled) - database.close() - true - } - unregister.onPreferenceClickListener = Preference.OnPreferenceClickListener { - showUnregisterConfirm(R.string.gcm_unregister_confirm_message) - true - } - } - - - private fun showUnregisterConfirm(unregisterConfirmDesc: Int) { - val pm = requireContext().packageManager - val applicationInfo = pm.getApplicationInfoIfExists(packageName) - AlertDialog.Builder(requireContext()) - .setTitle(getString(R.string.gcm_unregister_confirm_title, applicationInfo?.loadLabel(pm) - ?: packageName)) - .setMessage(unregisterConfirmDesc) - .setPositiveButton(android.R.string.yes) { _, _ -> unregister() } - .setNegativeButton(android.R.string.no) { _, _ -> }.show() - } - - private fun unregister() { - lifecycleScope.launchWhenResumed { - withContext(Dispatchers.IO) { - for (registration in database.getRegistrationsByApp(packageName)) { - PushRegisterManager.unregister(context, registration.packageName, registration.signature, null, null) - } - } - updateDetails() - } - } - - override fun onResume() { - super.onResume() - updateDetails() - } - - private fun updateDetails() { - lifecycleScope.launchWhenResumed { - val app = packageName?.let { database.getApp(it) } - wakeForDelivery.isChecked = app?.wakeForDelivery ?: true - allowRegister.isChecked = app?.allowRegister ?: true - val registrations = packageName?.let { database.getRegistrationsByApp(it) } ?: emptyList() - unregisterCat.isVisible = registrations.isNotEmpty() - - val sb = StringBuilder() - if ((app?.totalMessageCount ?: 0L) == 0L) { - sb.append(getString(R.string.gcm_no_message_yet)) - } else { - sb.append(getString(R.string.gcm_messages_counter, app?.totalMessageCount, app?.totalMessageBytes)) - if (app?.lastMessageTimestamp != 0L) { - sb.append("\n").append(getString(R.string.gcm_last_message_at, DateUtils.getRelativeDateTimeString(context, app?.lastMessageTimestamp ?: 0L, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME))) - } - } - for (registration in registrations) { - sb.append("\n") - if (registration.timestamp == 0L) { - sb.append(getString(R.string.gcm_registered)) - } else { - sb.append(getString(R.string.gcm_registered_since, DateUtils.getRelativeDateTimeString(context, registration.timestamp, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME))) - } - } - status.summary = sb.toString() - - database.close() - } - } - - override fun onPause() { - super.onPause() - database.close() - } -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt index cb5aaa18..d05afb3d 100755 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt @@ -2,61 +2,142 @@ * SPDX-FileCopyrightText: 2020, microG Project Team * SPDX-License-Identifier: Apache-2.0 */ + package org.microg.gms.ui +import android.annotation.SuppressLint import android.os.Bundle -import android.view.* -import androidx.fragment.app.Fragment +import android.os.Handler +import android.text.format.DateUtils +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import androidx.core.os.bundleOf import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat import com.mgoogle.android.gms.R -import com.mgoogle.android.gms.databinding.PushNotificationFragmentBinding -import org.microg.gms.checkin.getCheckinServiceInfo -import org.microg.gms.gcm.ServiceInfo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.microg.gms.checkin.CheckinPreferences +import org.microg.gms.gcm.GcmDatabase +import org.microg.gms.gcm.GcmPrefs import org.microg.gms.gcm.getGcmServiceInfo -import org.microg.gms.gcm.setGcmServiceConfiguration -@Suppress("Warnings") -class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) { - lateinit var binding: PushNotificationFragmentBinding +class PushNotificationFragment : PreferenceFragmentCompat() { + private lateinit var switchBarPreference: SwitchBarPreference + private lateinit var pushStatusCategory: PreferenceCategory + private lateinit var pushStatus: Preference + private lateinit var pushApps: PreferenceCategory + private lateinit var pushAppsAll: Preference + private lateinit var pushAppsNone: Preference + private lateinit var database: GcmDatabase + private val handler = Handler() + private val updateRunnable = Runnable { updateStatus() } - init { - setHasOptionsMenu(true) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + database = GcmDatabase(context) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - binding = PushNotificationFragmentBinding.inflate(inflater, container, false) - binding.switchBarCallback = object : PreferenceSwitchBarCallback { - override fun onChecked(newStatus: Boolean) { - setEnabled(newStatus) - } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences_push_notifications) + } + + @SuppressLint("RestrictedApi") + override fun onBindPreferences() { + switchBarPreference = preferenceScreen.findPreference("pref_push_enabled") ?: switchBarPreference + pushStatusCategory = preferenceScreen.findPreference("prefcat_push_status") ?: pushStatusCategory + pushStatus = preferenceScreen.findPreference("pref_push_status") ?: pushStatus + pushApps = preferenceScreen.findPreference("prefcat_push_apps") ?: pushApps + pushAppsAll = preferenceScreen.findPreference("pref_push_apps_all") ?: pushAppsAll + pushAppsNone = preferenceScreen.findPreference("pref_push_apps_none") ?: pushAppsNone + pushAppsAll.setOnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openAllGcmApps) + true } - return binding.root - } - - fun setEnabled(newStatus: Boolean) { - val appContext = requireContext().applicationContext - lifecycleScope.launchWhenResumed { - val info = getGcmServiceInfo(appContext) - val newConfiguration = info.configuration.copy(enabled = newStatus) - setGcmServiceConfiguration(appContext, newConfiguration) - displayServiceInfo(info.copy(configuration = newConfiguration)) + switchBarPreference.setOnPreferenceChangeListener { _, newValue -> + val newStatus = newValue as Boolean + GcmPrefs.setEnabled(requireContext(), newStatus) + true } } - private fun displayServiceInfo(serviceInfo: ServiceInfo) { - binding.gcmEnabled = serviceInfo.configuration.enabled - } - override fun onResume() { super.onResume() + + switchBarPreference.isEnabled = CheckinPreferences.isEnabled(requireContext()) + switchBarPreference.isChecked = GcmPrefs.get(requireContext()).isEnabled + + updateStatus() + updateContent() + } + + override fun onPause() { + super.onPause() + database.close() + handler.removeCallbacks(updateRunnable) + } + + private fun updateStatus() { + handler.postDelayed(updateRunnable, UPDATE_INTERVAL) val appContext = requireContext().applicationContext - lifecycleScope.launchWhenResumed { - displayServiceInfo(getGcmServiceInfo(appContext)) - binding.checkinEnabled = getCheckinServiceInfo(appContext).configuration.enabled + lifecycleScope.launchWhenStarted { + val statusInfo = getGcmServiceInfo(appContext) + switchBarPreference.isChecked = statusInfo.configuration.enabled + pushStatusCategory.isVisible = statusInfo != null && statusInfo.configuration.enabled + pushStatus.summary = if (statusInfo != null && statusInfo.connected) { + appContext.getString(R.string.gcm_network_state_connected, DateUtils.getRelativeTimeSpanString(statusInfo.startTimestamp, System.currentTimeMillis(), 0)) + } else { + appContext.getString(R.string.gcm_network_state_disconnected) + } } } + private fun updateContent() { + lifecycleScope.launchWhenResumed { + val context = requireContext() + val (apps, showAll) = withContext(Dispatchers.IO) { + val apps = database.appList.sortedByDescending { it.lastMessageTimestamp } + val res = apps.map { app -> + app to context.packageManager.getApplicationInfoIfExists(app.packageName) + }.mapNotNull { (app, info) -> + if (info == null) null else app to info + }.take(3).mapIndexed { idx, (app, applicationInfo) -> + val pref = AppIconPreference(context) + pref.order = idx + pref.applicationInfo = applicationInfo + pref.onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openGcmAppDetails, bundleOf( + "package" to app.packageName + )) + true + } + pref.key = "pref_push_app_" + app.packageName + pref + }.let { it to (it.size < apps.size) } + database.close() + res + } + pushAppsAll.isVisible = showAll + pushApps.removeAll() + for (app in apps) { + pushApps.addPreference(app) + } + if (showAll) { + pushApps.addPreference(pushAppsAll) + } else if (apps.isEmpty()) { + pushApps.addPreference(pushAppsNone) + } + } + } + + init { + setHasOptionsMenu(true) + } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { menu.add(0, MENU_ADVANCED, 0, R.string.menu_advanced) super.onCreateOptionsMenu(menu, inflater) @@ -73,7 +154,7 @@ class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) { } companion object { + private const val UPDATE_INTERVAL = 1000L private const val MENU_ADVANCED = Menu.FIRST } -} - +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt deleted file mode 100755 index ef027a84..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ -@file:Suppress("DEPRECATION") - -package org.microg.gms.ui - -import android.annotation.SuppressLint -import android.os.Bundle -import android.os.Handler -import android.text.format.DateUtils -import androidx.core.os.bundleOf -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import androidx.preference.Preference -import androidx.preference.PreferenceCategory -import androidx.preference.PreferenceFragmentCompat -import com.mgoogle.android.gms.R -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.microg.gms.gcm.GcmDatabase -import org.microg.gms.gcm.getGcmServiceInfo - -class PushNotificationPreferencesFragment : PreferenceFragmentCompat() { - private lateinit var pushStatusCategory: PreferenceCategory - private lateinit var pushStatus: Preference - private lateinit var pushApps: PreferenceCategory - private lateinit var pushAppsAll: Preference - private lateinit var pushAppsNone: Preference - private lateinit var database: GcmDatabase - private val handler = Handler() - private val updateRunnable = Runnable { updateStatus() } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - database = GcmDatabase(context) - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.preferences_push_notifications) - } - - @SuppressLint("RestrictedApi") - override fun onBindPreferences() { - pushStatusCategory = preferenceScreen.findPreference("prefcat_push_status") ?: pushStatusCategory - pushStatus = preferenceScreen.findPreference("pref_push_status") ?: pushStatus - pushApps = preferenceScreen.findPreference("prefcat_push_apps") ?: pushApps - pushAppsAll = preferenceScreen.findPreference("pref_push_apps_all") ?: pushAppsAll - pushAppsNone = preferenceScreen.findPreference("pref_push_apps_none") ?: pushAppsNone - pushAppsAll.onPreferenceClickListener = Preference.OnPreferenceClickListener { - findNavController().navigate(requireContext(), R.id.openAllGcmApps) - true - } - } - - override fun onResume() { - super.onResume() - updateStatus() - updateContent() - } - - override fun onPause() { - super.onPause() - database.close() - handler.removeCallbacks(updateRunnable) - } - - private fun updateStatus() { - try { - handler.postDelayed(updateRunnable, UPDATE_INTERVAL) - val appContext = requireContext().applicationContext - lifecycleScope.launchWhenStarted { - val statusInfo = getGcmServiceInfo(appContext) - pushStatusCategory.isVisible = statusInfo.configuration.enabled - pushStatus.summary = if (statusInfo.connected) { - appContext.getString(R.string.gcm_network_state_connected, DateUtils.getRelativeTimeSpanString(statusInfo.startTimestamp, System.currentTimeMillis(), 0)) - } else { - appContext.getString(R.string.gcm_network_state_disconnected) - } - } - } catch (_: Exception) {} - } - - private fun updateContent() { - val appContext = requireContext().applicationContext - val context = requireContext() - lifecycleScope.launchWhenResumed { - val (apps, showAll) = withContext(Dispatchers.IO) { - val apps = database.appList.sortedByDescending { it.lastMessageTimestamp } - val res = apps.map { app -> - app to appContext.packageManager.getApplicationInfoIfExists(app.packageName) - }.mapNotNull { (app, info) -> - if (info == null) null else app to info - }.take(3).mapIndexed { idx, (app, applicationInfo) -> - val pref = AppIconPreference(appContext) - pref.order = idx - pref.title = applicationInfo.loadLabel(appContext.packageManager) - pref.icon = applicationInfo.loadIcon(appContext.packageManager) - pref.onPreferenceClickListener = Preference.OnPreferenceClickListener { - findNavController().navigate(context, R.id.openGcmAppDetails, bundleOf( - "package" to app.packageName - )) - true - } - pref.key = "pref_push_app_" + app.packageName - pref - }.let { it to (it.size < apps.size) } - database.close() - res - } - pushAppsAll.isVisible = showAll - pushApps.removeAll() - for (app in apps) { - pushApps.addPreference(app) - } - if (showAll) { - pushApps.addPreference(pushAppsAll) - } else if (apps.isEmpty()) { - pushApps.addPreference(pushAppsNone) - } - } - } - - companion object { - private const val UPDATE_INTERVAL = 1000L - } -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt index 8d503a68..e7f83c28 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt @@ -8,21 +8,25 @@ package org.microg.gms.ui import android.content.ComponentName -import android.content.Context import android.content.pm.PackageManager import android.os.Bundle +import android.util.Log import androidx.preference.Preference +import androidx.preference.PreferenceCategory import androidx.preference.SwitchPreferenceCompat import com.mgoogle.android.gms.R -import org.microg.gms.checkin.CheckinPrefs +import org.microg.gms.checkin.CheckinPreferences import org.microg.gms.gcm.GcmDatabase -import org.microg.gms.gcm.getGcmServiceInfo -import org.microg.mgms.settings.SettingsContract +import org.microg.gms.gcm.GcmPrefs +import org.microg.gms.settings.SettingsContract +import org.microg.gms.ui.settings.SettingsProvider +import org.microg.gms.ui.settings.getAllSettingsProviders import org.microg.tools.ui.ResourceSettingsFragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController class SettingsFragment : ResourceSettingsFragment() { + private val createdPreferences = mutableListOf() override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { super.onCreatePreferences(savedInstanceState, rootKey) @@ -35,11 +39,13 @@ class SettingsFragment : ResourceSettingsFragment() { findNavController().navigate(requireContext(), R.id.openGcmSettings) true } - findPreference(PREF_ABOUT)?.setOnPreferenceClickListener { - findNavController().navigate(requireContext(), R.id.openAbout) - true + findPreference(PREF_ABOUT)!!.apply { + onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openAbout) + true + } + summary = getString(org.microg.tools.ui.R.string.about_version_str, AboutFragment.getSelfVersion(context)) } - findPreference(PREF_ABOUT)?.summary = getString(R.string.about_version_str, AboutFragment.getSelfVersion(context)) findPreference(SettingsContract.CheckIn.HIDE_LAUNCHER_ICON)?.apply { setOnPreferenceChangeListener { _, newValue -> @@ -48,19 +54,51 @@ class SettingsFragment : ResourceSettingsFragment() { } } + + for (entry in getAllSettingsProviders(requireContext()).flatMap { it.getEntriesStatic(requireContext()) }) { + entry.createPreference() + } + } + + private fun SettingsProvider.Companion.Entry.createPreference(): Preference? { + val preference = Preference(requireContext()).fillFromEntry(this) + try { + if (findPreference(when (group) { + SettingsProvider.Companion.Group.HEADER -> "prefcat_header" + SettingsProvider.Companion.Group.GOOGLE -> "prefcat_google_services" + SettingsProvider.Companion.Group.OTHER -> "prefcat_other_services" + SettingsProvider.Companion.Group.FOOTER -> "prefcat_footer" + })?.addPreference(preference) == true) { + createdPreferences.add(preference) + return preference + } else { + Log.w(TAG, "Preference not added $key") + } + } catch (e: Exception) { + Log.w(TAG, "Failed adding preference $key", e) + } + return null + } + + private fun Preference.fillFromEntry(entry: SettingsProvider.Companion.Entry): Preference { + key = entry.key + title = entry.title + summary = entry.summary + icon = entry.icon + isPersistent = false + isVisible = true + setOnPreferenceClickListener { + findNavController().navigate(requireContext(), entry.navigationId) + true + } + return this } override fun onResume() { super.onResume() val context = requireContext() - lifecycleScope.launchWhenResumed { - updateDetails(context) - } - } - private suspend fun updateDetails(context: Context) { - val gcmServiceInfo = getGcmServiceInfo(context) - if (gcmServiceInfo.configuration.enabled) { + if (GcmPrefs.get(requireContext()).isEnabled) { val database = GcmDatabase(context) val regCount = database.registrationList.size database.close() @@ -69,13 +107,26 @@ class SettingsFragment : ResourceSettingsFragment() { findPreference(PREF_GCM)?.setSummary(R.string.service_status_disabled_short) } - findPreference(PREF_CHECKIN)?.setSummary(if (CheckinPrefs.isEnabled(context) == true) R.string.service_status_enabled_short else R.string.service_status_disabled_short) + findPreference(PREF_CHECKIN)?.setSummary(if (CheckinPreferences.isEnabled(context)) R.string.service_status_enabled_short else R.string.service_status_disabled_short) + + lifecycleScope.launchWhenResumed { + val entries = getAllSettingsProviders(requireContext()).flatMap { it.getEntriesDynamic(requireContext()) } + for (preference in createdPreferences) { + if (!entries.any { it.key == preference.key }) preference.isVisible = false + } + for (entry in entries) { + val preference = createdPreferences.find { it.key == entry.key } + if (preference != null) preference.fillFromEntry(entry) + else entry.createPreference() + } + } } companion object { const val PREF_ABOUT = "pref_about" const val PREF_GCM = "pref_gcm" const val PREF_CHECKIN = "pref_checkin" + const val PREF_PLAY = "pref_play" } init { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SwitchBarPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SwitchBarPreference.kt new file mode 100644 index 00000000..12802a1e --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SwitchBarPreference.kt @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.ui + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.SwitchCompat +import androidx.preference.PreferenceViewHolder +import androidx.preference.TwoStatePreference +import org.microg.gms.base.core.R + +// TODO +class SwitchBarPreference : TwoStatePreference { + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context) : super(context) + + init { + layoutResource = R.layout.preference_switch_bar + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + holder.isDividerAllowedBelow = false + holder.isDividerAllowedAbove = false + val switch = holder.findViewById(R.id.switch_widget) as SwitchCompat + switch.setOnCheckedChangeListener(null) + switch.isChecked = isChecked + switch.setOnCheckedChangeListener { view, isChecked -> + if (!callChangeListener(isChecked)) { + view.isChecked = !isChecked + return@setOnCheckedChangeListener + } + this.isChecked = isChecked + } + holder.itemView.setBackgroundColor(when { + isChecked -> androidx.appcompat.R.attr.colorControlActivated + isEnabled -> androidx.appcompat.R.attr.colorButtonNormal + else -> androidx.appcompat.R.attr.colorControlHighlight + }) + } +} + +@Deprecated("Get rid") +interface PreferenceSwitchBarCallback { + fun onChecked(newStatus: Boolean) +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/TextPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/TextPreference.kt index 956386a0..a718b876 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/TextPreference.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/TextPreference.kt @@ -7,6 +7,7 @@ package org.microg.gms.ui import android.content.Context import android.util.AttributeSet +import android.util.TypedValue import android.view.Gravity import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.LinearLayout @@ -15,8 +16,7 @@ import androidx.preference.Preference import androidx.preference.PreferenceViewHolder class TextPreference : Preference { - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super( - context, attrs, defStyleAttr, defStyleRes) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context) : super(context) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/Utils.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/Utils.kt index 998770e3..b59142d3 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/Utils.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/Utils.kt @@ -15,6 +15,7 @@ import android.os.Bundle import android.provider.Settings import android.util.Log import androidx.annotation.IdRes +import androidx.annotation.RequiresApi import androidx.navigation.NavController import androidx.navigation.navOptions import androidx.navigation.ui.R @@ -41,12 +42,12 @@ fun NavController.navigate(context: Context, @IdRes resId: Int, args: Bundle? = fun Context.hideIcon(hide: Boolean) { packageManager.setComponentEnabledSetting( - ComponentName.createRelative(this, "org.microg.gms.ui.SettingsActivityLauncher"), - when (hide) { - true -> PackageManager.COMPONENT_ENABLED_STATE_DISABLED - false -> PackageManager.COMPONENT_ENABLED_STATE_ENABLED - }, - PackageManager.DONT_KILL_APP + ComponentName.createRelative(this, "org.microg.gms.ui.SettingsActivityLauncher"), + when (hide) { + true -> PackageManager.COMPONENT_ENABLED_STATE_DISABLED + false -> PackageManager.COMPONENT_ENABLED_STATE_ENABLED + }, + PackageManager.DONT_KILL_APP ) } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/settings/SettingsProvider.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/settings/SettingsProvider.kt new file mode 100644 index 00000000..ecebb042 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/settings/SettingsProvider.kt @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.ui.settings + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.util.Log +import androidx.navigation.NavController + +private const val TAG = "SettingsProvider" + +interface SettingsProvider { + fun getEntriesStatic(context: Context): List + suspend fun getEntriesDynamic(context: Context): List = getEntriesStatic(context) + + fun preProcessSettingsIntent(intent: Intent) + + fun extendNavigation(navController: NavController) + + companion object { + enum class Group { + HEADER, + GOOGLE, + OTHER, + FOOTER + } + + data class Entry( + val key: String, + val group: Group, + val navigationId: Int, + val title: String, + val summary: String? = null, + val icon: Drawable? = null, + ) + } +} + +fun getAllSettingsProviders(context: Context): List { + val metaData = runCatching { context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA).metaData }.getOrNull() ?: Bundle.EMPTY + return metaData.keySet().asSequence().filter { + it.startsWith("org.microg.gms.ui.settings.entry:") + }.mapNotNull { + runCatching { metaData.getString(it) }.onFailure { Log.w(TAG, it) }.getOrNull() + }.mapNotNull { + runCatching { Class.forName(it) }.onFailure { Log.w(TAG, it) }.getOrNull() + }.filter { + SettingsProvider::class.java.isAssignableFrom(it) + }.mapNotNull { + runCatching { it.getDeclaredField("INSTANCE").get(null) as SettingsProvider }.onFailure { Log.w(TAG, it) }.getOrNull() + }.toList() +} \ No newline at end of file diff --git a/play-services-core/src/main/res/color/preferences_material_switch_color.xml b/play-services-core/src/main/res/color/preferences_material_switch_color.xml new file mode 100644 index 00000000..1a6c9037 --- /dev/null +++ b/play-services-core/src/main/res/color/preferences_material_switch_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/drawable/dots_horizontal.xml b/play-services-core/src/main/res/drawable/dots_horizontal.xml index 6e0db150..6278c1cd 100755 --- a/play-services-core/src/main/res/drawable/dots_horizontal.xml +++ b/play-services-core/src/main/res/drawable/dots_horizontal.xml @@ -1,7 +1,7 @@ + + + + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/drawable/ic_play.xml b/play-services-core/src/main/res/drawable/ic_play.xml new file mode 100644 index 00000000..9db10ec6 --- /dev/null +++ b/play-services-core/src/main/res/drawable/ic_play.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/layout/device_registration_fragment.xml b/play-services-core/src/main/res/layout/device_registration_fragment.xml deleted file mode 100755 index fe6d1e51..00000000 --- a/play-services-core/src/main/res/layout/device_registration_fragment.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/play-services-core/src/main/res/layout/preference_app_heading.xml b/play-services-core/src/main/res/layout/preference_app_heading.xml new file mode 100644 index 00000000..cb7e4edf --- /dev/null +++ b/play-services-core/src/main/res/layout/preference_app_heading.xml @@ -0,0 +1,43 @@ + + + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/layout/preference_material_switch.xml b/play-services-core/src/main/res/layout/preference_material_switch.xml new file mode 100644 index 00000000..071caab2 --- /dev/null +++ b/play-services-core/src/main/res/layout/preference_material_switch.xml @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/layout/preference_switch_bar.xml b/play-services-core/src/main/res/layout/preference_switch_bar.xml index d45909cd..9818305b 100755 --- a/play-services-core/src/main/res/layout/preference_switch_bar.xml +++ b/play-services-core/src/main/res/layout/preference_switch_bar.xml @@ -1,71 +1,42 @@ - - - + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:gravity="center" + android:orientation="horizontal" + android:paddingStart="?attr/listPreferredItemPaddingStart" + android:paddingLeft="?attr/listPreferredItemPaddingLeft" + android:paddingEnd="?attr/listPreferredItemPaddingEnd" + android:paddingRight="?attr/listPreferredItemPaddingRight" + tools:background="?attr/colorControlActivated"> - + - - - - - - - - - - - - - - - - + app:thumbTint="?android:attr/textColorPrimaryInverse" + app:trackTint="?android:attr/textColorSecondaryInverse" + tools:checked="true" /> + \ No newline at end of file diff --git a/play-services-core/src/main/res/layout/push_notification_app_fragment.xml b/play-services-core/src/main/res/layout/push_notification_app_fragment.xml deleted file mode 100755 index dcc6e7ad..00000000 --- a/play-services-core/src/main/res/layout/push_notification_app_fragment.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/play-services-core/src/main/res/layout/push_notification_fragment.xml b/play-services-core/src/main/res/layout/push_notification_fragment.xml deleted file mode 100755 index fdd799bd..00000000 --- a/play-services-core/src/main/res/layout/push_notification_fragment.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/play-services-core/src/main/res/layout/settings_root_activity.xml b/play-services-core/src/main/res/layout/settings_root_activity.xml index d854b3a3..18ea2241 100755 --- a/play-services-core/src/main/res/layout/settings_root_activity.xml +++ b/play-services-core/src/main/res/layout/settings_root_activity.xml @@ -9,6 +9,13 @@ android:layout_height="match_parent" android:orientation="vertical"> + + To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-az/strings.xml b/play-services-core/src/main/res/values-az/strings.xml index 7aad8a9d..740a9b11 100644 --- a/play-services-core/src/main/res/values-az/strings.xml +++ b/play-services-core/src/main/res/values-az/strings.xml @@ -119,11 +119,23 @@ Bu bir neçə dəqiqə çəkə bilər." To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-be/strings.xml b/play-services-core/src/main/res/values-be/strings.xml index 1340f35e..1152301e 100644 --- a/play-services-core/src/main/res/values-be/strings.xml +++ b/play-services-core/src/main/res/values-be/strings.xml @@ -119,11 +119,23 @@ To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-bn/strings.xml b/play-services-core/src/main/res/values-bn/strings.xml index 8451c3b4..0156013d 100644 --- a/play-services-core/src/main/res/values-bn/strings.xml +++ b/play-services-core/src/main/res/values-bn/strings.xml @@ -120,11 +120,23 @@ To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-cs/strings.xml b/play-services-core/src/main/res/values-cs/strings.xml index 7b597075..7e021dd8 100644 --- a/play-services-core/src/main/res/values-cs/strings.xml +++ b/play-services-core/src/main/res/values-cs/strings.xml @@ -116,11 +116,23 @@ Tato akce může trvat několik minut." To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-es-rES/strings.xml b/play-services-core/src/main/res/values-es-rES/strings.xml index be3f6cd9..5b090832 100644 --- a/play-services-core/src/main/res/values-es-rES/strings.xml +++ b/play-services-core/src/main/res/values-es-rES/strings.xml @@ -119,11 +119,23 @@ Esto puede tardar unos minutos." To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-fr/strings.xml b/play-services-core/src/main/res/values-fr/strings.xml index 95cb22f8..0fcac240 100644 --- a/play-services-core/src/main/res/values-fr/strings.xml +++ b/play-services-core/src/main/res/values-fr/strings.xml @@ -119,11 +119,23 @@ Cela peut prendre quelques minutes." To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-he/strings.xml b/play-services-core/src/main/res/values-he/strings.xml index 097a7849..95511eb1 100644 --- a/play-services-core/src/main/res/values-he/strings.xml +++ b/play-services-core/src/main/res/values-he/strings.xml @@ -66,11 +66,23 @@ To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-hi/strings.xml b/play-services-core/src/main/res/values-hi/strings.xml index aad5f099..fdf8ea5a 100644 --- a/play-services-core/src/main/res/values-hi/strings.xml +++ b/play-services-core/src/main/res/values-hi/strings.xml @@ -118,11 +118,23 @@ To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-id/strings.xml b/play-services-core/src/main/res/values-id/strings.xml index 2b8b2698..ac8b9869 100644 --- a/play-services-core/src/main/res/values-id/strings.xml +++ b/play-services-core/src/main/res/values-id/strings.xml @@ -117,11 +117,23 @@ Ini bisa berlangsung beberapa menit." To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-it/strings.xml b/play-services-core/src/main/res/values-it/strings.xml index bac9f6a9..07277b42 100755 --- a/play-services-core/src/main/res/values-it/strings.xml +++ b/play-services-core/src/main/res/values-it/strings.xml @@ -116,11 +116,23 @@ Questo potrà richiedere un paio di minuti" To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-ja/strings.xml b/play-services-core/src/main/res/values-ja/strings.xml index ce8a6c5c..0d93256f 100644 --- a/play-services-core/src/main/res/values-ja/strings.xml +++ b/play-services-core/src/main/res/values-ja/strings.xml @@ -116,14 +116,26 @@ シリアル ソースコードを閲覧 GitHub でプロジェクトのソースコードを表示します - To remove signed in Google account, go to Device Settings -> Account. - Check for Vanced+ microG updates on GitHub - Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. - Allow connecting to Google servers - Requires Orbot to be installed\nNot yet supported - Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) - Build date + Google アカウントからログアウトしたい場合は、端末の設定 → アカウント に移動して該当するアカウントを選択し削除します + GitHub で Vanced+ microG の更新を確認する + 更新を確認 + 無効にすると、本来 Vanced+ microG によって行われる Google サーバーへのすべての接続が拒否されます。\nこれはサービス固有の設定を無視します。 + Google サーバーへの接続を許可する + Orbot がインストールされている必要があります。\nまだ実装されていません + Tor ネットワーク経由で Google に接続する + ビルド日時 + YouTube上のデータを閲覧 + YouTube のアクティビティを確認、削除できます。YouTube の利便性を向上させるために YouTube やその他の Google サービスのデータがどのように使用されているかを確認できます + Vanced+ microG で追加したGoogleアカウントはどうすれば削除できますか? + 端末の設定 → アカウント に移動して該当するアカウントを選択し削除します + GitLab で Vanced+ microG の更新を確認する + GitLab でプロジェクトのソースコードを表示します + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-ko/strings.xml b/play-services-core/src/main/res/values-ko/strings.xml index 1520b094..fa18f6d5 100644 --- a/play-services-core/src/main/res/values-ko/strings.xml +++ b/play-services-core/src/main/res/values-ko/strings.xml @@ -119,11 +119,23 @@ To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-lt/strings.xml b/play-services-core/src/main/res/values-lt/strings.xml index a3cd8ca1..bdf153d4 100644 --- a/play-services-core/src/main/res/values-lt/strings.xml +++ b/play-services-core/src/main/res/values-lt/strings.xml @@ -119,11 +119,23 @@ Tai gali užtrukti keletą minučių." To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-night-v23/themes.xml b/play-services-core/src/main/res/values-night-v23/themes.xml new file mode 100644 index 00000000..6ed526b7 --- /dev/null +++ b/play-services-core/src/main/res/values-night-v23/themes.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-night-v27/themes.xml b/play-services-core/src/main/res/values-night-v27/themes.xml new file mode 100644 index 00000000..0b22714a --- /dev/null +++ b/play-services-core/src/main/res/values-night-v27/themes.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-night-v31/themes.xml b/play-services-core/src/main/res/values-night-v31/themes.xml index de5386e8..7f89bd04 100644 --- a/play-services-core/src/main/res/values-night-v31/themes.xml +++ b/play-services-core/src/main/res/values-night-v31/themes.xml @@ -1,14 +1,11 @@ + - - + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-pt-rBR/plurals.xml b/play-services-core/src/main/res/values-pt-rBR/plurals.xml index 35222efa..ab9eb731 100644 --- a/play-services-core/src/main/res/values-pt-rBR/plurals.xml +++ b/play-services-core/src/main/res/values-pt-rBR/plurals.xml @@ -19,7 +19,7 @@ %1$d aplicativo registrado - %1$d aplicativo registrado + %1$d aplicativos registrados diff --git a/play-services-core/src/main/res/values-pt-rBR/strings.xml b/play-services-core/src/main/res/values-pt-rBR/strings.xml index 6497c7f1..1120864e 100644 --- a/play-services-core/src/main/res/values-pt-rBR/strings.xml +++ b/play-services-core/src/main/res/values-pt-rBR/strings.xml @@ -15,15 +15,16 @@ ~ limitations under the License. --> - Configuração do Vanece microG. + Vanced+ microG + Configurações do Vanced+ microG. Um momento… Gerenciador de Contas do Google Desculpe… "Um aplicativo em seu dispositivo está tentando entrar em uma conta do Google. -Se isso foi intencional, use o Login (ou Huawei, se você tiver um dispositivo com esta marca) para se conectar à página de login do Google, se não, pressione Cancelar para voltar ao aplicativo que fez com que este diálogo aparecesse." - Iniciar sessão - "Estabelecendo uma conexão com os servidores do Google para fazer ‘login’. +Se isso foi intencional, use o botão Entrar (ou Huawei, se você tiver um dispositivo com esta marca) para se conectar à página de login do Google." + Entrar + "Estabelecendo uma conexão com os servidores do Google para entrar em sua conta. Isso pode levar alguns segundos." "Você não tem uma conexão de rede. @@ -35,51 +36,66 @@ Tente novamente mais tarde." "Seu dispositivo está entrando em contato com o Google para salvar informações em sua conta. Isso pode demorar alguns minutos." + Ouvir transmissões de estado interno Escutar mensagens C2DM Enviar mensagens C2DM para outros aplicativos Trocar mensagens e receber notificações de sincronização de servidores do Google Acesso estendido aos serviços do Google - Google Account Manager not fully implemented - Manage your Google account - Manage your Google account information - Manage YouTube activities - Manage YouTube activities such as watch or search history + Gerenciador de Contas do Google não foi totalmente implementado + Gerenciar sua conta do Google + Gerenciar as informações da sua conta do Google + Gerenciar atividades no YouTube + Gerenciar atividades do YouTube, como histórico de exibição ou pesquisa + Registro de dispositivos do Google Cloud Messaging + Ligado Desligado + Avançado Nenhum Veja tudo + Rede móvel Wi-Fi Roaming Outras redes + + Sobre Serviços do Google Mais + Não registrado Último registro: %1$s + Preferências da conta Informações pessoais & privacidade Entrar & segurança + Confiar permissões de aplicativos no Google Quando desativado, o usuário é solicitado antes que uma solicitação de autorização de aplicativos seja enviada ao Google. Alguns aplicativos não usarão a conta do Google se isso for desativado. Permitir que aplicativos encontrem contas Quando ativado, todos os aplicativos deste dispositivo poderão ver o endereço de e-mail de suas contas do Google sem autorização prévia. + Conta Adicionar conta do Google Receba notificações push Cloud Messaging é um provedor de notificação push usado por muitos aplicativos de terceiros. Para usá-lo, você deve ativar ao registro do dispositivo. + Sobre o Vanced+ microG Ocultar ícone no menu + Huawei + Registar dispositivo Registre seu dispositivo nos serviços do Google e cria um identificador de dispositivo exclusivo. Vanced+ microG identifica e tiras bits que não sejam o nome da sua conta do Google a partir de dados de registro. ID do Android Status + Padrão Manual segundos @@ -95,6 +111,7 @@ Isso pode demorar alguns minutos." Mensagens: %1$d (%2$d bytes) Desconectado Conectado à %1$s + Permitir registro Permita que o aplicativo se registre para notificações push. Iniciar aplicativo na mensagem push @@ -104,6 +121,7 @@ Isso pode demorar alguns minutos." Aplicativos registrados Aplicativos não registrados Redes para usar em notificações push + Perfil do dispositivo Selecionar perfil Nativo @@ -114,16 +132,28 @@ Isso pode demorar alguns minutos." Importar perfil personalizado Importar perfil do dispositivo a partir do arquivo Serial - View source code - View project source code on GitHub - To remove signed in Google account, go to Device Settings -> Account. - Check for Vanced+ microG updates on GitHub - Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. - Allow connecting to Google servers - Requires Orbot to be installed\nNot yet supported - Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) - Build date + Ver código fonte + Veja o código fonte do projeto no GitHub + Para remover a conta do Google conectada, vá para Configurações do dispositivo -> Conta. + Verifique se há atualizações do Vanced+ microG no GitHub + Verifique se há atualizações + Se desativado, todas as conexões aos servidores do Google normalmente feitas pelo Vanced+ microG serão negadas. Isso substitui as configurações específicas do serviço. + Permitir conexão com servidores do Google + Requer que o Orbot esteja instalado\nAinda não é compatível + Conecte-se ao Google através da rede Tor + Data da Build + Seus dados no YouTube + Navegue ou exclua sua atividade no YouTube e descubra como seus dados fazem com que o YouTube e outros serviços do Google funcionem melhor para você + Deseja remover sua conta do Google do Vanced+ microG? + Conta para removê-las.]]> + Verificar se há atualizações do Vanced + microG no GitLab + Ver código fonte do projeto no GitLab + Verifique a conexão + Verifique a conexão com a Internet do seu dispositivo + Voltar ao YouTube + Toque para sair do Vanced+ microG e voltar ao YouTube + Informações do pacote + Gerenciar contas do Google conectadas + Isso abrirá as configurações do seu dispositivo ! + Configurações diversas diff --git a/play-services-core/src/main/res/values-ru/strings.xml b/play-services-core/src/main/res/values-ru/strings.xml index 57ebf8d1..61269e80 100644 --- a/play-services-core/src/main/res/values-ru/strings.xml +++ b/play-services-core/src/main/res/values-ru/strings.xml @@ -119,11 +119,23 @@ To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-th/strings.xml b/play-services-core/src/main/res/values-th/strings.xml index 1ab3d2ff..3f2df561 100644 --- a/play-services-core/src/main/res/values-th/strings.xml +++ b/play-services-core/src/main/res/values-th/strings.xml @@ -107,11 +107,23 @@ To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-tr/strings.xml b/play-services-core/src/main/res/values-tr/strings.xml index 789da9c2..d5f5ddf5 100644 --- a/play-services-core/src/main/res/values-tr/strings.xml +++ b/play-services-core/src/main/res/values-tr/strings.xml @@ -119,11 +119,23 @@ Bunu bilerek yaptıysanız, Google'ın oturum açma sayfasına bağlanmak için To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-uk/strings.xml b/play-services-core/src/main/res/values-uk/strings.xml index ebc20774..a63b5a67 100644 --- a/play-services-core/src/main/res/values-uk/strings.xml +++ b/play-services-core/src/main/res/values-uk/strings.xml @@ -119,11 +119,23 @@ To remove signed in Google account, go to Device Settings -> Account. Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-v23/themes.xml b/play-services-core/src/main/res/values-v23/themes.xml new file mode 100644 index 00000000..9009e5c5 --- /dev/null +++ b/play-services-core/src/main/res/values-v23/themes.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-v27/themes.xml b/play-services-core/src/main/res/values-v27/themes.xml new file mode 100644 index 00000000..434c86ec --- /dev/null +++ b/play-services-core/src/main/res/values-v27/themes.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-v31/themes.xml b/play-services-core/src/main/res/values-v31/themes.xml index 1719a989..6e96e2b4 100644 --- a/play-services-core/src/main/res/values-v31/themes.xml +++ b/play-services-core/src/main/res/values-v31/themes.xml @@ -1,13 +1,11 @@ + - - + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-vi/strings.xml b/play-services-core/src/main/res/values-vi/strings.xml index f16a95bb..df22a69d 100644 --- a/play-services-core/src/main/res/values-vi/strings.xml +++ b/play-services-core/src/main/res/values-vi/strings.xml @@ -17,11 +17,11 @@ Thiết lập Vanced+ microG. Hãy đợi giây lát… - Quản lý tài khoản Google + Quản lý/Thêm tài khoản Google Xin lỗi… "Một ứng dụng nào đó trên thiết bị của bạn đang yêu cầu đăng nhập vào tài khoản Google. -Nếu điều này do bạn thực hiện, hãy sử dụng nút Đăng nhập (hoặc Huawei nếu bạn có thiết bị hãng này) để kết nối đến máy chủ đăng nhập của Google. Nếu không, nhấn Hủy để quay lại ứng dụng khiến hộp thoại này xuất hiện." +Nếu điều này do bạn thực hiện, hãy sử dụng nút Đăng nhập (hoặc Huawei nếu bạn có thiết bị hãng này) để kết nối đến máy chủ đăng nhập của Google." Đăng nhập "Đang thiết lập kết nối đến máy chủ của Google để đăng nhập cho bạn. @@ -50,7 +50,7 @@ Hãy thử lại sau." Bật Tắt Nâng cao - Trống + Không có gì ở đây cả, thật kỳ lạ... Xem tất cả Mạng di động Wi-Fi @@ -70,7 +70,7 @@ Hãy thử lại sau." Cho phép ứng dụng tìm tài khoản Khi được bật, tất cả các ứng dụng trên thiết bị này sẽ có thể xem địa chỉ email trong Tài khoản Google của bạn mà không cần ủy quyền trước. Tài khoản - Thêm tài khoản Google + Thêm tài khoản Google mới Nhận thông báo đẩy Thông báo đẩy qua đám mây hay Cloud Messaging là một nhà cung cấp dịch vụ thông báo đẩy được nhiều ứng dụng bên thứ ba tin dùng. Để sử dụng nó, bạn phải kích hoạt Đăng ký thiết bị. Giới thiệu về Vanced+ microG @@ -79,6 +79,8 @@ Hãy thử lại sau." Đăng ký thiết bị Đăng ký thiết bị của bạn với các dịch vụ của Google và tạo một mã nhận dạng thiết bị duy nhất. Vanced+ microG tách một ít thông tin nhận dạng không phải tên tài khoản Google của bạn từ dữ liệu đăng ký. ID Android + Xác thực với đăng ký thiết bị + Khi vô hiệu hóa, các yêu cầu xác thực sẽ không được liên kết với đăng ký thiết bị, điều này cho phép các thiết bị trái phép đăng nhập nhưng có thể gây ra những hậu quả về tài khoản. Trạng thái Mặc định Thủ công @@ -98,7 +100,7 @@ Hãy thử lại sau." Cho phép đăng ký Cho phép ứng dụng đăng ký nhận thông báo đẩy. Khởi động ứng dụng từ tin nhắn đẩy - Khởi động ứng dụng ở chế độ nền để nhận các tin nhắn đẩy đến. + Khởi động ứng dụng ở chế độ nền để nhận các thông báo đẩy đến. Trạng thái Ứng dụng sử dụng thông báo đẩy Ứng dụng đã đăng ký @@ -119,11 +121,24 @@ Hãy thử lại sau." Để gỡ bỏ tài khoản Google đã đăng nhập, hãy vào Cài đặt thiết bị -> Tài khoản. Kiểm tra cập nhật mới của Vanced+ microG trên GitHub Kiểm tra cập nhật mới - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. - Allow connecting to Google servers + Nếu vô hiệu hóa, tất cả các kết nối tới máy chủ Google thường thực hiện bởi Vanced+ microG sẽ bị từ chối. Tùy chọn này sẽ ghi đè cài đặt đó. + Cho phép kết nối tới máy chủ Google Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Giả mạo chữ ký của ứng dụng Vanced+ microG để đánh lừa Google và khắc phục lỗi phát video trên YouTube Vanced+ - Giả mạo chữ ký Vanced+ microG (THỬ NGHIỆM) Ngày dựng + Dữ liệu của bạn trong YouTube + Xem hoặc xóa hoạt động của bạn trên YouTube và khám phá cách dữ liệu của bạn có thể giúp YouTube và các dịch vụ khác của Google hoạt động hiệu quả hơn cho bạn. + Muốn đăng xuất hoặc gỡ tài khoản đã đăng nhập khỏi Vanced+ microG? + Quản lý Tài khoản và gỡ bỏ nó]]> hoặc nhấp vào đây. + Kiểm tra cập nhật Vanced+ microG trên GitLab + Xem mã nguồn dự án trên GitLab + Kiểm tra kết nối + Kiểm tra kết nối mạng của thiết bị + Quay lại YouTube Vanced+ + Nhấn để thoát Vanced+ microG và quay lại YouTube Vanced+ (nếu được cài đặt) + + Thông tin package + Quản lý tài khoản Google đã đăng nhập + Điều này sẽ mở cài đặt thiết bị của bạn ! + Cài đặt khác diff --git a/play-services-core/src/main/res/values-zh-rCN/strings.xml b/play-services-core/src/main/res/values-zh-rCN/strings.xml index 9d584bda..5dc520b8 100644 --- a/play-services-core/src/main/res/values-zh-rCN/strings.xml +++ b/play-services-core/src/main/res/values-zh-rCN/strings.xml @@ -118,11 +118,23 @@ View project source code on GitHub Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values-zh-rTW/strings.xml b/play-services-core/src/main/res/values-zh-rTW/strings.xml index ac6ba8b8..f4105881 100644 --- a/play-services-core/src/main/res/values-zh-rTW/strings.xml +++ b/play-services-core/src/main/res/values-zh-rTW/strings.xml @@ -126,11 +126,23 @@ Vanced+ microG 會把 Google 帳戶名稱以外的識別位 View project source code on GitHub Check for Vanced+ microG updates on GitHub Check for updates - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them.]]> + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values/colors.xml b/play-services-core/src/main/res/values/colors.xml index 85c87c33..12402999 100755 --- a/play-services-core/src/main/res/values/colors.xml +++ b/play-services-core/src/main/res/values/colors.xml @@ -16,9 +16,9 @@ - #ff4285f4 - #ff3367d6 - #ffffab40 + #ff9a82db + #ff9a82db + #ff9a82db #ff9a82db diff --git a/play-services-core/src/main/res/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index dd94bb6f..236f1198 100755 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -40,17 +40,18 @@ Try again later." This can take a couple of minutes." listen to internal status broadcasts + read Google service configuration listen to C2DM messages send C2DM messages to other apps exchange messages and receive sync notifications from Google servers Extended access to Google services - Google Account Manager not fully implemented + Google Account Manager not yet fully implemented Manage your Google account Manage your Google account information Manage YouTube activities Manage YouTube activities such as watch or search history View source code - View project source code on GitHub + View project source code on GitLab Google device registration @@ -81,6 +82,8 @@ This can take a couple of minutes." Personal info & privacy Sign-in & security + Authenticate with device registration + When disabled, authentication requests won\'t be linked to the device registration, which allows unauthorized devices to sign in, but may have unforeseen consequences. Trust Google for app permissions When disabled, the user is asked before an apps authorization request is sent to Google. Some applications will fail to use the Google account if this is disabled. Allow apps to find accounts @@ -137,18 +140,26 @@ This can take a couple of minutes." Import custom profile Import device profile from file Serial - https://github.com/cuynu/VancedxMicroG To remove signed in Google account, go to Device Settings -> Account. - https://t.me/ytvancedxupdates - https://discord.gg/FXAe2eE7mx Check for Vanced+ microG updates on GitHub Check for updates - https://github.com/cuynu/VancedxMicroG/releases - If disabled, all connections to Google servers usually done by microG will be denied. This overrides service specific settings. + If disabled, all connections to Google servers usually done by Vanced+ microG will be denied. This overrides service specific settings. Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date + Your data in YouTube + Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you + Want to remove your Google account from Vanced+ microG? + Account to remove them]]> or click here. + Check for Vanced+ microG updates on GitLab + View project source code on GitLab + Check connection + Check your device internet connection + Back to YouTube Vanced+ + Tap to exit Vanced+ microG and back to YouTube Vanced+ (if installed) + Package information + Manage signed in Google accounts + This will open your device settings ! + Miscellaneous settings diff --git a/play-services-core/src/main/res/values/themes.xml b/play-services-core/src/main/res/values/themes.xml index d5cb5705..8e651928 100755 --- a/play-services-core/src/main/res/values/themes.xml +++ b/play-services-core/src/main/res/values/themes.xml @@ -22,6 +22,15 @@ @color/m3_theme_primary_dark + + + + + + + + diff --git a/play-services-core/src/main/res/xml/authenticator.xml b/play-services-core/src/main/res/xml/authenticator.xml index 72809e56..52772beb 100644 --- a/play-services-core/src/main/res/xml/authenticator.xml +++ b/play-services-core/src/main/res/xml/authenticator.xml @@ -17,7 +17,7 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/play-services-core/src/main/res/xml/preferences_auth.xml b/play-services-core/src/main/res/xml/preferences_auth.xml index 7ff14e56..18007889 100755 --- a/play-services-core/src/main/res/xml/preferences_auth.xml +++ b/play-services-core/src/main/res/xml/preferences_auth.xml @@ -18,16 +18,30 @@ - + - + - + + + + diff --git a/play-services-core/src/main/res/xml/preferences_device_registration.xml b/play-services-core/src/main/res/xml/preferences_device_registration.xml index ecd4b469..3fa45eb5 100755 --- a/play-services-core/src/main/res/xml/preferences_device_registration.xml +++ b/play-services-core/src/main/res/xml/preferences_device_registration.xml @@ -5,7 +5,14 @@ --> + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + + @@ -13,16 +20,11 @@ android:key="pref_device_profile" android:persistent="false" android:title="@string/profile_name" - tools:summary="Automatic (Google Pixel 3, Android 11)" /> + tools:summary="Automatic" /> - + tools:summary="Last registration: XX hours ago" /> + tools:summary="" /> + - - - + android:title="@string/account_manager_title"> + android:selectable="true"> + android:targetClass="com.android.settings.Settings$AccountDashboardActivity" + android:targetPackage="com.android.settings" /> + + + + + android:title="@string/manage_google_account" + android:summary="@string/manage_google_account_summary"> + android:data="https://myaccount.google.com"/> + + + + + + - - - + + + + + + + + + diff --git a/play-services-core/src/main/res/xml/preferences_push_notifications.xml b/play-services-core/src/main/res/xml/preferences_push_notifications.xml index 20862ae7..416cfac8 100755 --- a/play-services-core/src/main/res/xml/preferences_push_notifications.xml +++ b/play-services-core/src/main/res/xml/preferences_push_notifications.xml @@ -7,6 +7,11 @@ + + @@ -29,6 +34,13 @@ android:selectable="false" android:title="@string/prefcat_status_title" tools:summary="Connected since 15 minutes ago" /> + + + + + diff --git a/play-services-core/src/main/res/xml/preferences_start.xml b/play-services-core/src/main/res/xml/preferences_start.xml index 3ee2cea6..e7372cee 100755 --- a/play-services-core/src/main/res/xml/preferences_start.xml +++ b/play-services-core/src/main/res/xml/preferences_start.xml @@ -16,22 +16,14 @@ - + - - - + android:summary="@string/manage_google_account_summary"> + android:targetClass="org.microg.gms.ui.GoogleMoreFragment$AsActivity" + android:targetPackage="@string/var_pref_start_gms_package" /> - - - - + + android:title="@string/source_code" android:summary="@string/view_sourcecode_gitlab"> + android:data="https://gitlab.com/cuynu/VancedxMicroG" /> + + + diff --git a/play-services-core/src/main/res/xml/profile_googlevr.xml b/play-services-core/src/main/res/xml/profile_googlevr.xml new file mode 100644 index 00000000..1d0c7fba --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_googlevr.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/xml/profile_lineage_xperiaxz2.xml b/play-services-core/src/main/res/xml/profile_lineage_xperiaxz2.xml new file mode 100644 index 00000000..6aa36bfa --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_lineage_xperiaxz2.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/xml/profile_projectinfinityx_redminote7.xml b/play-services-core/src/main/res/xml/profile_projectinfinityx_redminote7.xml new file mode 100644 index 00000000..f92014ae --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_projectinfinityx_redminote7.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/xml/profile_sdk_gphone64_arm64_33.xml b/play-services-core/src/main/res/xml/profile_sdk_gphone64_arm64_33.xml new file mode 100644 index 00000000..62d223c5 --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_sdk_gphone64_arm64_33.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-iid-api/build.gradle b/play-services-iid-api/build.gradle index 0ea891dd..34ed3abb 100644 --- a/play-services-iid-api/build.gradle +++ b/play-services-iid-api/build.gradle @@ -26,13 +26,13 @@ android { } compileOptions { - sourceCompatibility = 11 - targetCompatibility = 11 + sourceCompatibility = 17 + targetCompatibility = 17 } - namespace 'org.microg.gms.iid.api' buildFeatures { aidl true } + namespace 'org.microg.gms.iid.api' } dependencies { diff --git a/play-services-iid-api/src/main/AndroidManifest.xml b/play-services-iid-api/src/main/AndroidManifest.xml new file mode 100644 index 00000000..ca617cfb --- /dev/null +++ b/play-services-iid-api/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + \ No newline at end of file diff --git a/play-services-iid-api/src/main/java/com/google/android/gms/iid/MessengerCompat.java b/play-services-iid-api/src/main/java/com/google/android/gms/iid/MessengerCompat.java index 1cc87ae3..7aebf9b4 100644 --- a/play-services-iid-api/src/main/java/com/google/android/gms/iid/MessengerCompat.java +++ b/play-services-iid-api/src/main/java/com/google/android/gms/iid/MessengerCompat.java @@ -26,7 +26,7 @@ import android.os.Parcelable; import android.os.RemoteException; public class MessengerCompat implements Parcelable { - private final Messenger messenger; + private Messenger messenger; private IMessengerCompat messengerCompat; public MessengerCompat(IBinder binder) { @@ -48,7 +48,7 @@ public class MessengerCompat implements Parcelable { } public IBinder getBinder() { - return messenger.getBinder(); + return messenger != null ? messenger.getBinder() : messengerCompat.asBinder(); } @Override @@ -57,7 +57,8 @@ public class MessengerCompat implements Parcelable { } public void send(Message message) throws RemoteException { - messenger.send(message); + if (messenger != null) messenger.send(message); + else messengerCompat.send(message); } @Override diff --git a/play-services-tasks/build.gradle b/play-services-tasks/build.gradle index 204e8d9e..068aeb92 100644 --- a/play-services-tasks/build.gradle +++ b/play-services-tasks/build.gradle @@ -22,8 +22,8 @@ android { } compileOptions { - sourceCompatibility = 11 - targetCompatibility = 11 + sourceCompatibility = 17 + targetCompatibility = 17 } namespace 'org.microg.gms.tasks' } diff --git a/play-services-tasks/src/main/AndroidManifest.xml b/play-services-tasks/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c256eb44 --- /dev/null +++ b/play-services-tasks/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/play-services-tasks/src/main/java/com/google/android/gms/tasks/TaskCompletionSource.java b/play-services-tasks/src/main/java/com/google/android/gms/tasks/TaskCompletionSource.java index cbb0a9e6..3cdf1f11 100644 --- a/play-services-tasks/src/main/java/com/google/android/gms/tasks/TaskCompletionSource.java +++ b/play-services-tasks/src/main/java/com/google/android/gms/tasks/TaskCompletionSource.java @@ -84,11 +84,14 @@ public class TaskCompletionSource { * Completes the Task with the specified exception, unless the Task has already completed. * If the Task has already completed, the call does nothing. * + * @return {@code true} if the exception was set successfully, {@code false} otherwise */ - public void trySetException(Exception e) { + public boolean trySetException(Exception e) { try { setException(e); + return true; } catch (DuplicateTaskCompletionException ignored) { + return false; } } @@ -105,11 +108,14 @@ public class TaskCompletionSource { * Completes the Task with the specified result, unless the Task has already completed. * If the Task has already completed, the call does nothing. * + * @return {@code true} if the result was set successfully, {@code false} otherwise */ - public void trySetResult(TResult result) { + public boolean trySetResult(TResult result) { try { setResult(result); + return true; } catch (DuplicateTaskCompletionException ignored) { + return false; } } } diff --git a/play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java b/play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java index 1736b868..8bbc7db6 100644 --- a/play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java +++ b/play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java @@ -8,17 +8,85 @@ package com.google.android.gms.tasks; -import org.microg.gms.common.PublicApi; +import android.os.Handler; +import android.os.Looper; +import org.microg.gms.common.PublicApi; +import org.microg.gms.tasks.CancellationTokenImpl; +import org.microg.gms.tasks.TaskImpl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; /** * {@link Task} utility methods. */ @PublicApi +@SuppressWarnings("unchecked") public final class Tasks { + /** + * Blocks until the specified Task is complete. + * + * @return the Task's result + * @throws ExecutionException if the Task fails + * @throws InterruptedException if an interrupt occurs while waiting for the Task to complete + * @throws TimeoutException if the specified timeout is reached before the Task completes + */ + public static TResult await(Task task, long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException { + if (task == null) throw new IllegalArgumentException("Task must not be null"); + if (timeout <= 0) throw new IllegalArgumentException("Timeout must be positive"); + if (unit == null) throw new IllegalArgumentException("TimeUnit must not be null"); + if (task.isComplete()) return handleCompletedTask(task); + CountDownLatch latch = new CountDownLatch(1); + task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown()); + if (latch.await(timeout, unit)) { + return handleCompletedTask(task); + } + throw new TimeoutException("Timed out waiting for Task"); + } + + /** + * Blocks until the specified Task is complete. + * + * @return the Task's result + * @throws ExecutionException if the Task fails + * @throws InterruptedException if an interrupt occurs while waiting for the Task to complete + */ + public static TResult await(Task task) throws ExecutionException, InterruptedException { + if (Looper.getMainLooper().getThread() == Thread.currentThread()) + throw new IllegalStateException("Must not be invoked on main thread"); + if (task == null) throw new IllegalArgumentException("Task must not be null"); + if (task.isComplete()) return handleCompletedTask(task); + CountDownLatch latch = new CountDownLatch(1); + task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown()); + latch.await(); + return handleCompletedTask(task); + } + + private static TResult handleCompletedTask(Task task) throws ExecutionException { + if (task.isSuccessful()) { + return task.getResult(); + } + if (task.isCanceled()) { + throw new CancellationException("Task is already canceled"); + } + throw new ExecutionException(task.getException()); + } + /** * Returns a {@link Task} that will be completed with the result of the specified {@code Callable}. *

@@ -60,4 +128,180 @@ public final class Tasks { return taskCompletionSource.getTask(); } + /** + * Returns a canceled Task. + */ + public static Task forCancelled() { + TaskImpl task = new TaskImpl<>(); + task.cancel(); + return task; + } + + /** + * Returns a completed Task with the specified exception. + */ + public static Task forException(Exception e) { + TaskImpl task = new TaskImpl<>(); + task.setException(e); + return task; + } + + /** + * Returns a completed Task with the specified result. + */ + public static Task forResult(TResult result) { + TaskImpl task = new TaskImpl<>(); + task.setResult(result); + return task; + } + + /** + * Returns a Task that completes successfully when all of the specified Tasks complete + * successfully. Does not accept nulls. + *

+ * The returned Task would fail if any of the provided Tasks fail. The returned Task would be set to canceled if + * any of the provided Tasks is canceled and no failure is detected. + * + * @throws NullPointerException if any of the provided Tasks are null + */ + public static Task whenAll(Collection> tasks) { + if (tasks == null || tasks.isEmpty()) { + return forResult(null); + } + for (Task task : tasks) { + if (task == null) throw new NullPointerException("null tasks are not accepted"); + } + TaskImpl allTask = new TaskImpl<>(); + AtomicInteger finishedTasks = new AtomicInteger(0); + AtomicInteger failedTasks = new AtomicInteger(0); + AtomicReference exceptionReference = new AtomicReference<>(null); + AtomicBoolean isCancelled = new AtomicBoolean(false); + for (Task task : tasks) { + task.addOnCompleteListener(Runnable::run, completedTask -> { + if (!completedTask.isSuccessful()) { + if (completedTask.isCanceled()) { + isCancelled.set(true); + } else { + exceptionReference.set(completedTask.getException()); + failedTasks.incrementAndGet(); + } + } + if (finishedTasks.incrementAndGet() != tasks.size()) return; + Exception exception = exceptionReference.get(); + if (exception != null) { + allTask.setException(new ExecutionException(failedTasks.get() + " out of " + tasks.size() + " underlying tasks failed", exception)); + } else if (isCancelled.get()) { + allTask.cancel(); + } else { + allTask.setResult(null); + } + }); + } + return allTask; + } + + /** + * Returns a Task that completes successfully when all of the specified Tasks complete + * successfully. Does not accept nulls. + *

+ * The returned Task would fail if any of the provided Tasks fail. The returned Task would be set to canceled if + * any of the provided Tasks is canceled and no failure is detected. + * + * @throws NullPointerException if any of the provided Tasks are null + */ + public static Task whenAll(Task... tasks) { + if (tasks == null || tasks.length == 0) { + return forResult(null); + } + return whenAll(Arrays.asList(tasks)); + } + + /** + * Returns a Task with a list of Tasks that completes successfully when all of the specified Tasks complete. This + * Task would always succeed even if any of the provided Tasks fail or canceled. Does not accept nulls. + * + * @throws NullPointerException if any of the provided Tasks are null + */ + public static Task>> whenAllComplete(Task... tasks) { + if (tasks == null || tasks.length == 0) { + return forResult(Collections.emptyList()); + } + return whenAllComplete(Arrays.asList(tasks)); + } + + /** + * Returns a Task with a list of Tasks that completes successfully when all of the specified Tasks complete. This + * Task would always succeed even if any of the provided Tasks fail or canceled. Does not accept nulls. + * + * @throws NullPointerException if any of the provided Tasks are null + */ + public static Task>> whenAllComplete(Collection> tasks) { + if (tasks == null || tasks.isEmpty()) { + return forResult(Collections.emptyList()); + } + return whenAll(tasks).continueWithTask(TaskExecutors.MAIN_THREAD, allTask -> forResult(new ArrayList<>(tasks))); + } + + /** + * Returns a Task with a list of Task results that completes successfully when all of the specified Tasks complete + * successfully. This Task would fail if any of the provided Tasks fail. Does not accept nulls. + *

+ * This Task would be set to canceled if any of the provided Tasks is canceled and no failure is detected. + * + * @throws NullPointerException if any of the provided Tasks are null + */ + public static Task> whenAllSuccess(Task... tasks) { + if (tasks == null || tasks.length == 0) { + return forResult(Collections.emptyList()); + } + return whenAllSuccess(Arrays.asList(tasks)); + } + + /** + * Returns a Task with a list of Task results that completes successfully when all of the specified Tasks complete + * successfully. This Task would fail if any of the provided Tasks fail. Does not accept nulls. + *

+ * This Task would be set to canceled if any of the provided Tasks is canceled and no failure is detected. + * + * @throws NullPointerException if any of the provided Tasks are null + */ + public static Task> whenAllSuccess(Collection> tasks) { + if (tasks == null || tasks.isEmpty()) { + return forResult(Collections.emptyList()); + } + return whenAll(tasks).continueWithTask(TaskExecutors.MAIN_THREAD, allTask -> { + List results = new ArrayList<>(tasks.size()); + for (Task task : tasks) { + results.add(task.getResult()); + } + return forResult(results); + }); + } + + /** + * Returns a new Task which will return a TimeoutException if a result is not returned within the specified time + * period. + * + * @return A new Task. + */ + public static Task withTimeout(Task task, long timeout, TimeUnit unit) { + if (task == null) throw new IllegalArgumentException("Task must not be null"); + if (timeout <= 0) throw new IllegalArgumentException("Timeout must be positive"); + if (unit == null) throw new IllegalArgumentException("TimeUnit must not be null"); + CancellationTokenImpl cancellationToken = new CancellationTokenImpl(); + TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(cancellationToken); + Handler handler = new Handler(Looper.getMainLooper()); + handler.postDelayed(() -> taskCompletionSource.trySetException(new TimeoutException()), unit.toMillis(timeout)); + task.addOnCompleteListener(completedTask -> { + handler.removeCallbacksAndMessages(null); + if (completedTask.isSuccessful()) { + taskCompletionSource.trySetResult(completedTask.getResult()); + } else if (completedTask.isCanceled()) { + cancellationToken.cancel(); + } else { + taskCompletionSource.trySetException(completedTask.getException()); + } + }); + return taskCompletionSource.getTask(); + } } diff --git a/play-services-tasks/src/main/java/org/microg/gms/tasks/TaskImpl.java b/play-services-tasks/src/main/java/org/microg/gms/tasks/TaskImpl.java index 1354fb7e..67b54ea8 100644 --- a/play-services-tasks/src/main/java/org/microg/gms/tasks/TaskImpl.java +++ b/play-services-tasks/src/main/java/org/microg/gms/tasks/TaskImpl.java @@ -17,7 +17,6 @@ import com.google.android.gms.tasks.RuntimeExecutionException; import com.google.android.gms.tasks.SuccessContinuation; import com.google.android.gms.tasks.Task; -import java.util.Objects; import java.util.Queue; import java.util.concurrent.CancellationException; import java.util.concurrent.Executor; @@ -139,7 +138,7 @@ public class TaskImpl extends Task { synchronized (lock) { if (!completed) throw new IllegalStateException("Task is not yet complete"); if (cancelled) throw new CancellationException("Task is canceled"); - if (exceptionType.isInstance(exception)) throw Objects.requireNonNull(exceptionType.cast(exception)); + if (exceptionType.isInstance(exception)) throw exceptionType.cast(exception); if (exception != null) throw new RuntimeExecutionException(exception); return result; } diff --git a/proguard.flags b/proguard.flags index fba8c5e2..7e287ce2 100644 --- a/proguard.flags +++ b/proguard.flags @@ -11,16 +11,24 @@ -dontwarn com.caverock.androidsvg.** -dontwarn org.slf4j.** -dontwarn org.codehaus.jackson.** +-dontwarn com.android.location.provider.** +-dontwarn java.lang.instrument.ClassFileTransformer +-dontwarn sun.misc.SignalHandler +-dontwarn com.android.org.conscrypt.SSLParametersImpl +-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl # Disable ProGuard Notes, they won't help here -dontnote # Keep dynamically loaded GMS classes -keep public class com.google.android.gms.common.security.ProviderInstallerImpl { public *; } +-keep public class com.google.android.gms.dynamic.IObjectWrapper { public *; } +-keep public class com.google.android.gms.dynamite.descriptors.** { public *; } # Keep AutoSafeParcelables -keep public class * extends org.microg.safeparcel.AutoSafeParcelable { @org.microg.safeparcel.SafeParceled *; + (...); } # Keep form data @@ -29,7 +37,12 @@ } # Keep our stuff --keep class org.microg.** { *; } +-keep class org.microg** { *; } +-keep class com.google.android.gms** { *; } +-keep class com.mgoogle** { *; } +-keepdirectories org/microg** +-keepdirectories com/google/android/gms** +-keepdirectories com/mgoogle** # Keep asInterface method cause it's accessed from SafeParcel -keepattributes InnerClasses diff --git a/settings.gradle b/settings.gradle index 7725a9a8..78cd762d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,5 @@ +include ':fake-signature' + include ':play-services-basement' include ':play-services-tasks' @@ -13,7 +15,4 @@ include ':play-services-core:microg-ui-tools' // Legacy include ':play-services-core' include ':play-services-base' -include ':play-services-base-core' - -include ':play-services-cast-framework' -include ':play-services-cast' \ No newline at end of file +include ':play-services-base-core' \ No newline at end of file