Repo Created
This commit is contained in:
parent
eb305e2886
commit
a8c22c65db
4784 changed files with 329907 additions and 2 deletions
62
play-services-core/src/huawei/AndroidManifest.xml
Normal file
62
play-services-core/src/huawei/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<!--
|
||||
~ SPDX-FileCopyrightText: 2023 microG Project Team
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application>
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.checkin_enable_service"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.gcm_enable_mcs_service"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.auth_manager_visible"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.auth_include_android_id"
|
||||
android:value="false" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.auth_strip_device_name"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.auth_two_step_verification"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.droidguard_enabled"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.safetynet_enabled"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.vending_billing"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.vending_licensing_purchase_free_apps"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.vending_licensing"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.vending_asset_delivery"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.vending_device_sync"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.vending_split_install"
|
||||
android:value="false" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.game_allow_create_player"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.allow_upload_game_played"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.vending_apps_install"
|
||||
android:value="true" />
|
||||
</application>
|
||||
</manifest>
|
||||
62
play-services-core/src/huaweilh/AndroidManifest.xml
Normal file
62
play-services-core/src/huaweilh/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<!--
|
||||
~ SPDX-FileCopyrightText: 2023 microG Project Team
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application>
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.checkin_enable_service"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.gcm_enable_mcs_service"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.auth_manager_visible"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.auth_include_android_id"
|
||||
android:value="false" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.auth_strip_device_name"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.auth_two_step_verification"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.droidguard_enabled"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.safetynet_enabled"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.vending_billing"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="org.microg.gms.settings.vending_licensing"
|
||||
android:value="true" />
|
||||
|
||||
<activity-alias
|
||||
android:name="org.microg.gms.ui.SettingsActivity"
|
||||
android:targetActivity="org.microg.gms.ui.MainSettingsActivity"
|
||||
tools:node="remove" />
|
||||
|
||||
<activity-alias
|
||||
android:name="org.lighthouse.SettingsActivity"
|
||||
android:icon="@mipmap/ic_app_settings"
|
||||
android:label="@string/gms_settings_name"
|
||||
android:roundIcon="@mipmap/ic_app_settings"
|
||||
android:process=":ui"
|
||||
android:targetActivity="org.microg.gms.ui.MainSettingsActivity"
|
||||
android:taskAffinity="org.microg.gms.settings"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
</application>
|
||||
</manifest>
|
||||
1331
play-services-core/src/main/AndroidManifest.xml
Normal file
1331
play-services-core/src/main/AndroidManifest.xml
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.analytics.service;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
public class AnalyticsService extends Service {
|
||||
private static final String TAG = "GmsAnalyticsSvc";
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
Log.d(TAG, "onBind: " + intent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
import org.microg.gms.auth.AuthManagerServiceImpl;
|
||||
|
||||
public class GetToken extends Service {
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return new AuthManagerServiceImpl(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
public class TokenActivity extends Activity {
|
||||
private static final String TAG = "GmsAuthTokenActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle extras = getIntent().getExtras();
|
||||
extras.get("KEY");
|
||||
Log.d(TAG, extras.toString());
|
||||
/*AccountManager accountManager = AccountManager.get(this);
|
||||
accountManager.getAuthToken(new Account(extras.getString("authAccount"), "com.google"), extras.getString("service"), extras.getBundle("callerExtras"), this, new AccountManagerCallback<Bundle>() {
|
||||
@Override
|
||||
public void run(AccountManagerFuture<Bundle> future) {
|
||||
try {
|
||||
Bundle result = future.getResult();
|
||||
if (result != null) {
|
||||
result.get("KEY");
|
||||
Log.d("TokenActivity", result.toString());
|
||||
} else {
|
||||
Log.d("TokenActivity", "null-result");
|
||||
}
|
||||
} catch (OperationCanceledException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (AuthenticatorException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}, new Handler(getMainLooper()));*/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2025 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.chimera;
|
||||
|
||||
import static android.os.Build.CPU_ABI;
|
||||
import static android.os.Build.SUPPORTED_32_BIT_ABIS;
|
||||
import static android.os.Build.SUPPORTED_64_BIT_ABIS;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.chimera.container.DynamiteContext;
|
||||
import com.google.android.gms.chimera.container.DynamiteModuleInfo;
|
||||
import com.google.android.gms.chimera.container.FilteredClassLoader;
|
||||
|
||||
import org.microg.gms.common.Constants;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import dalvik.system.PathClassLoader;
|
||||
|
||||
public class DynamiteContextFactory {
|
||||
private static final String TAG = "DynamiteContextFactory";
|
||||
private static final Map<String, DynamiteContext> sContextCache = new WeakHashMap<>();
|
||||
// WeakHashMap cannot be used, and there is a high probability that it will be recycled, causing ClassLoader to be rebuilt
|
||||
private static final Map<String, ClassLoader> sClassLoaderCache = new HashMap<>();
|
||||
|
||||
public static DynamiteContext createDynamiteContext(String moduleId, Context originalContext) {
|
||||
if (originalContext == null) {
|
||||
Log.w(TAG, "create <DynamiteContext> Original context is null");
|
||||
return null;
|
||||
}
|
||||
String cacheKey = moduleId + "-" + originalContext.getPackageName();
|
||||
synchronized (sContextCache) {
|
||||
DynamiteContext cached = sContextCache.get(cacheKey);
|
||||
if (cached != null) {
|
||||
Log.d(TAG, "Using cached DynamiteContext for cacheKey: " + cacheKey);
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
try {
|
||||
DynamiteModuleInfo moduleInfo = new DynamiteModuleInfo(moduleId);
|
||||
Context gmsContext = originalContext.createPackageContext(Constants.GMS_PACKAGE_NAME, 0);
|
||||
Context originalAppContext = originalContext.getApplicationContext();
|
||||
|
||||
DynamiteContext dynamiteContext;
|
||||
if (originalAppContext == null || originalAppContext == originalContext) {
|
||||
dynamiteContext = new DynamiteContext(moduleInfo, originalContext, gmsContext, null);
|
||||
} else {
|
||||
dynamiteContext = new DynamiteContext(moduleInfo, originalContext, gmsContext, new DynamiteContext(moduleInfo, originalAppContext, gmsContext, null));
|
||||
}
|
||||
moduleInfo.init(dynamiteContext);
|
||||
|
||||
synchronized (sContextCache) {
|
||||
sContextCache.put(cacheKey, dynamiteContext);
|
||||
}
|
||||
Log.d(TAG, "Created and cached a new DynamiteContext for cacheKey: " + cacheKey);
|
||||
return dynamiteContext;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ClassLoader createClassLoader(DynamiteModuleInfo moduleInfo, Context gmsContext, Context originalContext) {
|
||||
String cacheKey = moduleInfo.getModuleId() + "-" + originalContext.getPackageName();
|
||||
synchronized (sClassLoaderCache) {
|
||||
ClassLoader cached = sClassLoaderCache.get(cacheKey);
|
||||
if (cached != null) {
|
||||
Log.d(TAG, "Using cached ClassLoader for cacheKey: " + cacheKey + " cached: " + cached.hashCode());
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
StringBuilder nativeLoaderDirs = new StringBuilder(gmsContext.getApplicationInfo().nativeLibraryDir);
|
||||
if (SDK_INT >= 23 && Process.is64Bit()) {
|
||||
for (String abi : SUPPORTED_64_BIT_ABIS) {
|
||||
nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi);
|
||||
}
|
||||
} else if (SDK_INT >= 21) {
|
||||
for (String abi : SUPPORTED_32_BIT_ABIS) {
|
||||
nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi);
|
||||
}
|
||||
} else {
|
||||
nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(CPU_ABI);
|
||||
}
|
||||
ClassLoader classLoader = new PathClassLoader(gmsContext.getApplicationInfo().sourceDir, nativeLoaderDirs.toString(), new FilteredClassLoader(originalContext.getClassLoader(), moduleInfo.getMergedClasses(), moduleInfo.getMergedPackages()));
|
||||
synchronized (sClassLoaderCache) {
|
||||
sClassLoaderCache.put(cacheKey, classLoader);
|
||||
}
|
||||
Log.d(TAG, "Created and cached a new ClassLoader for cacheKey: " + cacheKey + " ClassLoader: " + classLoader.hashCode());
|
||||
return classLoader;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.chimera;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
@Keep
|
||||
public class DynamiteModuleInitializer {
|
||||
private static final String TAG = "DynamiteModule";
|
||||
|
||||
public static void initializeModuleV1(Context context) {
|
||||
initializeModuleV2(context, "com.google.android.gms".equals(context.getPackageName()));
|
||||
}
|
||||
|
||||
public static void initializeModuleV2(Context context, boolean withGmsPackage) {
|
||||
Log.d(TAG, "initializeModuleV2 context: " + context + ", withGmsPackage: " + withGmsPackage);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.chimera.container;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.google.android.gms.chimera.DynamiteContextFactory;
|
||||
|
||||
public class DynamiteContext extends ContextWrapper {
|
||||
private static final String TAG = "DynamiteContext";
|
||||
private DynamiteModuleInfo moduleInfo;
|
||||
private Context originalContext;
|
||||
private Context gmsContext;
|
||||
private DynamiteContext appContext;
|
||||
|
||||
private ClassLoader classLoader;
|
||||
|
||||
public DynamiteContext(DynamiteModuleInfo moduleInfo, Context base, Context gmsContext, DynamiteContext appContext) {
|
||||
super(base);
|
||||
this.moduleInfo = moduleInfo;
|
||||
this.originalContext = base;
|
||||
this.gmsContext = gmsContext;
|
||||
this.appContext = appContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getClassLoader() {
|
||||
if (classLoader == null) {
|
||||
classLoader = DynamiteContextFactory.createClassLoader(moduleInfo, gmsContext, originalContext);
|
||||
}
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPackageName() {
|
||||
return gmsContext.getPackageName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationInfo getApplicationInfo() {
|
||||
return gmsContext.getApplicationInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getApplicationContext() {
|
||||
return appContext;
|
||||
}
|
||||
|
||||
@RequiresApi(24)
|
||||
@Override
|
||||
public Context createDeviceProtectedStorageContext() {
|
||||
return new DynamiteContext(moduleInfo, originalContext.createDeviceProtectedStorageContext(), gmsContext.createDeviceProtectedStorageContext(), appContext);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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.chimera.container;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.chimera.DynamiteContextFactory;
|
||||
import com.google.android.gms.dynamic.IObjectWrapper;
|
||||
import com.google.android.gms.dynamic.ObjectWrapper;
|
||||
import com.google.android.gms.dynamite.IDynamiteLoader;
|
||||
|
||||
public class DynamiteLoaderImpl extends IDynamiteLoader.Stub {
|
||||
private static final String TAG = "GmsDynamiteLoaderImpl";
|
||||
|
||||
@Override
|
||||
public IObjectWrapper createModuleContext(IObjectWrapper wrappedContext, String moduleId, int minVersion) throws RemoteException {
|
||||
// We don't have crash utils, so just forward
|
||||
return createModuleContextV2(wrappedContext, moduleId, minVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IObjectWrapper createModuleContextV2(IObjectWrapper wrappedContext, String moduleId, int minVersion) throws RemoteException {
|
||||
Log.d(TAG, "createModuleContext for " + moduleId + " at version " + minVersion);
|
||||
final Context originalContext = (Context) ObjectWrapper.unwrap(wrappedContext);
|
||||
return ObjectWrapper.wrap(DynamiteContextFactory.createDynamiteContext(moduleId, originalContext));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IObjectWrapper createModuleContextV3(IObjectWrapper wrappedContext, String moduleId, int minVersion, IObjectWrapper wrappedCursor) throws RemoteException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIDynamiteLoaderVersion() throws RemoteException {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModuleVersion(IObjectWrapper wrappedContext, String moduleId) throws RemoteException {
|
||||
return getModuleVersion2(wrappedContext, moduleId, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModuleVersion2(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired) throws RemoteException {
|
||||
// We don't have crash utils, so just forward
|
||||
return getModuleVersionV2(wrappedContext, moduleId, updateConfigIfRequired);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModuleVersionV2(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired) throws RemoteException {
|
||||
final Context context = (Context) ObjectWrapper.unwrap(wrappedContext);
|
||||
if (context == null) {
|
||||
Log.w(TAG, "Invalid client context");
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return Class.forName("com.google.android.gms.dynamite.descriptors." + moduleId + ".ModuleDescriptor").getDeclaredField("MODULE_VERSION").getInt(null);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "No such module known: " + moduleId);
|
||||
}
|
||||
|
||||
if (moduleId.equals("com.google.android.gms.firebase_database")) {
|
||||
Log.d(TAG, "returning temp fix module version for " + moduleId + ". Firebase Database will not be functional!");
|
||||
return com.google.android.gms.dynamite.descriptors.com.google.android.gms.firebase_database.ModuleDescriptor.MODULE_VERSION;
|
||||
}
|
||||
if (moduleId.equals("com.google.android.gms.googlecertificates")) {
|
||||
return com.google.android.gms.dynamite.descriptors.com.google.android.gms.googlecertificates.ModuleDescriptor.MODULE_VERSION;
|
||||
}
|
||||
if (moduleId.equals("com.google.android.gms.cast.framework.dynamite")) {
|
||||
Log.d(TAG, "returning temp fix module version for " + moduleId + ". Cast API wil not be functional!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (moduleId.equals("com.google.android.gms.maps_dynamite")) {
|
||||
Log.d(TAG, "returning v1 for maps");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Log.d(TAG, "unimplemented Method: getModuleVersion for " + moduleId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IObjectWrapper getModuleVersionV3(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired, long requestStartTime) throws RemoteException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.chimera.container;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class DynamiteModuleInfo {
|
||||
private Class<?> descriptor;
|
||||
private String moduleId;
|
||||
|
||||
public DynamiteModuleInfo(String moduleId) {
|
||||
this.moduleId = moduleId;
|
||||
try {
|
||||
this.descriptor = Class.forName("com.google.android.gms.dynamite.descriptors." + moduleId + ".ModuleDescriptor");
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
public String getModuleId() {
|
||||
return moduleId;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
try {
|
||||
return descriptor.getDeclaredField("MODULE_VERSION").getInt(null);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<String> getMergedPackages() {
|
||||
try {
|
||||
return (Collection<String>) descriptor.getDeclaredField("MERGED_PACKAGES").get(null);
|
||||
} catch (Exception e) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<String> getMergedClasses() {
|
||||
try {
|
||||
return (Collection<String>) descriptor.getDeclaredField("MERGED_CLASSES").get(null);
|
||||
} catch (Exception e) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
public void init(Context dynamiteContext) {
|
||||
try {
|
||||
descriptor.getMethod("init", Context.class).invoke(null, dynamiteContext);
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.chimera.container;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class FilteredClassLoader extends ClassLoader {
|
||||
private static ClassLoader rootClassLoader;
|
||||
private final Collection<String> allowedClasses;
|
||||
private final Collection<String> allowedPackages;
|
||||
|
||||
static {
|
||||
rootClassLoader = ClassLoader.getSystemClassLoader();
|
||||
if (rootClassLoader == null) {
|
||||
rootClassLoader = FilteredClassLoader.class.getClassLoader();
|
||||
while (rootClassLoader.getParent() != null) {
|
||||
rootClassLoader = rootClassLoader.getParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FilteredClassLoader(ClassLoader parent, Collection<String> allowedClasses, Collection<String> allowedPackages) {
|
||||
super(parent);
|
||||
this.allowedClasses = new HashSet<>(allowedClasses);
|
||||
this.allowedPackages = new HashSet<>(allowedPackages);
|
||||
}
|
||||
|
||||
private String getPackageName(String name) {
|
||||
int lastIndex = name.lastIndexOf(".");
|
||||
if (lastIndex <= 0) return "";
|
||||
return name.substring(0, lastIndex);
|
||||
}
|
||||
|
||||
private String getClassName(String name) {
|
||||
int lastIndex = name.indexOf("$");
|
||||
if (lastIndex <= 0) return name;
|
||||
return name.substring(0, lastIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
if (name.startsWith("java.")) return rootClassLoader.loadClass(name);
|
||||
if (allowedClasses.contains(name) || allowedClasses.contains(getClassName(name)))
|
||||
return super.loadClass(name, resolve);
|
||||
if (allowedClasses.contains("!" + name) || allowedClasses.contains("!" + getClassName(name)))
|
||||
return rootClassLoader.loadClass(name);
|
||||
if (allowedPackages.contains(getPackageName(name))) return super.loadClass(name, resolve);
|
||||
return rootClassLoader.loadClass(name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.android.gms.common.internal.ISignInButtonCreator;
|
||||
import com.google.android.gms.dynamic.IObjectWrapper;
|
||||
import com.google.android.gms.dynamic.ObjectWrapper;
|
||||
|
||||
public class SignInButtonCreatorImpl extends ISignInButtonCreator.Stub {
|
||||
@Override
|
||||
public IObjectWrapper createSignInButton(IObjectWrapper contextWrapper, int size, int color) {
|
||||
Context context = (Context) ObjectWrapper.unwrap(contextWrapper);
|
||||
// TODO: real view :)
|
||||
return ObjectWrapper.wrap(new View(context));
|
||||
}
|
||||
}
|
||||
|
|
@ -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.dynamite.descriptors.com.google.android.gms.firebase_database;
|
||||
|
||||
public class ModuleDescriptor {
|
||||
public static final String MODULE_ID = "com.google.android.gms.firebase_database";
|
||||
public static final int MODULE_VERSION = 3;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.dynamite.descriptors.com.google.android.gms.googlecertificates;
|
||||
|
||||
public class ModuleDescriptor {
|
||||
public static final String MODULE_ID = "com.google.android.gms.googlecertificates";
|
||||
public static final int MODULE_VERSION = 1;
|
||||
}
|
||||
|
|
@ -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.dynamite.descriptors.com.google.android.gms.measurement.dynamite;
|
||||
|
||||
public class ModuleDescriptor {
|
||||
public static final String MODULE_ID = "com.google.android.gms.measurement.dynamite";
|
||||
public static final int MODULE_VERSION = 140;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.gcm.http;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.google.android.gms.http.IGoogleHttpService;
|
||||
|
||||
public class GoogleHttpService extends Service {
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return new IGoogleHttpService.Stub() {
|
||||
@Override
|
||||
public Bundle checkUrl(String url) throws RemoteException {
|
||||
return null; // allow
|
||||
}
|
||||
}.asBinder();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.plus.plusone;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.RemoteException;
|
||||
import com.google.android.gms.dynamic.IObjectWrapper;
|
||||
import com.google.android.gms.dynamic.ObjectWrapper;
|
||||
import com.google.android.gms.plus.internal.IPlusOneButtonCreator;
|
||||
|
||||
import org.microg.gms.auth.AuthConstants;
|
||||
import org.microg.gms.plus.PlusOneButtonImpl;
|
||||
|
||||
public class PlusOneButtonCreatorImpl extends IPlusOneButtonCreator.Stub {
|
||||
@Override
|
||||
public IObjectWrapper create(IObjectWrapper context, int size, int annotation, String url, int activityRequestCode) throws RemoteException {
|
||||
Context ctx = (Context) ObjectWrapper.unwrap(context);
|
||||
return ObjectWrapper.wrap(new PlusOneButtonImpl(ctx, size, annotation, url, AuthConstants.DEFAULT_ACCOUNT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IObjectWrapper createForAccount(IObjectWrapper context, int size, int annotation, String url, String account) throws RemoteException {
|
||||
Context ctx = (Context) ObjectWrapper.unwrap(context);
|
||||
return ObjectWrapper.wrap(new PlusOneButtonImpl(ctx, size, annotation, url, account));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.recovery;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
public class RecoveryService extends Service {
|
||||
private static final String TAG = "GmsRecoverySvc";
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
Log.d(TAG, "onBind: " + intent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.google.android.gms.semanticlocation
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.util.Log
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.Feature
|
||||
import com.google.android.gms.common.api.internal.IStatusCallback
|
||||
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.semanticlocation.internal.ISemanticLocationService
|
||||
import com.google.android.gms.semanticlocation.internal.SemanticLocationParameters
|
||||
import org.microg.gms.BaseService
|
||||
import org.microg.gms.common.GmsService
|
||||
|
||||
private const val TAG = "SemanticLocationService"
|
||||
|
||||
private val FEATURES = arrayOf(
|
||||
Feature("semanticlocation_events", 1L),
|
||||
)
|
||||
|
||||
class SemanticLocationService : BaseService(TAG, GmsService.SEMANTIC_LOCATION) {
|
||||
override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) {
|
||||
val connectionInfo = ConnectionInfo().apply {
|
||||
features = FEATURES
|
||||
}
|
||||
callback.onPostInitCompleteWithConnectionInfo(
|
||||
ConnectionResult.SUCCESS,
|
||||
SemanticLocationServiceImpl().asBinder(),
|
||||
connectionInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SemanticLocationServiceImpl : ISemanticLocationService.Stub() {
|
||||
override fun registerSemanticLocationEvents(
|
||||
params: SemanticLocationParameters,
|
||||
callback: IStatusCallback,
|
||||
request: SemanticLocationEventRequest,
|
||||
pendingIntent: PendingIntent
|
||||
) {
|
||||
Log.d(TAG, "registerSemanticLocationEvents: $params")
|
||||
}
|
||||
|
||||
override fun setIncognitoMode(params: SemanticLocationParameters, callback: IStatusCallback, mode: Boolean) {
|
||||
Log.d(TAG, "setIncognitoMode: $params")
|
||||
}
|
||||
|
||||
override fun unregisterSemanticLocationEvents(params: SemanticLocationParameters, callback: IStatusCallback, pendingIntent: PendingIntent) {
|
||||
Log.d(TAG, "unregisterSemanticLocationEvents: $params")
|
||||
}
|
||||
}
|
||||
|
|
@ -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.wallet.dynamite;
|
||||
|
||||
public class WalletDynamiteCreatorImpl {
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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.firebase.database.connection.idl;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.dynamic.IObjectWrapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class IPersistentConnectionImpl extends IPersistentConnection.Stub {
|
||||
private static final String TAG = "GmsFirebaseDbConImpl";
|
||||
|
||||
@Override
|
||||
public void setup(ConnectionConfig var1, IConnectionAuthTokenProvider var2, IObjectWrapper var3, IPersistentConnectionDelegate var4) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: setup");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: initialize");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: shutdown");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshAuthToken() throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: refreshAuthToken");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listen(List<String> var1, IObjectWrapper var2, IListenHashProvider var3, long var4, IRequestResultCallback var6) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: listen");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlisten(List<String> var1, IObjectWrapper var2) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: unlisten");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeOutstandingWrites() throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: purgeOutstandingWrites");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(List<String> var1, IObjectWrapper var2, IRequestResultCallback var3) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: put");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compareAndPut(List<String> var1, IObjectWrapper var2, String var3, IRequestResultCallback var4) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: compareAndPut");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(List<String> var1, IObjectWrapper var2, IRequestResultCallback var3) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: merge");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnectPut(List<String> var1, IObjectWrapper var2, IRequestResultCallback var3) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: onDisconnectPut");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnectMerge(List<String> var1, IObjectWrapper var2, IRequestResultCallback var3) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: onDisconnectMerge");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnectCancel(List<String> var1, IRequestResultCallback var2) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: onDisconnectCancel");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interrupt(String var1) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: interrupt");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume(String var1) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: resume");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterrupted(String var1) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: isInterrupted");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 org.microg.gms.ads;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
|
||||
import org.microg.gms.BaseService;
|
||||
import org.microg.gms.common.GmsService;
|
||||
|
||||
public class GService extends BaseService {
|
||||
|
||||
public GService() {
|
||||
super("GmsAdsGSvc", GmsService.GSERVICES, GmsService.ADREQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
|
||||
callback.onPostInitComplete(ConnectionResult.API_DISABLED, null, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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.auth;
|
||||
|
||||
import static android.accounts.AccountManager.VISIBILITY_VISIBLE;
|
||||
|
||||
import android.Manifest;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.microg.gms.common.GooglePackagePermission;
|
||||
import org.microg.gms.common.PackageUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static org.microg.gms.auth.AuthConstants.DEFAULT_ACCOUNT_TYPE;
|
||||
import static org.microg.gms.auth.AuthConstants.PROVIDER_EXTRA_ACCOUNTS;
|
||||
import static org.microg.gms.auth.AuthConstants.PROVIDER_EXTRA_CLEAR_PASSWORD;
|
||||
import static org.microg.gms.auth.AuthConstants.PROVIDER_METHOD_CLEAR_PASSWORD;
|
||||
import static org.microg.gms.auth.AuthConstants.PROVIDER_METHOD_GET_ACCOUNTS;
|
||||
|
||||
public class AccountContentProvider extends ContentProvider {
|
||||
private static final String TAG = "GmsAuthProvider";
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Bundle call(String method, String arg, Bundle extras) {
|
||||
String suggestedPackageName = null;
|
||||
if (SDK_INT > 19) {
|
||||
suggestedPackageName = getCallingPackage();
|
||||
}
|
||||
String packageName = PackageUtils.getAndCheckCallingPackage(getContext(), suggestedPackageName);
|
||||
boolean hasGooglePackagePermission = PackageUtils.callerHasGooglePackagePermission(getContext(), GooglePackagePermission.ACCOUNT);
|
||||
if (!hasGooglePackagePermission) {
|
||||
String[] packagesForUid = getContext().getPackageManager().getPackagesForUid(Binder.getCallingUid());
|
||||
if (packagesForUid != null && packagesForUid.length != 0)
|
||||
Log.w(TAG, "Not granting extended access to " + Arrays.toString(packagesForUid)
|
||||
+ ", signature: " + PackageUtils.firstSignatureDigest(getContext(), packagesForUid[0]));
|
||||
if (getContext().checkCallingPermission(Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED)
|
||||
throw new SecurityException("Access denied, missing google package permission or GET_ACCOUNTS");
|
||||
}
|
||||
long identityToken = Binder.clearCallingIdentity();
|
||||
try {
|
||||
if (PROVIDER_METHOD_GET_ACCOUNTS.equals(method)) {
|
||||
Bundle result = new Bundle();
|
||||
Account[] accounts = null;
|
||||
if (arg != null && (arg.equals(DEFAULT_ACCOUNT_TYPE) || arg.startsWith(DEFAULT_ACCOUNT_TYPE + "."))) {
|
||||
AccountManager am = AccountManager.get(getContext());
|
||||
accounts = am.getAccountsByTypeForPackage(arg, packageName);
|
||||
if (SDK_INT >= 26 && accounts != null && arg.equals(DEFAULT_ACCOUNT_TYPE)) {
|
||||
for (Account account : accounts) {
|
||||
if (am.getAccountVisibility(account, packageName) == AccountManager.VISIBILITY_UNDEFINED &&
|
||||
(hasGooglePackagePermission || AuthPrefs.isAuthVisible(getContext()))) {
|
||||
Log.d(TAG, "Make account " + account + " visible to " + packageName);
|
||||
am.setAccountVisibility(account, packageName, VISIBILITY_VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (accounts == null) {
|
||||
accounts = new Account[0];
|
||||
}
|
||||
|
||||
result.putParcelableArray(PROVIDER_EXTRA_ACCOUNTS, accounts);
|
||||
return result;
|
||||
} else if (PROVIDER_METHOD_CLEAR_PASSWORD.equals(method) && hasGooglePackagePermission) {
|
||||
Account a = extras.getParcelable(PROVIDER_EXTRA_CLEAR_PASSWORD);
|
||||
AccountManager.get(getContext()).clearPassword(a);
|
||||
return null;
|
||||
}
|
||||
throw new UnsupportedOperationException(String.format("Unsupported method call %s(%s).", method, arg));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identityToken);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* 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.auth;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountAuthenticatorActivity;
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.gms.R;
|
||||
|
||||
import org.microg.gms.common.PackageUtils;
|
||||
import org.microg.gms.people.PeopleManager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
|
||||
import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
|
||||
import static android.accounts.AccountManager.KEY_ANDROID_PACKAGE_NAME;
|
||||
import static android.accounts.AccountManager.KEY_AUTHTOKEN;
|
||||
import static android.accounts.AccountManager.KEY_CALLER_PID;
|
||||
import static android.accounts.AccountManager.KEY_CALLER_UID;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
public class AskPermissionActivity extends AccountAuthenticatorActivity {
|
||||
public static final String EXTRA_FROM_ACCOUNT_MANAGER = "from_account_manager";
|
||||
public static final String EXTRA_CONSENT_DATA = "consent_data";
|
||||
|
||||
private static final String TAG = "GmsAuthAskPermission";
|
||||
private AuthManager authManager;
|
||||
private IntentData data;
|
||||
|
||||
private static class IntentData {
|
||||
private String accountName;
|
||||
private String accountType;
|
||||
private Account account;
|
||||
|
||||
private String packageName;
|
||||
private String service;
|
||||
|
||||
private int callerUid;
|
||||
private int callerPid;
|
||||
|
||||
private ConsentData consentData;
|
||||
private boolean fromAccountManager = false;
|
||||
|
||||
private CharSequence appLabel;
|
||||
private Drawable appIcon;
|
||||
|
||||
private IntentData(Intent intent) {
|
||||
if (intent != null) {
|
||||
accountName = intent.getStringExtra(KEY_ACCOUNT_NAME);
|
||||
accountType = intent.getStringExtra(KEY_ACCOUNT_TYPE);
|
||||
packageName = intent.getStringExtra(KEY_ANDROID_PACKAGE_NAME);
|
||||
service = intent.getStringExtra(KEY_AUTHTOKEN);
|
||||
callerUid = intent.getIntExtra(KEY_CALLER_UID, 0);
|
||||
callerPid = intent.getIntExtra(KEY_CALLER_PID, 0);
|
||||
fromAccountManager = intent.hasExtra(EXTRA_FROM_ACCOUNT_MANAGER);
|
||||
if (intent.hasExtra(EXTRA_CONSENT_DATA)) {
|
||||
try {
|
||||
consentData = ConsentData.ADAPTER.decode(intent.getByteArrayExtra(EXTRA_CONSENT_DATA));
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
if (accountName != null && accountType != null) {
|
||||
account = new Account(accountName, accountType);
|
||||
}
|
||||
}
|
||||
|
||||
private void verify(Context context) throws Exception {
|
||||
if (accountName == null || accountType == null || account == null) throw new IllegalArgumentException("Required account information missing");
|
||||
if (packageName == null || service == null) throw new IllegalArgumentException("Required request information missing");
|
||||
if (callerUid == 0) throw new IllegalArgumentException("Required caller information missing");
|
||||
PackageUtils.getAndCheckPackage(context, packageName, callerUid, callerPid);
|
||||
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0);
|
||||
appLabel = packageManager.getApplicationLabel(applicationInfo);
|
||||
appIcon = packageManager.getApplicationIcon(applicationInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.ask_permission);
|
||||
data = new IntentData(getIntent());
|
||||
try {
|
||||
data.verify(this);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Verification failed", e);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// This makes the dialog take up the full width
|
||||
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
|
||||
lp.copyFrom(getWindow().getAttributes());
|
||||
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
getWindow().setAttributes(lp);
|
||||
|
||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.cancel(data.packageName.hashCode());
|
||||
|
||||
authManager = new AuthManager(this, data.accountName, data.packageName, data.service);
|
||||
|
||||
Bitmap profileIcon = PeopleManager.getOwnerAvatarBitmap(this, data.accountName, false);
|
||||
|
||||
// receive profile icon
|
||||
if (profileIcon != null) {
|
||||
((ImageView) findViewById(R.id.account_photo)).setImageBitmap(profileIcon);
|
||||
} else {
|
||||
new Thread(() -> {
|
||||
final Bitmap profileIcon1 = PeopleManager.getOwnerAvatarBitmap(AskPermissionActivity.this, data.accountName, true);
|
||||
runOnUiThread(() -> ((ImageView) findViewById(R.id.account_photo)).setImageBitmap(profileIcon1));
|
||||
}).start();
|
||||
}
|
||||
|
||||
((ImageView) findViewById(R.id.app_icon)).setImageDrawable(data.appIcon);
|
||||
if (isOAuth()) {
|
||||
((TextView) findViewById(R.id.title)).setText(getString(R.string.ask_scope_permission_title, data.appLabel));
|
||||
} else {
|
||||
((TextView) findViewById(R.id.title)).setText(getString(R.string.ask_service_permission_title, data.appLabel));
|
||||
}
|
||||
findViewById(android.R.id.button1).setOnClickListener(v -> onAllow());
|
||||
findViewById(android.R.id.button2).setOnClickListener(v -> onDeny());
|
||||
((ListView) findViewById(R.id.permissions)).setAdapter(new PermissionAdapter());
|
||||
}
|
||||
|
||||
public void onAllow() {
|
||||
authManager.setPermitted(true);
|
||||
findViewById(android.R.id.button1).setEnabled(false);
|
||||
findViewById(android.R.id.button2).setEnabled(false);
|
||||
findViewById(R.id.progress_bar).setVisibility(VISIBLE);
|
||||
findViewById(R.id.no_progress_bar).setVisibility(GONE);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
AuthResponse response = authManager.requestAuthWithBackgroundResolution(data.fromAccountManager);
|
||||
Bundle result = new Bundle();
|
||||
result.putString(KEY_AUTHTOKEN, response.auth);
|
||||
result.putString(KEY_ACCOUNT_NAME, data.accountName);
|
||||
result.putString(KEY_ACCOUNT_TYPE, data.accountType);
|
||||
result.putString(KEY_ANDROID_PACKAGE_NAME, data.packageName);
|
||||
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
|
||||
setAccountAuthenticatorResult(result);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
finish();
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void onDeny() {
|
||||
authManager.setPermitted(false);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (data.packageName != null) {
|
||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.cancel(data.packageName.hashCode());
|
||||
}
|
||||
super.finish();
|
||||
}
|
||||
|
||||
private boolean isOAuth() {
|
||||
return data.service.startsWith("oauth2:") || data.service.startsWith("oauth:");
|
||||
}
|
||||
|
||||
private String getScopeLabel(String scope) {
|
||||
if (data.consentData != null) {
|
||||
for (ConsentData.ScopeDetails scopeDetails : data.consentData.scopes) {
|
||||
if (scope.equals(scopeDetails.id)) {
|
||||
return scopeDetails.title;
|
||||
}
|
||||
}
|
||||
}
|
||||
String labelResourceId = "permission_scope_";
|
||||
String escapedScope = scope.replace("/", "_").replace("-", "_");
|
||||
if (scope.startsWith("https://")) {
|
||||
labelResourceId += escapedScope.substring(8);
|
||||
} else {
|
||||
labelResourceId += escapedScope;
|
||||
}
|
||||
int labelResource = getResources().getIdentifier(labelResourceId, "string", getPackageName());
|
||||
if (labelResource != 0) {
|
||||
return getString(labelResource);
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private String getScopeDescription(String scope) {
|
||||
if (data.consentData != null) {
|
||||
for (ConsentData.ScopeDetails scopeDetails : data.consentData.scopes) {
|
||||
if (scope.equals(scopeDetails.id)) {
|
||||
return scopeDetails.description;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getServiceLabel(String service) {
|
||||
int labelResource = getResources().getIdentifier("permission_service_" + service + "_label", "string", getPackageName());
|
||||
if (labelResource != 0) {
|
||||
return getString(labelResource);
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private class PermissionAdapter extends BaseAdapter {
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
if (isOAuth()) {
|
||||
return data.service.split(" ").length;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItem(int position) {
|
||||
if (isOAuth()) {
|
||||
String tokens = data.service.split(":", 2)[1];
|
||||
return tokens.split(" ")[position];
|
||||
}
|
||||
return data.service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
String item = getItem(position);
|
||||
String label;
|
||||
String description;
|
||||
if (isOAuth()) {
|
||||
label = getScopeLabel(item);
|
||||
description = getScopeDescription(item);
|
||||
} else {
|
||||
label = getServiceLabel(item);
|
||||
description = null;
|
||||
}
|
||||
View view = convertView;
|
||||
if (view == null) {
|
||||
view = LayoutInflater.from(AskPermissionActivity.this)
|
||||
.inflate(R.layout.ask_permission_list_entry, parent, false);
|
||||
}
|
||||
((TextView) view.findViewById(android.R.id.text1)).setText(label);
|
||||
TextView textView = (TextView) view.findViewById(android.R.id.text2);
|
||||
if (description != null && !description.isEmpty()) {
|
||||
textView.setText(Html.fromHtml(description.trim().replace("\n", "<br>")));
|
||||
textView.setVisibility(VISIBLE);
|
||||
} else {
|
||||
textView.setVisibility(GONE);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,367 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.auth;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import org.microg.gms.accountaction.ErrorResolverKt;
|
||||
import org.microg.gms.accountaction.Resolution;
|
||||
import org.microg.gms.common.NotOkayException;
|
||||
import org.microg.gms.common.PackageUtils;
|
||||
import org.microg.gms.settings.SettingsContract;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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;
|
||||
|
||||
public class AuthManager {
|
||||
|
||||
private static final String TAG = "GmsAuthManager";
|
||||
public static final String PERMISSION_TREE_BASE = "com.google.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;
|
||||
public Map<Object, Object> dynamicFields = new HashMap<>();
|
||||
private final Context context;
|
||||
private final String accountName;
|
||||
private final String packageName;
|
||||
private final String service;
|
||||
private AccountManager accountManager;
|
||||
private Account account;
|
||||
private String packageSignature;
|
||||
private String accountType;
|
||||
|
||||
|
||||
private int delegationType;
|
||||
private String delegateeUserId;
|
||||
private String oauth2Foreground;
|
||||
private String oauth2Prompt;
|
||||
private String itCaveatTypes;
|
||||
private String tokenRequestOptions;
|
||||
public String includeEmail;
|
||||
public String includeProfile;
|
||||
public boolean isGmsApp;
|
||||
public boolean ignoreStoredPermission = false;
|
||||
public boolean forceRefreshToken = false;
|
||||
|
||||
public AuthManager(Context context, String accountName, String packageName, String service) {
|
||||
this.context = context;
|
||||
this.accountName = accountName;
|
||||
this.packageName = packageName;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public String getAccountType() {
|
||||
if (accountType == null)
|
||||
accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE;
|
||||
return accountType;
|
||||
}
|
||||
|
||||
public AccountManager getAccountManager() {
|
||||
if (accountManager == null)
|
||||
accountManager = AccountManager.get(context);
|
||||
return accountManager;
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
if (account == null)
|
||||
account = new Account(accountName, getAccountType());
|
||||
return account;
|
||||
}
|
||||
|
||||
public void setPackageSignature(String packageSignature) {
|
||||
this.packageSignature = packageSignature;
|
||||
}
|
||||
|
||||
public String getPackageSignature() {
|
||||
if (packageSignature == null)
|
||||
packageSignature = PackageUtils.firstSignatureDigest(context, packageName);
|
||||
return packageSignature;
|
||||
}
|
||||
|
||||
public String buildTokenKey(String service) {
|
||||
Uri.Builder builder = Uri.EMPTY.buildUpon();
|
||||
if (delegationType != 0 && delegateeUserId != null)
|
||||
builder.appendQueryParameter("delegation_type", Integer.toString(delegationType))
|
||||
.appendQueryParameter("delegatee_user_id", delegateeUserId);
|
||||
if (tokenRequestOptions != null) builder.appendQueryParameter("token_request_options", tokenRequestOptions);
|
||||
if (includeEmail != null) builder.appendQueryParameter("include_email", includeEmail);
|
||||
if (includeProfile != null) builder.appendQueryParameter("include_profile", includeEmail);
|
||||
String query = builder.build().getEncodedQuery();
|
||||
return packageName + ":" + getPackageSignature() + ":" + service + (query != null ? ("?" + query) : "");
|
||||
}
|
||||
|
||||
public String buildTokenKey() {
|
||||
return buildTokenKey(service);
|
||||
}
|
||||
|
||||
public String buildPermKey() {
|
||||
return "perm." + buildTokenKey();
|
||||
}
|
||||
|
||||
public void setPermitted(boolean value) {
|
||||
setUserData(buildPermKey(), value ? "1" : "0");
|
||||
if (SDK_INT >= 26 && value && packageName != null) {
|
||||
// Make account persistently visible as we already granted access
|
||||
accountManager.setAccountVisibility(getAccount(), packageName, AccountManager.VISIBILITY_VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPermitted() {
|
||||
if (!service.startsWith("oauth")) {
|
||||
if (context.getPackageManager().checkPermission(PERMISSION_TREE_BASE + service, packageName) == PackageManager.PERMISSION_GRANTED) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
String perm = getUserData(buildPermKey());
|
||||
if (!"1".equals(perm)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setExpiry(long expiry) {
|
||||
setUserData(buildExpireKey(), Long.toString(expiry));
|
||||
}
|
||||
|
||||
public String getUserData(String key) {
|
||||
return getAccountManager().getUserData(getAccount(), key);
|
||||
}
|
||||
|
||||
public void setUserData(String key, String value) {
|
||||
getAccountManager().setUserData(getAccount(), key, value);
|
||||
}
|
||||
|
||||
public void setDelegation(int delegationType, String delegateeUserId) {
|
||||
if (delegationType != 0 && delegateeUserId != null) {
|
||||
this.delegationType = delegationType;
|
||||
this.delegateeUserId = delegateeUserId;
|
||||
} else {
|
||||
this.delegationType = 0;
|
||||
this.delegateeUserId = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setOauth2Foreground(String oauth2Foreground) {
|
||||
this.oauth2Foreground = oauth2Foreground;
|
||||
}
|
||||
|
||||
public void setOauth2Prompt(String oauth2Prompt) {
|
||||
this.oauth2Prompt = oauth2Prompt;
|
||||
}
|
||||
|
||||
public void setItCaveatTypes(String itCaveatTypes) {
|
||||
this.itCaveatTypes = itCaveatTypes;
|
||||
}
|
||||
|
||||
public void setTokenRequestOptions(String tokenRequestOptions) {
|
||||
this.tokenRequestOptions = tokenRequestOptions;
|
||||
}
|
||||
|
||||
public void putDynamicFiled(Object key, Object value) {
|
||||
this.dynamicFields.put(key, value);
|
||||
}
|
||||
|
||||
public boolean accountExists() {
|
||||
for (Account refAccount : getAccountManager().getAccountsByType(accountType)) {
|
||||
if (refAccount.name.equalsIgnoreCase(accountName)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String peekAuthToken() {
|
||||
Log.d(TAG, "peekAuthToken: " + buildTokenKey());
|
||||
return getAccountManager().peekAuthToken(getAccount(), buildTokenKey());
|
||||
}
|
||||
|
||||
public String getAuthToken() {
|
||||
if (service.startsWith("weblogin:")) return null;
|
||||
if (System.currentTimeMillis() / 1000L >= getExpiry() - 300L) {
|
||||
Log.d(TAG, "token present, but expired");
|
||||
return null;
|
||||
}
|
||||
return peekAuthToken();
|
||||
}
|
||||
|
||||
public String buildExpireKey() {
|
||||
return "EXP." + buildTokenKey();
|
||||
}
|
||||
|
||||
public long getExpiry() {
|
||||
String exp = getUserData(buildExpireKey());
|
||||
if (exp == null) return -1;
|
||||
return Long.parseLong(exp);
|
||||
}
|
||||
|
||||
public void setAuthToken(String auth) {
|
||||
setAuthToken(service, auth);
|
||||
}
|
||||
|
||||
public void setAuthToken(String service, String auth) {
|
||||
getAccountManager().setAuthToken(getAccount(), buildTokenKey(service), auth);
|
||||
if (SDK_INT >= 26 && packageName != null && auth != null) {
|
||||
// Make account persistently visible as we already granted access
|
||||
accountManager.setAccountVisibility(getAccount(), packageName, AccountManager.VISIBILITY_VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public void invalidateAuthToken() {
|
||||
String authToken = peekAuthToken();
|
||||
invalidateAuthToken(authToken);
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
public void invalidateAuthToken(String auth) {
|
||||
getAccountManager().invalidateAuthToken(accountType, auth);
|
||||
}
|
||||
|
||||
public void storeResponse(AuthResponse response) {
|
||||
if (service.startsWith("weblogin:")) return;
|
||||
if (response.accountId != null)
|
||||
setUserData("GoogleUserId", response.accountId);
|
||||
if (response.Sid != null)
|
||||
setAuthToken("SID", response.Sid);
|
||||
if (response.LSid != null)
|
||||
setAuthToken("LSID", response.LSid);
|
||||
if (response.auth != null && (response.expiry != 0 || response.storeConsentRemotely)) {
|
||||
setAuthToken(response.auth);
|
||||
if (response.expiry > 0) {
|
||||
setExpiry(response.expiry);
|
||||
} else {
|
||||
setExpiry(System.currentTimeMillis() / 1000 + ONE_HOUR_IN_SECONDS); // make valid for one hour by default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSystemApp() {
|
||||
try {
|
||||
int flags = context.getPackageManager().getApplicationInfo(packageName, 0).flags;
|
||||
return (flags & FLAG_SYSTEM) > 0 || (flags & FLAG_UPDATED_SYSTEM_APP) > 0;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public AuthResponse requestAuthWithBackgroundResolution(boolean legacy) throws IOException {
|
||||
try {
|
||||
return requestAuth(legacy);
|
||||
} catch (NotOkayException e) {
|
||||
if (e.getMessage() != null) {
|
||||
Resolution errorResolution = ErrorResolverKt.resolveAuthErrorMessage(context, e.getMessage());
|
||||
if (errorResolution != null) {
|
||||
AuthResponse response = ErrorResolverKt.initiateFromBackgroundBlocking(
|
||||
errorResolution,
|
||||
context,
|
||||
getAccount(),
|
||||
// infinite loop is prevented
|
||||
() -> requestAuth(legacy)
|
||||
);
|
||||
if (response == null) throw new IOException(e);
|
||||
return response;
|
||||
} else {
|
||||
throw new IOException(e);
|
||||
}
|
||||
} else {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public AuthResponse requestAuthWithForegroundResolution(boolean legacy) throws IOException {
|
||||
try {
|
||||
return requestAuth(legacy);
|
||||
} catch (NotOkayException e) {
|
||||
if (e.getMessage() != null) {
|
||||
Resolution errorResolution = ErrorResolverKt.resolveAuthErrorMessage(context, e.getMessage());
|
||||
if (errorResolution != null) {
|
||||
AuthResponse response = ErrorResolverKt.initiateFromForegroundBlocking(
|
||||
errorResolution,
|
||||
context,
|
||||
getAccount(),
|
||||
// infinite loop is prevented
|
||||
() -> requestAuth(legacy)
|
||||
);
|
||||
if (response == null) throw new IOException(e);
|
||||
return response;
|
||||
} else {
|
||||
throw new IOException(e);
|
||||
}
|
||||
} else {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public AuthResponse requestAuth(boolean legacy) throws IOException {
|
||||
if (service.equals(AuthConstants.SCOPE_GET_ACCOUNT_ID)) {
|
||||
AuthResponse response = new AuthResponse();
|
||||
response.accountId = response.auth = getAccountManager().getUserData(getAccount(), "GoogleUserId");
|
||||
return response;
|
||||
}
|
||||
if (isPermitted() || isTrustGooglePermitted(context)) {
|
||||
String token = getAuthToken();
|
||||
if (token != null && !forceRefreshToken) {
|
||||
AuthResponse response = new AuthResponse();
|
||||
response.issueAdvice = "stored";
|
||||
response.auth = token;
|
||||
if (service.startsWith("oauth2:")) {
|
||||
response.grantedScopes = service.substring(7);
|
||||
}
|
||||
response.expiry = getExpiry();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
AuthRequest request = new AuthRequest().fromContext(context)
|
||||
.source("android")
|
||||
.app(packageName, getPackageSignature())
|
||||
.email(accountName)
|
||||
.token(getAccountManager().getPassword(getAccount()))
|
||||
.service(service)
|
||||
.delegation(delegationType, delegateeUserId)
|
||||
.oauth2Foreground(oauth2Foreground)
|
||||
.oauth2Prompt(oauth2Prompt)
|
||||
.oauth2IncludeProfile(includeProfile)
|
||||
.oauth2IncludeEmail(includeEmail)
|
||||
.itCaveatTypes(itCaveatTypes)
|
||||
.tokenRequestOptions(tokenRequestOptions)
|
||||
.systemPartition(isSystemApp())
|
||||
.hasPermission(!ignoreStoredPermission && isPermitted())
|
||||
.putDynamicFiledMap(dynamicFields);
|
||||
if (isGmsApp) {
|
||||
request.appIsGms();
|
||||
}
|
||||
if (legacy) {
|
||||
request.callerIsGms().calledFromAccountManager();
|
||||
} else {
|
||||
request.callerIsApp();
|
||||
}
|
||||
AuthResponse response = request.getResponse();
|
||||
if (!isPermitted() && !isTrustGooglePermitted(context)) {
|
||||
response.auth = null;
|
||||
} else {
|
||||
storeResponse(response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
public String getService() {
|
||||
return service;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* 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.auth;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.NotificationManager;
|
||||
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 androidx.core.app.PendingIntentCompat;
|
||||
|
||||
import com.google.android.auth.IAuthManagerService;
|
||||
import com.google.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.HasCapabilitiesRequest;
|
||||
import com.google.android.gms.auth.TokenData;
|
||||
import com.google.android.gms.common.api.Scope;
|
||||
|
||||
import org.microg.gms.common.GooglePackagePermission;
|
||||
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.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static android.accounts.AccountManager.*;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static org.microg.gms.auth.AskPermissionActivity.EXTRA_CONSENT_DATA;
|
||||
|
||||
public class AuthManagerServiceImpl extends IAuthManagerService.Stub {
|
||||
private static final String TAG = "GmsAuthManagerSvc";
|
||||
|
||||
public static final String KEY_ACCOUNT_FEATURES = "account_features";
|
||||
public static final String KEY_AUTHORITY = "authority";
|
||||
public static final String KEY_CALLBACK_INTENT = "callback_intent";
|
||||
public static final String KEY_CALLER_UID = "callerUid";
|
||||
public static final String KEY_ANDROID_PACKAGE_NAME = "androidPackageName";
|
||||
public static final String KEY_CLIENT_PACKAGE_NAME = "clientPackageName";
|
||||
public static final String KEY_HANDLE_NOTIFICATION = "handle_notification";
|
||||
public static final String KEY_REQUEST_ACTIONS = "request_visible_actions";
|
||||
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;
|
||||
|
||||
public AuthManagerServiceImpl(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getToken(String accountName, String scope, Bundle extras) {
|
||||
return getTokenWithAccount(new Account(accountName, AuthConstants.DEFAULT_ACCOUNT_TYPE), scope, extras);
|
||||
}
|
||||
|
||||
private List<Scope> getScopes(String scope) {
|
||||
if (!scope.startsWith("oauth2:")) return null;
|
||||
String[] strings = scope.substring(7).split(" ");
|
||||
List<Scope> res = new ArrayList<Scope>();
|
||||
for (String string : strings) {
|
||||
res.add(new Scope(string));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static CharSequence getPackageLabel(String packageName, PackageManager pm) {
|
||||
try {
|
||||
return pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return packageName;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountChangeEventsResponse getChangeEvents(AccountChangeEventsRequest request) {
|
||||
return new AccountChangeEventsResponse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) {
|
||||
String packageName = extras.getString(KEY_ANDROID_PACKAGE_NAME);
|
||||
if (packageName == null || packageName.isEmpty())
|
||||
packageName = extras.getString(KEY_CLIENT_PACKAGE_NAME);
|
||||
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);
|
||||
|
||||
scope = Objects.equals(AuthConstants.SCOPE_OAUTH2, scope) ? AuthConstants.SCOPE_EM_OP_PRO : scope;
|
||||
|
||||
if (!AuthConstants.SCOPE_GET_ACCOUNT_ID.equals(scope))
|
||||
Log.d(TAG, "getToken: account:" + account.name + " scope:" + scope + " extras:" + extras + ", notify: " + notify);
|
||||
|
||||
scope = Objects.equals(AuthConstants.SCOPE_OAUTH2, scope) ? AuthConstants.SCOPE_EM_OP_PRO : scope;
|
||||
|
||||
/*
|
||||
* TODO: This scope seems to be invalid (according to https://developers.google.com/oauthplayground/),
|
||||
* but is used in some applications anyway. Removing it is unlikely a good solution, but works for now.
|
||||
*/
|
||||
scope = scope.replace("https://www.googleapis.com/auth/identity.plus.page.impersonation ", "");
|
||||
|
||||
AuthManager authManager = new AuthManager(context, account.name, packageName, scope);
|
||||
if (extras.containsKey(KEY_DELEGATION_TYPE) && extras.getInt(KEY_DELEGATION_TYPE) != 0 ) {
|
||||
authManager.setDelegation(extras.getInt(KEY_DELEGATION_TYPE), extras.getString("delegatee_user_id"));
|
||||
}
|
||||
authManager.setOauth2Foreground(notify ? "0" : "1");
|
||||
Bundle result = new Bundle();
|
||||
result.putString(KEY_ACCOUNT_NAME, account.name);
|
||||
result.putString(KEY_ACCOUNT_TYPE, authManager.getAccountType());
|
||||
if (!authManager.accountExists()) {
|
||||
result.putString(KEY_ERROR, "NetworkError");
|
||||
return result;
|
||||
}
|
||||
try {
|
||||
AuthResponse res = authManager.requestAuthWithBackgroundResolution(false);
|
||||
if (res.auth != null) {
|
||||
if (!AuthConstants.SCOPE_GET_ACCOUNT_ID.equals(scope))
|
||||
Log.d(TAG, "getToken: " + res);
|
||||
result.putString(KEY_AUTHTOKEN, res.auth);
|
||||
Bundle details = new Bundle();
|
||||
details.putParcelable("TokenData", new TokenData(res.auth, res.expiry, scope.startsWith("oauth2:"), getScopes(res.grantedScopes != null ? res.grantedScopes : scope)));
|
||||
result.putBundle("tokenDetails", details);
|
||||
result.putString(KEY_ERROR, "OK");
|
||||
} else {
|
||||
result.putString(KEY_ERROR, "NeedPermission");
|
||||
Intent i = new Intent(context, AskPermissionActivity.class);
|
||||
i.putExtras(extras);
|
||||
i.putExtra(KEY_ANDROID_PACKAGE_NAME, packageName);
|
||||
i.putExtra(KEY_ACCOUNT_TYPE, authManager.getAccountType());
|
||||
i.putExtra(KEY_ACCOUNT_NAME, account.name);
|
||||
i.putExtra(KEY_AUTHTOKEN, scope);
|
||||
i.putExtra(KEY_CALLER_UID, getCallingUid());
|
||||
i.putExtra(KEY_CALLER_PID, getCallingPid());
|
||||
try {
|
||||
if (res.consentDataBase64 != null)
|
||||
i.putExtra(EXTRA_CONSENT_DATA, Base64.decode(res.consentDataBase64, Base64.URL_SAFE));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Can't decode consent data: ", e);
|
||||
}
|
||||
if (notify) {
|
||||
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.notify(packageName.hashCode(), new NotificationCompat.Builder(context)
|
||||
.setContentIntent(PendingIntentCompat.getActivity(context, 0, i, 0, false))
|
||||
.setContentTitle(context.getString(R.string.auth_notification_title))
|
||||
.setContentText(context.getString(R.string.auth_notification_content, getPackageLabel(packageName, context.getPackageManager())))
|
||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
.build());
|
||||
}
|
||||
result.putParcelable(KEY_USER_RECOVERY_INTENT, i);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
result.putString(KEY_ERROR, "NetworkError");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getAccounts(Bundle extras) {
|
||||
PackageUtils.assertGooglePackagePermission(context, GooglePackagePermission.ACCOUNT);
|
||||
String[] accountFeatures = extras.getStringArray(KEY_ACCOUNT_FEATURES);
|
||||
String accountType = extras.getString(KEY_ACCOUNT_TYPE);
|
||||
Account[] accounts;
|
||||
if (accountFeatures != null) {
|
||||
try {
|
||||
accounts = AccountManager.get(context).getAccountsByTypeAndFeatures(accountType, accountFeatures, null, null).getResult(5, TimeUnit.SECONDS);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
accounts = AccountManager.get(context).getAccountsByType(accountType);
|
||||
}
|
||||
Bundle res = new Bundle();
|
||||
res.putParcelableArray(KEY_ACCOUNTS, accounts);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle removeAccount(Account account) {
|
||||
Log.w(TAG, "Not implemented: removeAccount(" + account + ")");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle requestGoogleAccountsAccess(String packageName) throws RemoteException {
|
||||
PackageUtils.assertGooglePackagePermission(context, GooglePackagePermission.ACCOUNT);
|
||||
if (SDK_INT >= 26) {
|
||||
Map<Account, Integer> visibilityForPackage = get(context).getAccountsAndVisibilityForPackage(packageName, AuthConstants.DEFAULT_ACCOUNT_TYPE);
|
||||
for (Account account : get(context).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE)) {
|
||||
Integer visibility = visibilityForPackage.get(account);
|
||||
if (visibility != null && visibility != VISIBILITY_VISIBLE) {
|
||||
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(HasCapabilitiesRequest request) throws RemoteException {
|
||||
PackageUtils.assertGooglePackagePermission(context, GooglePackagePermission.ACCOUNT);
|
||||
List<String> services = Arrays.asList(AccountManager.get(context).getUserData(request.account, "services").split(","));
|
||||
for (String capability : request.capabilities) {
|
||||
if (capability.startsWith("service_") && !services.contains(capability.substring(8)) || !services.contains(capability)) {
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
Log.w(TAG, "Not fully implemented: hasCapabilities(" + request.account + ", " + Arrays.toString(request.capabilities) + ")");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GetHubTokenInternalResponse getHubToken(GetHubTokenRequest request, Bundle extras) throws RemoteException {
|
||||
Log.w(TAG, "Not implemented: getHubToken()");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("MissingPermission") // Workaround bug in Android Linter
|
||||
public Bundle clearToken(String token, Bundle extras) {
|
||||
String packageName = extras.getString(KEY_ANDROID_PACKAGE_NAME);
|
||||
if (packageName == null) packageName = extras.getString(KEY_CLIENT_PACKAGE_NAME);
|
||||
packageName = PackageUtils.getAndCheckCallingPackage(context, packageName, extras.getInt(KEY_CALLER_UID, 0), extras.getInt(KEY_CALLER_PID, 0));
|
||||
|
||||
Log.d(TAG, "clearToken: token:" + token + " extras:" + extras);
|
||||
AccountManager.get(context).invalidateAuthToken(AuthConstants.DEFAULT_ACCOUNT_TYPE, token);
|
||||
|
||||
Bundle res = new Bundle();
|
||||
res.putString("Error", "Ok");
|
||||
res.putBoolean("booleanResult", true);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
if (super.onTransact(code, data, reply, flags)) return true;
|
||||
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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.auth.login;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.google.android.gms.R;
|
||||
|
||||
public abstract class AssistantActivity extends AppCompatActivity {
|
||||
private static final int TITLE_MIN_HEIGHT = 64;
|
||||
private static final double TITLE_WIDTH_FACTOR = (8.0 / 18.0);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.login_assistant);
|
||||
formatTitle();
|
||||
findViewById(R.id.next_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onNextButtonClicked();
|
||||
}
|
||||
});
|
||||
findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onBackButtonClicked();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("WrongViewCast")
|
||||
private void formatTitle() {
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
double widthPixels = (double) (getResources().getDisplayMetrics().widthPixels);
|
||||
findViewById(R.id.title_container).getLayoutParams().height =
|
||||
(int) (dpToPx(TITLE_MIN_HEIGHT) + (TITLE_WIDTH_FACTOR * widthPixels));
|
||||
} else {
|
||||
findViewById(R.id.title_container).getLayoutParams().height = dpToPx(TITLE_MIN_HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
public void setNextButtonText(@StringRes int res) {
|
||||
setNextButtonText(getText(res));
|
||||
}
|
||||
|
||||
public void setNextButtonText(CharSequence text) {
|
||||
if (text == null) {
|
||||
findViewById(R.id.next_button).setVisibility(View.GONE);
|
||||
} else {
|
||||
findViewById(R.id.next_button).setVisibility(View.VISIBLE);
|
||||
((Button) findViewById(R.id.next_button)).setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
public void setBackButtonText(@StringRes int res) {
|
||||
setBackButtonText(getText(res));
|
||||
}
|
||||
|
||||
public void setBackButtonText(CharSequence text) {
|
||||
if (text == null) {
|
||||
findViewById(R.id.back_button).setVisibility(View.GONE);
|
||||
} else {
|
||||
findViewById(R.id.back_button).setVisibility(View.VISIBLE);
|
||||
((Button) findViewById(R.id.back_button)).setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onNextButtonClicked() {
|
||||
|
||||
}
|
||||
|
||||
protected void onBackButtonClicked() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
formatTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTitleChanged(CharSequence title, int color) {
|
||||
super.onTitleChanged(title, color);
|
||||
((TextView) findViewById(R.id.title)).setText(title);
|
||||
}
|
||||
|
||||
public int dpToPx(int dp) {
|
||||
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
|
||||
return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,727 @@
|
|||
/*
|
||||
* 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.auth.login;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountAuthenticatorResponse;
|
||||
import android.accounts.AccountManager;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.webkit.WebViewClientCompat;
|
||||
|
||||
import com.google.android.gms.R;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.microg.gms.accountaction.AccountNotificationKt;
|
||||
import org.microg.gms.auth.AuthConstants;
|
||||
import org.microg.gms.auth.AuthManager;
|
||||
import org.microg.gms.auth.AuthRequest;
|
||||
import org.microg.gms.auth.AuthResponse;
|
||||
import org.microg.gms.checkin.CheckinManager;
|
||||
import org.microg.gms.checkin.LastCheckinInfo;
|
||||
import org.microg.gms.common.Constants;
|
||||
import org.microg.gms.common.HttpFormClient;
|
||||
import org.microg.gms.common.Utils;
|
||||
import org.microg.gms.people.PeopleManager;
|
||||
import org.microg.gms.profile.Build;
|
||||
import org.microg.gms.profile.ProfileManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Locale;
|
||||
|
||||
import static android.accounts.AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE;
|
||||
import static android.accounts.AccountManager.VISIBILITY_USER_MANAGED_VISIBLE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.telephony.TelephonyManager.SIM_STATE_UNKNOWN;
|
||||
import static android.view.KeyEvent.KEYCODE_BACK;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
||||
import static org.microg.gms.auth.AuthPrefs.isAuthVisible;
|
||||
import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME;
|
||||
import static org.microg.gms.common.Constants.GMS_VERSION_CODE;
|
||||
import static org.microg.gms.common.Constants.VENDING_PACKAGE_NAME;
|
||||
import static org.microg.gms.gcm.ExtensionsKt.ACTION_GCM_REGISTER_ACCOUNT;
|
||||
import static org.microg.gms.gcm.ExtensionsKt.KEY_GCM_REGISTER_ACCOUNT_NAME;
|
||||
|
||||
public class LoginActivity extends AssistantActivity {
|
||||
public static final String TMPL_NEW_ACCOUNT = "new_account";
|
||||
public static final String EXTRA_TMPL = "tmpl";
|
||||
public static final String EXTRA_EMAIL = "email";
|
||||
public static final String EXTRA_TOKEN = "masterToken";
|
||||
public static final String EXTRA_RE_AUTH_ACCOUNT = "re_auth_account";
|
||||
public static final int STATUS_BAR_DISABLE_BACK = 0x00400000;
|
||||
|
||||
private static final String TAG = "GmsAuthLoginBrowser";
|
||||
private static final String EMBEDDED_SETUP_URL = "https://accounts.google.com/EmbeddedSetup";
|
||||
private static final String EMBEDDED_RE_AUTH_URL = "https://accounts.google.com/embedded/reauth/v2/android";
|
||||
private static final String PROGRAMMATIC_AUTH_URL = "https://accounts.google.com/o/oauth2/programmatic_auth";
|
||||
private static final String GOOGLE_SUITE_URL = "https://accounts.google.com/signin/continue";
|
||||
private static final String MAGIC_USER_AGENT = " MinuteMaid";
|
||||
private static final String COOKIE_OAUTH_TOKEN = "oauth_token";
|
||||
private static final String ACTION_UPDATE_ACCOUNT = "com.google.android.gms.auth.GOOGLE_ACCOUNT_CHANGE";
|
||||
private static final String PERMISSION_UPDATE_ACCOUNT = "com.google.android.gms.auth.permission.GOOGLE_ACCOUNT_CHANGE";
|
||||
|
||||
private final FidoHandler fidoHandler = new FidoHandler(this);
|
||||
private final DroidGuardHandler dgHandler = new DroidGuardHandler(this);
|
||||
|
||||
private WebView webView;
|
||||
private String accountType;
|
||||
private AccountManager accountManager;
|
||||
private AccountAuthenticatorResponse response;
|
||||
private InputMethodManager inputMethodManager;
|
||||
private ViewGroup authContent;
|
||||
private int state = 0;
|
||||
private boolean isReAuth = false;
|
||||
private Account reAuthAccount;
|
||||
|
||||
@SuppressLint("AddJavascriptInterface")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE;
|
||||
accountManager = AccountManager.get(LoginActivity.this);
|
||||
inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
webView = createWebView(this);
|
||||
webView.addJavascriptInterface(new JsBridge(), "mm");
|
||||
authContent = (ViewGroup) findViewById(R.id.auth_content);
|
||||
((ViewGroup) findViewById(R.id.auth_root)).addView(webView);
|
||||
webView.setWebViewClient(new WebViewClientCompat() {
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
Log.d(TAG, "pageFinished: " + view.getUrl());
|
||||
Uri uri = Uri.parse(view.getUrl());
|
||||
|
||||
// Begin login.
|
||||
// Only required if client code does not invoke showView() via JSBridge
|
||||
if ("identifier".equals(uri.getFragment()) || uri.getPath().endsWith("/identifier"))
|
||||
runOnUiThread(() -> webView.setVisibility(VISIBLE));
|
||||
|
||||
// Normal login.
|
||||
if ("close".equals(uri.getFragment()))
|
||||
closeWeb(false);
|
||||
|
||||
// Google Suite login.
|
||||
if (url.startsWith(GOOGLE_SUITE_URL))
|
||||
closeWeb(false);
|
||||
|
||||
// IDK when this is called.
|
||||
if (url.startsWith(PROGRAMMATIC_AUTH_URL))
|
||||
closeWeb(true);
|
||||
}
|
||||
});
|
||||
if(getIntent().hasExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE)){
|
||||
Object tempObject = getIntent().getExtras().get(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
|
||||
if (tempObject instanceof AccountAuthenticatorResponse) {
|
||||
response = (AccountAuthenticatorResponse) tempObject;
|
||||
}
|
||||
}
|
||||
if (getIntent().hasExtra(EXTRA_RE_AUTH_ACCOUNT)) {
|
||||
reAuthAccount = getIntent().getParcelableExtra(EXTRA_RE_AUTH_ACCOUNT);
|
||||
isReAuth = reAuthAccount != null;
|
||||
}
|
||||
if (getIntent().hasExtra(EXTRA_TOKEN)) {
|
||||
if (getIntent().hasExtra(EXTRA_EMAIL)) {
|
||||
AccountManager accountManager = AccountManager.get(this);
|
||||
Account account = new Account(getIntent().getStringExtra(EXTRA_EMAIL), accountType);
|
||||
accountManager.addAccountExplicitly(account, getIntent().getStringExtra(EXTRA_TOKEN), null);
|
||||
if (isAuthVisible(this) && SDK_INT >= 26) {
|
||||
accountManager.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, VISIBILITY_USER_MANAGED_VISIBLE);
|
||||
}
|
||||
retrieveGmsToken(account);
|
||||
} else {
|
||||
retrieveRtToken(getIntent().getStringExtra(EXTRA_TOKEN));
|
||||
}
|
||||
} else if (android.os.Build.VERSION.SDK_INT < 21 || isReAuth) {
|
||||
init();
|
||||
} else {
|
||||
setMessage(R.string.auth_before_connect);
|
||||
setBackButtonText(android.R.string.cancel);
|
||||
setNextButtonText(R.string.auth_sign_in);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNextButtonClicked() {
|
||||
super.onNextButtonClicked();
|
||||
state++;
|
||||
if (state == 1) {
|
||||
init();
|
||||
} else if (state == -1) {
|
||||
loginCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBackButtonClicked() {
|
||||
super.onBackButtonClicked();
|
||||
state--;
|
||||
if (state == -1) {
|
||||
loginCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
public void loginCanceled() {
|
||||
Log.d(TAG, "loginCanceled: ");
|
||||
setResult(RESULT_CANCELED);
|
||||
if (response != null) {
|
||||
response.onError(AccountManager.ERROR_CODE_CANCELED, "Canceled");
|
||||
}
|
||||
if (SDK_INT >= 21) { finishAndRemoveTask(); } else finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
loginCanceled();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setTitle(R.string.just_a_sec);
|
||||
setBackButtonText(null);
|
||||
setNextButtonText(null);
|
||||
View loading = getLayoutInflater().inflate(R.layout.login_assistant_loading, authContent, false);
|
||||
authContent.removeAllViews();
|
||||
authContent.addView(loading);
|
||||
setMessage(R.string.auth_connecting);
|
||||
CookieManager.getInstance().setAcceptCookie(true);
|
||||
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);
|
||||
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);
|
||||
prepareWebViewSettings(context, webView.getSettings());
|
||||
return webView;
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private static void prepareWebViewSettings(Context context, WebSettings settings) {
|
||||
ProfileManager.ensureInitialized(context);
|
||||
settings.setUserAgentString(Build.INSTANCE.generateWebViewUserAgentString(settings.getUserAgentString()) + MAGIC_USER_AGENT);
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setSupportMultipleWindows(false);
|
||||
settings.setSaveFormData(false);
|
||||
settings.setAllowFileAccess(false);
|
||||
settings.setDatabaseEnabled(false);
|
||||
settings.setNeedInitialFocus(false);
|
||||
settings.setUseWideViewPort(false);
|
||||
settings.setSupportZoom(false);
|
||||
settings.setJavaScriptCanOpenWindowsAutomatically(false);
|
||||
}
|
||||
|
||||
private void start() {
|
||||
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
|
||||
if (networkInfo != null && networkInfo.isConnected()) {
|
||||
if (LastCheckinInfo.read(this).getAndroidId() == 0) {
|
||||
new Thread(() -> {
|
||||
Runnable next;
|
||||
next = checkin(false) ? this::loadLoginPage : () -> showError(R.string.auth_general_error_desc);
|
||||
LoginActivity.this.runOnUiThread(next);
|
||||
}).start();
|
||||
} else {
|
||||
loadLoginPage();
|
||||
}
|
||||
} else {
|
||||
showError(R.string.no_network_error_desc);
|
||||
}
|
||||
}
|
||||
|
||||
private void showError(int errorRes) {
|
||||
setTitle(R.string.sorry);
|
||||
findViewById(R.id.progress_bar).setVisibility(View.INVISIBLE);
|
||||
setMessage(errorRes);
|
||||
}
|
||||
|
||||
private void setMessage(@StringRes int res) {
|
||||
setMessage(getText(res));
|
||||
}
|
||||
|
||||
private void setMessage(CharSequence text) {
|
||||
((TextView) findViewById(R.id.description_text)).setText(text);
|
||||
}
|
||||
|
||||
private void loadLoginPage() {
|
||||
String tmpl = getIntent().hasExtra(EXTRA_TMPL) ? getIntent().getStringExtra(EXTRA_TMPL) : TMPL_NEW_ACCOUNT;
|
||||
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));
|
||||
String cookies = CookieManager.getInstance().getCookie(programmaticAuth ? PROGRAMMATIC_AUTH_URL : EMBEDDED_SETUP_URL);
|
||||
String[] temp = cookies.split(";");
|
||||
for (String ar1 : temp) {
|
||||
if (ar1.trim().startsWith(COOKIE_OAUTH_TOKEN + "=")) {
|
||||
String[] temp1 = ar1.split("=");
|
||||
retrieveRtToken(temp1[1]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
showError(R.string.auth_general_error_desc);
|
||||
}
|
||||
|
||||
private void retrieveRtToken(String oAuthToken) {
|
||||
new AuthRequest().fromContext(this)
|
||||
.appIsGms()
|
||||
.callerIsGms()
|
||||
.service("ac2dm")
|
||||
.token(oAuthToken).isAccessToken()
|
||||
.addAccount()
|
||||
.getAccountId()
|
||||
.droidguardResults(null /*TODO*/)
|
||||
.getResponseAsync(new HttpFormClient.Callback<AuthResponse>() {
|
||||
@Override
|
||||
public void onResponse(AuthResponse response) {
|
||||
Account account = new Account(response.email, accountType);
|
||||
if (isReAuth && reAuthAccount != null && reAuthAccount.name.equals(account.name)) {
|
||||
accountManager.removeAccount(account, future -> saveAccount(account, response), null);
|
||||
} else {
|
||||
saveAccount(account, response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception exception) {
|
||||
Log.w(TAG, "onException", exception);
|
||||
runOnUiThread(() -> {
|
||||
showError(R.string.auth_general_error_desc);
|
||||
setNextButtonText(android.R.string.ok);
|
||||
});
|
||||
state = -2;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void saveAccount(Account account, AuthResponse response) {
|
||||
if (accountManager.addAccountExplicitly(account, response.token, null)) {
|
||||
accountManager.setAuthToken(account, "SID", response.Sid);
|
||||
accountManager.setAuthToken(account, "LSID", response.LSid);
|
||||
accountManager.setUserData(account, "flags", "1");
|
||||
accountManager.setUserData(account, "services", response.services);
|
||||
accountManager.setUserData(account, "oauthAccessToken", "1");
|
||||
accountManager.setUserData(account, "firstName", response.firstName);
|
||||
accountManager.setUserData(account, "lastName", response.lastName);
|
||||
if (!TextUtils.isEmpty(response.accountId))
|
||||
accountManager.setUserData(account, "GoogleUserId", response.accountId);
|
||||
|
||||
retrieveGmsToken(account);
|
||||
setResult(RESULT_OK);
|
||||
} else {
|
||||
Log.w(TAG, "Account NOT created!");
|
||||
runOnUiThread(() -> {
|
||||
showError(R.string.auth_general_error_desc);
|
||||
setNextButtonText(android.R.string.ok);
|
||||
});
|
||||
state = -2;
|
||||
}
|
||||
}
|
||||
|
||||
private void returnSuccessResponse(Account account){
|
||||
if (isReAuth && reAuthAccount != null) {
|
||||
AccountNotificationKt.cancelAccountNotificationChannel(this, reAuthAccount);
|
||||
}
|
||||
if(response != null){
|
||||
Bundle bd = new Bundle();
|
||||
bd.putString(AccountManager.KEY_ACCOUNT_NAME,account.name);
|
||||
bd.putBoolean("new_account_created",false);
|
||||
bd.putString(AccountManager.KEY_ACCOUNT_TYPE,accountType);
|
||||
response.onResult(bd);
|
||||
}
|
||||
Intent intent = new Intent(ACTION_UPDATE_ACCOUNT);
|
||||
intent.setPackage(VENDING_PACKAGE_NAME);
|
||||
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
|
||||
sendBroadcast(intent, PERMISSION_UPDATE_ACCOUNT);
|
||||
}
|
||||
private void retrieveGmsToken(final Account account) {
|
||||
final AuthManager authManager = new AuthManager(this, account.name, GMS_PACKAGE_NAME, "ac2dm");
|
||||
authManager.setPermitted(true);
|
||||
new AuthRequest().fromContext(this)
|
||||
.appIsGms()
|
||||
.callerIsGms()
|
||||
.service(authManager.getService())
|
||||
.email(account.name)
|
||||
.token(AccountManager.get(this).getPassword(account))
|
||||
.systemPartition(true)
|
||||
.hasPermission(true)
|
||||
.addAccount()
|
||||
.getAccountId()
|
||||
.getResponseAsync(new HttpFormClient.Callback<AuthResponse>() {
|
||||
@Override
|
||||
public void onResponse(AuthResponse response) {
|
||||
authManager.storeResponse(response);
|
||||
String accountId = PeopleManager.loadUserInfo(LoginActivity.this, account);
|
||||
if (!TextUtils.isEmpty(accountId))
|
||||
accountManager.setUserData(account, "GoogleUserId", accountId);
|
||||
if (isAuthVisible(LoginActivity.this) && SDK_INT >= 26) {
|
||||
accountManager.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, VISIBILITY_USER_MANAGED_VISIBLE);
|
||||
}
|
||||
checkin(true);
|
||||
returnSuccessResponse(account);
|
||||
notifyGcmGroupUpdate(account.name);
|
||||
if (SDK_INT >= 21) { finishAndRemoveTask(); } else finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception exception) {
|
||||
Log.w(TAG, "onException", exception);
|
||||
runOnUiThread(() -> {
|
||||
showError(R.string.auth_general_error_desc);
|
||||
setNextButtonText(android.R.string.ok);
|
||||
});
|
||||
state = -2;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyGcmGroupUpdate(String accountName) {
|
||||
Intent intent = new Intent(ACTION_GCM_REGISTER_ACCOUNT);
|
||||
intent.setPackage(Constants.GMS_PACKAGE_NAME);
|
||||
intent.putExtra(KEY_GCM_REGISTER_ACCOUNT_NAME, accountName);
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
|
||||
private boolean checkin(boolean force) {
|
||||
try {
|
||||
CheckinManager.checkin(LoginActivity.this, force);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Checkin failed", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if ((keyCode == KEYCODE_BACK) && webView.canGoBack() && (webView.getVisibility() == VISIBLE)) {
|
||||
webView.goBack();
|
||||
return true;
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
private String buildUrl(String tmpl, Locale locale) {
|
||||
String uriString = isReAuth ? EMBEDDED_RE_AUTH_URL : EMBEDDED_SETUP_URL;
|
||||
Uri.Builder builder = Uri.parse(uriString).buildUpon()
|
||||
.appendQueryParameter("source", "android")
|
||||
.appendQueryParameter("xoauth_display_name", "Android Device")
|
||||
.appendQueryParameter("lang", locale.getLanguage())
|
||||
.appendQueryParameter("cc", locale.getCountry().toLowerCase(Locale.US))
|
||||
.appendQueryParameter("langCountry", locale.toString().toLowerCase(Locale.US))
|
||||
.appendQueryParameter("hl", locale.toString().replace("_", "-"))
|
||||
.appendQueryParameter("tmpl", tmpl);
|
||||
if (isReAuth && reAuthAccount != null) {
|
||||
builder.appendQueryParameter("Email", reAuthAccount.name);
|
||||
}
|
||||
return builder.build().toString();
|
||||
}
|
||||
|
||||
private class JsBridge {
|
||||
|
||||
@JavascriptInterface
|
||||
public final void addAccount(String json) {
|
||||
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 final void cancelFido2SignRequest() {
|
||||
Log.d(TAG, "JSBridge: cancelFido2SignRequest");
|
||||
fidoHandler.cancel();
|
||||
}
|
||||
|
||||
@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");
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingPermission")
|
||||
@JavascriptInterface
|
||||
public final String getAccounts() {
|
||||
Log.d(TAG, "JSBridge: getAccounts");
|
||||
Account[] accountsByType = accountManager.getAccountsByType(accountType);
|
||||
JSONArray json = new JSONArray();
|
||||
for (Account account : accountsByType) {
|
||||
json.put(account.name);
|
||||
}
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final String getAllowedDomains() {
|
||||
Log.d(TAG, "JSBridge: getAllowedDomains");
|
||||
return new JSONArray().toString();
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final String getAndroidId() {
|
||||
long androidId = LastCheckinInfo.read(LoginActivity.this).getAndroidId();
|
||||
Log.d(TAG, "JSBridge: getAndroidId");
|
||||
if (androidId == 0 || androidId == -1) return null;
|
||||
return Long.toHexString(androidId);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final int getAuthModuleVersionCode() {
|
||||
return GMS_VERSION_CODE;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final int getBuildVersionSdk() {
|
||||
return Build.VERSION.SDK_INT;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public int getDeviceContactsCount() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final int getDeviceDataVersionInfo() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final void getDroidGuardResult(String s) {
|
||||
Log.d(TAG, "JSBridge: getDroidGuardResult");
|
||||
try {
|
||||
JSONArray array = new JSONArray(s);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getAndroidId()).append(":").append(getBuildVersionSdk()).append(":").append(getPlayServicesVersionCode());
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
sb.append(":").append(array.getString(i));
|
||||
}
|
||||
String dg = Base64.encodeToString(MessageDigest.getInstance("SHA1").digest(sb.toString().getBytes()), 0);
|
||||
dgHandler.start(dg);
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final String getFactoryResetChallenges() {
|
||||
return new JSONArray().toString();
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final String getPhoneNumber() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final int getPlayServicesVersionCode() {
|
||||
return GMS_VERSION_CODE;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final String getSimSerial() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final int getSimState() {
|
||||
return SIM_STATE_UNKNOWN;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final void goBack() {
|
||||
Log.d(TAG, "JSBridge: goBack");
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final boolean hasPhoneNumber() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final boolean hasTelephony() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final void hideKeyboard() {
|
||||
inputMethodManager.hideSoftInputFromWindow(webView.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final boolean isUserOwner() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final void launchEmergencyDialer() {
|
||||
Log.d(TAG, "JSBridge: launchEmergencyDialer");
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final void log(String s) {
|
||||
Log.d(TAG, "JSBridge: log");
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final void notifyOnTermsOfServiceAccepted() {
|
||||
Log.d(TAG, "JSBridge: notifyOnTermsOfServiceAccepted");
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final void sendFido2SkUiEvent(String event) {
|
||||
Log.d(TAG, "JSBridge: sendFido2SkUiEvent");
|
||||
fidoHandler.onEvent(event);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
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 setBackButtonEnabled(boolean backButtonEnabled) {
|
||||
int visibility = getWindow().getDecorView().getSystemUiVisibility();
|
||||
if (backButtonEnabled)
|
||||
visibility &= -STATUS_BAR_DISABLE_BACK;
|
||||
else
|
||||
visibility |= STATUS_BAR_DISABLE_BACK;
|
||||
getWindow().getDecorView().setSystemUiVisibility(visibility);
|
||||
}
|
||||
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final void showView() {
|
||||
runOnUiThread(() -> webView.setVisibility(VISIBLE));
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final void skipLogin() {
|
||||
Log.d(TAG, "JSBridge: skipLogin");
|
||||
loginCanceled();
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final void startAfw() {
|
||||
Log.d(TAG, "JSBridge: startAfw");
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public final void startFido2SignRequest(String request) {
|
||||
Log.d(TAG, "JSBridge: startFido2SignRequest");
|
||||
fidoHandler.startSignRequest(request);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* 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.auth.loginservice;
|
||||
|
||||
import android.accounts.AbstractAccountAuthenticator;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountAuthenticatorResponse;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.NetworkErrorException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.common.internal.CertData;
|
||||
import org.microg.gms.auth.*;
|
||||
import org.microg.gms.auth.login.LoginActivity;
|
||||
import org.microg.gms.common.PackageUtils;
|
||||
import org.microg.gms.auth.AuthResponse;
|
||||
import org.microg.gms.utils.PackageManagerUtilsKt;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static android.accounts.AccountManager.*;
|
||||
|
||||
public class AccountAuthenticator extends AbstractAccountAuthenticator {
|
||||
private static final String TAG = "GmsAuthenticator";
|
||||
public static final String KEY_OVERRIDE_PACKAGE = "overridePackage";
|
||||
public static final String KEY_OVERRIDE_CERTIFICATE = "overrideCertificate";
|
||||
private final Context context;
|
||||
private final String accountType;
|
||||
|
||||
public AccountAuthenticator(Context context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
this.accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
|
||||
Log.d(TAG, "editProperties: " + accountType);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
|
||||
if (accountType.equals(this.accountType)) {
|
||||
final Intent i = new Intent(context, LoginActivity.class);
|
||||
i.putExtras(options);
|
||||
i.putExtra(LoginActivity.EXTRA_TMPL, LoginActivity.TMPL_NEW_ACCOUNT);
|
||||
i.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
|
||||
final Bundle result = new Bundle();
|
||||
result.putParcelable(KEY_INTENT, i);
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
|
||||
Log.d(TAG, "confirmCredentials: " + account + ", " + options);
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isPackageOverrideAllowed(Account account, String requestingPackage, String overridePackage, CertData overrideCertificate) {
|
||||
// Always allow for self package
|
||||
if (requestingPackage.equals(context.getPackageName())) return true;
|
||||
// if (requestingPackage.equals("org.microg.example.authwithoverride")) return true;
|
||||
String requestingDigestString = PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(PackageManagerUtilsKt.getCertificates(context.getPackageManager(), requestingPackage).get(0), "SHA-256"), "");
|
||||
String overrideCertificateDigestString = PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCertificate, "SHA-256"), "");
|
||||
String overrideUserDataKey = "override." + requestingPackage + ":" + requestingDigestString + ":" + overridePackage + ":" + overrideCertificateDigestString;
|
||||
String hasOverride = AccountManager.get(context).getUserData(account, overrideUserDataKey);
|
||||
return "1".equals(hasOverride);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
|
||||
options.keySet();
|
||||
Log.d(TAG, "getAuthToken: " + account + ", " + authTokenType + ", " + options);
|
||||
String app = options.getString(KEY_ANDROID_PACKAGE_NAME);
|
||||
app = PackageUtils.getAndCheckPackage(context, app, options.getInt(KEY_CALLER_UID), options.getInt(KEY_CALLER_PID));
|
||||
AuthManager authManager;
|
||||
if (app == null) {
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(KEY_ERROR_CODE, ERROR_CODE_BAD_REQUEST);
|
||||
return result;
|
||||
}
|
||||
if (options.containsKey(KEY_OVERRIDE_PACKAGE) || options.containsKey(KEY_OVERRIDE_CERTIFICATE)) {
|
||||
String overridePackage = options.getString(KEY_OVERRIDE_PACKAGE, app);
|
||||
byte[] overrideCertificateBytes = options.getByteArray(KEY_OVERRIDE_CERTIFICATE);
|
||||
CertData overrideCert;
|
||||
if (overrideCertificateBytes != null) {
|
||||
overrideCert = new CertData(overrideCertificateBytes);
|
||||
} else {
|
||||
overrideCert = PackageManagerUtilsKt.getCertificates(context.getPackageManager(), app).get(0);
|
||||
}
|
||||
if (isPackageOverrideAllowed(account, app, overridePackage, overrideCert)) {
|
||||
authManager = new AuthManager(context, account.name, overridePackage, authTokenType);
|
||||
authManager.setPackageSignature(PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCert, "SHA1"), ""));
|
||||
} else {
|
||||
Bundle result = new Bundle();
|
||||
Intent i = new Intent(context, AskPackageOverrideActivity.class);
|
||||
i.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
|
||||
i.putExtra(KEY_ANDROID_PACKAGE_NAME, app);
|
||||
i.putExtra(KEY_ACCOUNT_TYPE, account.type);
|
||||
i.putExtra(KEY_ACCOUNT_NAME, account.name);
|
||||
i.putExtra(KEY_OVERRIDE_PACKAGE, overridePackage);
|
||||
i.putExtra(KEY_OVERRIDE_CERTIFICATE, overrideCert.getBytes());
|
||||
result.putParcelable(KEY_INTENT, i);
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
authManager = new AuthManager(context, account.name, app, authTokenType);
|
||||
}
|
||||
try {
|
||||
AuthResponse res = authManager.requestAuthWithBackgroundResolution(true);
|
||||
if (res.auth != null) {
|
||||
Log.d(TAG, "getAuthToken: " + res.auth);
|
||||
Bundle result = new Bundle();
|
||||
result.putString(KEY_ACCOUNT_TYPE, account.type);
|
||||
result.putString(KEY_ACCOUNT_NAME, account.name);
|
||||
result.putString(KEY_AUTHTOKEN, res.auth);
|
||||
return result;
|
||||
} else {
|
||||
Bundle result = new Bundle();
|
||||
Intent i = new Intent(context, AskPermissionActivity.class);
|
||||
i.putExtras(options);
|
||||
i.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
|
||||
i.putExtra(KEY_ANDROID_PACKAGE_NAME, app);
|
||||
i.putExtra(KEY_ACCOUNT_TYPE, account.type);
|
||||
i.putExtra(KEY_ACCOUNT_NAME, account.name);
|
||||
i.putExtra(KEY_AUTHTOKEN, authTokenType);
|
||||
try {
|
||||
if (res.consentDataBase64 != null)
|
||||
i.putExtra(AskPermissionActivity.EXTRA_CONSENT_DATA, Base64.decode(res.consentDataBase64, Base64.URL_SAFE));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Can't decode consent data: ", e);
|
||||
}
|
||||
result.putParcelable(KEY_INTENT, i);
|
||||
return result;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthTokenLabel(String authTokenType) {
|
||||
Log.d(TAG, "getAuthTokenLabel: " + authTokenType);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
|
||||
Log.d(TAG, "updateCredentials: " + account + ", " + authTokenType + ", " + options);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
|
||||
AccountManager accountManager = AccountManager.get(context);
|
||||
String services = accountManager.getUserData(account, "services");
|
||||
boolean res = true;
|
||||
if (services != null) {
|
||||
List<String> servicesList = Arrays.asList(services.split(","));
|
||||
for (String feature : features) {
|
||||
if (feature.startsWith("service_") && !servicesList.contains(feature.substring(8))) {
|
||||
Log.d(TAG, "Feature " + feature + " not supported");
|
||||
res = false;
|
||||
} else if (!feature.startsWith("service_") && !servicesList.contains(feature)) {
|
||||
Log.d(TAG, "Feature " + feature + " not supported");
|
||||
res = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res = false;
|
||||
}
|
||||
Bundle result = new Bundle();
|
||||
result.putBoolean(KEY_BOOLEAN_RESULT, res);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.auth.loginservice;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
import static android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT;
|
||||
|
||||
public class GoogleLoginService extends Service {
|
||||
private AccountAuthenticator authenticator;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
authenticator = new AccountAuthenticator(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
if (intent.getAction().equals(ACTION_AUTHENTICATOR_INTENT)) {
|
||||
return authenticator.getIBinder();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.car;
|
||||
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
|
||||
import org.microg.gms.BaseService;
|
||||
import org.microg.gms.common.GmsService;
|
||||
|
||||
public class CarService extends BaseService {
|
||||
public CarService() {
|
||||
super("GmsCarSvc", GmsService.CAR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.checkin;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.microg.gms.common.DeviceConfigProtoKt;
|
||||
import org.microg.gms.common.DeviceConfiguration;
|
||||
import org.microg.gms.common.DeviceIdentifier;
|
||||
import org.microg.gms.common.PhoneInfo;
|
||||
import org.microg.gms.common.Utils;
|
||||
import org.microg.gms.profile.Build;
|
||||
import org.microg.gms.profile.ProfileManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.TimeZone;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
public class CheckinClient {
|
||||
private static final String TAG = "GmsCheckinClient";
|
||||
private static final Object TODO = null; // TODO
|
||||
private static final List<String> TODO_LIST_STRING = new ArrayList<>(); // TODO
|
||||
private static final List<CheckinRequest.Checkin.Statistic> TODO_LIST_CHECKIN = new ArrayList<>(); // TODO
|
||||
private static final String SERVICE_URL = "https://android.clients.google.com/checkin";
|
||||
|
||||
public static CheckinResponse request(CheckinRequest request) throws IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(SERVICE_URL).openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setDoInput(true);
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestProperty("Content-type", "application/x-protobuffer");
|
||||
connection.setRequestProperty("Content-Encoding", "gzip");
|
||||
connection.setRequestProperty("Accept-Encoding", "gzip");
|
||||
connection.setRequestProperty("User-Agent", "Android-Checkin/2.0 (vbox86p JLS36G); gzip");
|
||||
|
||||
Log.d(TAG, "-- Request --\n" + request);
|
||||
OutputStream os = new GZIPOutputStream(connection.getOutputStream());
|
||||
os.write(request.encode());
|
||||
os.close();
|
||||
|
||||
if (connection.getResponseCode() != 200) {
|
||||
try {
|
||||
throw new IOException(new String(Utils.readStreamToEnd(new GZIPInputStream(connection.getErrorStream()))));
|
||||
} catch (Exception e) {
|
||||
throw new IOException(connection.getResponseMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
InputStream is = connection.getInputStream();
|
||||
CheckinResponse response = CheckinResponse.ADAPTER.decode(new GZIPInputStream(is));
|
||||
is.close();
|
||||
return response;
|
||||
}
|
||||
|
||||
public static CheckinRequest makeRequest(Context context, DeviceConfiguration deviceConfiguration,
|
||||
DeviceIdentifier deviceIdent, PhoneInfo phoneInfo,
|
||||
LastCheckinInfo checkinInfo, Locale locale,
|
||||
List<Account> accounts) {
|
||||
ProfileManager.ensureInitialized(context);
|
||||
CheckinRequest.Builder builder = new CheckinRequest.Builder()
|
||||
.accountCookie(new ArrayList<>())
|
||||
.androidId(checkinInfo.getAndroidId())
|
||||
.checkin(new CheckinRequest.Checkin.Builder()
|
||||
.build(new CheckinRequest.Checkin.Build.Builder()
|
||||
.bootloader(Build.BOOTLOADER)
|
||||
.brand(Build.BRAND)
|
||||
.clientId("android-google")
|
||||
.device(Build.DEVICE)
|
||||
.fingerprint(Build.FINGERPRINT)
|
||||
.hardware(Build.HARDWARE)
|
||||
.manufacturer(Build.MANUFACTURER)
|
||||
.model(Build.MODEL)
|
||||
.otaInstalled(false) // TODO?
|
||||
//.packageVersionCode(Constants.MAX_REFERENCE_VERSION)
|
||||
.product(Build.PRODUCT)
|
||||
.radio(Build.RADIO)
|
||||
.sdkVersion(Build.VERSION.SDK_INT)
|
||||
.time(Build.TIME / 1000)
|
||||
.build())
|
||||
.cellOperator(phoneInfo.cellOperator)
|
||||
.event(Collections.singletonList(new CheckinRequest.Checkin.Event.Builder()
|
||||
.tag(checkinInfo.getAndroidId() == 0 ? "event_log_start" : "system_update")
|
||||
.value_(checkinInfo.getAndroidId() == 0 ? null : "1536,0,-1,NULL")
|
||||
.timeMs(new Date().getTime())
|
||||
.build()))
|
||||
.lastCheckinMs(checkinInfo.getLastCheckin())
|
||||
.requestedGroup(TODO_LIST_STRING)
|
||||
.roaming(phoneInfo.roaming)
|
||||
.simOperator(phoneInfo.simOperator)
|
||||
.stat(TODO_LIST_CHECKIN)
|
||||
.userNumber(0)
|
||||
.build())
|
||||
.deviceConfiguration(DeviceConfigProtoKt.asProto(deviceConfiguration))
|
||||
.digest(checkinInfo.getDigest())
|
||||
.esn(deviceIdent.esn)
|
||||
.fragment(0)
|
||||
.locale(locale.toString())
|
||||
.loggingId(new Random().nextLong()) // TODO: static
|
||||
.meid(deviceIdent.meid)
|
||||
.otaCert(Collections.singletonList("71Q6Rn2DDZl1zPDVaaeEHItd"))
|
||||
.serial(Build.SERIAL != null && !Build.SERIAL.isEmpty() ? Build.SERIAL : null)
|
||||
.timeZone(TimeZone.getDefault().getID())
|
||||
.userName((String) TODO)
|
||||
.userSerialNumber((Integer) TODO)
|
||||
.version(3);
|
||||
for (Account account : accounts) {
|
||||
builder.accountCookie.add("[" + account.name + "]");
|
||||
builder.accountCookie.add(account.authToken);
|
||||
}
|
||||
if (builder.accountCookie.isEmpty()) builder.accountCookie.add("");
|
||||
if (deviceIdent.wifiMac != null) {
|
||||
builder.macAddress(Arrays.asList(deviceIdent.wifiMac))
|
||||
.macAddressType(Arrays.asList("wifi"));
|
||||
}
|
||||
if (checkinInfo.getSecurityToken() != 0) {
|
||||
builder.securityToken(checkinInfo.getSecurityToken())
|
||||
.fragment(1);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static class Account {
|
||||
public final String name;
|
||||
public final String authToken;
|
||||
|
||||
public Account(String accountName, String authToken) {
|
||||
this.name = accountName;
|
||||
this.authToken = authToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.checkin;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
|
||||
import org.microg.gms.auth.AuthConstants;
|
||||
import org.microg.gms.auth.AuthRequest;
|
||||
import org.microg.gms.common.Constants;
|
||||
import org.microg.gms.common.DeviceConfiguration;
|
||||
import org.microg.gms.common.Utils;
|
||||
import org.microg.gms.gservices.GServices;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CheckinManager {
|
||||
private static final long MIN_CHECKIN_INTERVAL = 3 * 60 * 60 * 1000; // 3 hours
|
||||
|
||||
@SuppressWarnings("MissingPermission")
|
||||
public static synchronized LastCheckinInfo checkin(Context context, boolean force) throws IOException {
|
||||
LastCheckinInfo info = LastCheckinInfo.read(context);
|
||||
if (!force && info.getLastCheckin() > System.currentTimeMillis() - MIN_CHECKIN_INTERVAL)
|
||||
return null;
|
||||
if (!CheckinPreferences.isEnabled(context))
|
||||
return null;
|
||||
List<CheckinClient.Account> accounts = new ArrayList<CheckinClient.Account>();
|
||||
AccountManager accountManager = AccountManager.get(context);
|
||||
String accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE;
|
||||
for (Account account : accountManager.getAccountsByType(accountType)) {
|
||||
String token = new AuthRequest()
|
||||
.email(account.name).token(accountManager.getPassword(account))
|
||||
.hasPermission(true).service("ac2dm")
|
||||
.app("com.google.android.gsf", Constants.GMS_PACKAGE_SIGNATURE_SHA1)
|
||||
.getResponse().LSid;
|
||||
if (token != null) {
|
||||
accounts.add(new CheckinClient.Account(account.name, token));
|
||||
}
|
||||
}
|
||||
CheckinRequest request = CheckinClient.makeRequest(context,
|
||||
new DeviceConfiguration(context), Utils.getDeviceIdentifier(context),
|
||||
Utils.getPhoneInfo(context), info, Utils.getLocale(context), accounts);
|
||||
return handleResponse(context, CheckinClient.request(request));
|
||||
}
|
||||
|
||||
private static LastCheckinInfo handleResponse(Context context, CheckinResponse response) {
|
||||
LastCheckinInfo info = new LastCheckinInfo(response);
|
||||
info.write(context);
|
||||
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
for (CheckinResponse.GservicesSetting setting : response.setting) {
|
||||
GServices.setString(resolver, setting.name.utf8(), setting.value_.utf8());
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.checkin;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.Activity;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.IntentService;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.PendingIntentCompat;
|
||||
import androidx.legacy.content.WakefulBroadcastReceiver;
|
||||
|
||||
import com.google.android.gms.checkin.internal.ICheckinService;
|
||||
|
||||
import org.microg.gms.auth.AuthConstants;
|
||||
import org.microg.gms.common.ForegroundServiceInfo;
|
||||
import org.microg.gms.common.ForegroundServiceContext;
|
||||
import org.microg.gms.gcm.McsService;
|
||||
import org.microg.gms.people.PeopleManager;
|
||||
|
||||
@ForegroundServiceInfo(value = "Google device registration", resName = "service_name_checkin", resPackage = "com.google.android.gms")
|
||||
public class CheckinService extends IntentService {
|
||||
private static final String TAG = "GmsCheckinSvc";
|
||||
public static final long MAX_VALID_CHECKIN_AGE = 24 * 60 * 60 * 1000; // 12 hours
|
||||
public static final long REGULAR_CHECKIN_INTERVAL = 12 * 60 * 60 * 1000; // 12 hours
|
||||
public static final long BACKUP_CHECKIN_DELAY = 3 * 60 * 60 * 1000; // 3 hours
|
||||
public static final String BIND_ACTION = "com.google.android.gms.checkin.BIND_TO_SERVICE";
|
||||
public static final String EXTRA_FORCE_CHECKIN = "force";
|
||||
@Deprecated
|
||||
public static final String EXTRA_CALLBACK_INTENT = "callback";
|
||||
public static final String EXTRA_RESULT_RECEIVER = "receiver";
|
||||
public static final String EXTRA_NEW_CHECKIN_TIME = "checkin_time";
|
||||
|
||||
private ICheckinService iface = new ICheckinService.Stub() {
|
||||
@Override
|
||||
public String getDeviceDataVersionInfo() throws RemoteException {
|
||||
return LastCheckinInfo.read(CheckinService.this).getDeviceDataVersionInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastCheckinSuccessTime() throws RemoteException {
|
||||
return LastCheckinInfo.read(CheckinService.this).getLastCheckin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastSimOperator() throws RemoteException {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
public CheckinService() {
|
||||
super(TAG);
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingPermission")
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
try {
|
||||
ForegroundServiceContext.completeForegroundService(this, intent, TAG);
|
||||
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()));
|
||||
String accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE;
|
||||
for (Account account : AccountManager.get(this).getAccountsByType(accountType)) {
|
||||
PeopleManager.loadUserInfo(this, account);
|
||||
}
|
||||
McsService.scheduleReconnect(this);
|
||||
if (intent.hasExtra(EXTRA_CALLBACK_INTENT)) {
|
||||
startService((Intent) intent.getParcelableExtra(EXTRA_CALLBACK_INTENT));
|
||||
}
|
||||
if (intent.hasExtra(EXTRA_RESULT_RECEIVER)) {
|
||||
ResultReceiver receiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
|
||||
if (receiver != null) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putLong(EXTRA_NEW_CHECKIN_TIME, info.getLastCheckin());
|
||||
receiver.send(Activity.RESULT_OK, bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
} finally {
|
||||
if (intent != null) {
|
||||
WakefulBroadcastReceiver.completeWakefulIntent(intent);
|
||||
}
|
||||
schedule(this);
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
if (BIND_ACTION.equals(intent.getAction())) {
|
||||
return iface.asBinder();
|
||||
} else {
|
||||
return super.onBind(intent);
|
||||
}
|
||||
}
|
||||
|
||||
static void schedule(Context context) {
|
||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
PendingIntent pendingIntent = PendingIntentCompat.getService(context, TriggerReceiver.class.getName().hashCode(), new Intent(context, TriggerReceiver.class), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT, false);
|
||||
alarmManager.set(AlarmManager.RTC, Math.max(LastCheckinInfo.read(context).getLastCheckin() + REGULAR_CHECKIN_INTERVAL, System.currentTimeMillis() + BACKUP_CHECKIN_DELAY), pendingIntent);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.checkin;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.NetworkRequest;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.PendingIntentCompat;
|
||||
import androidx.legacy.content.WakefulBroadcastReceiver;
|
||||
|
||||
import org.microg.gms.common.ForegroundServiceContext;
|
||||
|
||||
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;
|
||||
|
||||
public class TriggerReceiver extends WakefulBroadcastReceiver {
|
||||
private static final String TAG = "GmsCheckinTrigger";
|
||||
private static boolean registered = false;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
try {
|
||||
boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction());
|
||||
|
||||
if (CheckinPreferences.isEnabled(context) || force) {
|
||||
if (LastCheckinInfo.read(context).getLastCheckin() > System.currentTimeMillis() - REGULAR_CHECKIN_INTERVAL && !force) {
|
||||
CheckinService.schedule(context);
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
|
||||
if (networkInfo != null && networkInfo.isConnected() || force) {
|
||||
Intent subIntent = new Intent(context, CheckinService.class);
|
||||
subIntent.putExtra(EXTRA_FORCE_CHECKIN, force);
|
||||
startWakefulService(new ForegroundServiceContext(context), subIntent);
|
||||
} else if (SDK_INT >= 23) {
|
||||
// no network, register a network callback to retry when we have internet
|
||||
NetworkRequest networkRequest = new NetworkRequest.Builder()
|
||||
.addCapability(NET_CAPABILITY_INTERNET)
|
||||
.build();
|
||||
Intent i = new Intent(context, TriggerReceiver.class);
|
||||
PendingIntent pendingIntent = PendingIntentCompat.getBroadcast(context, 0, i, FLAG_UPDATE_CURRENT, true);
|
||||
cm.registerNetworkCallback(networkRequest, pendingIntent);
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Ignoring " + intent + ": checkin is disabled");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.drive.api;
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
|
||||
import org.microg.gms.BaseService;
|
||||
import org.microg.gms.common.GmsService;
|
||||
|
||||
public class DriveApiService extends BaseService {
|
||||
private DriveServiceImpl impl = new DriveServiceImpl();
|
||||
|
||||
public DriveApiService() {
|
||||
super("GmsDriveApiSvc", GmsService.DRIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
|
||||
callback.onPostInitComplete(0, impl.asBinder(), null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
/*
|
||||
* 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.drive.api;
|
||||
|
||||
import android.content.IntentSender;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.drive.internal.AddEventListenerRequest;
|
||||
import com.google.android.gms.drive.internal.AddPermissionRequest;
|
||||
import com.google.android.gms.drive.internal.AuthorizeAccessRequest;
|
||||
import com.google.android.gms.drive.internal.CancelPendingActionsRequest;
|
||||
import com.google.android.gms.drive.internal.ChangeResourceParentsRequest;
|
||||
import com.google.android.gms.drive.internal.CheckResourceIdsExistRequest;
|
||||
import com.google.android.gms.drive.internal.CloseContentsAndUpdateMetadataRequest;
|
||||
import com.google.android.gms.drive.internal.CloseContentsRequest;
|
||||
import com.google.android.gms.drive.internal.ControlProgressRequest;
|
||||
import com.google.android.gms.drive.internal.CreateContentsRequest;
|
||||
import com.google.android.gms.drive.internal.CreateFileIntentSenderRequest;
|
||||
import com.google.android.gms.drive.internal.CreateFileRequest;
|
||||
import com.google.android.gms.drive.internal.CreateFolderRequest;
|
||||
import com.google.android.gms.drive.internal.DeleteResourceRequest;
|
||||
import com.google.android.gms.drive.internal.DisconnectRequest;
|
||||
import com.google.android.gms.drive.internal.DriveServiceResponse;
|
||||
import com.google.android.gms.drive.internal.FetchThumbnailRequest;
|
||||
import com.google.android.gms.drive.internal.GetChangesRequest;
|
||||
import com.google.android.gms.drive.internal.GetDriveIdFromUniqueIdRequest;
|
||||
import com.google.android.gms.drive.internal.GetMetadataRequest;
|
||||
import com.google.android.gms.drive.internal.GetPermissionsRequest;
|
||||
import com.google.android.gms.drive.internal.IDriveService;
|
||||
import com.google.android.gms.drive.internal.IDriveServiceCallbacks;
|
||||
import com.google.android.gms.drive.internal.IEventCallback;
|
||||
import com.google.android.gms.drive.internal.ListParentsRequest;
|
||||
import com.google.android.gms.drive.internal.LoadRealtimeRequest;
|
||||
import com.google.android.gms.drive.internal.OpenContentsRequest;
|
||||
import com.google.android.gms.drive.internal.OpenFileIntentSenderRequest;
|
||||
import com.google.android.gms.drive.internal.RealtimeDocumentSyncRequest;
|
||||
import com.google.android.gms.drive.internal.RemoveEventListenerRequest;
|
||||
import com.google.android.gms.drive.internal.RemovePermissionRequest;
|
||||
import com.google.android.gms.drive.internal.SetDrivePreferencesRequest;
|
||||
import com.google.android.gms.drive.internal.SetFileUploadPreferencesRequest;
|
||||
import com.google.android.gms.drive.internal.SetResourceParentsRequest;
|
||||
import com.google.android.gms.drive.internal.StreamContentsRequest;
|
||||
import com.google.android.gms.drive.internal.TrashResourceRequest;
|
||||
import com.google.android.gms.drive.internal.UnsubscribeResourceRequest;
|
||||
import com.google.android.gms.drive.internal.UntrashResourceRequest;
|
||||
import com.google.android.gms.drive.internal.UpdateMetadataRequest;
|
||||
import com.google.android.gms.drive.internal.UpdatePermissionRequest;
|
||||
|
||||
public class DriveServiceImpl extends IDriveService.Stub {
|
||||
private static final String TAG = "GmsDriveSvcImpl";
|
||||
|
||||
@Override
|
||||
public void getMetadata(GetMetadataRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: getMetadata");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMetadata(UpdateMetadataRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: updateMetadata");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(CreateContentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: createContents");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createFile(CreateFileRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: createFile");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createFolder(CreateFolderRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: createFolder");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public DriveServiceResponse openContents(OpenContentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: openContents");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeContents(CloseContentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: closeContents");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestSync(IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: requestSync");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntentSender openFileIntentSender(OpenFileIntentSenderRequest request) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: openFileIntentSender");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntentSender createFileIntentSender(CreateFileIntentSenderRequest request) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: createFileIntentSender");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authorizeAccess(AuthorizeAccessRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: authorizeAccess");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listParents(ListParentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: listParents");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEventListener(AddEventListenerRequest request, IEventCallback callback, String unused, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: addEventListener");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEventListener(RemoveEventListenerRequest request, IEventCallback callback, String unused, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: removeEventListener");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(DisconnectRequest request) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: disconnect");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trashResource(TrashResourceRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: trashResource");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeContentsAndUpdateMetadata(CloseContentsAndUpdateMetadataRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: closeContentsAndUpdateMetadata");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteResource(DeleteResourceRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: deleteResource");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadRealtime(LoadRealtimeRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: loadRealtime");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceParents(SetResourceParentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: setResourceParents");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDriveIdFromUniqueId(GetDriveIdFromUniqueIdRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: getDriveIdFromUniqueId");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkResourceIdsExist(CheckResourceIdsExistRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: checkResourceIdsExist");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completePendingAction(IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: completePendingAction");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDrivePreferences(IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: getDrivePreferences");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDrivePreferences(SetDrivePreferencesRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: setDrivePreferences");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void realtimeDocumentSync(RealtimeDocumentSyncRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: realtimeDocumentSync");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDeviceUsagePreferences(IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: getDeviceUsagePreferences");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFileUploadPreferences(SetFileUploadPreferencesRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: setFileUploadPreferences");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelPendingActions(CancelPendingActionsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: cancelPendingActions");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void untrashResource(UntrashResourceRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: untrashResource");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isAutoBackupEnabled(IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: isAutoBackupEnabled");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchThumbnail(FetchThumbnailRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: fetchThumbnail");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getChanges(GetChangesRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: getChanges");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeResource(UnsubscribeResourceRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: unsubscribeResource");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getPermissions(GetPermissionsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: getPermissions");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPermission(AddPermissionRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: addPermission");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePermission(UpdatePermissionRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: updatePermission");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePermission(RemovePermissionRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: removePermission");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeQueryResultListener(IEventCallback callback, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: removeQueryResultListener");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void controlProgress(ControlProgressRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: controlProgress");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeResourceParents(ChangeResourceParentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: changeResourceParents");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public DriveServiceResponse streamContents(StreamContentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: streamContents");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.feeds;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
|
||||
public class SubscribedFeedsProvider extends ContentProvider{
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
return new MatrixCursor(new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.games;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import androidx.core.app.PendingIntentCompat;
|
||||
import com.google.android.gms.common.api.CommonStatusCodes;
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
|
||||
import org.microg.gms.BaseService;
|
||||
import org.microg.gms.common.GmsService;
|
||||
|
||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME;
|
||||
import static org.microg.gms.games.UpgradeActivity.ACTION_PLAY_GAMES_UPGRADE;
|
||||
import static org.microg.gms.games.UpgradeActivity.EXTRA_GAME_PACACKE_NAME;
|
||||
|
||||
public class GamesStubService extends BaseService {
|
||||
|
||||
public static final String PARAM_GAME_PACKAGE_NAME = "com.google.android.gms.games.key.gamePackageName";
|
||||
|
||||
public GamesStubService() {
|
||||
super("GmsGamesSvc", GmsService.GAMES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
|
||||
String packageName = null;
|
||||
if (request.extras != null) {
|
||||
packageName = request.extras.getString(PARAM_GAME_PACKAGE_NAME);
|
||||
}
|
||||
if (packageName == null) packageName = GMS_PACKAGE_NAME;
|
||||
Intent intent = new Intent(ACTION_PLAY_GAMES_UPGRADE);
|
||||
intent.setPackage(GMS_PACKAGE_NAME);
|
||||
intent.putExtra(EXTRA_GAME_PACACKE_NAME, packageName);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable("pendingIntent", PendingIntentCompat.getActivity(this, packageName.hashCode(), intent, FLAG_UPDATE_CURRENT, false));
|
||||
callback.onPostInitComplete(CommonStatusCodes.RESOLUTION_REQUIRED, null, bundle);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.games;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.gms.R;
|
||||
|
||||
public class UpgradeActivity extends Activity {
|
||||
public static final String ACTION_PLAY_GAMES_UPGRADE = "com.google.android.gms.games.PLAY_GAMES_UPGRADE";
|
||||
public static final String EXTRA_GAME_PACACKE_NAME = "com.google.android.gms.games.GAME_PACKAGE_NAME";
|
||||
|
||||
private static final String TAG = "GmsUpgActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.games_info);
|
||||
|
||||
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
|
||||
lp.copyFrom(getWindow().getAttributes());
|
||||
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
getWindow().setAttributes(lp);
|
||||
|
||||
String packageName = getIntent().getStringExtra(EXTRA_GAME_PACACKE_NAME);
|
||||
|
||||
// receive package info
|
||||
PackageManager packageManager = getPackageManager();
|
||||
ApplicationInfo applicationInfo;
|
||||
try {
|
||||
applicationInfo = packageManager.getApplicationInfo(packageName, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.w(TAG, e);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
CharSequence appLabel = packageManager.getApplicationLabel(applicationInfo);
|
||||
Drawable appIcon = packageManager.getApplicationIcon(applicationInfo);
|
||||
|
||||
((ImageView) findViewById(R.id.app_icon)).setImageDrawable(appIcon);
|
||||
((TextView) findViewById(R.id.title)).setText(getString(R.string.games_info_title, appLabel));
|
||||
findViewById(android.R.id.button1).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2016, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.gcm;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
public class GcmDatabase extends SQLiteOpenHelper {
|
||||
private static final String TAG = GcmDatabase.class.getSimpleName();
|
||||
public static final String DB_NAME = "gcmstatus";
|
||||
private static int DB_VERSION = 1;
|
||||
private static final String CREATE_TABLE_APPS = "CREATE TABLE apps (" +
|
||||
"package_name TEXT," +
|
||||
"last_error TEXT DEFAULT ''," +
|
||||
"last_message_timestamp INTEGER," +
|
||||
"total_message_count INTEGER," +
|
||||
"total_message_bytes INTEGER," +
|
||||
"allow_register INTEGER DEFAULT 1," +
|
||||
"wake_for_delivery INTEGER DEFAULT 1," +
|
||||
"PRIMARY KEY (package_name));";
|
||||
private static final String TABLE_APPS = "apps";
|
||||
private static final String FIELD_PACKAGE_NAME = "package_name";
|
||||
private static final String FIELD_LAST_ERROR = "last_error";
|
||||
private static final String FIELD_LAST_MESSAGE_TIMESTAMP = "last_message_timestamp";
|
||||
private static final String FIELD_TOTAL_MESSAGE_COUNT = "total_message_count";
|
||||
private static final String FIELD_TOTAL_MESSAGE_BYTES = "total_message_bytes";
|
||||
private static final String FIELD_ALLOW_REGISTER = "allow_register";
|
||||
private static final String FIELD_WAKE_FOR_DELIVERY = "wake_for_delivery";
|
||||
|
||||
private static final String CREATE_TABLE_REGISTRATIONS = "CREATE TABLE registrations (" +
|
||||
"package_name TEXT," +
|
||||
"signature TEXT," +
|
||||
"timestamp INTEGER," +
|
||||
"register_id TEXT," +
|
||||
"PRIMARY KEY (package_name, signature));";
|
||||
private static final String TABLE_REGISTRATIONS = "registrations";
|
||||
private static final String FIELD_SIGNATURE = "signature";
|
||||
private static final String FIELD_TIMESTAMP = "timestamp";
|
||||
private static final String FIELD_REGISTER_ID = "register_id";
|
||||
|
||||
private Context context;
|
||||
|
||||
public GcmDatabase(Context context) {
|
||||
super(context, DB_NAME, null, DB_VERSION);
|
||||
this.context = context;
|
||||
if (SDK_INT >= 16) {
|
||||
this.setWriteAheadLoggingEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class App {
|
||||
public final String packageName;
|
||||
public final String lastError;
|
||||
public final long lastMessageTimestamp;
|
||||
public final long totalMessageCount;
|
||||
public final long totalMessageBytes;
|
||||
public final boolean allowRegister;
|
||||
public final boolean wakeForDelivery;
|
||||
|
||||
private App(Cursor cursor) {
|
||||
packageName = cursor.getString(cursor.getColumnIndexOrThrow(FIELD_PACKAGE_NAME));
|
||||
lastError = cursor.getString(cursor.getColumnIndexOrThrow(FIELD_LAST_ERROR));
|
||||
lastMessageTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(FIELD_LAST_MESSAGE_TIMESTAMP));
|
||||
totalMessageCount = cursor.getLong(cursor.getColumnIndexOrThrow(FIELD_TOTAL_MESSAGE_COUNT));
|
||||
totalMessageBytes = cursor.getLong(cursor.getColumnIndexOrThrow(FIELD_TOTAL_MESSAGE_BYTES));
|
||||
allowRegister = cursor.getLong(cursor.getColumnIndexOrThrow(FIELD_ALLOW_REGISTER)) == 1;
|
||||
wakeForDelivery = cursor.getLong(cursor.getColumnIndexOrThrow(FIELD_WAKE_FOR_DELIVERY)) == 1;
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return !TextUtils.isEmpty(lastError);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Registration {
|
||||
public final String packageName;
|
||||
public final String signature;
|
||||
public final long timestamp;
|
||||
public final String registerId;
|
||||
|
||||
public Registration(Cursor cursor) {
|
||||
packageName = cursor.getString(cursor.getColumnIndexOrThrow(FIELD_PACKAGE_NAME));
|
||||
signature = cursor.getString(cursor.getColumnIndexOrThrow(FIELD_SIGNATURE));
|
||||
timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(FIELD_TIMESTAMP));
|
||||
registerId = cursor.getString(cursor.getColumnIndexOrThrow(FIELD_REGISTER_ID));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(CREATE_TABLE_APPS);
|
||||
db.execSQL(CREATE_TABLE_REGISTRATIONS);
|
||||
importLegacyData(db);
|
||||
}
|
||||
|
||||
public synchronized List<App> getAppList() {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(TABLE_APPS, null, null, null, null, null, null);
|
||||
if (cursor != null) {
|
||||
List<App> result = new ArrayList<>();
|
||||
while (cursor.moveToNext()) {
|
||||
result.add(new App(cursor));
|
||||
}
|
||||
cursor.close();
|
||||
return result;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public synchronized List<Registration> getRegistrationList() {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(TABLE_REGISTRATIONS, null, null, null, null, null, null);
|
||||
if (cursor != null) {
|
||||
List<Registration> result = new ArrayList<>();
|
||||
while (cursor.moveToNext()) {
|
||||
result.add(new Registration(cursor));
|
||||
}
|
||||
cursor.close();
|
||||
return result;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
public synchronized List<Registration> getRegistrationsByApp(String packageName) {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(TABLE_REGISTRATIONS, null, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName}, null, null, null);
|
||||
if (cursor != null) {
|
||||
List<Registration> result = new ArrayList<>();
|
||||
while (cursor.moveToNext()) {
|
||||
result.add(new Registration(cursor));
|
||||
}
|
||||
cursor.close();
|
||||
return result;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public synchronized void setAppAllowRegister(String packageName, boolean allowRegister) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(FIELD_ALLOW_REGISTER, allowRegister ? 1 : 0);
|
||||
db.update(TABLE_APPS, cv, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName});
|
||||
}
|
||||
|
||||
public synchronized void setAppWakeForDelivery(String packageName, boolean wakeForDelivery) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(FIELD_WAKE_FOR_DELIVERY, wakeForDelivery ? 1 : 0);
|
||||
db.update(TABLE_APPS, cv, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName});
|
||||
}
|
||||
|
||||
|
||||
public synchronized void removeApp(String packageName) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.delete(TABLE_REGISTRATIONS, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName});
|
||||
db.delete(TABLE_APPS, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName});
|
||||
}
|
||||
|
||||
public synchronized void noteAppRegistrationError(String packageName, String error) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(FIELD_LAST_ERROR, error);
|
||||
db.update(TABLE_APPS, cv, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName});
|
||||
}
|
||||
|
||||
public synchronized void noteAppKnown(String packageName, boolean allowRegister) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.beginTransaction();
|
||||
|
||||
App app = getApp(db, packageName);
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(FIELD_ALLOW_REGISTER, allowRegister);
|
||||
if (app == null) {
|
||||
cv.put(FIELD_PACKAGE_NAME, packageName);
|
||||
db.insert(TABLE_APPS, null, cv);
|
||||
} else {
|
||||
db.update(TABLE_APPS, cv, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName});
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
public synchronized void noteAppMessage(String packageName, int numBytes) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.beginTransaction();
|
||||
|
||||
App app = getApp(db, packageName);
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(FIELD_LAST_MESSAGE_TIMESTAMP, System.currentTimeMillis());
|
||||
if (app == null) {
|
||||
cv.put(FIELD_PACKAGE_NAME, packageName);
|
||||
cv.put(FIELD_TOTAL_MESSAGE_COUNT, 1);
|
||||
cv.put(FIELD_TOTAL_MESSAGE_BYTES, numBytes);
|
||||
db.insert(TABLE_APPS, null, cv);
|
||||
} else {
|
||||
cv.put(FIELD_TOTAL_MESSAGE_COUNT, app.totalMessageCount + 1);
|
||||
cv.put(FIELD_TOTAL_MESSAGE_BYTES, app.totalMessageBytes + numBytes);
|
||||
db.update(TABLE_APPS, cv, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName});
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
public synchronized void noteAppRegistered(String packageName, String signature, String registrationId) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.beginTransaction();
|
||||
|
||||
App app = getApp(db, packageName);
|
||||
if (app == null) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(FIELD_PACKAGE_NAME, packageName);
|
||||
db.insert(TABLE_APPS, null, cv);
|
||||
} else {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(FIELD_LAST_ERROR, "");
|
||||
db.update(TABLE_APPS, cv, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName});
|
||||
}
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(FIELD_PACKAGE_NAME, packageName);
|
||||
cv.put(FIELD_SIGNATURE, signature);
|
||||
cv.put(FIELD_REGISTER_ID, registrationId);
|
||||
cv.put(FIELD_TIMESTAMP, System.currentTimeMillis());
|
||||
db.insertWithOnConflict(TABLE_REGISTRATIONS, null, cv, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
public synchronized void noteAppUnregistered(String packageName, String signature) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.delete(TABLE_REGISTRATIONS, FIELD_PACKAGE_NAME + " LIKE ? AND " + FIELD_SIGNATURE + " LIKE ?", new String[]{packageName, signature});
|
||||
}
|
||||
|
||||
public App getApp(String packageName) {
|
||||
return getApp(getReadableDatabase(), packageName);
|
||||
}
|
||||
|
||||
private App getApp(SQLiteDatabase db, String packageName) {
|
||||
Cursor cursor = db.query(TABLE_APPS, null, FIELD_PACKAGE_NAME + " LIKE ?", new String[]{packageName}, null, null, null, "1");
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToNext()) {
|
||||
return new App(cursor);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Registration getRegistration(String packageName, String signature) {
|
||||
return getRegistration(getReadableDatabase(), packageName, signature);
|
||||
}
|
||||
|
||||
private Registration getRegistration(SQLiteDatabase db, String packageName, String signature) {
|
||||
Cursor cursor = db.query(TABLE_REGISTRATIONS, null, FIELD_PACKAGE_NAME + " LIKE ? AND " + FIELD_SIGNATURE + " LIKE ?", new String[]{packageName, signature}, null, null, null, "1");
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToNext()) {
|
||||
return new Registration(cursor);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void importLegacyData(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
|
||||
GcmLegacyData legacyData = new GcmLegacyData(context);
|
||||
for (GcmLegacyData.LegacyAppInfo appInfo : legacyData.getAppsInfo()) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(FIELD_PACKAGE_NAME, appInfo.app);
|
||||
cv.put(FIELD_TOTAL_MESSAGE_COUNT, legacyData.getAppMessageCount(appInfo.app));
|
||||
cv.put(FIELD_LAST_ERROR, appInfo.hasUnregistrationError() ? "Unregistration error" : null);
|
||||
db.insert(TABLE_APPS, null, cv);
|
||||
cv.clear();
|
||||
if (appInfo.isRegistered()) {
|
||||
cv.put(FIELD_PACKAGE_NAME, appInfo.app);
|
||||
cv.put(FIELD_SIGNATURE, appInfo.appSignature);
|
||||
cv.put(FIELD_REGISTER_ID, appInfo.registerID);
|
||||
db.insert(TABLE_REGISTRATIONS, null, cv);
|
||||
}
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
throw new IllegalStateException("Upgrades not supported");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.gcm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Deprecated
|
||||
public class GcmLegacyData {
|
||||
private static final String GCM_REGISTRATION_PREF = "gcm_registrations";
|
||||
private static final String GCM_MESSAGES_PREF = "gcm_messages";
|
||||
|
||||
static final String REMOVED = "%%REMOVED%%";
|
||||
static final String ERROR = "%%ERROR%%";
|
||||
|
||||
private Context context;
|
||||
|
||||
public GcmLegacyData(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static class LegacyAppInfo implements Comparable<LegacyAppInfo> {
|
||||
public String app = null;
|
||||
public String appSignature = null;
|
||||
public String registerID = null;
|
||||
|
||||
private final int STATE_ERROR = 1;
|
||||
private final int STATE_REMOVED = 2;
|
||||
private final int STATE_REGISTERED = 3;
|
||||
private int state;
|
||||
|
||||
public LegacyAppInfo(String key, String value) {
|
||||
if (ERROR.equals(value)) {
|
||||
state = STATE_ERROR;
|
||||
} else if (REMOVED.equals(value)) {
|
||||
state = STATE_REMOVED;
|
||||
} else {
|
||||
state = STATE_REGISTERED;
|
||||
registerID = value;
|
||||
}
|
||||
String[] splitKey = key.split(":");
|
||||
app = splitKey[0];
|
||||
appSignature = splitKey[1];
|
||||
}
|
||||
|
||||
public boolean isRegistered() {
|
||||
return state == STATE_REGISTERED;
|
||||
}
|
||||
|
||||
public boolean isRemoved() {
|
||||
return state == STATE_REMOVED;
|
||||
}
|
||||
|
||||
public boolean hasUnregistrationError() {
|
||||
return state == STATE_ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(LegacyAppInfo another) {
|
||||
return app.compareTo(another.app);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public int getAppMessageCount(String app) {
|
||||
return getStatsSharedPreferences().getInt(app, 0);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public List<LegacyAppInfo> getAppsInfo() {
|
||||
ArrayList<LegacyAppInfo> ret = new ArrayList<>();
|
||||
Set<String> keys = getInfoSharedPreferences().getAll().keySet();
|
||||
for (String key : keys) {
|
||||
ret.add(getAppInfo(key));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private LegacyAppInfo getAppInfo(String key) {
|
||||
return new LegacyAppInfo(key, getInfoSharedPreferences().getString(key, ""));
|
||||
}
|
||||
|
||||
private SharedPreferences getInfoSharedPreferences() {
|
||||
return context.getSharedPreferences(GCM_REGISTRATION_PREF, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
private SharedPreferences getStatsSharedPreferences() {
|
||||
return context.getSharedPreferences(GCM_MESSAGES_PREF, Context.MODE_PRIVATE);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.gcm;
|
||||
|
||||
public final class McsConstants {
|
||||
public static final int MCS_HEARTBEAT_PING_TAG = 0;
|
||||
public static final int MCS_HEARTBEAT_ACK_TAG = 1;
|
||||
public static final int MCS_LOGIN_REQUEST_TAG = 2;
|
||||
public static final int MCS_LOGIN_RESPONSE_TAG = 3;
|
||||
public static final int MCS_CLOSE_TAG = 4;
|
||||
public static final int MCS_IQ_STANZA_TAG = 7;
|
||||
public static final int MCS_DATA_MESSAGE_STANZA_TAG = 8;
|
||||
|
||||
public static final int MCS_VERSION_CODE = 41;
|
||||
|
||||
public static final int MSG_INPUT = 10;
|
||||
public static final int MSG_INPUT_ERROR = 11;
|
||||
public static final int MSG_OUTPUT = 20;
|
||||
public static final int MSG_OUTPUT_ERROR = 21;
|
||||
public static final int MSG_OUTPUT_READY = 22;
|
||||
public static final int MSG_OUTPUT_DONE = 23;
|
||||
public static final int MSG_TEARDOWN = 30;
|
||||
public static final int MSG_CONNECT = 40;
|
||||
public static final int MSG_HEARTBEAT = 41;
|
||||
public static final int MSG_ACK = 42;
|
||||
|
||||
public static String ACTION_CONNECT = "org.microg.gms.gcm.mcs.CONNECT";
|
||||
public static String ACTION_RECONNECT = "org.microg.gms.gcm.mcs.RECONNECT";
|
||||
public static String ACTION_HEARTBEAT = "org.microg.gms.gcm.mcs.HEARTBEAT";
|
||||
public static String ACTION_SEND = "org.microg.gms.gcm.mcs.SEND";
|
||||
public static String ACTION_ACK = "org.microg.gms.gcm.mcs.ACK";
|
||||
public static String EXTRA_REASON = "org.microg.gms.gcm.mcs.REASON";
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.gcm;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import com.squareup.wire.Message;
|
||||
|
||||
import org.microg.gms.gcm.mcs.Close;
|
||||
import org.microg.gms.gcm.mcs.DataMessageStanza;
|
||||
import org.microg.gms.gcm.mcs.HeartbeatAck;
|
||||
import org.microg.gms.gcm.mcs.HeartbeatPing;
|
||||
import org.microg.gms.gcm.mcs.IqStanza;
|
||||
import org.microg.gms.gcm.mcs.LoginRequest;
|
||||
import org.microg.gms.gcm.mcs.LoginResponse;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
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_INPUT;
|
||||
import static org.microg.gms.gcm.McsConstants.MSG_INPUT_ERROR;
|
||||
import static org.microg.gms.gcm.McsConstants.MSG_TEARDOWN;
|
||||
|
||||
public class McsInputStream extends Thread implements Closeable {
|
||||
private static final String TAG = "GmsGcmMcsInput";
|
||||
|
||||
private final InputStream is;
|
||||
private final Handler mainHandler;
|
||||
|
||||
private boolean initialized;
|
||||
private int version = -1;
|
||||
private int lastStreamIdReported = -1;
|
||||
private int streamId = 0;
|
||||
private long lastMsgTime = 0;
|
||||
|
||||
private volatile boolean closed = false;
|
||||
|
||||
public McsInputStream(InputStream is, Handler mainHandler) {
|
||||
this(is, mainHandler, false);
|
||||
}
|
||||
|
||||
public McsInputStream(InputStream is, Handler mainHandler, boolean initialized) {
|
||||
this.is = is;
|
||||
this.mainHandler = mainHandler;
|
||||
this.initialized = initialized;
|
||||
setName("McsInputStream");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted() && !closed) {
|
||||
android.os.Message msg = read();
|
||||
if (msg != null) {
|
||||
mainHandler.dispatchMessage(msg);
|
||||
} else {
|
||||
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_TEARDOWN, "null message"));
|
||||
break; // if input is empty, do not continue looping
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (closed) {
|
||||
Log.d(TAG, "We were closed already. Ignoring IOException");
|
||||
} else {
|
||||
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_INPUT_ERROR, e));
|
||||
}
|
||||
}
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (!closed) {
|
||||
closed = true;
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public int getStreamId() {
|
||||
lastStreamIdReported = streamId;
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public boolean newStreamIdAvailable() {
|
||||
return lastStreamIdReported != streamId;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
ensureVersionRead();
|
||||
return version;
|
||||
}
|
||||
|
||||
private synchronized void ensureVersionRead() {
|
||||
if (!initialized) {
|
||||
try {
|
||||
version = is.read();
|
||||
Log.d(TAG, "Reading from MCS version: " + version);
|
||||
initialized = true;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Error reading version", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized android.os.Message read() throws IOException {
|
||||
ensureVersionRead();
|
||||
int mcsTag = is.read();
|
||||
int mcsSize = readVarint();
|
||||
if (mcsTag < 0 || mcsSize < 0) {
|
||||
Log.w(TAG, "mcsTag: " + mcsTag + " mcsSize: " + mcsSize);
|
||||
return null;
|
||||
}
|
||||
byte[] bytes = new byte[mcsSize];
|
||||
int len = 0, read = 0;
|
||||
while (len < mcsSize && read >= 0) {
|
||||
len += (read = is.read(bytes, len, mcsSize - len)) < 0 ? 0 : read;
|
||||
}
|
||||
Message message = read(mcsTag, bytes, len);
|
||||
if (message == null) return null;
|
||||
Log.d(TAG, "Incoming message: " + message);
|
||||
streamId++;
|
||||
return mainHandler.obtainMessage(MSG_INPUT, mcsTag, streamId, message);
|
||||
}
|
||||
|
||||
private static Message read(int mcsTag, byte[] bytes, int len) throws IOException {
|
||||
try {
|
||||
switch (mcsTag) {
|
||||
case MCS_HEARTBEAT_PING_TAG:
|
||||
return HeartbeatPing.ADAPTER.decode(bytes);
|
||||
case MCS_HEARTBEAT_ACK_TAG:
|
||||
return HeartbeatAck.ADAPTER.decode(bytes);
|
||||
case MCS_LOGIN_REQUEST_TAG:
|
||||
return LoginRequest.ADAPTER.decode(bytes);
|
||||
case MCS_LOGIN_RESPONSE_TAG:
|
||||
return LoginResponse.ADAPTER.decode(bytes);
|
||||
case MCS_CLOSE_TAG:
|
||||
return Close.ADAPTER.decode(bytes);
|
||||
case MCS_IQ_STANZA_TAG:
|
||||
return IqStanza.ADAPTER.decode(bytes);
|
||||
case MCS_DATA_MESSAGE_STANZA_TAG:
|
||||
return DataMessageStanza.ADAPTER.decode(bytes);
|
||||
default:
|
||||
Log.w(TAG, "Unknown tag: " + mcsTag);
|
||||
return null;
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
Log.w(TAG, "Error parsing tag: "+mcsTag, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private int readVarint() throws IOException {
|
||||
int res = 0, s = -7, read;
|
||||
do {
|
||||
res |= ((read = is.read()) & 0x7F) << (s += 7);
|
||||
} while (read >= 0 && (read & 0x80) == 0x80 && s < 32);
|
||||
if (read < 0) return -1;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.gcm;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.squareup.wire.Message;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import static org.microg.gms.gcm.McsConstants.MCS_VERSION_CODE;
|
||||
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;
|
||||
|
||||
public class McsOutputStream extends Thread implements Handler.Callback, Closeable {
|
||||
private static final String TAG = "GmsGcmMcsOutput";
|
||||
|
||||
private final OutputStream os;
|
||||
private boolean initialized;
|
||||
private int version = MCS_VERSION_CODE;
|
||||
private int streamId = 0;
|
||||
|
||||
private final Handler mainHandler;
|
||||
private Handler myHandler;
|
||||
|
||||
private volatile boolean closed = false;
|
||||
|
||||
public McsOutputStream(OutputStream os, Handler mainHandler) {
|
||||
this(os, mainHandler, false);
|
||||
}
|
||||
|
||||
public McsOutputStream(OutputStream os, Handler mainHandler, boolean initialized) {
|
||||
this.os = os;
|
||||
this.mainHandler = mainHandler;
|
||||
this.initialized = initialized;
|
||||
setName("McsOutputStream");
|
||||
}
|
||||
|
||||
public int getStreamId() {
|
||||
return streamId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
myHandler = new Handler(this);
|
||||
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_OUTPUT_READY));
|
||||
Looper.loop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(android.os.Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_OUTPUT:
|
||||
try {
|
||||
Log.d(TAG, "Outgoing message: " + msg.obj);
|
||||
writeInternal((Message) msg.obj, msg.arg1);
|
||||
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_OUTPUT_DONE, msg.arg1, msg.arg2, msg.obj));
|
||||
} catch (IOException e) {
|
||||
if (closed) {
|
||||
Log.d(TAG, "We were closed already. Ignoring IOException");
|
||||
} else {
|
||||
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_OUTPUT_ERROR, e));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case MSG_TEARDOWN:
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
try {
|
||||
Looper.myLooper().quit();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (!closed) {
|
||||
closed = true;
|
||||
myHandler.getLooper().quit();
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void writeInternal(Message message, int tag) throws IOException {
|
||||
if (!initialized) {
|
||||
Log.d(TAG, "Write MCS version code: " + version);
|
||||
os.write(version);
|
||||
initialized = true;
|
||||
}
|
||||
os.write(tag);
|
||||
byte[] bytes = message.encode();
|
||||
writeVarint(os, bytes.length);
|
||||
os.write(bytes);
|
||||
os.flush();
|
||||
streamId++;
|
||||
}
|
||||
|
||||
private void writeVarint(OutputStream os, int value) throws IOException {
|
||||
while (true) {
|
||||
if ((value & ~0x7FL) == 0) {
|
||||
os.write(value);
|
||||
return;
|
||||
} else {
|
||||
os.write((value & 0x7F) | 0x80);
|
||||
value >>>= 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Handler getHandler() {
|
||||
return myHandler;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,833 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.gcm;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PermissionInfo;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
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;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.PendingIntentCompat;
|
||||
import androidx.legacy.content.WakefulBroadcastReceiver;
|
||||
|
||||
import com.squareup.wire.Message;
|
||||
|
||||
import org.microg.gms.checkin.LastCheckinInfo;
|
||||
import org.microg.gms.common.Constants;
|
||||
import org.microg.gms.common.ForegroundServiceContext;
|
||||
import org.microg.gms.common.ForegroundServiceInfo;
|
||||
import org.microg.gms.common.PackageUtils;
|
||||
import org.microg.gms.gcm.mcs.AppData;
|
||||
import org.microg.gms.gcm.mcs.Close;
|
||||
import org.microg.gms.gcm.mcs.DataMessageStanza;
|
||||
import org.microg.gms.gcm.mcs.Extension;
|
||||
import org.microg.gms.gcm.mcs.HeartbeatAck;
|
||||
import org.microg.gms.gcm.mcs.HeartbeatPing;
|
||||
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 java.io.Closeable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static org.microg.gms.common.PackageUtils.warnIfNotPersistentProcess;
|
||||
import static org.microg.gms.gcm.GcmConstants.*;
|
||||
import static org.microg.gms.gcm.ExtensionsKt.ACTION_GCM_REGISTERED;
|
||||
import static org.microg.gms.gcm.McsConstants.*;
|
||||
|
||||
@ForegroundServiceInfo(value = "Cloud messaging", resName = "service_name_mcs", resPackage = "com.google.android.gms")
|
||||
public class McsService extends Service implements Handler.Callback {
|
||||
private static final String TAG = "GmsGcmMcsSvc";
|
||||
|
||||
public static final String SELF_CATEGORY = "com.google.android.gsf.gtalkservice";
|
||||
public static final String IDLE_NOTIFICATION = "IdleNotification";
|
||||
public static final String FROM_FIELD = "gcm@android.com";
|
||||
|
||||
public static final String SERVICE_HOST = "mtalk.google.com";
|
||||
// A few ports are available: 443, 5228-5230 but also 5222-5223
|
||||
// See https://github.com/microg/GmsCore/issues/408
|
||||
// Likely if the main port 5228 is blocked by a firewall, the other 52xx are blocked as well
|
||||
public static final int[] SERVICE_PORTS = {5228, 443};
|
||||
|
||||
private static final int WAKELOCK_TIMEOUT = 5000;
|
||||
// On bad mobile network a ping can take >60s, so we wait for an ACK for 90s
|
||||
private static final int HEARTBEAT_ACK_AFTER_PING_TIMEOUT_MS = 90000;
|
||||
|
||||
private static long lastHeartbeatPingElapsedRealtime = -1;
|
||||
private static long lastHeartbeatAckElapsedRealtime = -1;
|
||||
private static long lastIncomingNetworkRealtime = 0;
|
||||
private static long startTimestamp = 0;
|
||||
public static String activeNetworkPref = null;
|
||||
private boolean wasTornDown = false;
|
||||
private AtomicInteger nextMessageId = new AtomicInteger(0x1000000);
|
||||
|
||||
private static Socket sslSocket;
|
||||
private static McsInputStream inputStream;
|
||||
private static McsOutputStream outputStream;
|
||||
|
||||
private PendingIntent heartbeatIntent;
|
||||
|
||||
private static HandlerThread handlerThread;
|
||||
private static Handler rootHandler;
|
||||
|
||||
private GcmDatabase database;
|
||||
|
||||
private AlarmManager alarmManager;
|
||||
private PowerManager powerManager;
|
||||
private static PowerManager.WakeLock wakeLock;
|
||||
|
||||
private static long currentDelay = 0;
|
||||
|
||||
private Intent connectIntent;
|
||||
|
||||
private static int maxTtl = 24 * 60 * 60;
|
||||
|
||||
@Nullable
|
||||
private Method getUserIdMethod;
|
||||
@Nullable
|
||||
private Object deviceIdleController;
|
||||
@Nullable
|
||||
private Method addPowerSaveTempWhitelistAppMethod;
|
||||
@Nullable
|
||||
@RequiresApi(31)
|
||||
private Object powerExemptionManager;
|
||||
@Nullable
|
||||
@RequiresApi(31)
|
||||
private Method addToTemporaryAllowListMethod;
|
||||
|
||||
private class HandlerThread extends Thread {
|
||||
|
||||
public HandlerThread() {
|
||||
setName("McsHandler");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "mcs");
|
||||
wakeLock.setReferenceCounted(false);
|
||||
synchronized (McsService.class) {
|
||||
rootHandler = new Handler(Looper.myLooper(), McsService.this);
|
||||
if (connectIntent != null) {
|
||||
rootHandler.sendMessage(rootHandler.obtainMessage(MSG_CONNECT, connectIntent));
|
||||
WakefulBroadcastReceiver.completeWakefulIntent(connectIntent);
|
||||
}
|
||||
}
|
||||
Looper.loop();
|
||||
}
|
||||
}
|
||||
|
||||
private static void logd(Context context, String msg) {
|
||||
if (context == null || GcmPrefs.get(context).isGcmLogEnabled()) Log.d(TAG, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("PrivateApi")
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
TriggerReceiver.register(this);
|
||||
database = new GcmDatabase(this);
|
||||
heartbeatIntent = PendingIntentCompat.getService(this, 0, new Intent(ACTION_HEARTBEAT, null, this, McsService.class), 0, false);
|
||||
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
|
||||
powerManager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
if (SDK_INT >= 23 && checkSelfPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") == PackageManager.PERMISSION_GRANTED) {
|
||||
try {
|
||||
if (SDK_INT >= 31) {
|
||||
Class<?> powerExemptionManagerClass = Class.forName("android.os.PowerExemptionManager");
|
||||
powerExemptionManager = getSystemService(powerExemptionManagerClass);
|
||||
addToTemporaryAllowListMethod =
|
||||
powerExemptionManagerClass.getMethod("addToTemporaryAllowList", String.class, int.class, String.class, long.class);
|
||||
} else {
|
||||
String deviceIdleControllerName = "deviceidle";
|
||||
try {
|
||||
Field field = Context.class.getField("DEVICE_IDLE_CONTROLLER");
|
||||
deviceIdleControllerName = (String) field.get(null);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
IBinder binder = (IBinder) Class.forName("android.os.ServiceManager")
|
||||
.getMethod("getService", String.class).invoke(null, deviceIdleControllerName);
|
||||
if (binder != null) {
|
||||
deviceIdleController = Class.forName("android.os.IDeviceIdleController$Stub")
|
||||
.getMethod("asInterface", IBinder.class).invoke(null, binder);
|
||||
getUserIdMethod = UserHandle.class.getMethod("getUserId", int.class);
|
||||
addPowerSaveTempWhitelistAppMethod = deviceIdleController.getClass()
|
||||
.getMethod("addPowerSaveTempWhitelistApp", String.class, long.class, int.class, String.class);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
synchronized (McsService.class) {
|
||||
if (handlerThread == null) {
|
||||
handlerThread = new HandlerThread();
|
||||
handlerThread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void stop(Context context) {
|
||||
context.stopService(new Intent(context, McsService.class));
|
||||
closeAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.d(TAG, "onDestroy");
|
||||
alarmManager.cancel(heartbeatIntent);
|
||||
closeAll();
|
||||
database.close();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public synchronized static boolean isConnected(Context context) {
|
||||
warnIfNotPersistentProcess(McsService.class);
|
||||
if (inputStream == null || !inputStream.isAlive() || outputStream == null || !outputStream.isAlive()) {
|
||||
logd(null, "Connection is not enabled or dead.");
|
||||
return false;
|
||||
}
|
||||
// consider connection to be dead if we did not receive an ack within 90s to our ping
|
||||
int heartbeatMs = GcmPrefs.get(context).getHeartbeatMsFor(activeNetworkPref);
|
||||
// if disabled for active network, heartbeatMs will be -1
|
||||
if (heartbeatMs < 0) {
|
||||
closeAll();
|
||||
return false;
|
||||
} else {
|
||||
boolean noAckReceived = lastHeartbeatAckElapsedRealtime < lastHeartbeatPingElapsedRealtime;
|
||||
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");
|
||||
GcmPrefs.get(context).learnTimeout(context, activeNetworkPref);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static long getStartTimestamp() {
|
||||
warnIfNotPersistentProcess(McsService.class);
|
||||
return startTimestamp;
|
||||
}
|
||||
|
||||
public static void scheduleReconnect(Context context) {
|
||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE);
|
||||
long delay = getCurrentDelay();
|
||||
logd(context, "Scheduling reconnect in " + delay / 1000 + " seconds...");
|
||||
PendingIntent pi = PendingIntentCompat.getBroadcast(context, 1, new Intent(ACTION_RECONNECT, null, context, TriggerReceiver.class), 0, false);
|
||||
if (SDK_INT >= 23) {
|
||||
alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delay, pi);
|
||||
} else {
|
||||
alarmManager.set(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delay, pi);
|
||||
}
|
||||
}
|
||||
|
||||
public void scheduleHeartbeat(Context context) {
|
||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE);
|
||||
|
||||
int heartbeatMs = GcmPrefs.get(this).getHeartbeatMsFor(activeNetworkPref);
|
||||
if (heartbeatMs < 0) {
|
||||
closeAll();
|
||||
}
|
||||
logd(context, "Scheduling heartbeat in " + heartbeatMs / 1000 + " seconds...");
|
||||
if (SDK_INT >= 23) {
|
||||
// This is supposed to work even when running in idle and without battery optimization disabled
|
||||
alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, heartbeatIntent);
|
||||
} else if (SDK_INT >= 19) {
|
||||
// With KitKat, the alarms become inexact by default, but with the newly available setWindow we can get inexact alarms with guarantees.
|
||||
// Schedule the alarm to fire within the interval [heartbeatMs/3*4, heartbeatMs]
|
||||
alarmManager.setWindow(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs / 4 * 3, heartbeatMs / 4,
|
||||
heartbeatIntent);
|
||||
} else {
|
||||
alarmManager.set(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, heartbeatIntent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public synchronized static long getCurrentDelay() {
|
||||
long delay = currentDelay == 0 ? 5000 : currentDelay;
|
||||
if (currentDelay < 60000) currentDelay += 10000;
|
||||
if (currentDelay >= 60000 && currentDelay < 600000) currentDelay += 60000;
|
||||
return delay;
|
||||
}
|
||||
|
||||
public synchronized static void resetCurrentDelay() {
|
||||
currentDelay = 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
ForegroundServiceContext.completeForegroundService(this, intent, TAG);
|
||||
synchronized (McsService.class) {
|
||||
if (rootHandler != null) {
|
||||
if (intent == null) return START_REDELIVER_INTENT;
|
||||
wakeLock.acquire(WAKELOCK_TIMEOUT);
|
||||
Object reason = intent.hasExtra(EXTRA_REASON) ? intent.getExtras().get(EXTRA_REASON) : intent;
|
||||
if (ACTION_CONNECT.equals(intent.getAction())) {
|
||||
rootHandler.sendMessage(rootHandler.obtainMessage(MSG_CONNECT, reason));
|
||||
} else if (ACTION_HEARTBEAT.equals(intent.getAction())) {
|
||||
rootHandler.sendMessage(rootHandler.obtainMessage(MSG_HEARTBEAT, reason));
|
||||
} else if (ACTION_SEND.equals(intent.getAction())) {
|
||||
handleSendMessage(intent);
|
||||
} else if (ACTION_ACK.equals(intent.getAction())) {
|
||||
rootHandler.sendMessage(rootHandler.obtainMessage(MSG_ACK, reason));
|
||||
}
|
||||
WakefulBroadcastReceiver.completeWakefulIntent(intent);
|
||||
} else if (connectIntent == null) {
|
||||
connectIntent = intent;
|
||||
} else if (intent != null) {
|
||||
WakefulBroadcastReceiver.completeWakefulIntent(intent);
|
||||
}
|
||||
}
|
||||
return START_REDELIVER_INTENT;
|
||||
}
|
||||
|
||||
private void handleSendMessage(Intent intent) {
|
||||
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);
|
||||
String packageName = null;
|
||||
if (app instanceof PendingIntent) {
|
||||
packageName = PackageUtils.packageFromPendingIntent((PendingIntent) app);
|
||||
}
|
||||
if (packageName == null) {
|
||||
Log.w(TAG, "Failed to send message, missing package name");
|
||||
return;
|
||||
}
|
||||
if (packageName.equals(getPackageName()) && intent.hasExtra(EXTRA_APP_OVERRIDE)) {
|
||||
packageName = intent.getStringExtra(EXTRA_APP_OVERRIDE);
|
||||
intent.removeExtra(EXTRA_APP_OVERRIDE);
|
||||
}
|
||||
intent.removeExtra(EXTRA_APP);
|
||||
|
||||
int ttl;
|
||||
try {
|
||||
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) {
|
||||
Log.w(TAG, e);
|
||||
ttl = maxTtl;
|
||||
}
|
||||
|
||||
String to = intent.getStringExtra(EXTRA_SEND_TO);
|
||||
if (to == null) {
|
||||
// TODO: error missing_to
|
||||
Log.w(TAG, "missing to");
|
||||
return;
|
||||
}
|
||||
|
||||
String from = intent.getStringExtra(EXTRA_SEND_FROM);
|
||||
if (from != null) {
|
||||
intent.removeExtra(EXTRA_SEND_FROM);
|
||||
} else {
|
||||
from = intent.getStringExtra(EXTRA_FROM);
|
||||
}
|
||||
if (from == null) {
|
||||
GcmDatabase.Registration reg = database.getRegistration(packageName, PackageUtils.firstSignatureDigest(this, packageName));
|
||||
if (reg != null) from = reg.registerId;
|
||||
}
|
||||
if (from == null) {
|
||||
Log.e(TAG, "Can't send message, missing from!");
|
||||
return;
|
||||
}
|
||||
|
||||
String registrationId = intent.getStringExtra(EXTRA_REGISTRATION_ID);
|
||||
intent.removeExtra(EXTRA_REGISTRATION_ID);
|
||||
|
||||
List<AppData> appData = new ArrayList<>();
|
||||
Bundle extras = intent.getExtras();
|
||||
for (String key : extras.keySet()) {
|
||||
if (!key.startsWith("google.")) {
|
||||
Object val = extras.get(key);
|
||||
if (val instanceof String) {
|
||||
appData.add(new AppData.Builder().key(key).value_((String) val).build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] rawDataArray = intent.getByteArrayExtra("rawData");
|
||||
ByteString rawData = rawDataArray != null ? ByteString.of(rawDataArray) : null;
|
||||
|
||||
try {
|
||||
DataMessageStanza msg = new DataMessageStanza.Builder()
|
||||
.sent(System.currentTimeMillis() / 1000L)
|
||||
.id(Integer.toHexString(nextMessageId.incrementAndGet()))
|
||||
.persistent_id(messageId)
|
||||
.token(collapseKey)
|
||||
.from(from)
|
||||
.reg_id(registrationId)
|
||||
.to(to)
|
||||
.category(packageName)
|
||||
.raw_data(rawData)
|
||||
.ttl(ttl)
|
||||
.app_data(appData).build();
|
||||
|
||||
send(MCS_DATA_MESSAGE_STANZA_TAG, msg);
|
||||
if (messenger != null) {
|
||||
messenger.send(android.os.Message.obtain());
|
||||
}
|
||||
database.noteAppMessage(packageName, DataMessageStanza.ADAPTER.encodedSize(msg));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void connect(int port) throws Exception {
|
||||
this.wasTornDown = false;
|
||||
|
||||
logd(this, "Starting MCS connection to port " + port + "...");
|
||||
Socket socket = new Socket(SERVICE_HOST, port);
|
||||
logd(this, "Connected to " + SERVICE_HOST + ":" + port);
|
||||
sslSocket = SSLContext.getDefault().getSocketFactory().createSocket(socket, SERVICE_HOST, port, true);
|
||||
logd(this, "Activated SSL with " + SERVICE_HOST + ":" + port);
|
||||
inputStream = new McsInputStream(sslSocket.getInputStream(), rootHandler);
|
||||
outputStream = new McsOutputStream(sslSocket.getOutputStream(), rootHandler);
|
||||
inputStream.start();
|
||||
outputStream.start();
|
||||
|
||||
startTimestamp = System.currentTimeMillis();
|
||||
lastHeartbeatPingElapsedRealtime = SystemClock.elapsedRealtime();
|
||||
lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime();
|
||||
lastIncomingNetworkRealtime = SystemClock.elapsedRealtime();
|
||||
scheduleHeartbeat(this);
|
||||
}
|
||||
|
||||
private synchronized void connect() {
|
||||
closeAll();
|
||||
|
||||
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
|
||||
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 {
|
||||
logd(this, "Don't connect, no active network");
|
||||
}
|
||||
scheduleReconnect(this);
|
||||
return;
|
||||
}
|
||||
|
||||
Exception exception = null;
|
||||
for (int port : SERVICE_PORTS) {
|
||||
try {
|
||||
connect(port);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
exception = e;
|
||||
Log.w(TAG, "Exception while connecting to " + SERVICE_HOST + ":" + port, e);
|
||||
closeAll();
|
||||
}
|
||||
}
|
||||
|
||||
logd(this, "Unable to connect to all different ports, retrying later");
|
||||
rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, exception));
|
||||
}
|
||||
|
||||
private void handleClose(Close close) {
|
||||
throw new RuntimeException("Server requested close!");
|
||||
}
|
||||
|
||||
private void handleLoginResponse(LoginResponse loginResponse) {
|
||||
if (loginResponse.error == null) {
|
||||
GcmPrefs.clearLastPersistedId(this);
|
||||
logd(this, "Logged in");
|
||||
notifyGcmRegistered();
|
||||
wakeLock.release();
|
||||
} else {
|
||||
throw new RuntimeException("Could not login: " + loginResponse.error);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyGcmRegistered() {
|
||||
Intent intent = new Intent(ACTION_GCM_REGISTERED);
|
||||
intent.setPackage(Constants.GMS_PACKAGE_NAME);
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
|
||||
private void handleCloudMessage(DataMessageStanza message) {
|
||||
if (message.persistent_id != null) {
|
||||
GcmPrefs.get(this).extendLastPersistedId(this, message.persistent_id);
|
||||
}
|
||||
if (SELF_CATEGORY.equals(message.category)) {
|
||||
handleSelfMessage(message);
|
||||
} else {
|
||||
handleAppMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHeartbeatPing(HeartbeatPing ping) {
|
||||
HeartbeatAck.Builder ack = new HeartbeatAck.Builder().status(ping.status);
|
||||
if (inputStream.newStreamIdAvailable()) {
|
||||
ack.last_stream_id_received(inputStream.getStreamId());
|
||||
}
|
||||
send(MCS_HEARTBEAT_ACK_TAG, ack.build());
|
||||
}
|
||||
|
||||
private void handleHeartbeatAck(HeartbeatAck ack) {
|
||||
GcmPrefs.get(this).learnReached(this, activeNetworkPref, SystemClock.elapsedRealtime() - lastIncomingNetworkRealtime);
|
||||
lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime();
|
||||
wakeLock.release();
|
||||
}
|
||||
|
||||
private LoginRequest buildLoginRequest() {
|
||||
LastCheckinInfo info = LastCheckinInfo.read(this);
|
||||
return new LoginRequest.Builder()
|
||||
.adaptive_heartbeat(false)
|
||||
.auth_service(LoginRequest.AuthService.ANDROID_ID)
|
||||
.auth_token(Long.toString(info.getSecurityToken()))
|
||||
.id("android-" + SDK_INT)
|
||||
.domain("mcs.android.com")
|
||||
.device_id("android-" + Long.toHexString(info.getAndroidId()))
|
||||
.network_type(1)
|
||||
.resource(Long.toString(info.getAndroidId()))
|
||||
.user(Long.toString(info.getAndroidId()))
|
||||
.use_rmq2(true)
|
||||
.setting(Collections.singletonList(new Setting.Builder().name("new_vc").value_("1").build()))
|
||||
.received_persistent_id(GcmPrefs.get(this).getLastPersistedIds())
|
||||
.build();
|
||||
}
|
||||
|
||||
private void handleAppMessage(DataMessageStanza msg) {
|
||||
String packageName = msg.category;
|
||||
database.noteAppMessage(packageName, DataMessageStanza.ADAPTER.encodedSize(msg));
|
||||
GcmDatabase.App app = database.getApp(packageName);
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(ACTION_C2DM_RECEIVE);
|
||||
intent.putExtra(EXTRA_FROM, msg.from);
|
||||
intent.putExtra(EXTRA_MESSAGE_ID, msg.id);
|
||||
if (msg.sent != null && msg.sent != 0) intent.putExtra(EXTRA_SENT_TIME, msg.sent);
|
||||
if (msg.ttl != null && msg.ttl != 0) intent.putExtra(EXTRA_TTL, msg.ttl);
|
||||
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);
|
||||
}
|
||||
for (AppData appData : msg.app_data) {
|
||||
if (appData.key == null) continue;
|
||||
String key = appData.key.toLowerCase(Locale.US);
|
||||
// Some keys are exclusively set by the client and not the app.
|
||||
if (key.equals(EXTRA_FROM) || (key.startsWith("google.") && !key.startsWith("google.c."))) continue;
|
||||
intent.putExtra(appData.key, appData.value_);
|
||||
}
|
||||
|
||||
String receiverPermission = null;
|
||||
try {
|
||||
String name = packageName + ".permission.C2D_MESSAGE";
|
||||
PermissionInfo info = getPackageManager().getPermissionInfo(name, 0);
|
||||
if (info.packageName.equals(packageName)) {
|
||||
receiverPermission = name;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// Keep null, no valid permission found
|
||||
}
|
||||
|
||||
if (receiverPermission == null) {
|
||||
// Without receiver permission, we only restrict by package name
|
||||
if (app.wakeForDelivery) addPowerSaveTempWhitelistApp(packageName);
|
||||
logd(this, "Deliver message to all receivers in package " + packageName);
|
||||
intent.setPackage(packageName);
|
||||
sendOrderedBroadcast(intent, null);
|
||||
} else {
|
||||
List<ResolveInfo> infos = getPackageManager().queryBroadcastReceivers(intent, PackageManager.GET_RESOLVED_FILTER);
|
||||
if (infos == null || infos.isEmpty()) {
|
||||
logd(this, "No target for message, wut?");
|
||||
} else {
|
||||
for (ResolveInfo resolveInfo : infos) {
|
||||
Intent targetIntent = new Intent(intent);
|
||||
targetIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name));
|
||||
if (resolveInfo.activityInfo.packageName.equals(packageName)) {
|
||||
if (app.wakeForDelivery) addPowerSaveTempWhitelistApp(packageName);
|
||||
// We don't need receiver permission for our own package
|
||||
logd(this, "Deliver message to own receiver " + resolveInfo);
|
||||
sendOrderedBroadcast(targetIntent, null);
|
||||
} else if (resolveInfo.filter.hasCategory(packageName)) {
|
||||
// Permission required
|
||||
logd(this, "Deliver message to third-party receiver (with permission check)" + resolveInfo);
|
||||
sendOrderedBroadcast(targetIntent, receiverPermission);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addPowerSaveTempWhitelistApp(String packageName) {
|
||||
if (SDK_INT >= 31) {
|
||||
try {
|
||||
if (addToTemporaryAllowListMethod != null && powerExemptionManager != null) {
|
||||
logd(this, "Adding app " + packageName + " to the temp allowlist");
|
||||
addToTemporaryAllowListMethod.invoke(powerExemptionManager, packageName, 0, "GCM Push", 10000);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error adding app" + packageName + " to the temp allowlist.", e);
|
||||
}
|
||||
} else if (SDK_INT >= 23) {
|
||||
try {
|
||||
if (getUserIdMethod != null && addPowerSaveTempWhitelistAppMethod != null && deviceIdleController != null) {
|
||||
int userId = (int) getUserIdMethod.invoke(null, getPackageManager().getApplicationInfo(packageName, 0).uid);
|
||||
logd(this, "Adding app " + packageName + " for userId " + userId + " to the temp whitelist");
|
||||
addPowerSaveTempWhitelistAppMethod.invoke(deviceIdleController, packageName, 10000, userId, "GCM Push");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSelfMessage(DataMessageStanza msg) {
|
||||
for (AppData appData : msg.app_data) {
|
||||
if (IDLE_NOTIFICATION.equals(appData.key)) {
|
||||
DataMessageStanza.Builder msgResponse = new DataMessageStanza.Builder()
|
||||
.id(Integer.toHexString(nextMessageId.incrementAndGet()))
|
||||
.from(FROM_FIELD)
|
||||
.sent(System.currentTimeMillis() / 1000)
|
||||
.ttl(0)
|
||||
.category(SELF_CATEGORY)
|
||||
.app_data(Collections.singletonList(new AppData.Builder().key(IDLE_NOTIFICATION).value_("false").build()));
|
||||
if (inputStream.newStreamIdAvailable()) {
|
||||
msgResponse.last_stream_id_received(inputStream.getStreamId());
|
||||
}
|
||||
send(MCS_DATA_MESSAGE_STANZA_TAG, msgResponse.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void send(int type, Message message) {
|
||||
rootHandler.sendMessage(rootHandler.obtainMessage(MSG_OUTPUT, type, 0, message));
|
||||
}
|
||||
|
||||
private void sendOutputStream(int what, int arg, Object obj) {
|
||||
McsOutputStream os = outputStream;
|
||||
if (os != null && os.isAlive()) {
|
||||
Handler outputHandler = os.getHandler();
|
||||
if (outputHandler != null)
|
||||
outputHandler.sendMessage(outputHandler.obtainMessage(what, arg, 0, obj));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(android.os.Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_INPUT:
|
||||
handleInput(msg.arg1, (Message) msg.obj);
|
||||
return true;
|
||||
case MSG_OUTPUT:
|
||||
sendOutputStream(MSG_OUTPUT, msg.arg1, msg.obj);
|
||||
return true;
|
||||
case MSG_INPUT_ERROR:
|
||||
case MSG_OUTPUT_ERROR:
|
||||
logd(this, "I/O error: " + msg.obj);
|
||||
if (msg.obj instanceof SocketException) {
|
||||
SocketException e = (SocketException) msg.obj;
|
||||
if ("Connection reset".equals(e.getMessage())) {
|
||||
GcmPrefs.get(this).learnTimeout(this, activeNetworkPref);
|
||||
}
|
||||
}
|
||||
rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, msg.obj));
|
||||
return true;
|
||||
case MSG_TEARDOWN:
|
||||
logd(this, "Teardown initiated, reason: " + msg.obj);
|
||||
handleTeardown(msg);
|
||||
return true;
|
||||
case MSG_CONNECT:
|
||||
logd(this, "Connect initiated, reason: " + msg.obj);
|
||||
if (!isConnected(this)) {
|
||||
connect();
|
||||
}
|
||||
return true;
|
||||
case MSG_HEARTBEAT:
|
||||
logd(this, "Heartbeat initiated, reason: " + msg.obj);
|
||||
if (isConnected(this)) {
|
||||
HeartbeatPing.Builder ping = new HeartbeatPing.Builder();
|
||||
if (inputStream.newStreamIdAvailable()) {
|
||||
ping.last_stream_id_received(inputStream.getStreamId());
|
||||
}
|
||||
send(MCS_HEARTBEAT_PING_TAG, ping.build());
|
||||
lastHeartbeatPingElapsedRealtime = SystemClock.elapsedRealtime();
|
||||
scheduleHeartbeat(this);
|
||||
} else {
|
||||
logd(this, "Ignoring heartbeat, not connected!");
|
||||
scheduleReconnect(this);
|
||||
}
|
||||
return true;
|
||||
case MSG_ACK:
|
||||
logd(this, "Ack initiated, reason: " + msg.obj);
|
||||
if (isConnected(this)) {
|
||||
IqStanza.Builder iq = new IqStanza.Builder()
|
||||
.type(IqStanza.IqType.SET)
|
||||
.id("")
|
||||
.extension(new Extension.Builder().id(13).data_(ByteString.EMPTY).build()) // StreamAck
|
||||
.status(0L);
|
||||
if (inputStream.newStreamIdAvailable()) {
|
||||
iq.last_stream_id_received(inputStream.getStreamId());
|
||||
}
|
||||
send(MCS_IQ_STANZA_TAG, iq.build());
|
||||
} else {
|
||||
logd(this, "Ignoring ack, not connected!");
|
||||
}
|
||||
return true;
|
||||
case MSG_OUTPUT_READY:
|
||||
logd(this, "Sending login request...");
|
||||
send(MCS_LOGIN_REQUEST_TAG, buildLoginRequest());
|
||||
return true;
|
||||
case MSG_OUTPUT_DONE:
|
||||
handleOutputDone(msg);
|
||||
return true;
|
||||
}
|
||||
Log.w(TAG, "Unknown message (" + msg.what + "): " + msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleOutputDone(android.os.Message msg) {
|
||||
switch (msg.arg1) {
|
||||
case MCS_HEARTBEAT_PING_TAG:
|
||||
wakeLock.release();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
private void handleInput(int type, Message message) {
|
||||
try {
|
||||
switch (type) {
|
||||
case MCS_DATA_MESSAGE_STANZA_TAG:
|
||||
handleCloudMessage((DataMessageStanza) message);
|
||||
break;
|
||||
case MCS_HEARTBEAT_PING_TAG:
|
||||
handleHeartbeatPing((HeartbeatPing) message);
|
||||
break;
|
||||
case MCS_HEARTBEAT_ACK_TAG:
|
||||
handleHeartbeatAck((HeartbeatAck) message);
|
||||
break;
|
||||
case MCS_CLOSE_TAG:
|
||||
handleClose((Close) message);
|
||||
break;
|
||||
case MCS_LOGIN_RESPONSE_TAG:
|
||||
handleLoginResponse((LoginResponse) message);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Unknown message: " + message);
|
||||
}
|
||||
resetCurrentDelay();
|
||||
lastIncomingNetworkRealtime = SystemClock.elapsedRealtime();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Exception when handling input: " + message, e);
|
||||
rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, e));
|
||||
}
|
||||
}
|
||||
|
||||
private static void tryClose(Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void closeAll() {
|
||||
logd(null, "Closing all sockets...");
|
||||
tryClose(inputStream);
|
||||
tryClose(outputStream);
|
||||
if (sslSocket != null) {
|
||||
try {
|
||||
sslSocket.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTeardown(android.os.Message msg) {
|
||||
if (wasTornDown) {
|
||||
// This can get called multiple times from different places via MSG_TEARDOWN
|
||||
// this causes the reconnect delay to increase with each call to scheduleReconnect(),
|
||||
// increasing the time we are disconnected.
|
||||
logd(this, "Was torn down already, not doing it again");
|
||||
return;
|
||||
}
|
||||
wasTornDown = true;
|
||||
closeAll();
|
||||
|
||||
scheduleReconnect(this);
|
||||
|
||||
alarmManager.cancel(heartbeatIntent);
|
||||
if (wakeLock != null) {
|
||||
try {
|
||||
wakeLock.release();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright (C) 2018 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.gcm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import org.microg.gms.checkin.LastCheckinInfo;
|
||||
import org.microg.gms.common.HttpFormClient;
|
||||
import org.microg.gms.utils.ExtendedPackageInfo;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.microg.gms.gcm.GcmConstants.ERROR_SERVICE_NOT_AVAILABLE;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_ERROR;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_REGISTRATION_ID;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_RETRY_AFTER;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_UNREGISTERED;
|
||||
|
||||
public class PushRegisterManager {
|
||||
private static final String TAG = "GmsGcmRegisterMgr";
|
||||
|
||||
public static RegisterResponse unregister(Context context, String packageName, String pkgSignature, String sender, String info) {
|
||||
GcmDatabase database = new GcmDatabase(context);
|
||||
RegisterResponse response = new RegisterResponse();
|
||||
try {
|
||||
response = new RegisterRequest()
|
||||
.build(context)
|
||||
.sender(sender)
|
||||
.info(info)
|
||||
.checkin(LastCheckinInfo.read(context))
|
||||
.app(packageName, pkgSignature)
|
||||
.delete(true)
|
||||
.getResponse();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
if (!packageName.equals(response.deleted)) {
|
||||
database.noteAppRegistrationError(packageName, response.responseText);
|
||||
} else {
|
||||
database.noteAppUnregistered(packageName, pkgSignature);
|
||||
}
|
||||
database.close();
|
||||
return response;
|
||||
}
|
||||
|
||||
public interface BundleCallback {
|
||||
void onResult(Bundle bundle);
|
||||
}
|
||||
|
||||
public static void completeRegisterRequest(Context context, GcmDatabase database, RegisterRequest request, BundleCallback callback) {
|
||||
completeRegisterRequest(context, database, null, request, callback);
|
||||
}
|
||||
|
||||
public static void completeRegisterRequest(Context context, GcmDatabase database, String requestId, RegisterRequest request, BundleCallback callback) {
|
||||
if (request.app != null) {
|
||||
ExtendedPackageInfo packageInfo = new ExtendedPackageInfo(context, request.app);
|
||||
if (request.appVersion <= 0)
|
||||
request.appVersion = packageInfo.getShortVersionCode();
|
||||
if (!request.delete) {
|
||||
if (request.appSignature == null) {
|
||||
request.appSignature = packageInfo.getFirstCertificateSha1Hex();
|
||||
}
|
||||
request.sdkVersion = packageInfo.getTargetSdkVersion();
|
||||
if (!request.hasExtraParam(GcmConstants.EXTRA_APP_VERSION_NAME))
|
||||
request.extraParam(GcmConstants.EXTRA_APP_VERSION_NAME, packageInfo.getVersionName());
|
||||
}
|
||||
}
|
||||
|
||||
GcmDatabase.App app = database.getApp(request.app);
|
||||
GcmPrefs prefs = GcmPrefs.get(context);
|
||||
if (!request.delete) {
|
||||
if (!prefs.isEnabled() ||
|
||||
(app != null && !app.allowRegister) ||
|
||||
LastCheckinInfo.read(context).getLastCheckin() <= 0 ||
|
||||
(app == null && prefs.getConfirmNewApps())) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(EXTRA_ERROR, ERROR_SERVICE_NOT_AVAILABLE);
|
||||
callback.onResult(bundle);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (database.getRegistrationsByApp(request.app).isEmpty()) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(EXTRA_UNREGISTERED, attachRequestId(request.app, requestId));
|
||||
callback.onResult(bundle);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
request.getResponseAsync(new HttpFormClient.Callback<RegisterResponse>() {
|
||||
@Override
|
||||
public void onResponse(RegisterResponse response) {
|
||||
callback.onResult(handleResponse(database, request, response, requestId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception e) {
|
||||
Log.w(TAG, e);
|
||||
callback.onResult(handleResponse(database, request, e, requestId));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static Bundle handleResponse(GcmDatabase database, RegisterRequest request, RegisterResponse response, String requestId) {
|
||||
return handleResponse(database, request, response, null, requestId);
|
||||
}
|
||||
|
||||
private static Bundle handleResponse(GcmDatabase database, RegisterRequest request, Exception e, String requestId) {
|
||||
return handleResponse(database, request, null, e, requestId);
|
||||
}
|
||||
|
||||
private static Bundle handleResponse(GcmDatabase database, RegisterRequest request, RegisterResponse response, Exception e, String requestId) {
|
||||
Bundle resultBundle = new Bundle();
|
||||
if (response == null && e == null) {
|
||||
resultBundle.putString(EXTRA_ERROR, attachRequestId(ERROR_SERVICE_NOT_AVAILABLE, requestId));
|
||||
} else if (e != null) {
|
||||
if (e.getMessage() != null && e.getMessage().startsWith("Error=")) {
|
||||
String errorMessage = e.getMessage().substring(6);
|
||||
database.noteAppRegistrationError(request.app, errorMessage);
|
||||
resultBundle.putString(EXTRA_ERROR, attachRequestId(errorMessage, requestId));
|
||||
} else {
|
||||
resultBundle.putString(EXTRA_ERROR, attachRequestId(ERROR_SERVICE_NOT_AVAILABLE, requestId));
|
||||
}
|
||||
} else {
|
||||
if (!request.delete) {
|
||||
if (response.token == null) {
|
||||
database.noteAppRegistrationError(request.app, response.responseText);
|
||||
resultBundle.putString(EXTRA_ERROR, attachRequestId(ERROR_SERVICE_NOT_AVAILABLE, requestId));
|
||||
} else {
|
||||
database.noteAppRegistered(request.app, request.appSignature, response.token);
|
||||
resultBundle.putString(EXTRA_REGISTRATION_ID, attachRequestId(response.token, requestId));
|
||||
}
|
||||
} else {
|
||||
if (!request.app.equals(response.deleted) && !request.app.equals(response.token) && !request.sender.equals(response.token) && !"".equals(response.token)) {
|
||||
database.noteAppRegistrationError(request.app, response.responseText);
|
||||
resultBundle.putString(EXTRA_ERROR, attachRequestId(ERROR_SERVICE_NOT_AVAILABLE, requestId));
|
||||
} else {
|
||||
database.noteAppUnregistered(request.app, request.appSignature);
|
||||
resultBundle.putString(EXTRA_UNREGISTERED, attachRequestId(request.app, requestId));
|
||||
}
|
||||
}
|
||||
|
||||
if (response.retryAfter != null && !response.retryAfter.contains(":")) {
|
||||
resultBundle.putLong(EXTRA_RETRY_AFTER, Long.parseLong(response.retryAfter));
|
||||
}
|
||||
}
|
||||
return resultBundle;
|
||||
}
|
||||
|
||||
public static String attachRequestId(String msg, String requestId) {
|
||||
if (requestId == null) return msg;
|
||||
return "|ID|" + requestId + "|" + msg;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.gcm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.microg.gms.checkin.LastCheckinInfo;
|
||||
import org.microg.gms.common.HttpFormClient;
|
||||
import org.microg.gms.profile.Build;
|
||||
import org.microg.gms.profile.ProfileManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.microg.gms.common.HttpFormClient.RequestContent;
|
||||
import static org.microg.gms.common.HttpFormClient.RequestContentDynamic;
|
||||
import static org.microg.gms.common.HttpFormClient.RequestHeader;
|
||||
|
||||
public class RegisterRequest extends HttpFormClient.Request {
|
||||
private static final String SERVICE_URL = "https://android.clients.google.com/c2dm/register3";
|
||||
private static final String USER_AGENT = "Android-GCM/1.5 (%s %s)";
|
||||
|
||||
@RequestHeader("Authorization")
|
||||
private String auth;
|
||||
@RequestHeader("User-Agent")
|
||||
private String userAgent;
|
||||
|
||||
@RequestHeader("app")
|
||||
@RequestContent("app")
|
||||
public String app;
|
||||
@RequestContent("cert")
|
||||
public String appSignature;
|
||||
@RequestContent("app_ver")
|
||||
public int appVersion;
|
||||
@RequestContent("info")
|
||||
public String info;
|
||||
@RequestContent("sender")
|
||||
public String sender;
|
||||
@RequestContent("device")
|
||||
public long androidId;
|
||||
@RequestContent("delete")
|
||||
public boolean delete;
|
||||
public long securityToken;
|
||||
public String deviceName;
|
||||
public String buildVersion;
|
||||
@RequestContent("target_ver")
|
||||
public Integer sdkVersion;
|
||||
@RequestContentDynamic
|
||||
private Map<String, String> extraParams = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public void prepare() {
|
||||
userAgent = String.format(USER_AGENT, deviceName, buildVersion);
|
||||
auth = "AidLogin " + androidId + ":" + securityToken;
|
||||
}
|
||||
|
||||
public RegisterRequest checkin(LastCheckinInfo lastCheckinInfo) {
|
||||
androidId = lastCheckinInfo.getAndroidId();
|
||||
securityToken = lastCheckinInfo.getSecurityToken();
|
||||
return this;
|
||||
}
|
||||
|
||||
public RegisterRequest app(String app) {
|
||||
this.app = app;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RegisterRequest app(String app, String appSignature) {
|
||||
this.app = app;
|
||||
this.appSignature = appSignature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RegisterRequest app(String app, String appSignature, int appVersion) {
|
||||
this.app = app;
|
||||
this.appSignature = appSignature;
|
||||
this.appVersion = appVersion;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RegisterRequest info(String info) {
|
||||
this.info = info;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RegisterRequest sender(String sender) {
|
||||
this.sender = sender;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RegisterRequest build(Context context) {
|
||||
ProfileManager.ensureInitialized(context);
|
||||
deviceName = Build.DEVICE;
|
||||
buildVersion = Build.ID;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RegisterRequest delete() {
|
||||
return delete(true);
|
||||
}
|
||||
|
||||
public RegisterRequest delete(boolean delete) {
|
||||
this.delete = delete;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RegisterRequest extraParams(Bundle extraBundle) {
|
||||
for (String key : extraBundle.keySet()) {
|
||||
if (!key.equals(GcmConstants.EXTRA_SENDER) && !key.equals(GcmConstants.EXTRA_DELETE) && !key.equals(GcmConstants.EXTRA_APP)) {
|
||||
extraParam(key, extraBundle.getString(key));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public RegisterRequest extraParam(String key, String value) {
|
||||
// Ignore empty registration extras
|
||||
if (!TextUtils.isEmpty(value)) {
|
||||
extraParams.put(extraParamKey(key), value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean hasExtraParam(String key) {
|
||||
return extraParams.containsKey(extraParamKey(key));
|
||||
}
|
||||
|
||||
private static String extraParamKey(String key) {
|
||||
return "X-" + key;
|
||||
}
|
||||
|
||||
public RegisterResponse getResponse() throws IOException {
|
||||
return HttpFormClient.request(SERVICE_URL, this, RegisterResponse.class);
|
||||
}
|
||||
|
||||
public void getResponseAsync(HttpFormClient.Callback<RegisterResponse> callback) {
|
||||
HttpFormClient.requestAsync(SERVICE_URL, this, RegisterResponse.class, callback);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.gcm;
|
||||
|
||||
import org.microg.gms.common.HttpFormClient.ResponseHeader;
|
||||
import org.microg.gms.common.HttpFormClient.ResponseStatusText;
|
||||
|
||||
import static org.microg.gms.common.HttpFormClient.ResponseField;
|
||||
|
||||
public class RegisterResponse {
|
||||
@ResponseField("token")
|
||||
public String token;
|
||||
@ResponseHeader("Retry-After")
|
||||
public String retryAfter;
|
||||
@ResponseField("deleted")
|
||||
public String deleted;
|
||||
@ResponseStatusText
|
||||
public String responseText;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RegisterResponse{" +
|
||||
"token='" + token + '\'' +
|
||||
", retryAfter='" + retryAfter + '\'' +
|
||||
", deleted='" + deleted + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.gcm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.legacy.content.WakefulBroadcastReceiver;
|
||||
|
||||
import static org.microg.gms.gcm.McsConstants.ACTION_SEND;
|
||||
|
||||
public class SendReceiver extends WakefulBroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getExtras() == null) return;
|
||||
Bundle extras = intent.getExtras();
|
||||
Log.d("GmsMcsSendRcvr", "original extras: " + extras);
|
||||
for (String key : extras.keySet()) {
|
||||
if (key.startsWith("GOOG.") || key.startsWith("GOOGLE.")) {
|
||||
extras.remove(key);
|
||||
}
|
||||
}
|
||||
Intent i = new Intent(context, McsService.class);
|
||||
i.setAction(ACTION_SEND);
|
||||
i.putExtras(extras);
|
||||
startWakefulService(context, i);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.gcm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.legacy.content.WakefulBroadcastReceiver;
|
||||
|
||||
import org.microg.gms.checkin.CheckinPreferences;
|
||||
import org.microg.gms.checkin.LastCheckinInfo;
|
||||
import org.microg.gms.common.ForegroundServiceContext;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
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;
|
||||
|
||||
public class TriggerReceiver extends WakefulBroadcastReceiver {
|
||||
private static final String TAG = "GmsGcmTrigger";
|
||||
public static final String FORCE_TRY_RECONNECT = "org.microg.gms.gcm.FORCE_TRY_RECONNECT";
|
||||
private static boolean registered = false;
|
||||
|
||||
/**
|
||||
* "Project Svelte" is just there to f**k things up...
|
||||
*/
|
||||
public synchronized static void register(Context context) {
|
||||
if (SDK_INT >= 24 && !registered) {
|
||||
IntentFilter intentFilter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
|
||||
context.getApplicationContext().registerReceiver(new TriggerReceiver(), intentFilter);
|
||||
registered = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
try {
|
||||
boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction()) || FORCE_TRY_RECONNECT.equals(intent.getAction());
|
||||
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
if (!GcmPrefs.get(context).isEnabled() && !force) {
|
||||
Log.d(TAG, "Ignoring " + intent + ": gcm is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
|
||||
McsService.resetCurrentDelay();
|
||||
}
|
||||
|
||||
if (LastCheckinInfo.read(context).getAndroidId() == 0) {
|
||||
Log.d(TAG, "Ignoring " + intent + ": need to checkin first.");
|
||||
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...");
|
||||
String action = "android.server.checkin.CHECKIN";
|
||||
Class<?> clazz = org.microg.gms.checkin.TriggerReceiver.class;
|
||||
context.sendBroadcast(new Intent(action, null, context, clazz));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
force |= "android.intent.action.BOOT_COMPLETED".equals(intent.getAction());
|
||||
|
||||
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
|
||||
|
||||
if (!force) {
|
||||
if (networkInfo == null || !networkInfo.isConnected()) {
|
||||
Log.d(TAG, "Ignoring " + intent + ": network is offline, scheduling new attempt.");
|
||||
McsService.scheduleReconnect(context);
|
||||
return;
|
||||
} else if (!GcmPrefs.get(context).isEnabledFor(networkInfo)) {
|
||||
Log.d(TAG, "Ignoring " + intent + ": gcm is disabled for " + networkInfo.getTypeName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!McsService.isConnected(context) || force) {
|
||||
Log.d(TAG, "Not connected to GCM but should be, asking the service to start up. Triggered by: " + intent);
|
||||
startWakefulService(new ForegroundServiceContext(context), new Intent(ACTION_CONNECT, null, context, McsService.class)
|
||||
.putExtra(EXTRA_REASON, intent));
|
||||
} else {
|
||||
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
|
||||
Log.d(TAG, "Ignoring " + intent + ": service is running. schedule reconnect instead.");
|
||||
McsService.scheduleReconnect(context);
|
||||
} else {
|
||||
Log.d(TAG, "Ignoring " + intent + ": service is running. heartbeat instead.");
|
||||
startWakefulService(new ForegroundServiceContext(context), new Intent(ACTION_HEARTBEAT, null, context, McsService.class)
|
||||
.putExtra(EXTRA_REASON, intent));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package org.microg.gms.gcm;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
|
||||
import static android.content.Intent.ACTION_PACKAGE_DATA_CLEARED;
|
||||
import static android.content.Intent.ACTION_PACKAGE_FULLY_REMOVED;
|
||||
import static android.content.Intent.EXTRA_DATA_REMOVED;
|
||||
import static android.content.Intent.EXTRA_REPLACING;
|
||||
|
||||
public class UnregisterReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "GmsGcmUnregisterRcvr";
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
Log.d(TAG, "Package changed: " + intent);
|
||||
if ((ACTION_PACKAGE_REMOVED.contains(intent.getAction()) && intent.getBooleanExtra(EXTRA_DATA_REMOVED, false) &&
|
||||
!intent.getBooleanExtra(EXTRA_REPLACING, false)) ||
|
||||
ACTION_PACKAGE_FULLY_REMOVED.contains(intent.getAction()) ||
|
||||
ACTION_PACKAGE_DATA_CLEARED.contains(intent.getAction())) {
|
||||
final GcmDatabase database = new GcmDatabase(context);
|
||||
final String packageName = intent.getData().getSchemeSpecificPart();
|
||||
Log.d(TAG, "Package removed or data cleared: " + packageName);
|
||||
final GcmDatabase.App app = database.getApp(packageName);
|
||||
if (app != null) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
List<GcmDatabase.Registration> registrations = database.getRegistrationsByApp(packageName);
|
||||
boolean deletedAll = true;
|
||||
for (GcmDatabase.Registration registration : registrations) {
|
||||
deletedAll &= PushRegisterManager.unregister(context, registration.packageName, registration.signature, null, null).deleted != null;
|
||||
}
|
||||
if (deletedAll) {
|
||||
database.removeApp(packageName);
|
||||
}
|
||||
database.close();
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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.gservices;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
private static final int DB_VERSION = 3;
|
||||
private static final int DB_VERSION_OLD = 1;
|
||||
public static final String DB_NAME = "gservices.db";
|
||||
|
||||
public DatabaseHelper(Context context) {
|
||||
super(context, DB_NAME, null, DB_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL("CREATE TABLE main (name TEXT PRIMARY KEY, value TEXT)");
|
||||
db.execSQL("CREATE TABLE overrides (name TEXT PRIMARY KEY, value TEXT)");
|
||||
db.execSQL("CREATE TABLE saved_system (name TEXT PRIMARY KEY, value TEXT)");
|
||||
db.execSQL("CREATE TABLE saved_secure (name TEXT PRIMARY KEY, value TEXT)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion == DB_VERSION_OLD) {
|
||||
db.execSQL("DROP TABLE IF EXISTS main");
|
||||
db.execSQL("DROP TABLE IF EXISTS overrides");
|
||||
onCreate(db);
|
||||
}
|
||||
db.setVersion(newVersion);
|
||||
}
|
||||
|
||||
public String get(String name) {
|
||||
String result = null;
|
||||
Cursor cursor = getReadableDatabase().query("overrides", new String[]{"value"}, "name=?",
|
||||
new String[]{name}, null, null, null, null);
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToNext()) {
|
||||
result = cursor.getString(0);
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
if (result != null) return result;
|
||||
cursor = getReadableDatabase().query("main", new String[]{"value"}, "name=?",
|
||||
new String[]{name}, null, null, null, null);
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToNext()) {
|
||||
result = cursor.getString(0);
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, String> search(String search) {
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
Cursor cursor = getReadableDatabase().query("overrides", new String[]{"name", "value"},
|
||||
"name LIKE ?", new String[]{search}, null, null, null, null);
|
||||
if (cursor != null) {
|
||||
while (cursor.moveToNext()) {
|
||||
map.put(cursor.getString(0), cursor.getString(1));
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
cursor = getReadableDatabase().query("main", new String[]{"name", "value"},
|
||||
"name LIKE ?", new String[]{search}, null, null, null, null);
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToNext()) {
|
||||
if (!map.containsKey(cursor.getString(0)))
|
||||
map.put(cursor.getString(0), cursor.getString(1));
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return map;
|
||||
|
||||
}
|
||||
|
||||
public void put(String table, ContentValues values) {
|
||||
getWritableDatabase().insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.gservices;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
public class GServices {
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://com.google.android.gsf.gservices");
|
||||
public static final Uri MAIN_URI = Uri.parse("content://com.google.android.gsf.gservices/main");
|
||||
public static final Uri OVERRIDE_URI = Uri.parse("content://com.google.android.gsf.gservices/override");
|
||||
|
||||
public static int setString(ContentResolver resolver, String key, String value) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("name", key);
|
||||
values.put("value", value);
|
||||
return resolver.update(MAIN_URI, values, null, null);
|
||||
}
|
||||
|
||||
public static String getString(ContentResolver resolver, String key) {
|
||||
return getString(resolver, key, null);
|
||||
}
|
||||
|
||||
public static String getString(ContentResolver resolver, String key, String defaultValue) {
|
||||
String result = defaultValue;
|
||||
Cursor cursor = resolver.query(CONTENT_URI, null, null, new String[]{key}, null);
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToNext()) {
|
||||
result = cursor.getString(1);
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int getInt(ContentResolver resolver, String key, int defaultValue) {
|
||||
String result = getString(resolver, key);
|
||||
if (result != null) {
|
||||
try {
|
||||
return Integer.parseInt(result);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static long getLong(ContentResolver resolver, String key, long defaultValue) {
|
||||
String result = getString(resolver, key);
|
||||
if (result != null) {
|
||||
try {
|
||||
return Long.parseLong(result);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* 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.gservices;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p/>
|
||||
* Google uses the checkin process to store various device or country specific settings and
|
||||
* if certain "experiments" are enabled on the device.
|
||||
*/
|
||||
public class GServicesProvider extends ContentProvider {
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://com.google.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");
|
||||
|
||||
private static final String TAG = "GmsServicesProvider";
|
||||
|
||||
private DatabaseHelper databaseHelper;
|
||||
private Map<String, String> cache = new HashMap<String, String>();
|
||||
private Set<String> cachedPrefixes = new HashSet<String>();
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
databaseHelper = new DatabaseHelper(getContext());
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getCallingPackageName() {
|
||||
if (SDK_INT >= 19) {
|
||||
return getCallingPackage();
|
||||
} else {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
MatrixCursor cursor = new MatrixCursor(new String[]{"name", "value"});
|
||||
if (PREFIX_URI.equals(uri)) {
|
||||
for (String prefix : selectionArgs) {
|
||||
if (!cachedPrefixes.contains(prefix)) {
|
||||
cache.putAll(databaseHelper.search(prefix + "%"));
|
||||
cachedPrefixes.add(prefix);
|
||||
}
|
||||
|
||||
for (String name : cache.keySet()) {
|
||||
if (name.startsWith(prefix)) {
|
||||
String value = cache.get(name);
|
||||
cursor.addRow(new String[]{name, value});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (String name : selectionArgs) {
|
||||
String value;
|
||||
if (cache.containsKey(name)) {
|
||||
value = cache.get(name);
|
||||
} else {
|
||||
value = databaseHelper.get(name);
|
||||
cache.put(name, value);
|
||||
}
|
||||
if (value != null) {
|
||||
cursor.addRow(new String[]{name, value});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cursor.getCount() == 0) return null;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
Log.d(TAG, "update caller=" + getCallingPackageName() + " table=" + uri.getLastPathSegment()
|
||||
+ " name=" + values.getAsString("name") + " value=" + values.getAsString("value"));
|
||||
if (uri.equals(MAIN_URI)) {
|
||||
databaseHelper.put("main", values);
|
||||
} else if (uri.equals(OVERRIDE_URI)) {
|
||||
databaseHelper.put("override", values);
|
||||
}
|
||||
String name = values.getAsString("name");
|
||||
cache.remove(name);
|
||||
Iterator<String> iterator = cachedPrefixes.iterator();
|
||||
while (iterator.hasNext()) if (name.startsWith(iterator.next())) iterator.remove();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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.icing;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.appdatasearch.CorpusStatus;
|
||||
import com.google.android.gms.appdatasearch.PIMEUpdateResponse;
|
||||
import com.google.android.gms.appdatasearch.RequestIndexingSpecification;
|
||||
import com.google.android.gms.appdatasearch.SuggestSpecification;
|
||||
import com.google.android.gms.appdatasearch.SuggestionResults;
|
||||
import com.google.android.gms.appdatasearch.internal.IAppDataSearch;
|
||||
|
||||
public class AppDataSearchImpl extends IAppDataSearch.Stub {
|
||||
private static final String TAG = "GmsIcingAppDataImpl";
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
if (super.onTransact(code, data, reply, flags)) return true;
|
||||
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SuggestionResults getSuggestions(String var1, String packageName, String[] accounts, int maxNum, SuggestSpecification specs) throws RemoteException {
|
||||
return new SuggestionResults("Unknown error");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requestIndexing(String packageName, String accountName, long l, RequestIndexingSpecification specs) throws RemoteException {
|
||||
Log.d(TAG, "requestIndexing: " + accountName + " @ " + packageName + ", " + l);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CorpusStatus getStatus(String packageName, String accountName) throws RemoteException {
|
||||
Log.d(TAG, "getStatus: " + accountName + " @ " + packageName);
|
||||
CorpusStatus status = new CorpusStatus();
|
||||
status.found = true;
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PIMEUpdateResponse requestPIMEUpdate(String s1, String s2, int i, byte[] bs) throws RemoteException {
|
||||
Log.d(TAG, "requestPIMEUpdate: " + s1 + ", " + s2 + ", " + i + ", " + (bs == null ? "null" : new String(bs)));
|
||||
return new PIMEUpdateResponse();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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.icing;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.search.global.GetCurrentExperimentIdsRequest;
|
||||
import com.google.android.gms.search.global.GetCurrentExperimentIdsResponse;
|
||||
import com.google.android.gms.search.global.GetGlobalSearchSourcesRequest;
|
||||
import com.google.android.gms.search.global.GetGlobalSearchSourcesResponse;
|
||||
import com.google.android.gms.search.global.GetPendingExperimentIdsRequest;
|
||||
import com.google.android.gms.search.global.GetPendingExperimentIdsResponse;
|
||||
import com.google.android.gms.search.global.SetExperimentIdsRequest;
|
||||
import com.google.android.gms.search.global.SetExperimentIdsResponse;
|
||||
import com.google.android.gms.search.global.SetIncludeInGlobalSearchRequest;
|
||||
import com.google.android.gms.search.global.SetIncludeInGlobalSearchResponse;
|
||||
import com.google.android.gms.search.global.internal.IGlobalSearchAdminCallbacks;
|
||||
import com.google.android.gms.search.global.internal.IGlobalSearchAdminService;
|
||||
|
||||
public class GlobalSearchAdminImpl extends IGlobalSearchAdminService.Stub {
|
||||
private static final String TAG = "GmsIcingGlobalImpl";
|
||||
|
||||
@Override
|
||||
public void getGlobalSearchSources(GetGlobalSearchSourcesRequest request, IGlobalSearchAdminCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "getGlobalSearchSources: " + request);
|
||||
callbacks.onGetGlobalSearchSourcesResponse(new GetGlobalSearchSourcesResponse(Status.SUCCESS, new Parcelable[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExperimentIds(SetExperimentIdsRequest request, IGlobalSearchAdminCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "setExperimentIds: " + request);
|
||||
callbacks.onSetExperimentIdsResponse(new SetExperimentIdsResponse(Status.SUCCESS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getCurrentExperimentIds(GetCurrentExperimentIdsRequest request, IGlobalSearchAdminCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "getCurrentExperimentIds: " + request);
|
||||
callbacks.onGetCurrentExperimentIdsResponse(new GetCurrentExperimentIdsResponse(Status.SUCCESS, new int[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getPendingExperimentIds(GetPendingExperimentIdsRequest request, IGlobalSearchAdminCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "getPendingExperimentIds: " + request);
|
||||
callbacks.onGetPendingExperimentIdsResponse(new GetPendingExperimentIdsResponse(Status.SUCCESS, new int[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIncludeInGlobalSearch(SetIncludeInGlobalSearchRequest request, IGlobalSearchAdminCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "setIncludeInGlobalSearch: " + request);
|
||||
callbacks.onSetIncludeInGlobalSearchResponse(new SetIncludeInGlobalSearchResponse(Status.SUCCESS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
if (super.onTransact(code, data, reply, flags)) return true;
|
||||
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.icing;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.common.api.CommonStatusCodes;
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
|
||||
import org.microg.gms.BaseService;
|
||||
import org.microg.gms.common.GmsService;
|
||||
|
||||
public class IndexService extends BaseService {
|
||||
private AppDataSearchImpl appDataSearch = new AppDataSearchImpl();
|
||||
private GlobalSearchAdminImpl globalSearchAdmin = new GlobalSearchAdminImpl();
|
||||
private SearchCorporaImpl searchCorpora = new SearchCorporaImpl();
|
||||
private SearchQueriesImpl searchQueries = new SearchQueriesImpl();
|
||||
|
||||
public IndexService() {
|
||||
super("GmsIcingIndexSvc",
|
||||
GmsService.INDEX, GmsService.SEARCH_ADMINISTRATION, GmsService.SEARCH_CORPORA,
|
||||
GmsService.SEARCH_GLOBAL, GmsService.SEARCH_IME, GmsService.SEARCH_QUERIES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
|
||||
switch (service) {
|
||||
case INDEX:
|
||||
callback.onPostInitComplete(0, appDataSearch.asBinder(), null);
|
||||
break;
|
||||
case SEARCH_ADMINISTRATION:
|
||||
Log.w(TAG, "Service not yet implemented: " + service);
|
||||
callback.onPostInitComplete(CommonStatusCodes.ERROR, null, null);
|
||||
break;
|
||||
case SEARCH_QUERIES:
|
||||
callback.onPostInitComplete(0, searchQueries.asBinder(), null);
|
||||
break;
|
||||
case SEARCH_GLOBAL:
|
||||
callback.onPostInitComplete(0, globalSearchAdmin.asBinder(), null);
|
||||
break;
|
||||
case SEARCH_CORPORA:
|
||||
callback.onPostInitComplete(0, searchCorpora.asBinder(), null);
|
||||
break;
|
||||
case SEARCH_IME:
|
||||
Log.w(TAG, "Service not yet implemented: " + service);
|
||||
callback.onPostInitComplete(CommonStatusCodes.ERROR, null, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.icing;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.appdatasearch.UsageInfo;
|
||||
import com.google.android.gms.appdatasearch.internal.ILightweightAppDataSearch;
|
||||
import com.google.android.gms.appdatasearch.internal.ILightweightAppDataSearchCallbacks;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class LightweightAppDataSearchImpl extends ILightweightAppDataSearch.Stub {
|
||||
private static final String TAG = "GmsIcingLightSearchImpl";
|
||||
|
||||
public void view(ILightweightAppDataSearchCallbacks callbacks, String packageName, UsageInfo[] usageInfos) {
|
||||
Log.d(TAG, "view: " + Arrays.toString(usageInfos));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
if (super.onTransact(code, data, reply, flags)) return true;
|
||||
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.icing;
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
|
||||
import org.microg.gms.BaseService;
|
||||
import org.microg.gms.common.GmsService;
|
||||
|
||||
public class LightweightIndexService extends BaseService {
|
||||
private LightweightAppDataSearchImpl appDataSearch = new LightweightAppDataSearchImpl();
|
||||
|
||||
public LightweightIndexService() {
|
||||
super("GmsIcingLightIndexSvc", GmsService.LIGHTWEIGHT_INDEX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
|
||||
callback.onPostInitComplete(0, appDataSearch.asBinder(), null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.icing;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.appdatasearch.CorpusStatus;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.search.corpora.ClearCorpusRequest;
|
||||
import com.google.android.gms.search.corpora.GetCorpusInfoRequest;
|
||||
import com.google.android.gms.search.corpora.GetCorpusStatusRequest;
|
||||
import com.google.android.gms.search.corpora.GetCorpusStatusResponse;
|
||||
import com.google.android.gms.search.corpora.RequestIndexingRequest;
|
||||
import com.google.android.gms.search.corpora.RequestIndexingResponse;
|
||||
import com.google.android.gms.search.corpora.internal.ISearchCorporaCallbacks;
|
||||
import com.google.android.gms.search.corpora.internal.ISearchCorporaService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class SearchCorporaImpl extends ISearchCorporaService.Stub {
|
||||
private static final String TAG = "GmsIcingCorporaImpl";
|
||||
|
||||
// We count the sequence number here to make clients happy.
|
||||
private Map<String, Long> corpusSequenceNumbers = new HashMap<String, Long>();
|
||||
|
||||
@Override
|
||||
public void requestIndexing(RequestIndexingRequest request, ISearchCorporaCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "requestIndexing: " + request);
|
||||
corpusSequenceNumbers.put(request.packageName + "/" + request.corpus, request.sequenceNumber);
|
||||
callbacks.onRequestIndexing(new RequestIndexingResponse(Status.SUCCESS, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCorpus(ClearCorpusRequest request, ISearchCorporaCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "clearCorpus");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getCorpusStatus(GetCorpusStatusRequest request, ISearchCorporaCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "getCorpusStatus: " + request);
|
||||
CorpusStatus status = new CorpusStatus();
|
||||
String numIndex = request.packageName + "/" + request.corpus;
|
||||
if (corpusSequenceNumbers.containsKey(numIndex)) {
|
||||
status.found = true;
|
||||
status.lastIndexedSeqno = corpusSequenceNumbers.get(numIndex);
|
||||
status.lastCommittedSeqno = status.lastIndexedSeqno;
|
||||
}
|
||||
callbacks.onGetCorpusStatus(new GetCorpusStatusResponse(Status.SUCCESS, status));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getCorpusInfo(GetCorpusInfoRequest request, ISearchCorporaCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "getCorpusInfo");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
if (super.onTransact(code, data, reply, flags)) return true;
|
||||
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.icing;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.search.queries.QueryRequest;
|
||||
import com.google.android.gms.search.queries.QueryResponse;
|
||||
import com.google.android.gms.search.queries.internal.ISearchQueriesCallbacks;
|
||||
import com.google.android.gms.search.queries.internal.ISearchQueriesService;
|
||||
|
||||
public class SearchQueriesImpl extends ISearchQueriesService.Stub {
|
||||
private static final String TAG = "GmsIcingQueriesImpl";
|
||||
|
||||
@Override
|
||||
public void query(QueryRequest request, ISearchQueriesCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "query: " + request);
|
||||
callbacks.onQuery(new QueryResponse(Status.CANCELED, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
if (super.onTransact(code, data, reply, flags)) return true;
|
||||
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.location;
|
||||
|
||||
public class LocationConstants {
|
||||
public static final String KEY_MOCK_LOCATION = "mockLocation";
|
||||
|
||||
// Place picker client->service
|
||||
public static final String EXTRA_PRIMARY_COLOR = "primary_color";
|
||||
public static final String EXTRA_PRIMARY_COLOR_DARK = "primary_color_dark";
|
||||
public static final String EXTRA_CLIENT_VERSION = "gmscore_client_jar_version";
|
||||
public static final String EXTRA_BOUNDS = "latlng_bounds";
|
||||
|
||||
// Place picker service->client
|
||||
public static final String EXTRA_ATTRIBUTION = "third_party_attributions";
|
||||
public static final String EXTRA_FINAL_BOUNDS = "final_latlng_bounds";
|
||||
public static final String EXTRA_PLACE = "selected_place";
|
||||
public static final String EXTRA_STATUS = "status";
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.mdm;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
public class NetworkQualityService extends Service {
|
||||
private static final String TAG = "GmsMdmQualitySvc";
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
Log.d(TAG, "onBind: " + intent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.people;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Service;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.Intent;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class ContactSyncService extends Service {
|
||||
private static final String TAG = "GmsContactSync";
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return (new AbstractThreadedSyncAdapter(this, true) {
|
||||
@Override
|
||||
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
|
||||
Log.d(TAG, "unimplemented Method: onPerformSync");
|
||||
}
|
||||
}).getSyncAdapterBinder();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* 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.people;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
private static final int DB_VERSION = 5;
|
||||
private static final String DB_NAME = "pluscontacts.db";
|
||||
private static final String CREATE_OWNERS = "CREATE TABLE owners (" +
|
||||
"_id INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
"account_name TEXT NOT NULL UNIQUE," + // example@gmail.com
|
||||
"gaia_id TEXT," + // 123456789123456789123
|
||||
"page_gaia_id TEXT," +
|
||||
"display_name TEXT," + // firstName lastName
|
||||
"avatar TEXT," + // url (relative?)
|
||||
"cover_photo_url TEXT," + // cover url (relative?)
|
||||
"cover_photo_height INTEGER NOT NULL DEFAULT 0," +
|
||||
"cover_photo_width INTEGER NOT NULL DEFAULT 0," +
|
||||
"cover_photo_id TEXT," +
|
||||
"last_sync_start_time INTEGER NOT NULL DEFAULT 0," + // timestamp
|
||||
"last_sync_finish_time INTEGER NOT NULL DEFAULT 0," + // timestamp
|
||||
"last_sync_status INTEGER NOT NULL DEFAULT 0," + // eg. 2
|
||||
"last_successful_sync_time INTEGER NOT NULL DEFAULT 0," + // timestamp
|
||||
"sync_to_contacts INTEGER NOT NULL DEFAULT 0," + // 0
|
||||
"is_dasher INTEGER NOT NULL DEFAULT 0," + // 0
|
||||
"dasher_domain TEXT," +
|
||||
"etag TEXT," +
|
||||
"sync_circles_to_contacts INTEGER NOT NULL DEFAULT 0," + // 0
|
||||
"sync_evergreen_to_contacts INTEGER NOT NULL DEFAULT 0," + // 0
|
||||
"last_full_people_sync_time INTEGER NOT NULL DEFAULT 0," + // timestamp
|
||||
"is_active_plus_account INTEGER NOT NULL DEFAULT 0," + // 0
|
||||
"sync_me_to_contacts INTEGER NOT NULL DEFAULT 0," +
|
||||
"given_name TEXT," +
|
||||
"family_name TEXT," +
|
||||
"contacts_backup_and_sync INTEGER NOT NULL DEFAULT 0);"; // 0
|
||||
private static final String CREATE_CIRCLES = "CREATE TABLE circles (" +
|
||||
"_id INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
"owner_id INTEGER NOT NULL," +
|
||||
"circle_id TEXT NOT NULL," +
|
||||
"name TEXT,sort_key TEXT," +
|
||||
"type INTEGER NOT NULL," +
|
||||
"for_sharing INTEGER NOT NULL DEFAULT 0," +
|
||||
"people_count INTEGER NOT NULL DEFAULT -1," +
|
||||
"client_policies INTEGER NOT NULL DEFAULT 0," +
|
||||
"etag TEXT,last_modified INTEGER NOT NULL DEFAULT 0," +
|
||||
"sync_to_contacts INTEGER NOT NULL DEFAULT 0," +
|
||||
"UNIQUE (owner_id,circle_id)," +
|
||||
"FOREIGN KEY (owner_id) REFERENCES owners(_id) ON DELETE CASCADE);";
|
||||
private static final String ALTER_OWNERS_3_1 = "ALTER TABLE owners ADD COLUMN is_active_plus_account INTEGER NOT NULL DEFAULT 0;";
|
||||
private static final String ALTER_OWNERS_3_2 = "ALTER TABLE owners ADD COLUMN sync_me_to_contacts INTEGER NOT NULL DEFAULT 0;";
|
||||
private static final String ALTER_OWNERS_4_1 = "ALTER TABLE owners ADD COLUMN given_name TEXT;";
|
||||
private static final String ALTER_OWNERS_4_2 = "ALTER TABLE owners ADD COLUMN family_name TEXT;";
|
||||
private static final String ALTER_OWNERS_5_1 = "ALTER TABLE owners ADD COLUMN contacts_backup_and_sync INTEGER NOT NULL DEFAULT 0;";
|
||||
public static final String OWNERS_TABLE = "owners";
|
||||
public static final String CIRCLES_TABLE = "circles";
|
||||
|
||||
|
||||
public DatabaseHelper(Context context) {
|
||||
super(context, DB_NAME, null, DB_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(CREATE_OWNERS);
|
||||
db.execSQL(CREATE_CIRCLES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion < 2) db.execSQL(CREATE_CIRCLES);
|
||||
if (oldVersion < 3) {
|
||||
db.execSQL(ALTER_OWNERS_3_1);
|
||||
db.execSQL(ALTER_OWNERS_3_2);
|
||||
}
|
||||
if (oldVersion < 4) {
|
||||
db.execSQL(ALTER_OWNERS_4_1);
|
||||
db.execSQL(ALTER_OWNERS_4_2);
|
||||
}
|
||||
if (oldVersion < 5) {
|
||||
db.execSQL(ALTER_OWNERS_5_1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
// silently accept
|
||||
}
|
||||
|
||||
public Cursor getOwners() {
|
||||
return getReadableDatabase().query(OWNERS_TABLE, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public void putOwner(ContentValues contentValues) {
|
||||
getWritableDatabase().insertWithOnConflict(OWNERS_TABLE, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
close();
|
||||
}
|
||||
|
||||
public Cursor getOwner(String accountName) {
|
||||
return getReadableDatabase().query(OWNERS_TABLE, null, "account_name=?", new String[]{accountName}, null, null, null);
|
||||
}
|
||||
|
||||
public Cursor getCircles(int ownerId, String circleId, int type) {
|
||||
return getReadableDatabase().query(CIRCLES_TABLE, null,
|
||||
"owner_id=?1 AND (circle_id = ?2 OR ?2 = '') AND (type = ?3 OR ?3 = -999 OR (?3 = -998 AND type = -1))",
|
||||
new String[]{Integer.toString(ownerId), circleId != null ? circleId : "", Integer.toString(type)}, null, null, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* 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.people;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.common.Scopes;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.microg.gms.auth.AuthManager;
|
||||
import org.microg.gms.auth.AuthResponse;
|
||||
import org.microg.gms.common.Constants;
|
||||
import org.microg.gms.common.Utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
public class PeopleManager {
|
||||
private static final String TAG = "GmsPeopleManager";
|
||||
public static final String USERINFO_SCOPE = "oauth2:" + Scopes.USERINFO_PROFILE;
|
||||
public static final String USERINFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo";
|
||||
public static final String REGEX_SEARCH_USER_PHOTO = "https?\\:\\/\\/lh([0-9]*)\\.googleusercontent\\.com/";
|
||||
|
||||
public static String getDisplayName(Context context, String accountName) {
|
||||
DatabaseHelper databaseHelper = new DatabaseHelper(context);
|
||||
Cursor cursor = databaseHelper.getOwner(accountName);
|
||||
String displayName = null;
|
||||
try {
|
||||
if (cursor.moveToNext()) {
|
||||
int idx = cursor.getColumnIndex("display_name");
|
||||
if (idx >= 0 && !cursor.isNull(idx)) displayName = cursor.getString(idx);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
databaseHelper.close();
|
||||
}
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public static String getGivenName(Context context, String accountName) {
|
||||
DatabaseHelper databaseHelper = new DatabaseHelper(context);
|
||||
Cursor cursor = databaseHelper.getOwner(accountName);
|
||||
String displayName = null;
|
||||
try {
|
||||
if (cursor.moveToNext()) {
|
||||
int idx = cursor.getColumnIndex("given_name");
|
||||
if (idx >= 0 && !cursor.isNull(idx)) displayName = cursor.getString(idx);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
databaseHelper.close();
|
||||
}
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public static File getOwnerAvatarFile(Context context, String accountName, boolean network) {
|
||||
DatabaseHelper databaseHelper = new DatabaseHelper(context);
|
||||
Cursor cursor = databaseHelper.getOwner(accountName);
|
||||
String url = null;
|
||||
if (cursor.moveToNext()) {
|
||||
int idx = cursor.getColumnIndex("avatar");
|
||||
if (idx >= 0 && !cursor.isNull(idx)) url = cursor.getString(idx);
|
||||
}
|
||||
cursor.close();
|
||||
databaseHelper.close();
|
||||
if (url == null) return null;
|
||||
String urlLastPart = url.replaceFirst(REGEX_SEARCH_USER_PHOTO, "");
|
||||
File file = new File(context.getCacheDir(), urlLastPart);
|
||||
if (!file.getParentFile().mkdirs() && file.exists()) {
|
||||
return file;
|
||||
}
|
||||
if (!network) return null;
|
||||
try {
|
||||
URLConnection conn = new URL(url).openConnection();
|
||||
conn.setDoInput(true);
|
||||
byte[] bytes = Utils.readStreamToEnd(conn.getInputStream());
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
outputStream.write(bytes);
|
||||
outputStream.close();
|
||||
return file;
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Bitmap getOwnerAvatarBitmap(Context context, String accountName, boolean network) {
|
||||
File avaterFile = getOwnerAvatarFile(context, accountName, network);
|
||||
if (avaterFile == null) return null;
|
||||
return BitmapFactory.decodeFile(avaterFile.getPath());
|
||||
}
|
||||
|
||||
public static void updateOwnerAvatar(Context context, String accountName, String newAvatar) {
|
||||
try (DatabaseHelper databaseHelper = new DatabaseHelper(context); SQLiteDatabase db = databaseHelper.getWritableDatabase()) {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put("avatar", newAvatar);
|
||||
int rowsAffected = db.update(DatabaseHelper.OWNERS_TABLE, contentValues, "account_name = ?", new String[]{accountName});
|
||||
Log.d(TAG, "updateOwnerAvatar affected: " + rowsAffected);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error updating avatar: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static String loadUserInfo(Context context, Account account) {
|
||||
try {
|
||||
URLConnection conn = new URL(USERINFO_URL).openConnection();
|
||||
conn.addRequestProperty("Authorization", "Bearer " + getUserInfoAuthKey(context, account));
|
||||
conn.setDoInput(true);
|
||||
byte[] bytes = Utils.readStreamToEnd(conn.getInputStream());
|
||||
JSONObject info = new JSONObject(new String(bytes));
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put("account_name", account.name);
|
||||
if (info.has("id")) contentValues.put("gaia_id", info.getString("id"));
|
||||
if (info.has("picture")) contentValues.put("avatar", info.getString("picture"));
|
||||
if (info.has("name")) contentValues.put("display_name", info.getString("name"));
|
||||
if (info.has("given_name")) contentValues.put("given_name", info.getString("given_name"));
|
||||
if (info.has("family_name")) contentValues.put("family_name", info.getString("family_name"));
|
||||
contentValues.put("last_sync_start_time", System.currentTimeMillis());
|
||||
contentValues.put("last_sync_finish_time", System.currentTimeMillis());
|
||||
contentValues.put("last_successful_sync_time", System.currentTimeMillis());
|
||||
contentValues.put("last_full_people_sync_time", System.currentTimeMillis());
|
||||
DatabaseHelper databaseHelper = new DatabaseHelper(context);
|
||||
databaseHelper.putOwner(contentValues);
|
||||
databaseHelper.close();
|
||||
return contentValues.getAsString("gaia_id");
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getUserInfoAuthKey(Context context, Account account) {
|
||||
AuthManager authManager = new AuthManager(context, account.name, Constants.GMS_PACKAGE_NAME, USERINFO_SCOPE);
|
||||
authManager.setPermitted(true);
|
||||
String result = authManager.getAuthToken();
|
||||
if (result == null) {
|
||||
try {
|
||||
AuthResponse response = authManager.requestAuthWithBackgroundResolution(false);
|
||||
result = response.auth;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.people;
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
|
||||
import org.microg.gms.BaseService;
|
||||
import org.microg.gms.common.GmsService;
|
||||
|
||||
public class PeopleService extends BaseService {
|
||||
private PeopleServiceImpl impl = new PeopleServiceImpl(this);
|
||||
|
||||
public PeopleService() {
|
||||
super("GmsPeopleSvc", GmsService.PEOPLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
|
||||
callback.onPostInitComplete(0, impl.asBinder(), null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* 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.people;
|
||||
|
||||
import android.Manifest;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.common.data.DataHolder;
|
||||
import com.google.android.gms.common.internal.ICancelToken;
|
||||
import com.google.android.gms.people.internal.IPeopleCallbacks;
|
||||
import com.google.android.gms.people.internal.IPeopleService;
|
||||
import com.google.android.gms.people.model.AccountMetadata;
|
||||
|
||||
import org.microg.gms.auth.AuthConstants;
|
||||
import org.microg.gms.common.GooglePackagePermission;
|
||||
import org.microg.gms.common.NonCancelToken;
|
||||
import org.microg.gms.common.PackageUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class PeopleServiceImpl extends IPeopleService.Stub {
|
||||
private static final String TAG = "GmsPeopleSvcImpl";
|
||||
private final Context context;
|
||||
|
||||
public PeopleServiceImpl(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingPermission")
|
||||
@Override
|
||||
public void loadOwners(final IPeopleCallbacks callbacks, boolean var2, boolean var3, final String accountName, String var5, int sortOrder) {
|
||||
Log.d(TAG, "loadOwners: " + var2 + ", " + var3 + ", " + accountName + ", " + var5 + ", " + sortOrder);
|
||||
if (context.checkCallingPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
|
||||
PackageUtils.assertGooglePackagePermission(context, GooglePackagePermission.OWNER);
|
||||
}
|
||||
AccountManager accountManager = AccountManager.get(context);
|
||||
Bundle accountMetadata = new Bundle();
|
||||
String accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE;
|
||||
for (Account account : accountManager.getAccountsByType(accountType)) {
|
||||
if (accountName == null || account.name.equals(accountName)) {
|
||||
accountMetadata.putParcelable(account.name, new AccountMetadata());
|
||||
}
|
||||
}
|
||||
Bundle extras = new Bundle();
|
||||
extras.putBundle("account_metadata", accountMetadata);
|
||||
try {
|
||||
DatabaseHelper databaseHelper = new DatabaseHelper(context);
|
||||
DataHolder dataHolder = new DataHolder(databaseHelper.getOwners(), 0, extras);
|
||||
Log.d(TAG, "loadOwners[result]: " + dataHolder);
|
||||
callbacks.onDataHolder(0, extras, dataHolder);
|
||||
databaseHelper.close();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPeopleForAggregation(IPeopleCallbacks callbacks, String account, String var3, String filter, int var5, boolean var6, int var7, int var8, String var9, boolean var10, int var11, int var12) throws RemoteException {
|
||||
Log.d(TAG, "loadPeopleForAggregation: " + account + ", " + var3 + ", " + filter + ", " + var5 + ", " + var6 + ", " + var7 + ", " + var8 + ", " + var9 + ", " + var10 + ", " + var11 + ", " + var12);
|
||||
callbacks.onDataHolder(0, new Bundle(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle registerDataChangedListener(IPeopleCallbacks callbacks, boolean register, String var3, String var4, int scopes) throws RemoteException {
|
||||
Log.d(TAG, "registerDataChangedListener: " + register + ", " + var3 + ", " + var4 + ", " + scopes);
|
||||
callbacks.onDataHolder(0, new Bundle(), null);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadCircles(IPeopleCallbacks callbacks, String account, String pageGaiaId, String circleId, int type, String var6, boolean var7) throws RemoteException {
|
||||
Log.d(TAG, "loadCircles: " + account + ", " + pageGaiaId + ", " + circleId + ", " + type + ", " + var6 + ", " + var7);
|
||||
PackageUtils.assertGooglePackagePermission(context, GooglePackagePermission.PEOPLE);
|
||||
try {
|
||||
DatabaseHelper databaseHelper = new DatabaseHelper(context);
|
||||
Cursor owner = databaseHelper.getOwner(account);
|
||||
int ownerId = -1;
|
||||
if (owner.moveToNext()) {
|
||||
ownerId = owner.getInt(0);
|
||||
}
|
||||
owner.close();
|
||||
Bundle extras = new Bundle();
|
||||
DataHolder dataHolder = new DataHolder(databaseHelper.getCircles(ownerId, circleId, type), 0, extras);
|
||||
callbacks.onDataHolder(0, new Bundle(), dataHolder);
|
||||
databaseHelper.close();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle requestSync(String account, String var2, long var3, boolean var5, boolean var6) throws RemoteException {
|
||||
Log.d(TAG, "requestSync: " + account + ", " + var2 + ", " + var3 + ", " + var5 + ", " + var6);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICancelToken loadOwnerAvatar(final IPeopleCallbacks callbacks, final String account, String pageId, int size, int flags) {
|
||||
Log.d(TAG, "loadOwnerAvatar: " + account + ", " + pageId + ", " + size + ", " + flags);
|
||||
PackageUtils.assertGooglePackagePermission(context, GooglePackagePermission.OWNER);
|
||||
final Thread thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putBoolean("rewindable", false);
|
||||
extras.putInt("width", 0);
|
||||
extras.putInt("height", 0);
|
||||
File avaterFile = PeopleManager.getOwnerAvatarFile(context, account, true);
|
||||
try {
|
||||
ParcelFileDescriptor fileDescriptor = null;
|
||||
if (avaterFile != null) {
|
||||
fileDescriptor = ParcelFileDescriptor.open(avaterFile, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
}
|
||||
callbacks.onParcelFileDescriptor(0, extras, fileDescriptor, extras);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
return new ICancelToken.Stub() {
|
||||
@Override
|
||||
public void cancel() throws RemoteException {
|
||||
thread.interrupt();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICancelToken loadAutocompleteList(IPeopleCallbacks callbacks, String account, String pageId, boolean directorySearch, String var5, String query, int autocompleteType, int var8, int numberOfResults, boolean var10) throws RemoteException {
|
||||
Log.d(TAG, "loadAutocompleteList: " + account + ", " + pageId + ", " + directorySearch + ", " + var5 + ", " + query + ", " + autocompleteType + ", " + var8 + ", " + numberOfResults + ", " + var10);
|
||||
callbacks.onDataHolder(0, new Bundle(), null);
|
||||
return new NonCancelToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
if (super.onTransact(code, data, reply, flags)) return true;
|
||||
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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.phenotype;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class ConfigurationProvider extends ContentProvider {
|
||||
private static final String TAG = "GmsPhenotypeCfgProvider";
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
Log.d(TAG, "unimplemented Method: onCreate");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
|
||||
selection = Uri.decode(uri.getLastPathSegment());
|
||||
if (selection == null) return null;
|
||||
return new MatrixCursor(new String[]{"key", "value"});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.places;
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
|
||||
import org.microg.gms.BaseService;
|
||||
import org.microg.gms.common.GmsService;
|
||||
|
||||
public class GeoDataService extends BaseService {
|
||||
public GeoDataService() {
|
||||
super("GmsPlcGeoSvc", GmsService.GEODATA);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
|
||||
callback.onPostInitComplete(0, new PlacesServiceImpl().asBinder(), null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.places;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
|
||||
import org.microg.gms.BaseService;
|
||||
import org.microg.gms.common.GmsService;
|
||||
|
||||
public class PlaceDetectionService extends BaseService {
|
||||
public PlaceDetectionService() {
|
||||
super("GmsPlcDtctSvc", GmsService.PLACE_DETECTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
|
||||
callback.onPostInitComplete(0, new PlaceDetectionServiceImpl().asBinder(), null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.places;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.location.places.internal.IGooglePlaceDetectionService;
|
||||
|
||||
public class PlaceDetectionServiceImpl extends IGooglePlaceDetectionService.Stub{
|
||||
private static final String TAG = "GmsPlcDtctSvcImpl";
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
if (super.onTransact(code, data, reply, flags)) return true;
|
||||
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.places;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.location.places.internal.IGooglePlacesService;
|
||||
|
||||
public class PlacesServiceImpl extends IGooglePlacesService.Stub {
|
||||
private static final String TAG = "GmsPlcSvcImpl";
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
if (super.onTransact(code, data, reply, flags)) return true;
|
||||
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.playlog;
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
|
||||
import org.microg.gms.BaseService;
|
||||
import org.microg.gms.common.GmsService;
|
||||
|
||||
@Deprecated
|
||||
public class PlayLogService extends BaseService {
|
||||
|
||||
private PlayLogServiceImpl playLogService = new PlayLogServiceImpl();
|
||||
|
||||
public PlayLogService() {
|
||||
super("GmsPlayLogSvc", GmsService.PLAY_LOG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
|
||||
callback.onPostInitComplete(0, playLogService.asBinder(), null);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.playlog;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.playlog.internal.IPlayLogService;
|
||||
import com.google.android.gms.playlog.internal.LogEvent;
|
||||
import com.google.android.gms.clearcut.internal.PlayLoggerContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Deprecated
|
||||
public class PlayLogServiceImpl extends IPlayLogService.Stub {
|
||||
private static final String TAG = "GmsPlayLogSvcImpl";
|
||||
|
||||
@Override
|
||||
public void onEvent(String packageName, PlayLoggerContext context, LogEvent event) throws RemoteException {
|
||||
Log.d(TAG, "onEvent: context[packageName]:" + context.packageName + " event[tag]:" + event.tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMultiEvent(String packageName, PlayLoggerContext context, List<LogEvent> events) throws RemoteException {
|
||||
for (LogEvent event : events) {
|
||||
onEvent(packageName, context, event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
if (super.onTransact(code, data, reply, flags)) return true;
|
||||
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.plus;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
public class PlusOneButtonImpl extends View {
|
||||
public PlusOneButtonImpl(Context context, int size, int annotation, String url,
|
||||
String account) {
|
||||
super(context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.reminders;
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
|
||||
import org.microg.gms.BaseService;
|
||||
import org.microg.gms.common.GmsService;
|
||||
|
||||
public class RemindersService extends BaseService {
|
||||
|
||||
public RemindersService() {
|
||||
super("GmsRemindSvc", GmsService.REMINDERS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
|
||||
callback.onPostInitComplete(0, new RemindersServiceImpl(), null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* 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.reminders;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.reminders.AccountState;
|
||||
import com.google.android.gms.reminders.CreateReminderOptionsInternal;
|
||||
import com.google.android.gms.reminders.LoadRemindersOptions;
|
||||
import com.google.android.gms.reminders.ReindexDueDatesOptions;
|
||||
import com.google.android.gms.reminders.UpdateRecurrenceOptions;
|
||||
import com.google.android.gms.reminders.internal.IRemindersCallbacks;
|
||||
import com.google.android.gms.reminders.internal.IRemindersService;
|
||||
import com.google.android.gms.reminders.model.CustomizedSnoozePresetEntity;
|
||||
import com.google.android.gms.reminders.model.TaskEntity;
|
||||
import com.google.android.gms.reminders.model.TaskIdEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RemindersServiceImpl extends IRemindersService.Stub {
|
||||
private static final String TAG = RemindersServiceImpl.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void loadReminders(IRemindersCallbacks callbacks, LoadRemindersOptions options) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: loadReminders");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(IRemindersCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: addListener");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createReminder(IRemindersCallbacks callbacks, TaskEntity task) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: createReminder");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateReminder(IRemindersCallbacks callbacks, TaskEntity task) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: updateReminder");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteReminder(IRemindersCallbacks callbacks, TaskIdEntity taskId) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: deleteReminder");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bumpReminder(IRemindersCallbacks callbacks, TaskIdEntity taskId) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: bumpReminder");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasUpcomingReminders(IRemindersCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: hasUpcomingReminders");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createRecurrence(IRemindersCallbacks callbacks, TaskEntity task) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: createRecurrence");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRecurrence(IRemindersCallbacks callbacks, String s1, TaskEntity task, UpdateRecurrenceOptions options) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: updateRecurrence");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRecurrence(IRemindersCallbacks callbacks, String s1, UpdateRecurrenceOptions options) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: deleteRecurrence");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeRecurrence(IRemindersCallbacks callbacks, String s1, TaskEntity task, UpdateRecurrenceOptions options) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: changeRecurrence");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makeTaskRecurring(IRemindersCallbacks callbacks, TaskEntity task) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: makeTaskRecurring");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makeRecurrenceSingleInstance(IRemindersCallbacks callbacks, String s1, TaskEntity task, UpdateRecurrenceOptions options) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: makeRecurrenceSingleInstance");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearListeners() throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: clearListeners");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void batchUpdateReminders(IRemindersCallbacks callbacks, List<TaskEntity> tasks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: batchUpdateReminders");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createReminderWithOptions(IRemindersCallbacks callbacks, TaskEntity task, CreateReminderOptionsInternal options) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: createReminderWithOptions");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getCustomizedSnoozePreset(IRemindersCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: getCustomizedSnoozePreset");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCustomizedSnoozePreset(IRemindersCallbacks callbacks, CustomizedSnoozePresetEntity preset) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: setCustomizedSnoozePreset");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccountState(IRemindersCallbacks callbacks, AccountState accountState) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: setAccountState");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getAccountState(IRemindersCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: getAccountState");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkReindexDueDatesNeeded(IRemindersCallbacks callbacks, ReindexDueDatesOptions options) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: checkReindexDueDatesNeeded");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reindexDueDates(IRemindersCallbacks callbacks, ReindexDueDatesOptions options) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: reindexDueDates");
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2025 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ProviderInfo
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.net.Uri
|
||||
import android.os.CancellationSignal
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.util.Log
|
||||
import androidx.core.content.FileProvider
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
private const val TAG = "GmsFileProvider"
|
||||
|
||||
class GmsFileProvider : FileProvider() {
|
||||
private val emptyProjection = arrayOfNulls<String>(0)
|
||||
private var initializationFailed = false
|
||||
|
||||
override fun attachInfo(context: Context, info: ProviderInfo) {
|
||||
try {
|
||||
super.attachInfo(context, info)
|
||||
} catch (e: Exception) {
|
||||
initializationFailed = true
|
||||
Log.e(TAG, "attachInfo error:${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getType(uri: Uri): String? {
|
||||
if (initializationFailed) {
|
||||
return null
|
||||
}
|
||||
return super.getType(uri)
|
||||
}
|
||||
|
||||
override fun openFile(
|
||||
uri: Uri, mode: String, signal: CancellationSignal?
|
||||
): ParcelFileDescriptor? {
|
||||
if (!initializationFailed) {
|
||||
return super.openFile(uri, mode, signal)
|
||||
}
|
||||
throw FileNotFoundException("FileProvider creation failed")
|
||||
}
|
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
|
||||
if (initializationFailed) {
|
||||
return 0
|
||||
}
|
||||
return super.delete(uri, selection, selectionArgs)
|
||||
}
|
||||
|
||||
override fun query(
|
||||
uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?
|
||||
): Cursor {
|
||||
if (initializationFailed) {
|
||||
return MatrixCursor(emptyProjection)
|
||||
}
|
||||
return super.query(uri, projection, selection, selectionArgs, sortOrder)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.settings;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
public class GoogleSettingsProvider extends ContentProvider {
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.ui;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.google.android.gms.BuildConfig;
|
||||
|
||||
import org.microg.tools.ui.AbstractAboutFragment;
|
||||
import org.microg.tools.ui.AbstractSettingsActivity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AboutFragment extends AbstractAboutFragment {
|
||||
|
||||
@Override
|
||||
protected void collectLibraries(List<AbstractAboutFragment.Library> libraries) {
|
||||
if ("vtm".equalsIgnoreCase(BuildConfig.FLAVOR_maps)) {
|
||||
libraries.add(new AbstractAboutFragment.Library("org.oscim.android", "V™", "GNU LGPLv3, Hannes Janetzek and devemux86"));
|
||||
libraries.add(new AbstractAboutFragment.Library("org.slf4j", "SLF4J", "MIT License, QOS.ch"));
|
||||
} else if ("mapbox".equalsIgnoreCase(BuildConfig.FLAVOR_maps) || "maplibre".equalsIgnoreCase(BuildConfig.FLAVOR_maps)){
|
||||
libraries.add(new AbstractAboutFragment.Library("com.mapbox.mapboxsdk", "MapLibre Native for Android", "Two-Clause BSD, MapLibre contributors"));
|
||||
} else if ("hms".equalsIgnoreCase(BuildConfig.FLAVOR_maps)) {
|
||||
libraries.add(new AbstractAboutFragment.Library("com.huawei.hms.maps.api", "Huawei MapKit", "Proprietary, Huawei Technologies"));
|
||||
}
|
||||
libraries.add(new AbstractAboutFragment.Library("androidx", "Android Jetpack", "Apache License 2.0, The Android Open Source Project"));
|
||||
libraries.add(new AbstractAboutFragment.Library("com.upokecenter.cbor", "CBOR", "Creative Commons Zero, Peter O"));
|
||||
libraries.add(new AbstractAboutFragment.Library("de.hdodenhof.circleimageview", "CircleImageView", "Apache License 2.0, Henning Dodenhof"));
|
||||
libraries.add(new AbstractAboutFragment.Library("su.litvak.chromecast.api.v2", "ChromeCast Java API v2", "Apache License 2.0, Vitaly Litvak"));
|
||||
libraries.add(new AbstractAboutFragment.Library("org.conscrypt", "Conscrypt", "Apache License 2.0, The Android Open Source Project"));
|
||||
libraries.add(new AbstractAboutFragment.Library("org.chromium.net", "Cronet", "BSD-style License, The Chromium Authors"));
|
||||
libraries.add(new AbstractAboutFragment.Library("org.jetbrains.kotlin", "Kotlin", "Apache License 2.0, JetBrains s.r.o."));
|
||||
libraries.add(new AbstractAboutFragment.Library("com.google.android.material", "Material Components", "Apache License 2.0, The Android Open Source Project"));
|
||||
libraries.add(new AbstractAboutFragment.Library("com.android.volley", "Volley", "Apache License 2.0, The Android Open Source Project"));
|
||||
libraries.add(new AbstractAboutFragment.Library("com.squareup.wire", "Wire Protocol Buffers", "Apache License 2.0, Square Inc."));
|
||||
libraries.add(new AbstractAboutFragment.Library("com.google.zxing", "ZXing", "Apache License 2.0, ZXing authors"));
|
||||
}
|
||||
|
||||
public static class AsActivity extends AbstractSettingsActivity {
|
||||
public AsActivity() {
|
||||
showHomeAsUp = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Fragment getFragment() {
|
||||
return new AboutFragment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package org.microg.gms.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.os.ResultReceiver;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.google.android.gms.R;
|
||||
|
||||
import org.microg.gms.gcm.GcmDatabase;
|
||||
|
||||
public class AskPushPermission extends FragmentActivity {
|
||||
public static final String EXTRA_REQUESTED_PACKAGE = "package";
|
||||
public static final String EXTRA_RESULT_RECEIVER = "receiver";
|
||||
public static final String EXTRA_FORCE_ASK = "force";
|
||||
public static final String EXTRA_EXPLICIT = "explicit";
|
||||
|
||||
private GcmDatabase database;
|
||||
|
||||
private String packageName;
|
||||
private ResultReceiver resultReceiver;
|
||||
private boolean answered;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
database = new GcmDatabase(this);
|
||||
|
||||
packageName = getIntent().getStringExtra(EXTRA_REQUESTED_PACKAGE);
|
||||
resultReceiver = getIntent().getParcelableExtra(EXTRA_RESULT_RECEIVER);
|
||||
boolean forceAsk = getIntent().getBooleanExtra(EXTRA_FORCE_ASK, false);
|
||||
if (packageName == null || (resultReceiver == null && !forceAsk)) {
|
||||
answered = true;
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!forceAsk && database.getApp(packageName) != null) {
|
||||
resultReceiver.send(Activity.RESULT_OK, Bundle.EMPTY);
|
||||
answered = true;
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
View view = getLayoutInflater().inflate(R.layout.ask_gcm, null);
|
||||
PackageManager pm = getPackageManager();
|
||||
final ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
|
||||
String label = pm.getApplicationLabel(info).toString();
|
||||
String raw = getString(R.string.gcm_allow_app_popup, label);
|
||||
SpannableString s = new SpannableString(raw);
|
||||
s.setSpan(new StyleSpan(Typeface.BOLD), raw.indexOf(label), raw.indexOf(label) + label.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
|
||||
((TextView) view.findViewById(R.id.permission_message)).setText(s);
|
||||
UtilsKt.buildAlertDialog(this)
|
||||
.setView(view)
|
||||
.setPositiveButton(R.string.allow, (dialog, which) -> {
|
||||
if (answered) return;
|
||||
database.noteAppKnown(packageName, true);
|
||||
answered = true;
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBoolean(EXTRA_EXPLICIT, true);
|
||||
if (resultReceiver != null) resultReceiver.send(Activity.RESULT_OK, bundle);
|
||||
finish();
|
||||
})
|
||||
.setNegativeButton(R.string.deny, (dialog, which) -> {
|
||||
if (answered) return;
|
||||
database.noteAppKnown(packageName, false);
|
||||
answered = true;
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBoolean(EXTRA_EXPLICIT, true);
|
||||
if (resultReceiver != null) resultReceiver.send(Activity.RESULT_CANCELED, bundle);
|
||||
finish();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (!answered) {
|
||||
if (resultReceiver != null) resultReceiver.send(Activity.RESULT_CANCELED, Bundle.EMPTY);
|
||||
}
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.google.android.gms.R;
|
||||
|
||||
import org.microg.gms.gcm.GcmPrefs;
|
||||
import org.microg.tools.ui.Condition;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.Manifest.permission.GET_ACCOUNTS;
|
||||
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||
import static android.Manifest.permission.READ_PHONE_STATE;
|
||||
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
public class Conditions {
|
||||
public static final Condition GCM_BATTERY_OPTIMIZATIONS = new Condition.Builder()
|
||||
.title(R.string.cond_gcm_bat_title)
|
||||
.summary(R.string.cond_gcm_bat_summary)
|
||||
.evaluation(new Condition.Evaluation() {
|
||||
@Override
|
||||
public boolean isActive(Context context) {
|
||||
if (SDK_INT < 23) return false;
|
||||
if (!GcmPrefs.get(context).isEnabled()) return false;
|
||||
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
return !pm.isIgnoringBatteryOptimizations(context.getPackageName());
|
||||
}
|
||||
})
|
||||
.firstAction(R.string.cond_gcm_bat_action, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (SDK_INT < 23) return;
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.parse("package:" + v.getContext().getPackageName()));
|
||||
v.getContext().startActivity(intent);
|
||||
}
|
||||
}).build();
|
||||
|
||||
private static final String[] REQUIRED_PERMISSIONS = new String[]{ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE, GET_ACCOUNTS, READ_PHONE_STATE};
|
||||
public static final Condition PERMISSIONS = new Condition.Builder()
|
||||
.title(R.string.cond_perm_title)
|
||||
.summaryPlurals(R.plurals.cond_perm_summary)
|
||||
.evaluation(new Condition.Evaluation() {
|
||||
int count = 0;
|
||||
@Override
|
||||
public boolean isActive(Context context) {
|
||||
count = 0;
|
||||
if (SDK_INT >= 23) {
|
||||
for (String permission : REQUIRED_PERMISSIONS) {
|
||||
if (ContextCompat.checkSelfPermission(context, permission) != PERMISSION_GRANTED)
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPluralsCount() {
|
||||
return count;
|
||||
}
|
||||
})
|
||||
.firstActionPlurals(R.plurals.cond_perm_action, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getContext() instanceof Activity) {
|
||||
ActivityCompat.requestPermissions((Activity) v.getContext(), REQUIRED_PERMISSIONS, 0);
|
||||
}
|
||||
}
|
||||
}).build();
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.ui;
|
||||
|
||||
import com.google.android.gms.R;
|
||||
|
||||
import org.microg.tools.ui.AbstractSettingsActivity;
|
||||
|
||||
public class GoogleMoreFragment {
|
||||
public static class AsActivity extends AbstractSettingsActivity {
|
||||
public AsActivity() {
|
||||
showHomeAsUp = true;
|
||||
preferencesResource = R.xml.preferences_google_more;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 org.microg.gms.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
public class LocationSettingsActivity extends Activity {
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package org.microg.gms.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.navigation.ui.AppBarConfiguration;
|
||||
import androidx.navigation.ui.NavigationUI;
|
||||
|
||||
import com.google.android.gms.BuildConfig;
|
||||
import com.google.android.gms.R;
|
||||
import com.google.android.material.appbar.CollapsingToolbarLayout;
|
||||
|
||||
import org.microg.gms.common.Constants;
|
||||
import org.microg.gms.ui.settings.SettingsProvider;
|
||||
|
||||
import static org.microg.gms.ui.UtilsKt.buildAlertDialog;
|
||||
import static org.microg.gms.ui.settings.SettingsProviderKt.getAllSettingsProviders;
|
||||
|
||||
public class MainSettingsActivity extends AppCompatActivity {
|
||||
private AppBarConfiguration appBarConfiguration;
|
||||
|
||||
private static final String FIRST_RUN_MASTER = "org.microg.gms_firstRun";
|
||||
private static final String FIRST_RUN_PREF = "as_run";
|
||||
|
||||
private NavController getNavController() {
|
||||
return ((NavHostFragment)getSupportFragmentManager().findFragmentById(R.id.navhost)).getNavController();
|
||||
}
|
||||
|
||||
private void showDialogIfNeeded() {
|
||||
SharedPreferences prefs = getSharedPreferences(FIRST_RUN_MASTER, MODE_PRIVATE);
|
||||
if (BuildConfig.APPLICATION_ID == Constants.USER_MICROG_PACKAGE_NAME &&
|
||||
prefs.getBoolean(FIRST_RUN_PREF, true)) {
|
||||
buildAlertDialog(this)
|
||||
.setMessage(R.string.limited_services_dialog_information)
|
||||
.setTitle(R.string.limited_services_app_name)
|
||||
.setPositiveButton(R.string.limited_services_dialog_information_ack, (dialog, id) -> {
|
||||
prefs.edit().putBoolean(FIRST_RUN_PREF, false).apply();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
for (SettingsProvider settingsProvider : getAllSettingsProviders(this)) {
|
||||
settingsProvider.preProcessSettingsIntent(intent);
|
||||
}
|
||||
|
||||
setContentView(R.layout.settings_root_activity);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
CollapsingToolbarLayout toolbarLayout = findViewById(R.id.collapsing_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
for (SettingsProvider settingsProvider : getAllSettingsProviders(this)) {
|
||||
settingsProvider.extendNavigation(getNavController());
|
||||
}
|
||||
|
||||
appBarConfiguration = new AppBarConfiguration.Builder(getNavController().getGraph()).build();
|
||||
NavigationUI.setupWithNavController(toolbarLayout, toolbar, getNavController(), appBarConfiguration);
|
||||
showDialogIfNeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
return NavigationUI.navigateUp(getNavController(), appBarConfiguration) || super.onSupportNavigateUp();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* Copyright (C) 2013-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.
|
||||
*/
|
||||
|
||||
package org.microg.gms.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.location.Address;
|
||||
import android.location.Geocoder;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.view.MenuItemCompat;
|
||||
|
||||
import com.google.android.gms.R;
|
||||
import com.google.android.gms.common.api.CommonStatusCodes;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer;
|
||||
import com.google.android.gms.location.places.internal.PlaceImpl;
|
||||
import com.google.android.gms.maps.model.LatLng;
|
||||
import com.google.android.gms.maps.model.LatLngBounds;
|
||||
|
||||
import org.microg.gms.location.LocationConstants;
|
||||
//import org.microg.gms.maps.vtm.BackendMapView;
|
||||
//import org.microg.gms.maps.vtm.GmsMapsTypeHelper;
|
||||
//import org.oscim.core.MapPosition;
|
||||
//import org.oscim.event.Event;
|
||||
//import org.oscim.map.Map;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static org.microg.gms.location.LocationConstants.EXTRA_PRIMARY_COLOR;
|
||||
import static org.microg.gms.location.LocationConstants.EXTRA_PRIMARY_COLOR_DARK;
|
||||
//import static org.microg.gms.maps.vtm.GmsMapsTypeHelper.fromLatLngBounds;
|
||||
|
||||
public class
|
||||
|
||||
|
||||
PlacePickerActivity extends AppCompatActivity /*implements Map.UpdateListener*/ {
|
||||
private static final String TAG = "GmsPlacePicker";
|
||||
|
||||
private PlaceImpl place;
|
||||
// private BackendMapView mapView;
|
||||
private Intent resultIntent;
|
||||
private AtomicBoolean geocoderInProgress = new AtomicBoolean(false);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
resultIntent = new Intent();
|
||||
place = new PlaceImpl();
|
||||
|
||||
setContentView(R.layout.pick_place);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(org.microg.tools.ui.R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
|
||||
if (getIntent().hasExtra(EXTRA_PRIMARY_COLOR)) {
|
||||
toolbar.setBackgroundColor(getIntent().getIntExtra(EXTRA_PRIMARY_COLOR, 0));
|
||||
if (SDK_INT >= 21)
|
||||
getWindow().setStatusBarColor(getIntent().getIntExtra(EXTRA_PRIMARY_COLOR_DARK, 0));
|
||||
((TextView) findViewById(R.id.place_picker_title)).setTextColor(getIntent().getIntExtra(EXTRA_PRIMARY_COLOR_DARK, 0));
|
||||
}
|
||||
|
||||
// mapView = (BackendMapView) findViewById(R.id.map);
|
||||
// mapView.map().getEventLayer().enableRotation(false);
|
||||
// mapView.map().getEventLayer().enableTilt(false);
|
||||
// mapView.map().events.bind(this);
|
||||
|
||||
LatLngBounds latLngBounds = getIntent().getParcelableExtra(LocationConstants.EXTRA_BOUNDS);
|
||||
if (latLngBounds != null) {
|
||||
place.viewport = latLngBounds;
|
||||
// MapPosition mp = new MapPosition();
|
||||
// mp.setByBoundingBox(fromLatLngBounds(latLngBounds), mapView.map().getWidth(), mapView.map().getHeight());
|
||||
// mapView.map().getMapPosition(mp);
|
||||
} else {
|
||||
if (ActivityCompat.checkSelfPermission(PlacePickerActivity.this, ACCESS_FINE_LOCATION) != PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(PlacePickerActivity.this, new String[]{ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}, 0);
|
||||
} else {
|
||||
updateMapFromLocationManager();
|
||||
}
|
||||
}
|
||||
|
||||
findViewById(R.id.place_picker_select).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
resultIntent.putExtra(LocationConstants.EXTRA_STATUS, SafeParcelableSerializer.serializeToBytes(new Status(CommonStatusCodes.SUCCESS)));
|
||||
resultIntent.putExtra(LocationConstants.EXTRA_PLACE, SafeParcelableSerializer.serializeToBytes(place));
|
||||
resultIntent.putExtra(LocationConstants.EXTRA_FINAL_BOUNDS, SafeParcelableSerializer.serializeToBytes(place.viewport));
|
||||
setResult(RESULT_OK, resultIntent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingPermission")
|
||||
private void updateMapFromLocationManager() {
|
||||
LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE);
|
||||
Location last = null;
|
||||
for (String provider : lm.getAllProviders()) {
|
||||
if (lm.isProviderEnabled(provider)) {
|
||||
Location t = lm.getLastKnownLocation(provider);
|
||||
if (t != null && (last == null || t.getTime() > last.getTime())) {
|
||||
last = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "Set location to " + last);
|
||||
if (last != null) {
|
||||
// mapView.map().setMapPosition(new MapPosition(last.getLatitude(), last.getLongitude(), 4096));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == 0) {
|
||||
for (int grantResult : grantResults) {
|
||||
if (grantResult != PERMISSION_GRANTED) return;
|
||||
}
|
||||
updateMapFromLocationManager();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.pick_place, menu);
|
||||
SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.menu_action_search));
|
||||
// TODO: search
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// mapView.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
// mapView.onPause();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public void onMapEvent(Event event, MapPosition position) {
|
||||
// place.viewport = GmsMapsTypeHelper.toLatLngBounds(mapView.map().viewport().getBBox(null, 0));
|
||||
// resultIntent.putExtra(LocationConstants.EXTRA_FINAL_BOUNDS, place.viewport);
|
||||
// place.latLng = GmsMapsTypeHelper.toLatLng(position.getGeoPoint());
|
||||
place.name = "";
|
||||
place.address = "";
|
||||
updateInfoText();
|
||||
if (geocoderInProgress.compareAndSet(false, true)) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
LatLng ll = null;
|
||||
while (ll != place.latLng) {
|
||||
ll = place.latLng;
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
Geocoder geocoder = new Geocoder(PlacePickerActivity.this);
|
||||
List<Address> addresses = geocoder.getFromLocation(place.latLng.latitude, place.latLng.longitude, 1);
|
||||
if (addresses != null && !addresses.isEmpty() && addresses.get(0).getMaxAddressLineIndex() > 0) {
|
||||
Address address = addresses.get(0);
|
||||
StringBuilder sb = new StringBuilder(address.getAddressLine(0));
|
||||
for (int i = 1; i < address.getMaxAddressLineIndex(); ++i) {
|
||||
if (i == 1 && sb.toString().equals(address.getFeatureName())) {
|
||||
sb = new StringBuilder(address.getAddressLine(i));
|
||||
continue;
|
||||
}
|
||||
sb.append(", ").append(address.getAddressLine(i));
|
||||
}
|
||||
if (place.latLng == ll) {
|
||||
place.address = sb.toString();
|
||||
place.name = address.getFeatureName();
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateInfoText();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
Log.w(TAG, ignored);
|
||||
} finally {
|
||||
geocoderInProgress.lazySet(false);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}*/
|
||||
|
||||
private void updateInfoText() {
|
||||
if (TextUtils.isEmpty(place.address)) {
|
||||
((TextView) findViewById(R.id.place_picker_info)).setText(getString(R.string.place_picker_location_lat_lng, place.latLng.latitude, place.latLng.longitude));
|
||||
} else if (TextUtils.isEmpty(place.name)) {
|
||||
((TextView) findViewById(R.id.place_picker_info)).setText(place.address);
|
||||
} else {
|
||||
((TextView) findViewById(R.id.place_picker_info)).setText(Html.fromHtml("<b>" + place.name + "</b>, " + place.address));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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.ui;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.google.android.gms.R;
|
||||
|
||||
import org.microg.tools.ui.AbstractSettingsActivity;
|
||||
import org.microg.tools.ui.RadioButtonPreference;
|
||||
import org.microg.tools.ui.ResourceSettingsFragment;
|
||||
|
||||
//import static org.microg.gms.safetynet.SafetyNetPrefs.PREF_SNET_OFFICIAL;
|
||||
//import static org.microg.gms.safetynet.SafetyNetPrefs.PREF_SNET_SELF_SIGNED;
|
||||
//import static org.microg.gms.safetynet.SafetyNetPrefs.PREF_SNET_THIRD_PARTY;
|
||||
|
||||
//public class SafetyNetAdvancedFragment extends ResourceSettingsFragment {
|
||||
//
|
||||
// public SafetyNetAdvancedFragment() {
|
||||
// preferencesResource = R.xml.preferences_snet_advanced;
|
||||
// }
|
||||
//
|
||||
// private RadioButtonPreference radioOfficial;
|
||||
// private RadioButtonPreference radioSelfSigned;
|
||||
// private RadioButtonPreference radioThirdParty;
|
||||
//
|
||||
// @Override
|
||||
// public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
// super.onCreatePreferences(savedInstanceState, rootKey);
|
||||
//
|
||||
// radioOfficial = (RadioButtonPreference) findPreference(PREF_SNET_OFFICIAL);
|
||||
// radioSelfSigned = (RadioButtonPreference) findPreference(PREF_SNET_SELF_SIGNED);
|
||||
// radioThirdParty = (RadioButtonPreference) findPreference(PREF_SNET_THIRD_PARTY);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean onPreferenceTreeClick(Preference preference) {
|
||||
// if (preference == radioOfficial) {
|
||||
// radioOfficial.setChecked(true);
|
||||
// radioSelfSigned.setChecked(false);
|
||||
// radioThirdParty.setChecked(false);
|
||||
// return true;
|
||||
// } else if (preference == radioSelfSigned) {
|
||||
// radioOfficial.setChecked(false);
|
||||
// radioSelfSigned.setChecked(true);
|
||||
// radioThirdParty.setChecked(false);
|
||||
// return true;
|
||||
// } else if (preference == radioThirdParty) {
|
||||
// radioOfficial.setChecked(false);
|
||||
// radioSelfSigned.setChecked(false);
|
||||
// radioThirdParty.setChecked(true);
|
||||
// return true;
|
||||
// }
|
||||
// return super.onPreferenceTreeClick(preference);
|
||||
// }
|
||||
//
|
||||
// public static class AsActivity extends AbstractSettingsActivity {
|
||||
// public AsActivity() {
|
||||
// showHomeAsUp = true;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected Fragment getFragment() {
|
||||
// return new SafetyNetAdvancedFragment();
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* 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.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.CrossProfileApps;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PermissionInfo;
|
||||
import android.net.Uri;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.microg.gms.common.Constants;
|
||||
import org.microg.tools.selfcheck.InstalledPackagesChecks;
|
||||
//import org.microg.tools.selfcheck.NlpOsCompatChecks;
|
||||
//import org.microg.tools.selfcheck.NlpStatusChecks;
|
||||
import org.microg.tools.selfcheck.PermissionCheckGroup;
|
||||
import org.microg.tools.selfcheck.RomSpoofSignatureChecks;
|
||||
import org.microg.tools.selfcheck.SelfCheckGroup;
|
||||
import org.microg.tools.selfcheck.SystemChecks;
|
||||
import org.microg.tools.ui.AbstractSelfCheckFragment;
|
||||
import org.microg.tools.ui.AbstractSettingsActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
|
||||
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.Manifest.permission.GET_ACCOUNTS;
|
||||
import static android.Manifest.permission.POST_NOTIFICATIONS;
|
||||
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||
import static android.Manifest.permission.READ_PHONE_STATE;
|
||||
import static android.Manifest.permission.RECEIVE_SMS;
|
||||
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
public class SelfCheckFragment extends AbstractSelfCheckFragment {
|
||||
|
||||
@Override
|
||||
protected void prepareSelfCheckList(Context context, List<SelfCheckGroup> checks) {
|
||||
if (Objects.equals(context.getPackageName(), Constants.GMS_PACKAGE_NAME)) {
|
||||
checks.add(new RomSpoofSignatureChecks());
|
||||
}
|
||||
checks.add(new InstalledPackagesChecks());
|
||||
if (SDK_INT >= 23) {
|
||||
List<String> permissions = new ArrayList<>();
|
||||
permissions.add(ACCESS_COARSE_LOCATION);
|
||||
permissions.add(ACCESS_FINE_LOCATION);
|
||||
if (SDK_INT >= 29) {
|
||||
permissions.add(ACCESS_BACKGROUND_LOCATION);
|
||||
}
|
||||
permissions.add(READ_EXTERNAL_STORAGE);
|
||||
permissions.add(WRITE_EXTERNAL_STORAGE);
|
||||
permissions.add(GET_ACCOUNTS);
|
||||
if (SDK_INT >= 33) {
|
||||
permissions.add(POST_NOTIFICATIONS);
|
||||
}
|
||||
permissions.add(READ_PHONE_STATE);
|
||||
permissions.add(RECEIVE_SMS);
|
||||
checks.add(new PermissionCheckGroup(permissions.toArray(new String[0])) {
|
||||
@Override
|
||||
public void doChecks(Context context, ResultCollector collector) {
|
||||
super.doChecks(context, collector);
|
||||
PackageManager pm = context.getPackageManager();
|
||||
// Add SYSTEM_ALERT_WINDOW appops permission
|
||||
try {
|
||||
PermissionInfo info = pm.getPermissionInfo("android.permission.SYSTEM_ALERT_WINDOW", 0);
|
||||
CharSequence permLabel = info.loadLabel(pm);
|
||||
collector.addResult(
|
||||
context.getString(org.microg.tools.ui.R.string.self_check_name_permission, permLabel),
|
||||
Settings.canDrawOverlays(context) ? Result.Positive : Result.Negative,
|
||||
context.getString(org.microg.tools.ui.R.string.self_check_resolution_permission),
|
||||
fragment -> {
|
||||
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName()));
|
||||
startActivityForResult(intent, 42);
|
||||
}
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Log.w("SelfCheckPerms", e);
|
||||
}
|
||||
// Add INTERACT_ACROSS_PROFILES appop permission (INTERACT_ACROSS_USERS is superior)
|
||||
if (SDK_INT >= 30) try {
|
||||
CrossProfileApps crossProfile = context.getSystemService(CrossProfileApps.class);
|
||||
collector.addResult(
|
||||
context.getString(org.microg.tools.ui.R.string.self_check_name_permission_interact_across_profiles),
|
||||
context.checkSelfPermission("android.permission.INTERACT_ACROSS_USERS") == PackageManager.PERMISSION_GRANTED
|
||||
|| crossProfile.canInteractAcrossProfiles() ? Result.Positive : Result.Negative,
|
||||
context.getString(org.microg.tools.ui.R.string.self_check_resolution_permission),
|
||||
crossProfile.canRequestInteractAcrossProfiles() ? fragment -> {
|
||||
Intent intent = crossProfile.createRequestInteractAcrossProfilesIntent();
|
||||
startActivityForResult(intent, 43);
|
||||
} : null
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Log.w("SelfCheckPerms", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (SDK_INT >= 23) {
|
||||
checks.add(new SystemChecks());
|
||||
}
|
||||
// checks.add(new NlpOsCompatChecks());
|
||||
// checks.add(new NlpStatusChecks());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
reset(LayoutInflater.from(getContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
reset(LayoutInflater.from(getContext()));
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
public static class AsActivity extends AbstractSettingsActivity {
|
||||
public AsActivity() {
|
||||
showHomeAsUp = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Fragment getFragment() {
|
||||
return new SelfCheckFragment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 org.microg.gms.ui;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.google.android.gms.R;
|
||||
|
||||
//import org.microg.nlp.Preferences;
|
||||
import org.microg.tools.ui.AbstractDashboardActivity;
|
||||
|
||||
public class SettingsDashboardActivity extends AbstractDashboardActivity {
|
||||
|
||||
public SettingsDashboardActivity() {
|
||||
preferencesResource = R.xml.preferences_start;
|
||||
addCondition(Conditions.GCM_BATTERY_OPTIMIZATIONS);
|
||||
addCondition(Conditions.PERMISSIONS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Fragment getFragment() {
|
||||
return new SettingsFragment();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.wallet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.wallet.GetClientTokenRequest;
|
||||
import com.google.android.gms.wallet.GetClientTokenResponse;
|
||||
import com.google.android.gms.wallet.IsReadyToPayRequest;
|
||||
import com.google.android.gms.wallet.internal.IOwService;
|
||||
import com.google.android.gms.wallet.internal.IWalletServiceCallbacks;
|
||||
|
||||
public class OwServiceImpl extends IOwService.Stub {
|
||||
private static final String TAG = "GmsWalletOwSvc";
|
||||
private Context context;
|
||||
|
||||
public OwServiceImpl(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isReadyToPay(IsReadyToPayRequest request, Bundle args, IWalletServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "isReadyToPay: " + request.toJson());
|
||||
try {
|
||||
callbacks.onIsReadyToPayResponse(Status.SUCCESS, false, Bundle.EMPTY);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getClientToken(GetClientTokenRequest getClientTokenRequest, Bundle options, IWalletServiceCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "getClientToken: " + options);
|
||||
try {
|
||||
callbacks.onClientTokenReceived(Status.INTERNAL_ERROR, new GetClientTokenResponse(), Bundle.EMPTY);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
if (super.onTransact(code, data, reply, flags)) return true;
|
||||
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.wallet;
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.google.android.gms.common.Feature;
|
||||
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 org.microg.gms.BaseService;
|
||||
import org.microg.gms.common.GmsService;
|
||||
|
||||
public class PaymentService extends BaseService {
|
||||
|
||||
public static final Feature[] FEATURES = new Feature[]{
|
||||
new Feature("wallet", 1L),
|
||||
new Feature("wallet_biometric_auth_keys", 1L),
|
||||
new Feature("wallet_payment_dynamic_update", 2L),
|
||||
new Feature("wallet_1p_initialize_buyflow", 1L),
|
||||
new Feature("wallet_warm_up_ui_process", 1L),
|
||||
new Feature("wallet_get_setup_wizard_intent", 4L),
|
||||
new Feature("wallet_get_payment_card_recognition_intent", 1L),
|
||||
new Feature("wallet_save_instrument", 1L)
|
||||
};
|
||||
|
||||
public PaymentService() {
|
||||
super("GmsWalletPaySvc", GmsService.WALLET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
|
||||
ConnectionInfo connectionInfo = new ConnectionInfo();
|
||||
connectionInfo.features = FEATURES;
|
||||
callback.onPostInitCompleteWithConnectionInfo(CommonStatusCodes.SUCCESS, new OwServiceImpl(this), connectionInfo);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.tools;
|
||||
|
||||
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 AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle extras = getIntent().getExtras();
|
||||
Intent intent = new Intent();
|
||||
ComponentName componentName =
|
||||
ComponentName.unflattenFromString("android/.accounts.ChooseTypeAndAccountActivity");
|
||||
intent.setClassName(componentName.getPackageName(), componentName.getClassName());
|
||||
intent.putExtras(extras);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.tools.selfcheck;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.google.android.gms.R;
|
||||
|
||||
import org.microg.gms.common.Constants;
|
||||
import org.microg.gms.common.PackageUtils;
|
||||
|
||||
import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Negative;
|
||||
import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Positive;
|
||||
|
||||
public class InstalledPackagesChecks implements SelfCheckGroup {
|
||||
|
||||
@Override
|
||||
public String getGroupName(Context context) {
|
||||
return context.getString(R.string.self_check_cat_gms_packages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doChecks(Context context, ResultCollector collector) {
|
||||
addPackageInstalledAndSignedResult(context, collector, context.getString(R.string.self_check_pkg_gms), Constants.GMS_PACKAGE_NAME, Constants.GMS_PACKAGE_SIGNATURE_SHA1);
|
||||
addPackageInstalledAndSignedResult(context, collector, context.getString(R.string.self_check_pkg_vending), Constants.VENDING_PACKAGE_NAME, Constants.GMS_PACKAGE_SIGNATURE_SHA1);
|
||||
addPackageInstalledResult(context, collector, context.getString(R.string.self_check_pkg_gsf), Constants.GSF_PACKAGE_NAME);
|
||||
}
|
||||
|
||||
private void addPackageInstalledAndSignedResult(Context context, ResultCollector collector, String nicePackageName, String androidPackageName, String signatureHash) {
|
||||
if (addPackageInstalledResult(context, collector, nicePackageName, androidPackageName)) {
|
||||
addPackageSignedResult(context, collector, nicePackageName, androidPackageName, signatureHash);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean addPackageSignedResult(Context context, ResultCollector collector, String nicePackageName, String androidPackageName, String signatureHash) {
|
||||
boolean hashMatches = signatureHash.equals(PackageUtils.firstSignatureDigest(context, androidPackageName, true)) &&
|
||||
signatureHash.equals(PackageUtils.firstSignatureDigest(context, androidPackageName, false));
|
||||
collector.addResult(context.getString(R.string.self_check_name_correct_sig, nicePackageName),
|
||||
hashMatches ? Positive : Negative,
|
||||
context.getString(R.string.self_check_resolution_correct_sig, nicePackageName),
|
||||
fragment -> tryGrantFakeSignaturePermissionActivity(fragment, androidPackageName));
|
||||
return hashMatches;
|
||||
}
|
||||
|
||||
private void tryGrantFakeSignaturePermissionActivity(Fragment fragment, String androidPackageName) {
|
||||
ComponentName grantPermissionActivity = new ComponentName(androidPackageName, androidPackageName + ".GrantFakeSignaturePermissionActivity");
|
||||
try {
|
||||
Intent intent = new Intent();
|
||||
intent.setPackage(androidPackageName);
|
||||
intent.setComponent(grantPermissionActivity);
|
||||
fragment.startActivityForResult(intent, 1);
|
||||
} catch (Exception e) {
|
||||
Log.w("SelfCheck", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean addPackageInstalledResult(Context context, ResultCollector collector, String nicePackageName, String androidPackageName) {
|
||||
boolean packageExists = true;
|
||||
try {
|
||||
context.getPackageManager().getPackageInfo(androidPackageName, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
packageExists = false;
|
||||
}
|
||||
collector.addResult(context.getString(R.string.self_check_name_app_installed, nicePackageName), packageExists ? Positive : Negative,
|
||||
context.getString(R.string.self_check_resolution_app_installed, nicePackageName));
|
||||
return packageExists;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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.tools.selfcheck;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.google.android.gms.R;
|
||||
|
||||
import org.microg.gms.common.Constants;
|
||||
import org.microg.gms.common.PackageUtils;
|
||||
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static org.microg.gms.common.Constants.GMS_PACKAGE_SIGNATURE_SHA1;
|
||||
import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Negative;
|
||||
import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Positive;
|
||||
import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Unknown;
|
||||
|
||||
public class RomSpoofSignatureChecks implements SelfCheckGroup {
|
||||
|
||||
public static final String FAKE_SIGNATURE_PERMISSION = "android.permission.FAKE_PACKAGE_SIGNATURE";
|
||||
|
||||
@Override
|
||||
public String getGroupName(Context context) {
|
||||
return context.getString(R.string.self_check_cat_fake_sig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doChecks(Context context, ResultCollector collector) {
|
||||
addSystemSpoofsSignature(context, collector);
|
||||
}
|
||||
|
||||
private boolean addSystemSpoofsSignature(Context context, ResultCollector collector) {
|
||||
boolean knowsPermission = true;
|
||||
try {
|
||||
context.getPackageManager().getPermissionInfo(FAKE_SIGNATURE_PERMISSION, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
knowsPermission = false;
|
||||
}
|
||||
boolean grantsPermission = false;
|
||||
if (knowsPermission) {
|
||||
grantsPermission = ContextCompat.checkSelfPermission(context, FAKE_SIGNATURE_PERMISSION) == PERMISSION_GRANTED;
|
||||
}
|
||||
boolean spoofsSignature = GMS_PACKAGE_SIGNATURE_SHA1.equals(PackageUtils.firstSignatureDigest(context, Constants.GMS_PACKAGE_NAME, true)) &&
|
||||
GMS_PACKAGE_SIGNATURE_SHA1.equals(PackageUtils.firstSignatureDigest(context, Constants.GMS_PACKAGE_NAME, false));
|
||||
if (knowsPermission && !spoofsSignature && !grantsPermission) {
|
||||
collector.addResult(
|
||||
context.getString(R.string.self_check_name_system_spoofs),
|
||||
spoofsSignature ? Positive : Negative,
|
||||
context.getString(org.microg.tools.ui.R.string.self_check_resolution_permission),
|
||||
fragment -> fragment.requestPermissions(new String[]{FAKE_SIGNATURE_PERMISSION}, 0)
|
||||
);
|
||||
} else {
|
||||
collector.addResult(
|
||||
context.getString(R.string.self_check_name_system_spoofs),
|
||||
spoofsSignature ? Positive : Negative,
|
||||
context.getString(R.string.self_check_resolution_system_spoofs),
|
||||
fragment -> fragment.requestPermissions(new String[]{FAKE_SIGNATURE_PERMISSION}, 0)
|
||||
);
|
||||
}
|
||||
return spoofsSignature;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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.tools.selfcheck;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.google.android.gms.R;
|
||||
|
||||
import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Negative;
|
||||
import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Positive;
|
||||
|
||||
@TargetApi(23)
|
||||
public class SystemChecks implements SelfCheckGroup, SelfCheckGroup.CheckResolver {
|
||||
|
||||
public static final int REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = 417;
|
||||
|
||||
@Override
|
||||
public String getGroupName(Context context) {
|
||||
return context.getString(R.string.self_check_cat_system);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doChecks(Context context, ResultCollector collector) {
|
||||
isBatterySavingDisabled(context, collector);
|
||||
}
|
||||
|
||||
private void isBatterySavingDisabled(final Context context, ResultCollector collector) {
|
||||
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
collector.addResult(context.getString(R.string.self_check_name_battery_optimizations),
|
||||
pm.isIgnoringBatteryOptimizations(context.getPackageName()) ? Positive : Negative,
|
||||
context.getString(R.string.self_check_resolution_battery_optimizations), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tryResolve(Fragment fragment) {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.parse("package:" + fragment.getActivity().getPackageName()));
|
||||
fragment.startActivityForResult(intent, REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.ads
|
||||
|
||||
import android.app.Activity
|
||||
|
||||
class AdActivity : Activity()
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.google.android.gms.ads.omid
|
||||
|
||||
class AdSession
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue