Skip to content

Commit ff4987f

Browse files
Mlkit: Implementing face detection module based on openCV (#2793)
Co-authored-by: Marvin W <git@larma.de>
1 parent 69102ce commit ff4987f

File tree

44 files changed

+1902
-55
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1902
-55
lines changed

play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesUtil.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,11 @@ public static String getOpenSourceSoftwareLicenseInfo(Context context) {
136136
* @return The Context object of the Buddy APK or null if the Buddy APK is not installed on the device.
137137
*/
138138
public static Context getRemoteContext(Context context) {
139-
return null; // TODO
139+
try {
140+
return context.createPackageContext(Constants.GMS_PACKAGE_NAME, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
141+
} catch (PackageManager.NameNotFoundException unused) {
142+
return null;
143+
}
140144
}
141145

142146
/**
@@ -145,7 +149,11 @@ public static Context getRemoteContext(Context context) {
145149
* @return The Resources object of the Buddy APK or null if the Buddy APK is not installed on the device.
146150
*/
147151
public static Resources getRemoteResources(Context context) {
148-
return null; // TODO
152+
try {
153+
return context.getPackageManager().getResourcesForApplication(Constants.GMS_PACKAGE_NAME);
154+
} catch (PackageManager.NameNotFoundException unused) {
155+
return null;
156+
}
149157
}
150158

151159
/**

play-services-core/src/main/AndroidManifest.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@
188188
androidx.compose.ui.graphics,
189189
androidx.compose.ui.geometry,
190190
androidx.compose.ui.tooling.preview,
191-
androidx.compose.runtime.saveable"
191+
androidx.compose.runtime.saveable,
192+
org.opencv"
192193
/>
193194
<application
194195
android:allowBackup="true"
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2025 microG Project Team
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package com.google.android.gms.chimera;
7+
8+
import static android.os.Build.CPU_ABI;
9+
import static android.os.Build.SUPPORTED_32_BIT_ABIS;
10+
import static android.os.Build.SUPPORTED_64_BIT_ABIS;
11+
import static android.os.Build.VERSION.SDK_INT;
12+
13+
import android.content.Context;
14+
import android.content.pm.PackageManager;
15+
import android.os.Process;
16+
import android.util.Log;
17+
18+
import com.google.android.gms.chimera.container.DynamiteContext;
19+
import com.google.android.gms.chimera.container.DynamiteModuleInfo;
20+
import com.google.android.gms.chimera.container.FilteredClassLoader;
21+
22+
import org.microg.gms.common.Constants;
23+
24+
import java.io.File;
25+
import java.util.HashMap;
26+
import java.util.Map;
27+
import java.util.WeakHashMap;
28+
29+
import dalvik.system.PathClassLoader;
30+
31+
public class DynamiteContextFactory {
32+
private static final String TAG = "DynamiteContextFactory";
33+
private static final Map<String, DynamiteContext> sContextCache = new WeakHashMap<>();
34+
// WeakHashMap cannot be used, and there is a high probability that it will be recycled, causing ClassLoader to be rebuilt
35+
private static final Map<String, ClassLoader> sClassLoaderCache = new HashMap<>();
36+
37+
public static DynamiteContext createDynamiteContext(String moduleId, Context originalContext) {
38+
if (originalContext == null) {
39+
Log.w(TAG, "create <DynamiteContext> Original context is null");
40+
return null;
41+
}
42+
String cacheKey = moduleId + "-" + originalContext.getPackageName();
43+
synchronized (sContextCache) {
44+
DynamiteContext cached = sContextCache.get(cacheKey);
45+
if (cached != null) {
46+
Log.d(TAG, "Using cached DynamiteContext for cacheKey: " + cacheKey);
47+
return cached;
48+
}
49+
}
50+
try {
51+
DynamiteModuleInfo moduleInfo = new DynamiteModuleInfo(moduleId);
52+
Context gmsContext = originalContext.createPackageContext(Constants.GMS_PACKAGE_NAME, 0);
53+
Context originalAppContext = originalContext.getApplicationContext();
54+
55+
DynamiteContext dynamiteContext;
56+
if (originalAppContext == null || originalAppContext == originalContext) {
57+
dynamiteContext = new DynamiteContext(moduleInfo, originalContext, gmsContext, null);
58+
} else {
59+
dynamiteContext = new DynamiteContext(moduleInfo, originalContext, gmsContext, new DynamiteContext(moduleInfo, originalAppContext, gmsContext, null));
60+
}
61+
moduleInfo.init(dynamiteContext);
62+
63+
synchronized (sContextCache) {
64+
sContextCache.put(cacheKey, dynamiteContext);
65+
}
66+
Log.d(TAG, "Created and cached a new DynamiteContext for cacheKey: " + cacheKey);
67+
return dynamiteContext;
68+
} catch (PackageManager.NameNotFoundException e) {
69+
Log.w(TAG, e);
70+
return null;
71+
}
72+
}
73+
74+
public static ClassLoader createClassLoader(DynamiteModuleInfo moduleInfo, Context gmsContext, Context originalContext) {
75+
String cacheKey = moduleInfo.getModuleId() + "-" + originalContext.getPackageName();
76+
synchronized (sClassLoaderCache) {
77+
ClassLoader cached = sClassLoaderCache.get(cacheKey);
78+
if (cached != null) {
79+
Log.d(TAG, "Using cached ClassLoader for cacheKey: " + cacheKey + " cached: " + cached.hashCode());
80+
return cached;
81+
}
82+
}
83+
StringBuilder nativeLoaderDirs = new StringBuilder(gmsContext.getApplicationInfo().nativeLibraryDir);
84+
if (SDK_INT >= 23 && Process.is64Bit()) {
85+
for (String abi : SUPPORTED_64_BIT_ABIS) {
86+
nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi);
87+
}
88+
} else if (SDK_INT >= 21) {
89+
for (String abi : SUPPORTED_32_BIT_ABIS) {
90+
nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi);
91+
}
92+
} else {
93+
nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(CPU_ABI);
94+
}
95+
ClassLoader classLoader = new PathClassLoader(gmsContext.getApplicationInfo().sourceDir, nativeLoaderDirs.toString(), new FilteredClassLoader(originalContext.getClassLoader(), moduleInfo.getMergedClasses(), moduleInfo.getMergedPackages()));
96+
synchronized (sClassLoaderCache) {
97+
sClassLoaderCache.put(cacheKey, classLoader);
98+
}
99+
Log.d(TAG, "Created and cached a new ClassLoader for cacheKey: " + cacheKey + " ClassLoader: " + classLoader.hashCode());
100+
return classLoader;
101+
}
102+
}
103+

play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteContext.java

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,10 @@
88
import android.content.Context;
99
import android.content.ContextWrapper;
1010
import android.content.pm.ApplicationInfo;
11-
import android.content.pm.PackageManager;
12-
import android.os.Process;
13-
import android.util.Log;
1411

1512
import androidx.annotation.RequiresApi;
1613

17-
import org.microg.gms.common.Constants;
18-
19-
import java.io.File;
20-
21-
import dalvik.system.PathClassLoader;
22-
23-
import static android.os.Build.CPU_ABI;
24-
import static android.os.Build.SUPPORTED_32_BIT_ABIS;
25-
import static android.os.Build.SUPPORTED_64_BIT_ABIS;
26-
import static android.os.Build.VERSION.SDK_INT;
14+
import com.google.android.gms.chimera.DynamiteContextFactory;
2715

2816
public class DynamiteContext extends ContextWrapper {
2917
private static final String TAG = "DynamiteContext";
@@ -45,19 +33,7 @@ public DynamiteContext(DynamiteModuleInfo moduleInfo, Context base, Context gmsC
4533
@Override
4634
public ClassLoader getClassLoader() {
4735
if (classLoader == null) {
48-
StringBuilder nativeLoaderDirs = new StringBuilder(gmsContext.getApplicationInfo().nativeLibraryDir);
49-
if (SDK_INT >= 23 && Process.is64Bit()) {
50-
for (String abi : SUPPORTED_64_BIT_ABIS) {
51-
nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi);
52-
}
53-
} else if (SDK_INT >= 21) {
54-
for (String abi : SUPPORTED_32_BIT_ABIS) {
55-
nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi);
56-
}
57-
} else {
58-
nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(CPU_ABI);
59-
}
60-
classLoader = new PathClassLoader(gmsContext.getApplicationInfo().sourceDir, nativeLoaderDirs.toString(), new FilteredClassLoader(originalContext.getClassLoader(), moduleInfo.getMergedClasses(), moduleInfo.getMergedPackages()));
36+
classLoader = DynamiteContextFactory.createClassLoader(moduleInfo, gmsContext, originalContext);
6137
}
6238
return classLoader;
6339
}
@@ -82,23 +58,4 @@ public Context getApplicationContext() {
8258
public Context createDeviceProtectedStorageContext() {
8359
return new DynamiteContext(moduleInfo, originalContext.createDeviceProtectedStorageContext(), gmsContext.createDeviceProtectedStorageContext(), appContext);
8460
}
85-
86-
public static DynamiteContext create(String moduleId, Context originalContext) {
87-
try {
88-
DynamiteModuleInfo moduleInfo = new DynamiteModuleInfo(moduleId);
89-
Context gmsContext = originalContext.createPackageContext(Constants.GMS_PACKAGE_NAME, 0);
90-
Context originalAppContext = originalContext.getApplicationContext();
91-
DynamiteContext dynamiteContext;
92-
if (originalAppContext == null || originalAppContext == originalContext) {
93-
dynamiteContext = new DynamiteContext(moduleInfo, originalContext, gmsContext, null);
94-
} else {
95-
dynamiteContext = new DynamiteContext(moduleInfo, originalContext, gmsContext, new DynamiteContext(moduleInfo, originalAppContext, gmsContext, null));
96-
}
97-
moduleInfo.init(dynamiteContext);
98-
return dynamiteContext;
99-
} catch (PackageManager.NameNotFoundException e) {
100-
Log.w(TAG, e);
101-
return null;
102-
}
103-
}
10461
}

play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteLoaderImpl.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,14 @@
1717
package com.google.android.gms.chimera.container;
1818

1919
import android.content.Context;
20-
import android.content.ContextWrapper;
21-
import android.content.pm.PackageManager;
2220
import android.os.RemoteException;
2321
import android.util.Log;
2422

23+
import com.google.android.gms.chimera.DynamiteContextFactory;
2524
import com.google.android.gms.dynamic.IObjectWrapper;
2625
import com.google.android.gms.dynamic.ObjectWrapper;
2726
import com.google.android.gms.dynamite.IDynamiteLoader;
2827

29-
import org.microg.gms.common.Constants;
30-
31-
import java.lang.reflect.Field;
32-
3328
public class DynamiteLoaderImpl extends IDynamiteLoader.Stub {
3429
private static final String TAG = "GmsDynamiteLoaderImpl";
3530

@@ -43,7 +38,7 @@ public IObjectWrapper createModuleContext(IObjectWrapper wrappedContext, String
4338
public IObjectWrapper createModuleContextV2(IObjectWrapper wrappedContext, String moduleId, int minVersion) throws RemoteException {
4439
Log.d(TAG, "createModuleContext for " + moduleId + " at version " + minVersion);
4540
final Context originalContext = (Context) ObjectWrapper.unwrap(wrappedContext);
46-
return ObjectWrapper.wrap(DynamiteContext.create(moduleId, originalContext));
41+
return ObjectWrapper.wrap(DynamiteContextFactory.createDynamiteContext(moduleId, originalContext));
4742
}
4843

4944
@Override
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 microG Project Team
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
apply plugin: 'com.android.library'
7+
apply plugin: 'maven-publish'
8+
apply plugin: 'signing'
9+
10+
android {
11+
namespace "com.google.mlkit.vision.face"
12+
13+
compileSdkVersion androidCompileSdk
14+
buildToolsVersion "$androidBuildVersionTools"
15+
16+
buildFeatures {
17+
aidl = true
18+
}
19+
20+
defaultConfig {
21+
versionName version
22+
minSdkVersion androidMinSdk
23+
targetSdkVersion androidTargetSdk
24+
}
25+
26+
compileOptions {
27+
sourceCompatibility = 1.8
28+
targetCompatibility = 1.8
29+
}
30+
}
31+
32+
apply from: '../../gradle/publish-android.gradle'
33+
34+
description = 'microG implementation of play-services-mlkit-face-detection'
35+
36+
dependencies {
37+
// Dependencies from play-services-mlkit-face-detection:17.1.0
38+
api project(':play-services-base')
39+
api project(':play-services-basement')
40+
api project(':play-services-tasks')
41+
42+
annotationProcessor project(":safe-parcel-processor")
43+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ SPDX-FileCopyrightText: 2023 microG Project Team
4+
~ SPDX-License-Identifier: Apache-2.0
5+
-->
6+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
7+
8+
<application />
9+
</manifest>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 microG Project Team
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package com.google.mlkit.vision.face;
7+
8+
parcelable FaceDetectionOptions;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 microG Project Team
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package com.google.mlkit.vision.face;
7+
8+
parcelable FrameMetadataParcel;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 microG Project Team
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package com.google.mlkit.vision.face.aidls;
7+
8+
parcelable FaceParcel;

0 commit comments

Comments
 (0)