diff --git a/play-services-chimera-core/build.gradle b/play-services-chimera-core/build.gradle new file mode 100644 index 00000000..d70a72c2 --- /dev/null +++ b/play-services-chimera-core/build.gradle @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + api project(':play-services-basement') + api "androidx.lifecycle:lifecycle-service:$lifecycleVersion" + + implementation "androidx.annotation:annotation:$annotationVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" +} + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG chimera implementation' diff --git a/play-services-chimera-core/src/main/AndroidManifest.xml b/play-services-chimera-core/src/main/AndroidManifest.xml new file mode 100644 index 00000000..ffeb76d6 --- /dev/null +++ b/play-services-chimera-core/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/play-services-chimera-core/src/main/java/com/google/android/chimera/InstanceProvider.java b/play-services-chimera-core/src/main/java/com/google/android/chimera/InstanceProvider.java new file mode 100644 index 00000000..d05b3448 --- /dev/null +++ b/play-services-chimera-core/src/main/java/com/google/android/chimera/InstanceProvider.java @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.chimera; + +public interface InstanceProvider { + Object getChimeraImpl(); +} diff --git a/play-services-chimera-core/src/main/java/com/google/android/chimera/IntentService.java b/play-services-chimera-core/src/main/java/com/google/android/chimera/IntentService.java new file mode 100644 index 00000000..51204deb --- /dev/null +++ b/play-services-chimera-core/src/main/java/com/google/android/chimera/IntentService.java @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.chimera; + +import android.content.Intent; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; + +public abstract class IntentService extends Service { + private Looper serviceLooper; + private ServiceHandler serviceHandler; + private String name; + private boolean redelivery; + + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + onHandleIntent((Intent)msg.obj); + stopSelf(msg.arg1); + } + } + + public IntentService(String name) { + this.name = name; + } + + /** + * Sets intent redelivery preferences. Usually called from the constructor + * with your preferred semantics. + * + *

If enabled is true, + * {@link #onStartCommand(Intent, int, int)} will return + * {@link Service#START_REDELIVER_INTENT}, so if this process dies before + * {@link #onHandleIntent(Intent)} returns, the process will be restarted + * and the intent redelivered. If multiple Intents have been sent, only + * the most recent one is guaranteed to be redelivered. + * + *

If enabled is false (the default), + * {@link #onStartCommand(Intent, int, int)} will return + * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent + * dies along with it. + */ + public void setIntentRedelivery(boolean redelivery) { + this.redelivery = redelivery; + } + + @Override + public void onCreate() { + super.onCreate(); + HandlerThread thread = new HandlerThread("IntentService[" + name + "]"); + thread.start(); + serviceLooper = thread.getLooper(); + serviceHandler = new ServiceHandler(serviceLooper); + } + + @Override + public void onStart(Intent intent, int startId) { + Message msg = this.serviceHandler.obtainMessage(); + msg.arg1 = startId; + msg.obj = intent; + this.serviceHandler.sendMessage(msg); + } + + /** + * You should not override this method for your IntentService. Instead, + * override {@link #onHandleIntent}, which the system calls when the IntentService + * receives a start request. + * @see Service#onStartCommand + */ + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + onStart(intent, startId); + return redelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; + } + + @Override + public void onDestroy() { + serviceLooper.quit(); + } + + /** + * Unless you provide binding for your service, you don't need to implement this + * method, because the default implementation returns null. + * @see Service#onBind + */ + @Override + public IBinder onBind(Intent intent) { + return null; + } + + /** + * This method is invoked on the worker thread with a request to process. + * Only one Intent is processed at a time, but the processing happens on a + * worker thread that runs independently from other application logic. + * So, if this code takes a long time, it will hold up other requests to + * the same IntentService, but it will not hold up anything else. + * When all requests have been handled, the IntentService stops itself, + * so you should not call {@link #stopSelf}. + * + * @param intent The value passed to {@link + * android.content.Context#startService(Intent)}. + * This may be null if the service is being restarted after + * its process has gone away; see + * {@link Service#onStartCommand} + * for details. + */ + public abstract void onHandleIntent(Intent intent); +} diff --git a/play-services-chimera-core/src/main/java/com/google/android/chimera/Service.java b/play-services-chimera-core/src/main/java/com/google/android/chimera/Service.java new file mode 100644 index 00000000..b8ed0e4e --- /dev/null +++ b/play-services-chimera-core/src/main/java/com/google/android/chimera/Service.java @@ -0,0 +1,131 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.chimera; + +import android.app.Application; +import android.app.Notification; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.IBinder; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +public abstract class Service extends ContextWrapper implements InstanceProvider { + public static final int START_CONTINUATION_MASK = 0xf; + public static final int START_FLAG_REDELIVERY = 1; + public static final int START_FLAG_RETRY = 2; + public static final int START_NOT_STICKY = 2; + public static final int START_REDELIVER_INTENT = 3; + public static final int START_STICKY = 1; + public static final int START_STICKY_COMPATIBILITY = 0; + + private android.app.Service containerService; + private ProxyCallbacks callbacks; + + public interface ProxyCallbacks { + void superOnCreate(); + + void superOnDestroy(); + + int superOnStartCommand(Intent intent, int flags, int startId); + + void superStopSelf(); + + void superStopSelf(int startId); + + boolean superStopSelfResult(int startId); + } + + public Service() { + super(null); + } + + protected void dump(FileDescriptor fs, PrintWriter writer, String[] args) { + } + + public final Application getApplication() { + return containerService.getApplication(); + } + + @Override + public Object getChimeraImpl() { + return this; + } + + public android.app.Service getContainerService() { + return containerService; + } + + public abstract IBinder onBind(Intent intent); + + + public void onConfigurationChanged(Configuration configuration) { + } + + public void onCreate() { + this.callbacks.superOnCreate(); + } + + public void onDestroy() { + this.callbacks.superOnDestroy(); + } + + public void onLowMemory() { + } + + public void onRebind(Intent intent) { + } + + public void onStart(Intent intent, int startId) { + } + + public int onStartCommand(Intent intent, int flags, int startId) { + return this.callbacks.superOnStartCommand(intent, flags, startId); + } + + public void onTaskRemoved(Intent rootIntent) { + } + + public void onTrimMemory(int level) { + } + + public boolean onUnbind(Intent intent) { + return false; + } + + public void publicDump(FileDescriptor fd, PrintWriter writer, String[] args) { + dump(fd, writer, args); + } + + public void setProxy(android.app.Service service, Context context) { + this.containerService = service; + this.callbacks = (ProxyCallbacks) service; + attachBaseContext(context); + } + + public final void startForeground(int id, Notification notification) { + this.containerService.startForeground(id, notification); + } + + public final void stopForeground(boolean removeNotification) { + this.containerService.stopForeground(removeNotification); + } + + public final void stopSelf() { + this.callbacks.superStopSelf(); + } + + public final boolean stopSelfResult(int startId) { + return this.callbacks.superStopSelfResult(startId); + } + + public final void stopSelf(int startId) { + this.callbacks.superStopSelf(startId); + } +} diff --git a/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceLoader.kt b/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceLoader.kt new file mode 100644 index 00000000..5e3da725 --- /dev/null +++ b/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceLoader.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.chimera + +import android.content.Context +import com.google.android.chimera.Service + +interface ServiceLoader { + fun loadService(context: Context): Service + + companion object { + inline fun static() = StaticServiceLoader(T::class.java) + } +} + diff --git a/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceProxy.kt b/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceProxy.kt new file mode 100644 index 00000000..d61c242a --- /dev/null +++ b/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceProxy.kt @@ -0,0 +1,134 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.chimera + +import android.content.Context +import android.content.Intent +import android.content.res.Configuration +import com.google.android.chimera.Service.ProxyCallbacks +import android.os.IBinder +import com.google.android.chimera.Service +import java.io.FileDescriptor +import java.io.PrintWriter + +abstract class ServiceProxy(private val loader: ServiceLoader) : android.app.Service(), ProxyCallbacks { + private var actualService: Service? = null + override fun attachBaseContext(base: Context) { + super.attachBaseContext(base) + + if (actualService == null) { + val service = loader.loadService(base) + actualService = service + service.setProxy(this, this) + } + } + + override fun dump(fs: FileDescriptor, writer: PrintWriter, args: Array) { + if (actualService != null) { + actualService!!.publicDump(fs, writer, args) + } + } + + override fun onBind(intent: Intent): IBinder? { + return if (actualService != null) { + actualService!!.onBind(intent) + } else null + } + + override fun onConfigurationChanged(newConfig: Configuration) { + if (actualService != null) { + actualService!!.onConfigurationChanged(newConfig) + } + } + + override fun onCreate() { + if (actualService != null) { + actualService!!.onCreate() + } + } + + override fun onDestroy() { + if (actualService != null) { + actualService!!.onDestroy() + } + } + + override fun onLowMemory() { + if (actualService != null) { + actualService!!.onLowMemory() + } + } + + override fun onRebind(intent: Intent) { + if (actualService != null) { + if (intent != null) intent.setExtrasClassLoader(actualService!!.classLoader) + actualService!!.onRebind(intent) + } + } + + override fun onStart(intent: Intent, startId: Int) { + if (actualService != null) { + if (intent != null) intent.setExtrasClassLoader(actualService!!.classLoader) + actualService!!.onStart(intent, startId) + } else { + stopSelf(startId) + } + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + return if (actualService != null) { + if (intent != null) intent.setExtrasClassLoader(actualService!!.classLoader) + actualService!!.onStartCommand(intent, flags, startId) + } else { + super.onStartCommand(intent, flags, startId) + } + } + + override fun onTaskRemoved(rootIntent: Intent) { + if (actualService != null) { + if (rootIntent != null) rootIntent.setExtrasClassLoader(actualService!!.classLoader) + actualService!!.onTaskRemoved(rootIntent) + } + } + + override fun onTrimMemory(level: Int) { + if (actualService != null) { + actualService!!.onTrimMemory(level) + } + } + + override fun onUnbind(intent: Intent): Boolean { + return if (actualService != null) { + if (intent != null) intent.setExtrasClassLoader(actualService!!.classLoader) + actualService!!.onUnbind(intent) + } else { + false + } + } + + override fun superOnCreate() { + super.onCreate() + } + + override fun superOnDestroy() { + super.onDestroy() + } + + override fun superOnStartCommand(intent: Intent, flags: Int, startId: Int): Int { + return super.onStartCommand(intent, flags, startId) + } + + override fun superStopSelf() { + super.stopSelf() + } + + override fun superStopSelf(startId: Int) { + super.stopSelf(startId) + } + + override fun superStopSelfResult(startId: Int): Boolean { + return super.stopSelfResult(startId) + } +} diff --git a/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/StaticServiceLoader.kt b/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/StaticServiceLoader.kt new file mode 100644 index 00000000..4989f177 --- /dev/null +++ b/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/StaticServiceLoader.kt @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.chimera + +import android.content.Context +import com.google.android.chimera.Service + +class StaticServiceLoader(private val serviceClass: Class) : ServiceLoader { + override fun loadService(context: Context): Service { + return serviceClass.getDeclaredConstructor().newInstance() + } +} diff --git a/settings.gradle b/settings.gradle index 1d7ba5c6..bda5a7e1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -43,6 +43,7 @@ include ':play-services-basement-ktx' include ':play-services-tasks-ktx' include ':play-services-base-core' +include ':play-services-chimera-core' include ':play-services-conscrypt-provider-core' include ':play-services-cronet-core' include ':play-services-location-core'