Repo Created

This commit is contained in:
Fr4nz D13trich 2025-11-15 17:44:12 +01:00
parent eb305e2886
commit a8c22c65db
4784 changed files with 329907 additions and 2 deletions

View 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>

View 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>

File diff suppressed because it is too large Load diff

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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()));*/
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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
}
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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();
}
}

View file

@ -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));
}
}

View file

@ -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;
}
}

View file

@ -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")
}
}

View file

@ -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 {
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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();
}
});
}
}

View file

@ -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");
}
}

View file

@ -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);
}
}

View file

@ -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";
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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) {
}
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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 + '\'' +
'}';
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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";
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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");
}
}

View file

@ -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)
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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;
}
}
}

View file

@ -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 {
}

View file

@ -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();
}
}

View file

@ -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));
}
}
}

View file

@ -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();
// }
// }
//}

View file

@ -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();
}
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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()

View file

@ -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