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,35 @@
/*
* SPDX-FileCopyrightText: 2020 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
apply plugin: 'signing'
android {
namespace "com.google.android.gms.tasks"
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
}
apply from: '../gradle/publish-android.gradle'
description = 'microG implementation of play-services-tasks'
dependencies {
// Dependencies from play-services-tasks:18.4.0
api project(':play-services-basement')
}

View file

@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2021, 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-tasks")
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
}
android {
namespace "org.microg.gms.tasks.ktx"
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
}
kotlinOptions {
jvmTarget = "1.8"
}
}
apply from: '../../gradle/publish-android.gradle'
description = 'microG kotlin extensions for play-services-tasks'

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2021, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest />

View file

@ -0,0 +1,151 @@
/*
* SPDX-FileCopyrightText: 2016 JetBrains s.r.o.
* SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.tasks
import kotlinx.coroutines.*
import kotlin.coroutines.*
/**
* Converts this deferred to the instance of [Task].
* If deferred is cancelled then resulting task will be cancelled as well.
*/
fun <T> Deferred<T>.asTask(): Task<T> {
val cancellation = CancellationTokenSource()
val source = TaskCompletionSource<T>(cancellation.token)
invokeOnCompletion callback@{
if (it is CancellationException) {
cancellation.cancel()
return@callback
}
val t = getCompletionExceptionOrNull()
if (t == null) {
source.setResult(getCompleted())
} else {
source.setException(t as? Exception ?: RuntimeExecutionException(t))
}
}
return source.task
}
/**
* Converts this task to an instance of [Deferred].
* If task is cancelled then resulting deferred will be cancelled as well.
* However, the opposite is not true: if the deferred is cancelled, the [Task] will not be cancelled.
* For bi-directional cancellation, an overload that accepts [CancellationTokenSource] can be used.
*/
fun <T> Task<T>.asDeferred(): Deferred<T> = asDeferredImpl(null)
/**
* Converts this task to an instance of [Deferred] with a [CancellationTokenSource] to control cancellation.
* The cancellation of this function is bi-directional:
* * If the given task is cancelled, the resulting deferred will be cancelled.
* * If the resulting deferred is cancelled, the provided [cancellationTokenSource] will be cancelled.
*
* Providing a [CancellationTokenSource] that is unrelated to the receiving [Task] is not supported and
* leads to an unspecified behaviour.
*/
@ExperimentalCoroutinesApi // Since 1.5.1, tentatively until 1.6.0
fun <T> Task<T>.asDeferred(cancellationTokenSource: CancellationTokenSource): Deferred<T> =
asDeferredImpl(cancellationTokenSource)
private fun <T> Task<T>.asDeferredImpl(cancellationTokenSource: CancellationTokenSource?): Deferred<T> {
val deferred = CompletableDeferred<T>()
if (isComplete) {
val e = exception
if (e == null) {
if (isCanceled) {
deferred.cancel()
} else {
@Suppress("UNCHECKED_CAST")
deferred.complete(result as T)
}
} else {
deferred.completeExceptionally(e)
}
} else {
addOnCompleteListener {
val e = it.exception
if (e == null) {
@Suppress("UNCHECKED_CAST")
if (it.isCanceled) deferred.cancel() else deferred.complete(it.result as T)
} else {
deferred.completeExceptionally(e)
}
}
}
if (cancellationTokenSource != null) {
deferred.invokeOnCompletion {
cancellationTokenSource.cancel()
}
}
// Prevent casting to CompletableDeferred and manual completion.
return object : Deferred<T> by deferred {}
}
/**
* Awaits the completion of the task without blocking a thread.
*
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* stops waiting for the completion stage and immediately resumes with [CancellationException].
*
* For bi-directional cancellation, an overload that accepts [CancellationTokenSource] can be used.
*/
suspend fun <T> Task<T>.await(): T = awaitImpl(null)
/**
* Awaits the completion of the task that is linked to the given [CancellationTokenSource] to control cancellation.
*
* This suspending function is cancellable and cancellation is bi-directional:
* * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* cancels the [cancellationTokenSource] and throws a [CancellationException].
* * If the task is cancelled, then this function will throw a [CancellationException].
*
* Providing a [CancellationTokenSource] that is unrelated to the receiving [Task] is not supported and
* leads to an unspecified behaviour.
*/
@ExperimentalCoroutinesApi // Since 1.5.1, tentatively until 1.6.0
suspend fun <T> Task<T>.await(cancellationTokenSource: CancellationTokenSource): T = awaitImpl(cancellationTokenSource)
private suspend fun <T> Task<T>.awaitImpl(cancellationTokenSource: CancellationTokenSource?): T {
// fast path
if (isComplete) {
val e = exception
return if (e == null) {
if (isCanceled) {
throw CancellationException("Task $this was cancelled normally.")
} else {
@Suppress("UNCHECKED_CAST")
result as T
}
} else {
throw e
}
}
return suspendCancellableCoroutine { cont ->
addOnCompleteListener {
val e = it.exception
if (e == null) {
@Suppress("UNCHECKED_CAST")
if (it.isCanceled) cont.cancel() else cont.resume(it.result as T)
} else {
cont.resumeWithException(e)
}
}
if (cancellationTokenSource != null) {
cont.invokeOnCancellation {
cancellationTokenSource.cancel()
}
}
}
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2020, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest />

View file

@ -0,0 +1,88 @@
/*
* SPDX-FileCopyrightText: 2020 microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import org.microg.gms.common.PublicApi;
/**
* Propagates notification that operations should be canceled.
* <p/>
* Developers writing methods that return a Task should take a {@code CancellationToken} as a parameter if they wish to
* make the Task cancelable (see below code snippet). A {@code CancellationToken} can only be created by creating a new
* instance of {@link CancellationTokenSource}. {@code CancellationToken} is immutable and must be canceled by calling
* {@link CancellationTokenSource#cancel()} on the {@link CancellationTokenSource} that creates it. It can only be
* canceled once. If canceled, it should not be passed to future operations.
* <p/>
* When {@link CancellationTokenSource#cancel()} is called, all the Tasks with the {@code CancellationToken} from that
* {@link CancellationTokenSource} will be canceled. This operation only flags those Tasks as canceled, and the API
* author is responsible for stopping whatever the Task is actually doing to free up the resources.
* <p/>
* Cancellable {@link Task} example:
* <pre>
* public Task<Integer> doSomething(CancellationToken token) {
*
* // Attach a listener that will be called once cancellation is requested.
* token.onCanceledRequested(new OnTokenCanceledListener() {
* &#64;Override
* public void onCanceled() {
* // Some other operations to cancel this Task, such as free resources...
* }
* });
*
* final TaskCompletionSource<Integer> tcs = new TaskCompletionSource<>(token);
*
* // do something...
*
* }
*
* CancellationTokenSource cts = new CancellationTokenSource();
* Task<Integer> task = doSomething(cts.getToken());
* cts.cancel();
* </pre>
* Cancellable {@link Task} example in {@link android.app.Activity} context:
* <pre>
* public class MyActivity extends Activity {
* // ...
*
* &#64;Override
* public void onStart() {
* super.onStart();
*
* // Typically use one cancellation source per lifecycle.
* cancellationSource = new TaskCancellationSource();
*
* // That source's token can be passed to multiple calls.
* doSomethingCancellable(cancellationSource.getToken())
* .onSuccessTask(result -> doSomethingElse(result, cancellationSource.getToken()));
* }
*
* &#64;Override
* public void onStop() {
* super.onStop();
* cancellationSource.cancel();
* }
* }
* </pre>
*/
@PublicApi
public abstract class CancellationToken {
/**
* Checks if cancellation has been requested from the {@link CancellationTokenSource}.
*
* @return {@code true} if cancellation is requested, {@code false} otherwise
*/
public abstract boolean isCancellationRequested();
/**
* Adds an {@link OnTokenCanceledListener} to this {@link CancellationToken}.
*
* @param listener the listener that will fire once the cancellation request succeeds.
*/
public abstract CancellationToken onCanceledRequested(OnTokenCanceledListener listener);
}

View file

@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import org.microg.gms.common.PublicApi;
import org.microg.gms.tasks.CancellationTokenImpl;
/**
* Creates a new {@link CancellationToken} or cancels one that has already created. There is a 1:1 {@link CancellationTokenSource} to {@link CancellationToken} relationship.
* <p>
* To create a {@link CancellationToken}, create a {@link CancellationTokenSource} first and then call {@link #getToken()} to get the {@link CancellationToken} for this {@link CancellationTokenSource}.
*
* @see CancellationToken
*/
@PublicApi
public class CancellationTokenSource {
private CancellationTokenImpl token = new CancellationTokenImpl();
/**
* Creates a new {@link CancellationTokenSource} instance.
*/
public CancellationTokenSource() {
}
/**
* Cancels the {@link CancellationToken} if cancellation has not been requested yet.
*/
public void cancel() {
token.cancel();
}
/**
* Gets the {@link CancellationToken} for this {@link CancellationTokenSource}.
*
* @return the {@link CancellationToken} that can be passed to asynchronous {@link Task} to cancel the Task.
*/
public CancellationToken getToken() {
return token;
}
}

View file

@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2016 microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import org.microg.gms.common.PublicApi;
/**
* A function that is called to continue execution after completion of a {@link Task}.
*
* @see Task#continueWith(Continuation)
* @see Task#continueWithTask(Continuation)
*/
@PublicApi
public interface Continuation<TResult, TContinuationResult> {
/**
* Returns the result of applying this Continuation to {@code task}.
* <p/>
* To propagate failure from the completed Task call {@link Task#getResult()} and allow the
* {@link RuntimeExecutionException} to propagate. The RuntimeExecutionException will be
* unwrapped such that the Task returned by {@link Task#continueWith(Continuation)} or
* {@link Task#continueWithTask(Continuation)} fails with the original exception.
* <p/>
* To suppress specific failures call {@link Task#getResult(Class)} and catch the exception
* types of interest:
* <pre>
* task.continueWith(new Continuation<String, String>() {
* &#64;Override
* public String then(Task<String> task) {
* try {
* return task.getResult(IOException.class);
* } catch (FileNotFoundException e) {
* return "Not found";
* } catch (IOException e) {
* return "Read failed";
* }
* }
* }</pre>
* <p/>
* To suppress all failures guard any calls to {@link Task#getResult()} with {@link Task#isSuccessful()}:
* <pre>
* task.continueWith(new Continuation<String, String>() {
* &#64;Override
* public String then(Task<String> task) {
* if (task.isSuccessful()) {
* return task.getResult();
* } else {
* return DEFAULT_VALUE;
* }
* }
* }</pre>
*
* @param task the completed Task. Never null
* @throws Exception if the result couldn't be produced
*/
TContinuationResult then(Task<TResult> task);
}

View file

@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import org.microg.gms.common.PublicApi;
/**
* An exception indicating that something attempted to set a result, exception, or cancellation on a {@link Task} that was already completed.
*/
@PublicApi
public class DuplicateTaskCompletionException extends IllegalStateException {
private DuplicateTaskCompletionException(String s) {
super(s);
}
/**
* Creates a DuplicateTaskCompletionException from a {@link Task}.
*
* The {@link Task} must be complete.
*/
public static DuplicateTaskCompletionException of(Task<?> task) {
if (!task.isComplete()) throw new IllegalStateException("Task is not yet completed");
return new DuplicateTaskCompletionException("Task is already completed");
}
}

View file

@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import org.microg.gms.common.PublicApi;
/**
* Listener called when a {@link Task} is canceled.
*/
@PublicApi
public interface OnCanceledListener {
/**
* Called when the Task is canceled successfully.
*/
public abstract void onCanceled();
}

View file

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2016, microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import org.microg.gms.common.PublicApi;
/**
* Listener called when a {@link Task} completes.
*
* @see Task#addOnCompleteListener(OnCompleteListener)
*/
@PublicApi
public interface OnCompleteListener<TResult> {
/**
* Called when the Task completes.
*
* @param task the completed Task. Never null
*/
void onComplete(Task<TResult> task);
}

View file

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2016, microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import org.microg.gms.common.PublicApi;
/**
* Listener called when a {@link Task} fails with an exception.
*
* @see Task#addOnFailureListener(OnFailureListener)
*/
@PublicApi
public interface OnFailureListener {
/**
* Called when the Task fails with an exception.
*
* @param e the exception that caused the Task to fail. Never null
*/
void onFailure(Exception e);
}

View file

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2016, microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import org.microg.gms.common.PublicApi;
/**
* Listener called when a {@link Task} completes successfully.
*
* @see Task#addOnSuccessListener(OnSuccessListener)
*/
@PublicApi
public interface OnSuccessListener<TResult> {
/**
* Called when the {@link Task} completes successfully.
*
* @param result the result of the Task
*/
void onSuccess(TResult result);
}

View file

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import org.microg.gms.common.PublicApi;
/**
* Listener called when a {@link CancellationToken} is canceled successfully.
*
* @see CancellationToken#onCanceledRequested(OnTokenCanceledListener)
*/
@PublicApi
public interface OnTokenCanceledListener {
/**
* Called when the {@link CancellationToken} is canceled successfully.
*/
void onCanceled();
}

View file

@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2016, microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import org.microg.gms.common.PublicApi;
import java.util.concurrent.ExecutionException;
/**
* Runtime version of {@link ExecutionException}.
*
* @see Task#getResult(Class)
*/
@PublicApi
public class RuntimeExecutionException extends RuntimeException {
public RuntimeExecutionException(Throwable cause) {
super(cause);
}
}

View file

@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: 2020 microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import org.microg.gms.common.PublicApi;
/**
* A function that is called to continue execution then a {@link Task} succeeds.
* @see Task#onSuccessTask
*/
@PublicApi
public interface SuccessContinuation<TResult, TContinuationResult> {
/**
* Returns the result of applying this SuccessContinuation to Task.
* <p>
* The SuccessContinuation only happens then the Task is successful. If the previous Task fails, the onSuccessTask
* continuation will be skipped and failure listeners will be invoked.
* <p>
* <pre>
* private Task<String> doSomething(String string) {
* // do something
* }
* task.onSuccessTask(new SuccessContinuation<String, String>() {
* &#64;NonNull
* &#64;Override
* public Task<String> then(String string) {
* return doSomething(string);
* }
* });
* </pre>
*
* @param result the result of completed Task
* @throws Exception if the result couldn't be produced
*/
Task<TContinuationResult> then(TResult result) throws Exception;
}

View file

@ -0,0 +1,292 @@
/*
* SPDX-FileCopyrightText: 2016 microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import android.app.Activity;
import org.microg.gms.common.PublicApi;
import java.util.concurrent.Executor;
/**
* Represents an asynchronous operation.
*/
@PublicApi
public abstract class Task<TResult> {
public Task() {
}
/**
* Adds a listener that is called if the Task is canceled.
* <p>
* The listener will be called on main application thread. If the Task has already been canceled, a call to the listener will be immediately scheduled. If multiple listeners are added, they will be called in the order in which they were added.
*
* @return this Task
*/
public Task<TResult> addOnCanceledListener(OnCanceledListener listener) {
throw new UnsupportedOperationException("addOnCanceledListener is not implemented");
}
/**
* Adds a listener that is called if the Task is canceled.
* <p>
* If the Task has already been canceled, a call to the listener will be immediately scheduled. If multiple listeners are added, they will be called in the order in which they were added.
*
* @param executor the executor to use to call the listener
* @return this Task
*/
public Task<TResult> addOnCanceledListener(Executor executor, OnCanceledListener listener) {
throw new UnsupportedOperationException("addOnCanceledListener is not implemented");
}
/**
* Adds an Activity-scoped listener that is called if the Task is canceled.
* <p>
* The listener will be called on main application thread. If the Task has already been canceled, a call to the listener will be immediately scheduled. If multiple listeners are added, they will be called in the order in which they were added.
* <p>
* The listener will be automatically removed during {@link Activity#onStop()}.
*
* @return this Task
*/
public Task<TResult> addOnCanceledListener(Activity activity, OnCanceledListener listener) {
throw new UnsupportedOperationException("addOnCanceledListener is not implemented");
}
/**
* Adds a listener that is called when the Task completes.
* <p/>
* The listener will be called on main application thread. If the Task is already complete, a
* call to the listener will be immediately scheduled. If multiple listeners are added, they
* will be called in the order in which they were added.
*
* @return this Task
*/
public Task<TResult> addOnCompleteListener(OnCompleteListener<TResult> listener) {
throw new UnsupportedOperationException("addOnCompleteListener is not implemented");
}
/**
* Adds an Activity-scoped listener that is called when the Task completes.
* <p/>
* The listener will be called on main application thread. If the Task is already complete, a
* call to the listener will be immediately scheduled. If multiple listeners are added, they
* will be called in the order in which they were added.
* <p/>
* The listener will be automatically removed during {@link Activity#onStop()}.
*
* @return this Task
*/
public Task<TResult> addOnCompleteListener(Activity activity, OnCompleteListener<TResult> listener) {
throw new UnsupportedOperationException("addOnCompleteListener is not implemented");
}
/**
* Adds a listener that is called when the Task completes.
* <p/>
* If the Task is already complete, a call to the listener will be immediately scheduled. If
* multiple listeners are added, they will be called in the order in which they were added.
*
* @param executor the executor to use to call the listener
* @return this Task
*/
public Task<TResult> addOnCompleteListener(Executor executor, OnCompleteListener<TResult> listener) {
throw new UnsupportedOperationException("addOnCompleteListener is not implemented");
}
/**
* Adds an Activity-scoped listener that is called if the Task fails.
* <p/>
* The listener will be called on main application thread. If the Task has already failed, a
* call to the listener will be immediately scheduled. If multiple listeners are added, they
* will be called in the order in which they were added.
* <p/>
* The listener will be automatically removed during {@link Activity#onStop()}.
*
* @return this Task
*/
public abstract Task<TResult> addOnFailureListener(Activity activity, OnFailureListener listener);
/**
* Adds a listener that is called if the Task fails.
* <p/>
* The listener will be called on main application thread. If the Task has already failed, a
* call to the listener will be immediately scheduled. If multiple listeners are added, they
* will be called in the order in which they were added.
*
* @return this Task
*/
public abstract Task<TResult> addOnFailureListener(OnFailureListener listener);
/**
* Adds a listener that is called if the Task fails.
* <p/>
* If the Task has already failed, a call to the listener will be immediately scheduled. If
* multiple listeners are added, they will be called in the order in which they were added.
*
* @param executor the executor to use to call the listener
* @return this Task
*/
public abstract Task<TResult> addOnFailureListener(Executor executor, OnFailureListener listener);
/**
* Adds a listener that is called if the Task completes successfully.
* <p/>
* If multiple listeners are added, they will be called in the order in which they were added. If
* the Task has already completed successfully, a call to the listener will be immediately scheduled.
*
* @param executor the executor to use to call the listener
* @return this Task
*/
public abstract Task<TResult> addOnSuccessListener(Executor executor, OnSuccessListener<? super TResult> listener);
/**
* Adds a listener that is called if the Task completes successfully.
* <p/>
* The listener will be called on the main application thread. If the Task has already
* completed successfully, a call to the listener will be immediately scheduled. If multiple
* listeners are added, they will be called in the order in which they were added.
*
* @return this Task
*/
public abstract Task<TResult> addOnSuccessListener(OnSuccessListener<? super TResult> listener);
/**
* Adds an Activity-scoped listener that is called if the Task completes successfully.
* <p/>
* The listener will be called on the main application thread. If the Task has already
* completed successfully, a call to the listener will be immediately scheduled. If multiple
* listeners are added, they will be called in the order in which they were added.
* <p/>
* The listener will be automatically removed during {@link Activity#onStop()}.
*
* @return this Task
*/
public abstract Task<TResult> addOnSuccessListener(Activity activity, OnSuccessListener<? super TResult> listener);
/**
* Returns a new Task that will be completed with the result of applying the specified
* Continuation to this Task.
* <p/>
* The Continuation will be called on the main application thread.
* <p/>
* If the previous Task is canceled, the returned Task will also be canceled and the Continuation would not execute.
*
* @see Continuation#then(Task)
*/
public <TContinuationResult> Task<TContinuationResult> continueWith(Continuation<TResult, TContinuationResult> continuation) {
throw new UnsupportedOperationException("continueWith is not implemented");
}
/**
* Returns a new Task that will be completed with the result of applying the specified Continuation to this Task.
* <p/>
* If the previous Task is canceled, the returned Task will also be canceled and the Continuation would not execute.
*
* @param executor the executor to use to call the Continuation
* @see Continuation#then(Task)
*/
public <TContinuationResult> Task<TContinuationResult> continueWith(Executor executor, Continuation<TResult, TContinuationResult> continuation) {
throw new UnsupportedOperationException("continueWith is not implemented");
}
/**
* Returns a new Task that will be completed with the result of applying the specified
* Continuation to this Task.
* <p/>
* The Continuation will be called on the main application thread.
* <p/>
* If the previous Task is canceled, the Continuation would still execute and task.isCanceled() is true can be
* observed in the Continuation.
*
* @see Continuation#then(Task)
*/
public <TContinuationResult> Task<TContinuationResult> continueWithTask(Continuation<TResult, Task<TContinuationResult>> continuation) {
throw new UnsupportedOperationException("continueWithTask is not implemented");
}
/**
* Returns a new Task that will be completed with the result of applying the specified Continuation to this Task.
* <p/>
* If the previous Task is canceled, the Continuation would still execute and task.isCanceled() is true can be
* observed in the Continuation.
*
* @param executor the executor to use to call the Continuation
* @see Continuation#then(Task)
*/
public <TContinuationResult> Task<TContinuationResult> continueWithTask(Executor executor, Continuation<TResult, Task<TContinuationResult>> continuation) {
throw new UnsupportedOperationException("continueWithTask is not implemented");
}
/**
* Returns the exception that caused the Task to fail. Returns {@code null} if the Task is not
* yet complete, or completed successfully.
*/
public abstract Exception getException();
/**
* Gets the result of the Task, if it has already completed.
*
* @throws IllegalStateException if the Task is not yet complete
* @throws RuntimeExecutionException if the Task failed with an exception
*/
public abstract TResult getResult();
/**
* Gets the result of the Task, if it has already completed.
*
* @throws IllegalStateException if the Task is not yet complete
* @throws X if the Task failed with an exception of type X
* @throws RuntimeExecutionException if the Task failed with an exception that was not of type X
*/
public abstract <X extends Throwable> TResult getResult(Class<X> exceptionType) throws X;
/**
* Returns {@code true} if the Task is canceled; {@code false} otherwise.
*/
public abstract boolean isCanceled();
/**
* Returns {@code true} if the Task is complete; {@code false} otherwise.
*/
public abstract boolean isComplete();
/**
* Returns {@code true} if the Task has completed successfully; {@code false} otherwise.
*/
public abstract boolean isSuccessful();
/**
* Returns a new Task that will be completed with the result of applying the specified SuccessContinuation to this Task when this Task completes successfully. If the previous Task fails, the onSuccessTask completion will be skipped and failure listeners will be invoked.
* <p>
* The SuccessContinuation will be called on the main application thread.
* <p>
* If the previous Task is canceled, the returned Task will also be canceled and the SuccessContinuation would not execute.
*
* @see SuccessContinuation#then
*/
public <TContinuationResult> Task<TContinuationResult> onSuccessTask(SuccessContinuation<TResult, TContinuationResult> successContinuation) {
throw new UnsupportedOperationException("onSuccessTask is not implemented");
}
/**
* Returns a new Task that will be completed with the result of applying the specified SuccessContinuation to this Task when this Task completes successfully. If the previous Task fails, the onSuccessTask completion will be skipped and failure listeners will be invoked.
* <p>
* If the previous Task is canceled, the returned Task will also be canceled and the SuccessContinuation would not execute.
*
* @param executor the executor to use to call the SuccessContinuation
* @see SuccessContinuation#then
*/
public <TContinuationResult> Task<TContinuationResult> onSuccessTask(Executor executor, SuccessContinuation<TResult, TContinuationResult> successContinuation) {
throw new UnsupportedOperationException("onSuccessTask is not implemented");
}
}

View file

@ -0,0 +1,121 @@
/*
* SPDX-FileCopyrightText: 2016 microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import org.microg.gms.common.PublicApi;
import org.microg.gms.tasks.TaskImpl;
/**
* Provides the ability to create an incomplete {@link Task}-based APIs.
* <p/>
* Use a {@code TaskCompletionSource} to set a result or exception on a Task returned from an asynchronous API:
* <pre>
* public class MarcoPolo {
* public static Task<String> marco(int delay) {
* TaskCompletionSource<String> taskCompletionSource = new TaskCompletionSource<>();
*
* new Handler().postDelayed(() -> taskCompletionSource.setResult("polo"), delay);
*
* return taskCompletionSource.getTask();
* }
* }
* </pre>
* And then your APIs can be used as any other {@link Task}-consuming APIs:
* <pre>
* public class MyActivity extends Activity {
* &#64;Override
* public void onStart() {
* super.onStart();
*
* marco(1000).addOnCompleteListener(
* task -> Log.d(TAG, "got message after one second: " + task.getResult()));
* }
* }
* </pre>
*
* and later complete it by either
* calling {@link #setResult(TResult)} or {@link #setException(Exception)}.
*/
@PublicApi
public class TaskCompletionSource<TResult> {
private TaskImpl<TResult> task = new TaskImpl<>();
/**
* Creates an instance of {@link TaskCompletionSource}.
*/
public TaskCompletionSource() {
}
/**
* Creates an instance of {@link TaskCompletionSource} with a {@link CancellationToken} so that the Task can be set to canceled when {@link CancellationToken} is canceled.
*/
public TaskCompletionSource(CancellationToken token) {
token.onCanceledRequested(() -> {
try {
task.cancel();
} catch (DuplicateTaskCompletionException ignored) {
}
});
}
/**
* Returns the Task.
*/
public Task<TResult> getTask() {
return task;
}
/**
* Completes the Task with the specified exception.
*
* @throws IllegalStateException if the Task is already complete
*/
public void setException(Exception e) {
task.setException(e);
}
/**
* Completes the Task with the specified exception, unless the Task has already completed.
* If the Task has already completed, the call does nothing.
*
* @return {@code true} if the exception was set successfully, {@code false} otherwise
*/
public boolean trySetException(Exception e) {
try {
setException(e);
return true;
} catch (DuplicateTaskCompletionException ignored) {
return false;
}
}
/**
* Completes the Task with the specified result.
*
* @throws IllegalStateException if the Task is already complete
*/
public void setResult(TResult result) {
task.setResult(result);
}
/**
* Completes the Task with the specified result, unless the Task has already completed.
* If the Task has already completed, the call does nothing.
*
* @return {@code true} if the result was set successfully, {@code false} otherwise
*/
public boolean trySetResult(TResult result) {
try {
setResult(result);
return true;
} catch (DuplicateTaskCompletionException ignored) {
return false;
}
}
}

View file

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import android.os.Handler;
import android.os.Looper;
import org.microg.gms.common.PublicApi;
import java.util.concurrent.Executor;
/**
* Standard {@link Executor} instances for use with {@link Task}.
*/
@PublicApi
public final class TaskExecutors {
/**
* An Executor that uses the main application thread.
*/
public static final Executor MAIN_THREAD = new Executor() {
private Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}

View file

@ -0,0 +1,306 @@
/*
* SPDX-FileCopyrightText: 2016 microG Project Team
* SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.tasks;
import android.os.Handler;
import android.os.Looper;
import org.microg.gms.common.PublicApi;
import org.microg.gms.tasks.CancellationTokenImpl;
import org.microg.gms.tasks.TaskImpl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* {@link Task} utility methods.
*/
@PublicApi
public final class Tasks {
/**
* Blocks until the specified Task is complete.
*
* @return the Task's result
* @throws ExecutionException if the Task fails
* @throws InterruptedException if an interrupt occurs while waiting for the Task to complete
* @throws TimeoutException if the specified timeout is reached before the Task completes
*/
public static <TResult> TResult await(Task<TResult> task, long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException {
if (task == null) throw new IllegalArgumentException("Task must not be null");
if (timeout <= 0) throw new IllegalArgumentException("Timeout must be positive");
if (unit == null) throw new IllegalArgumentException("TimeUnit must not be null");
if (task.isComplete()) return handleCompletedTask(task);
CountDownLatch latch = new CountDownLatch(1);
task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown());
if (latch.await(timeout, unit)) {
return handleCompletedTask(task);
}
throw new TimeoutException("Timed out waiting for Task");
}
/**
* Blocks until the specified Task is complete.
*
* @return the Task's result
* @throws ExecutionException if the Task fails
* @throws InterruptedException if an interrupt occurs while waiting for the Task to complete
*/
public static <TResult> TResult await(Task<TResult> task) throws ExecutionException, InterruptedException {
if (Looper.getMainLooper().getThread() == Thread.currentThread())
throw new IllegalStateException("Must not be invoked on main thread");
if (task == null) throw new IllegalArgumentException("Task must not be null");
if (task.isComplete()) return handleCompletedTask(task);
CountDownLatch latch = new CountDownLatch(1);
task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown());
latch.await();
return handleCompletedTask(task);
}
private static <TResult> TResult handleCompletedTask(Task<TResult> task) throws ExecutionException {
if (task.isSuccessful()) {
return task.getResult();
}
if (task.isCanceled()) {
throw new CancellationException("Task is already canceled");
}
throw new ExecutionException(task.getException());
}
/**
* Returns a {@link Task} that will be completed with the result of the specified {@code Callable}.
* <p/>
* If a non-{@link Exception} throwable is thrown in the callable, the {@link Task} will be failed with a
* {@link RuntimeException} whose cause is the original throwable.
* <p/>
* The {@code Callable} will be called on the main application thread.
*
* @deprecated Use {@link TaskCompletionSource} instead, which allows the caller to manage their own Executor.
*/
@Deprecated
public static <TResult> Task<TResult> call(Callable<TResult> callable) {
return call(TaskExecutors.MAIN_THREAD, callable);
}
/**
* Returns a {@link Task} that will be completed with the result of the specified {@code Callable}.
* <p/>
* If a non-{@link Exception} throwable is thrown in the callable, the {@link Task} will be failed with a
* {@link RuntimeException} whose cause is the original throwable.
*
* @param executor the Executor to use to call the {@code Callable}
* @deprecated Use {@link TaskCompletionSource} instead, which allows the caller to manage their own Executor.
*/
@Deprecated
public static <TResult> Task<TResult> call(Executor executor, Callable<TResult> callable) {
if (executor == null) throw new IllegalArgumentException("Executor must not be null");
if (callable == null) throw new IllegalArgumentException("Callable must not be null");
TaskCompletionSource<TResult> taskCompletionSource = new TaskCompletionSource<>();
executor.execute(() -> {
try {
taskCompletionSource.setResult(callable.call());
} catch (Exception e) {
taskCompletionSource.trySetException(e);
} catch (Throwable t) {
taskCompletionSource.trySetException(new RuntimeException(t));
}
});
return taskCompletionSource.getTask();
}
/**
* Returns a canceled Task.
*/
public static <TResult> Task<TResult> forCancelled() {
TaskImpl<TResult> task = new TaskImpl<>();
task.cancel();
return task;
}
/**
* Returns a completed Task with the specified exception.
*/
public static <TResult> Task<TResult> forException(Exception e) {
TaskImpl<TResult> task = new TaskImpl<>();
task.setException(e);
return task;
}
/**
* Returns a completed Task with the specified result.
*/
public static <TResult> Task<TResult> forResult(TResult result) {
TaskImpl<TResult> task = new TaskImpl<>();
task.setResult(result);
return task;
}
/**
* Returns a Task that completes successfully when all of the specified Tasks complete
* successfully. Does not accept nulls.
* <p/>
* The returned Task would fail if any of the provided Tasks fail. The returned Task would be set to canceled if
* any of the provided Tasks is canceled and no failure is detected.
*
* @throws NullPointerException if any of the provided Tasks are null
*/
public static Task<Void> whenAll(Collection<? extends Task<?>> tasks) {
if (tasks == null || tasks.isEmpty()) {
return forResult(null);
}
for (Task<?> task : tasks) {
if (task == null) throw new NullPointerException("null tasks are not accepted");
}
TaskImpl<Void> allTask = new TaskImpl<>();
AtomicInteger finishedTasks = new AtomicInteger(0);
AtomicInteger failedTasks = new AtomicInteger(0);
AtomicReference<Exception> exceptionReference = new AtomicReference<>(null);
AtomicBoolean isCancelled = new AtomicBoolean(false);
for (Task<?> task : tasks) {
task.addOnCompleteListener(Runnable::run, completedTask -> {
if (!completedTask.isSuccessful()) {
if (completedTask.isCanceled()) {
isCancelled.set(true);
} else {
exceptionReference.set(completedTask.getException());
failedTasks.incrementAndGet();
}
}
if (finishedTasks.incrementAndGet() != tasks.size()) return;
Exception exception = exceptionReference.get();
if (exception != null) {
allTask.setException(new ExecutionException(failedTasks.get() + " out of " + tasks.size() + " underlying tasks failed", exception));
} else if (isCancelled.get()) {
allTask.cancel();
} else {
allTask.setResult(null);
}
});
}
return allTask;
}
/**
* Returns a Task that completes successfully when all of the specified Tasks complete
* successfully. Does not accept nulls.
* <p/>
* The returned Task would fail if any of the provided Tasks fail. The returned Task would be set to canceled if
* any of the provided Tasks is canceled and no failure is detected.
*
* @throws NullPointerException if any of the provided Tasks are null
*/
public static Task<Void> whenAll(Task<?>... tasks) {
if (tasks == null || tasks.length == 0) {
return forResult(null);
}
return whenAll(Arrays.asList(tasks));
}
/**
* Returns a Task with a list of Tasks that completes successfully when all of the specified Tasks complete. This
* Task would always succeed even if any of the provided Tasks fail or canceled. Does not accept nulls.
*
* @throws NullPointerException if any of the provided Tasks are null
*/
public static Task<List<Task<?>>> whenAllComplete(Task<?>... tasks) {
if (tasks == null || tasks.length == 0) {
return forResult(Collections.emptyList());
}
return whenAllComplete(Arrays.asList(tasks));
}
/**
* Returns a Task with a list of Tasks that completes successfully when all of the specified Tasks complete. This
* Task would always succeed even if any of the provided Tasks fail or canceled. Does not accept nulls.
*
* @throws NullPointerException if any of the provided Tasks are null
*/
public static Task<List<Task<?>>> whenAllComplete(Collection<? extends Task<?>> tasks) {
if (tasks == null || tasks.isEmpty()) {
return forResult(Collections.emptyList());
}
return whenAll(tasks).continueWithTask(TaskExecutors.MAIN_THREAD, allTask -> forResult(new ArrayList<>(tasks)));
}
/**
* Returns a Task with a list of Task results that completes successfully when all of the specified Tasks complete
* successfully. This Task would fail if any of the provided Tasks fail. Does not accept nulls.
* <p/>
* This Task would be set to canceled if any of the provided Tasks is canceled and no failure is detected.
*
* @throws NullPointerException if any of the provided Tasks are null
*/
public static <TResult> Task<List<TResult>> whenAllSuccess(Task<? extends TResult>... tasks) {
if (tasks == null || tasks.length == 0) {
return forResult(Collections.emptyList());
}
return whenAllSuccess(Arrays.asList(tasks));
}
/**
* Returns a Task with a list of Task results that completes successfully when all of the specified Tasks complete
* successfully. This Task would fail if any of the provided Tasks fail. Does not accept nulls.
* <p/>
* This Task would be set to canceled if any of the provided Tasks is canceled and no failure is detected.
*
* @throws NullPointerException if any of the provided Tasks are null
*/
public static <TResult> Task<List<TResult>> whenAllSuccess(Collection<? extends Task<? extends TResult>> tasks) {
if (tasks == null || tasks.isEmpty()) {
return forResult(Collections.emptyList());
}
return whenAll(tasks).continueWithTask(TaskExecutors.MAIN_THREAD, allTask -> {
List<TResult> results = new ArrayList<>(tasks.size());
for (Task<? extends TResult> task : tasks) {
results.add(task.getResult());
}
return forResult(results);
});
}
/**
* Returns a new Task which will return a TimeoutException if a result is not returned within the specified time
* period.
*
* @return A new Task.
*/
public static <T> Task<T> withTimeout(Task<T> task, long timeout, TimeUnit unit) {
if (task == null) throw new IllegalArgumentException("Task must not be null");
if (timeout <= 0) throw new IllegalArgumentException("Timeout must be positive");
if (unit == null) throw new IllegalArgumentException("TimeUnit must not be null");
CancellationTokenImpl cancellationToken = new CancellationTokenImpl();
TaskCompletionSource<T> taskCompletionSource = new TaskCompletionSource<>(cancellationToken);
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(() -> taskCompletionSource.trySetException(new TimeoutException()), unit.toMillis(timeout));
task.addOnCompleteListener(completedTask -> {
handler.removeCallbacksAndMessages(null);
if (completedTask.isSuccessful()) {
taskCompletionSource.trySetResult(completedTask.getResult());
} else if (completedTask.isCanceled()) {
cancellationToken.cancel();
} else {
taskCompletionSource.trySetException(completedTask.getException());
}
});
return taskCompletionSource.getTask();
}
}

View file

@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.tasks;
import com.google.android.gms.tasks.CancellationToken;
import com.google.android.gms.tasks.DuplicateTaskCompletionException;
import com.google.android.gms.tasks.OnTokenCanceledListener;
public class CancellationTokenImpl extends CancellationToken {
private TaskImpl<Void> task = new TaskImpl<>();
@Override
public boolean isCancellationRequested() {
return task.isComplete();
}
@Override
public CancellationToken onCanceledRequested(OnTokenCanceledListener listener) {
task.addOnSuccessListener(aVoid -> listener.onCanceled());
return this;
}
public void cancel() {
try {
task.cancel();
} catch (DuplicateTaskCompletionException ignored) {
}
}
}

View file

@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.tasks;
import com.google.android.gms.tasks.OnCanceledListener;
import com.google.android.gms.tasks.Task;
import java.util.concurrent.Executor;
public class CancelledExecutor<TResult> extends UpdateExecutor<TResult> {
private OnCanceledListener listener;
public CancelledExecutor(Executor executor, OnCanceledListener listener) {
super(executor);
this.listener = listener;
}
@Override
public void onTaskUpdate(Task<TResult> task) {
if (task.isCanceled()) {
execute(() -> listener.onCanceled());
}
}
@Override
public void cancel() {
super.cancel();
listener = null;
}
}

View file

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.tasks;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import java.util.concurrent.Executor;
public class CompletedExecutor<TResult> extends UpdateExecutor<TResult> {
private OnCompleteListener<TResult> listener;
public CompletedExecutor(Executor executor, OnCompleteListener<TResult> listener) {
super(executor);
this.listener = listener;
}
@Override
public void onTaskUpdate(Task<TResult> task) {
if (task.isComplete()) {
execute(() -> listener.onComplete(task));
}
}
@Override
public void cancel() {
super.cancel();
listener = null;
}
}

View file

@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.tasks;
import com.google.android.gms.tasks.Continuation;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.TaskCompletionSource;
import java.util.concurrent.Executor;
public class ContinuationExecutor<TResult, TContinuationResult> extends UpdateExecutor<TResult> {
private Continuation<TResult, TContinuationResult> continuation;
private TaskCompletionSource<TContinuationResult> completionSource = new TaskCompletionSource<>();
public ContinuationExecutor(Executor executor, Continuation<TResult, TContinuationResult> continuation) {
super(executor);
this.continuation = continuation;
}
@Override
public void onTaskUpdate(Task<TResult> task) {
if (task.isComplete()) {
execute(() -> {
try {
completionSource.setResult(continuation.then(task));
} catch (Exception e) {
completionSource.setException(e);
}
});
}
}
public Task<TContinuationResult> getTask() {
return completionSource.getTask();
}
@Override
public void cancel() {
super.cancel();
continuation = null;
completionSource = null;
}
}

View file

@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.tasks;
import com.google.android.gms.tasks.Continuation;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.TaskCompletionSource;
import java.util.concurrent.Executor;
public class ContinuationWithExecutor<TResult, TContinuationResult> extends UpdateExecutor<TResult> {
private Continuation<TResult, Task<TContinuationResult>> continuation;
private TaskCompletionSource<TContinuationResult> completionSource = new TaskCompletionSource<>();
public ContinuationWithExecutor(Executor executor, Continuation<TResult, Task<TContinuationResult>> continuation) {
super(executor);
this.continuation = continuation;
}
@Override
public void onTaskUpdate(Task<TResult> task) {
if (task.isComplete()) {
execute(() -> {
try {
continuation.then(task).addOnCompleteListener(this, (subTask) -> {
if (subTask.isSuccessful()) {
completionSource.setResult(subTask.getResult());
} else {
completionSource.setException(subTask.getException());
}
});
} catch (Exception e) {
completionSource.setException(e);
}
});
}
}
public Task<TContinuationResult> getTask() {
return completionSource.getTask();
}
@Override
public void cancel() {
super.cancel();
continuation = null;
completionSource = null;
}
}

View file

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.tasks;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.Task;
import java.util.concurrent.Executor;
public class FailureExecutor<TResult> extends UpdateExecutor<TResult> {
private OnFailureListener listener;
public FailureExecutor(Executor executor, OnFailureListener listener) {
super(executor);
this.listener = listener;
}
@Override
public void onTaskUpdate(Task<TResult> task) {
if (task.isComplete() && !task.isSuccessful() && !task.isCanceled()) {
execute(() -> listener.onFailure(task.getException()));
}
}
@Override
public void cancel() {
super.cancel();
listener = null;
}
}

View file

@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.tasks;
import com.google.android.gms.tasks.SuccessContinuation;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.TaskCompletionSource;
import java.util.concurrent.Executor;
public class SuccessContinuationExecutor<TResult, TContinuationResult> extends UpdateExecutor<TResult> {
private SuccessContinuation<TResult, TContinuationResult> continuation;
private TaskCompletionSource<TContinuationResult> completionSource = new TaskCompletionSource<>();
public SuccessContinuationExecutor(Executor executor, SuccessContinuation<TResult, TContinuationResult> continuation) {
super(executor);
this.continuation = continuation;
}
@Override
public void onTaskUpdate(Task<TResult> task) {
if (task.isSuccessful()) {
execute(() -> {
try {
continuation.then(task.getResult()).addOnCompleteListener(this, (subTask) -> {
if (subTask.isSuccessful()) {
completionSource.setResult(subTask.getResult());
} else {
completionSource.setException(subTask.getException());
}
});
} catch (Exception e) {
completionSource.setException(e);
}
});
} else {
completionSource.setException(task.getException());
}
}
public Task<TContinuationResult> getTask() {
return completionSource.getTask();
}
@Override
public void cancel() {
super.cancel();
continuation = null;
completionSource = null;
}
}

View file

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.tasks;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import java.util.concurrent.Executor;
public class SuccessExecutor<TResult> extends UpdateExecutor<TResult> {
private OnSuccessListener<? super TResult> listener;
public SuccessExecutor(Executor executor, OnSuccessListener<? super TResult> listener) {
super(executor);
this.listener = listener;
}
@Override
public void onTaskUpdate(Task<TResult> task) {
if (task.isSuccessful()) {
execute(() -> listener.onSuccess(task.getResult()));
}
}
@Override
public void cancel() {
super.cancel();
listener = null;
}
}

View file

@ -0,0 +1,240 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.tasks;
import android.app.Activity;
import com.google.android.gms.tasks.Continuation;
import com.google.android.gms.tasks.DuplicateTaskCompletionException;
import com.google.android.gms.tasks.OnCanceledListener;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.RuntimeExecutionException;
import com.google.android.gms.tasks.SuccessContinuation;
import com.google.android.gms.tasks.Task;
import java.util.Queue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import static com.google.android.gms.tasks.TaskExecutors.MAIN_THREAD;
public class TaskImpl<TResult> extends Task<TResult> {
private final Object lock = new Object();
private boolean completed;
private boolean cancelled;
private TResult result;
private Exception exception;
private Queue<UpdateListener<TResult>> completionQueue = new LinkedBlockingQueue<>();
@Override
public Task<TResult> addOnCanceledListener(OnCanceledListener listener) {
return addOnCanceledListener(MAIN_THREAD, listener);
}
@Override
public Task<TResult> addOnCanceledListener(Executor executor, OnCanceledListener listener) {
return enqueueOrInvoke(new CancelledExecutor<>(executor, listener));
}
@Override
public Task<TResult> addOnCanceledListener(Activity activity, OnCanceledListener listener) {
return enqueueOrInvoke(activity, new CancelledExecutor<>(MAIN_THREAD, listener));
}
@Override
public Task<TResult> addOnCompleteListener(OnCompleteListener<TResult> listener) {
return addOnCompleteListener(MAIN_THREAD, listener);
}
@Override
public Task<TResult> addOnCompleteListener(Executor executor, OnCompleteListener<TResult> listener) {
return enqueueOrInvoke(new CompletedExecutor<>(executor, listener));
}
@Override
public Task<TResult> addOnCompleteListener(Activity activity, OnCompleteListener<TResult> listener) {
return enqueueOrInvoke(activity, new CompletedExecutor<>(MAIN_THREAD, listener));
}
@Override
public Task<TResult> addOnFailureListener(OnFailureListener listener) {
return addOnFailureListener(MAIN_THREAD, listener);
}
@Override
public Task<TResult> addOnFailureListener(Executor executor, OnFailureListener listener) {
return enqueueOrInvoke(new FailureExecutor<>(executor, listener));
}
@Override
public Task<TResult> addOnFailureListener(Activity activity, OnFailureListener listener) {
return enqueueOrInvoke(activity, new FailureExecutor<>(MAIN_THREAD, listener));
}
@Override
public Task<TResult> addOnSuccessListener(OnSuccessListener<? super TResult> listener) {
return addOnSuccessListener(MAIN_THREAD, listener);
}
@Override
public Task<TResult> addOnSuccessListener(Executor executor, OnSuccessListener<? super TResult> listener) {
return enqueueOrInvoke(new SuccessExecutor<>(executor, listener));
}
@Override
public Task<TResult> addOnSuccessListener(Activity activity, OnSuccessListener<? super TResult> listener) {
return enqueueOrInvoke(activity, new SuccessExecutor<>(MAIN_THREAD, listener));
}
@Override
public <TContinuationResult> Task<TContinuationResult> continueWith(Continuation<TResult, TContinuationResult> continuation) {
return continueWith(MAIN_THREAD, continuation);
}
@Override
public <TContinuationResult> Task<TContinuationResult> continueWith(Executor executor, Continuation<TResult, TContinuationResult> continuation) {
ContinuationExecutor<TResult, TContinuationResult> c = new ContinuationExecutor<>(executor, continuation);
enqueueOrInvoke(c);
return c.getTask();
}
@Override
public <TContinuationResult> Task<TContinuationResult> continueWithTask(Continuation<TResult, Task<TContinuationResult>> continuation) {
return continueWithTask(MAIN_THREAD, continuation);
}
@Override
public <TContinuationResult> Task<TContinuationResult> continueWithTask(Executor executor, Continuation<TResult, Task<TContinuationResult>> continuation) {
ContinuationWithExecutor<TResult, TContinuationResult> c = new ContinuationWithExecutor<>(executor, continuation);
enqueueOrInvoke(c);
return c.getTask();
}
@Override
public Exception getException() {
synchronized (lock) {
return exception;
}
}
@Override
public TResult getResult() {
synchronized (lock) {
if (!completed) throw new IllegalStateException("Task is not yet complete");
if (cancelled) throw new CancellationException("Task is canceled");
if (exception != null) throw new RuntimeExecutionException(exception);
return result;
}
}
@Override
public <X extends Throwable> TResult getResult(Class<X> exceptionType) throws X {
synchronized (lock) {
if (!completed) throw new IllegalStateException("Task is not yet complete");
if (cancelled) throw new CancellationException("Task is canceled");
if (exceptionType.isInstance(exception)) throw exceptionType.cast(exception);
if (exception != null) throw new RuntimeExecutionException(exception);
return result;
}
}
@Override
public boolean isCanceled() {
synchronized (lock) {
return cancelled;
}
}
@Override
public boolean isComplete() {
synchronized (lock) {
return completed;
}
}
@Override
public boolean isSuccessful() {
synchronized (lock) {
return completed && !cancelled && exception == null;
}
}
private void registerActivityStop(Activity activity, UpdateListener<TResult> listener) {
UpdateListenerLifecycleObserver.getObserverForActivity(activity).registerActivityStopListener(listener);
}
private Task<TResult> enqueueOrInvoke(Activity activity, UpdateListener<TResult> listener) {
synchronized (lock) {
if (completed) {
listener.onTaskUpdate(this);
} else {
completionQueue.offer(listener);
registerActivityStop(activity, listener);
}
}
return this;
}
private Task<TResult> enqueueOrInvoke(UpdateListener<TResult> listener) {
synchronized (lock) {
if (completed) {
listener.onTaskUpdate(this);
} else {
completionQueue.offer(listener);
}
}
return this;
}
private void notifyQueue() {
UpdateListener<TResult> listener;
while ((listener = completionQueue.poll()) != null) {
listener.onTaskUpdate(this);
}
}
public void cancel() {
synchronized (lock) {
if (completed) throw DuplicateTaskCompletionException.of(this);
this.completed = true;
this.cancelled = true;
notifyQueue();
}
}
public void setResult(TResult result) {
synchronized (lock) {
if (completed) throw DuplicateTaskCompletionException.of(this);
this.completed = true;
this.result = result;
notifyQueue();
}
}
public void setException(Exception exception) {
synchronized (lock) {
if (completed) throw DuplicateTaskCompletionException.of(this);
this.completed = true;
this.exception = exception;
notifyQueue();
}
}
@Override
public <TContinuationResult> Task<TContinuationResult> onSuccessTask(SuccessContinuation<TResult, TContinuationResult> successContinuation) {
return onSuccessTask(MAIN_THREAD, successContinuation);
}
@Override
public <TContinuationResult> Task<TContinuationResult> onSuccessTask(Executor executor, SuccessContinuation<TResult, TContinuationResult> successContinuation) {
SuccessContinuationExecutor<TResult, TContinuationResult> c = new SuccessContinuationExecutor<>(executor, successContinuation);
enqueueOrInvoke(c);
return c.getTask();
}
}

View file

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.tasks;
import java.util.concurrent.Executor;
public abstract class UpdateExecutor<TResult> implements UpdateListener<TResult>, Executor {
private Executor executor;
public UpdateExecutor(Executor executor) {
this.executor = executor;
}
@Override
public void execute(Runnable runnable) {
if (executor == null) return;
executor.execute(runnable);
}
@Override
public void cancel() {
executor = null;
}
}

View file

@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.tasks;
import com.google.android.gms.tasks.Task;
public interface UpdateListener<TResult> {
void onTaskUpdate(Task<TResult> task);
void cancel();
}

View file

@ -0,0 +1,98 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.tasks;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.WeakHashMap;
public class UpdateListenerLifecycleObserver {
private static WeakHashMap<Activity, WeakReference<UpdateListenerLifecycleObserver>> map = new WeakHashMap<>();
private static boolean activityLifecycleCallbacksRegistered = false;
private List<WeakReference<UpdateListener<?>>> list = new ArrayList<>();
public synchronized static UpdateListenerLifecycleObserver getObserverForActivity(Activity activity) {
WeakReference<UpdateListenerLifecycleObserver> ref = map.get(activity);
if (ref != null) {
UpdateListenerLifecycleObserver observer = ref.get();
if (observer != null) {
return observer;
}
}
if (!activityLifecycleCallbacksRegistered) {
activity.getApplication().registerActivityLifecycleCallbacks(new MyActivityLifecycleCallbacks());
activityLifecycleCallbacksRegistered = true;
}
UpdateListenerLifecycleObserver newInstance = new UpdateListenerLifecycleObserver();
map.put(activity, new WeakReference<>(newInstance));
return newInstance;
}
private UpdateListenerLifecycleObserver() {
}
public synchronized void registerActivityStopListener(UpdateListener<?> listener) {
list.add(new WeakReference<>(listener));
}
public synchronized void onStop() {
for (WeakReference<UpdateListener<?>> ref : list) {
UpdateListener<?> listener = ref.get();
listener.cancel();
}
list.clear();
}
private static class MyActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
WeakReference<UpdateListenerLifecycleObserver> ref = map.get(activity);
if (ref != null) {
UpdateListenerLifecycleObserver observer = ref.get();
if (observer != null) {
observer.onStop();
}
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
}
}