Repo cloned

This commit is contained in:
Fr4nz D13trich 2025-12-29 13:18:34 +01:00
commit 496ae75f58
7988 changed files with 1451097 additions and 0 deletions

View file

@ -0,0 +1,22 @@
plugins {
id("signal-library")
}
val gmsVersionCode = 12451000
android {
namespace = "com.google.android.gms"
buildFeatures {
buildConfig = true
}
defaultConfig {
buildConfigField("int", "GMS_VERSION_CODE", gmsVersionCode.toString())
resValue("integer", "google_play_services_version", gmsVersionCode.toString())
}
lint {
disable += setOf("LogNotSignal")
}
}

View file

@ -0,0 +1,17 @@
package com.google.android.gms.common;
import androidx.annotation.Keep;
@Keep
public class ConnectionResult {
public static final int UNKNOWN = -1;
public static final int SUCCESS = 0;
public static final int SERVICE_MISSING = 1;
public static final int SERVICE_VERSION_UPDATE_REQUIRED = 2;
public static final int SERVICE_DISABLED = 3;
public static final int NETWORK_ERROR = 7;
public static final int SERVICE_INVALID = 9;
public static final int API_UNAVAILABLE = 16;
public static final int SERVICE_UPDATING = 18;
public static final int SERVICE_MISSING_PERMISSION = 19;
}

View file

@ -0,0 +1,17 @@
package com.google.android.gms.common;
import androidx.annotation.Keep;
@Keep
public final class GoogleApiAvailability extends GoogleApiAvailabilityLight {
private static final class InstanceHolder {
private static final GoogleApiAvailability instance = new GoogleApiAvailability();
}
private GoogleApiAvailability() {}
public static GoogleApiAvailability getInstance() {
return InstanceHolder.instance;
}
}

View file

@ -0,0 +1,65 @@
package com.google.android.gms.common;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.Settings;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.BuildConfig;
@Keep
public class GoogleApiAvailabilityLight {
public static final String GOOGLE_PLAY_SERVICES_PACKAGE = "com.google.android.gms";
public static final String GOOGLE_PLAY_STORE_PACKAGE = "com.android.vending";
public static final int GOOGLE_PLAY_SERVICES_VERSION_CODE = BuildConfig.GMS_VERSION_CODE;
public int isGooglePlayServicesAvailable(@NonNull Context context) {
return isGooglePlayServicesAvailable(context, GOOGLE_PLAY_SERVICES_VERSION_CODE);
}
public int isGooglePlayServicesAvailable(@NonNull Context context, int minApkVersion) {
int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context, minApkVersion);
if (GooglePlayServicesUtil.isPlayServicesPossiblyUpdating(context, status)) {
return ConnectionResult.SERVICE_UPDATING;
}
return status;
}
@Nullable
public PendingIntent getErrorResolutionPendingIntent(@NonNull Context context, int errorCode, int requestCode) {
Intent intent = getErrorResolutionIntent(errorCode);
if (intent == null) {
return null;
}
return PendingIntent.getActivity(
context, requestCode, intent,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
);
}
@Nullable
private Intent getErrorResolutionIntent(int errorCode) {
switch (errorCode) {
case ConnectionResult.SERVICE_MISSING:
case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED:
return new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse("market://details?id=" + GOOGLE_PLAY_SERVICES_PACKAGE))
.setPackage(GOOGLE_PLAY_STORE_PACKAGE)
.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
case ConnectionResult.SERVICE_DISABLED:
return new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", GOOGLE_PLAY_SERVICES_PACKAGE, null));
default:
return null;
}
}
}

View file

@ -0,0 +1,23 @@
package com.google.android.gms.common;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import static com.google.android.gms.common.GoogleApiAvailability.GOOGLE_PLAY_SERVICES_VERSION_CODE;
@Keep
public final class GooglePlayServicesIncorrectManifestValueException extends GooglePlayServicesManifestException {
public GooglePlayServicesIncorrectManifestValueException(int actualVersion) {
super(actualVersion, getString(actualVersion));
}
@NonNull
private static String getString(int actualVersion) {
return "The meta-data tag in your app's AndroidManifest.xml does not have the right value. " +
" Expected " + GOOGLE_PLAY_SERVICES_VERSION_CODE + " but found " + actualVersion + ". " +
"You must have the following declaration within the <application> element: " +
"<meta-data android:name=\"com.google.android.gms.version\" " +
"android:value=\"@integer/google_play_services_version\" />";
}
}

View file

@ -0,0 +1,24 @@
package com.google.android.gms.common;
import androidx.annotation.Keep;
import static com.google.android.gms.common.GoogleApiAvailability.GOOGLE_PLAY_SERVICES_VERSION_CODE;
@Keep
public class GooglePlayServicesManifestException extends IllegalStateException {
private final int actualVersion;
public GooglePlayServicesManifestException(int actualVersion, String message) {
super(message);
this.actualVersion = actualVersion;
}
public int getActualVersion() {
return actualVersion;
}
public int getExpectedVersion() {
return GOOGLE_PLAY_SERVICES_VERSION_CODE;
}
}

View file

@ -0,0 +1,17 @@
package com.google.android.gms.common;
import androidx.annotation.Keep;
@Keep
public final class GooglePlayServicesMissingManifestValueException extends GooglePlayServicesManifestException {
private static final String ERROR_MESSAGE =
"A required meta-data tag in your app's AndroidManifest.xml does not exist. " +
"You must have the following declaration within the <application> element: " +
"<meta-data android:name=\"com.google.android.gms.version\" " +
"android:value=\"@integer/google_play_services_version\" />";
public GooglePlayServicesMissingManifestValueException() {
super(0, ERROR_MESSAGE);
}
}

View file

@ -0,0 +1,13 @@
package com.google.android.gms.common;
import androidx.annotation.Keep;
@Keep
public final class GooglePlayServicesNotAvailableException extends Exception {
public final int errorCode;
public GooglePlayServicesNotAvailableException(int errorCode) {
this.errorCode = errorCode;
}
}

View file

@ -0,0 +1,21 @@
package com.google.android.gms.common;
import android.content.Intent;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@Keep
public class GooglePlayServicesRepairableException extends UserRecoverableException {
public final int connectionStatusCode;
public GooglePlayServicesRepairableException(int connectionStatusCode, @NonNull String msg, @NonNull Intent intent) {
super(msg, intent);
this.connectionStatusCode = connectionStatusCode;
}
public int getConnectionStatusCode() {
return connectionStatusCode;
}
}

View file

@ -0,0 +1,103 @@
package com.google.android.gms.common;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.util.Log;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import com.google.android.gms.common.internal.Preconditions;
import com.google.android.gms.common.wrappers.PackageManagerWrapper;
import com.google.android.gms.common.wrappers.Wrappers;
import java.util.List;
import static com.google.android.gms.common.GoogleApiAvailability.GOOGLE_PLAY_SERVICES_PACKAGE;
import static com.google.android.gms.common.GoogleApiAvailability.GOOGLE_PLAY_SERVICES_VERSION_CODE;
@Keep
public class GooglePlayServicesUtil {
private static final String TAG = "GooglePlayServicesUtil";
GooglePlayServicesUtil() {}
public static int isGooglePlayServicesAvailable(@NonNull Context context) {
return isGooglePlayServicesAvailable(context, GOOGLE_PLAY_SERVICES_VERSION_CODE);
}
public static int isGooglePlayServicesAvailable(@NonNull Context context, int minApkVersion) {
Preconditions.checkArgument(minApkVersion >= 0, "minApkVersion must be non-negative");
int manifestVersion = PlayServicesOptions.getVersionCodeFromResource(context);
if (manifestVersion == 0) {
throw new GooglePlayServicesMissingManifestValueException();
}
if (manifestVersion != GOOGLE_PLAY_SERVICES_VERSION_CODE) {
throw new GooglePlayServicesIncorrectManifestValueException(manifestVersion);
}
final PackageManagerWrapper pm = Wrappers.packageManager(context);
PackageInfo gmsInfo;
try {
gmsInfo = pm.getPackageInfo(GOOGLE_PLAY_SERVICES_PACKAGE, PackageManager.GET_SIGNATURES);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, context.getPackageName() + " requires Google Play services, but they are missing.");
return ConnectionResult.SERVICE_MISSING;
}
if (!GoogleSignatureVerifier.isGooglePublicSignedPackage(gmsInfo)) {
Log.w(TAG, context.getPackageName() + " requires Google Play services, but their signature is invalid.");
return ConnectionResult.SERVICE_INVALID;
}
if (normalizeVersionCode(gmsInfo.versionCode) < normalizeVersionCode(minApkVersion)) {
Log.w(TAG, context.getPackageName() + " requires Google Play services version " +
minApkVersion + ", but found " + gmsInfo.versionCode);
return ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED;
}
if (gmsInfo.applicationInfo == null || !gmsInfo.applicationInfo.enabled) {
return ConnectionResult.SERVICE_DISABLED;
}
return ConnectionResult.SUCCESS;
}
public static boolean isPlayServicesPossiblyUpdating(@NonNull Context context, int status) {
return switch (status) {
case ConnectionResult.SERVICE_UPDATING -> true;
case ConnectionResult.SERVICE_MISSING -> isUpdatingOrEnabled(context);
default -> false;
};
}
private static boolean isUpdatingOrEnabled(Context context) {
final PackageManagerWrapper pm = Wrappers.packageManager(context);
try {
List<PackageInstaller.SessionInfo> sessions =
pm.getPackageInstaller().getAllSessions();
for (PackageInstaller.SessionInfo session : sessions) {
if (GOOGLE_PLAY_SERVICES_PACKAGE.equals(session.getAppPackageName())) {
return true;
}
}
} catch (Exception ignored) {}
try {
return pm.getApplicationInfo(GOOGLE_PLAY_SERVICES_PACKAGE, 0).enabled;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
private static int normalizeVersionCode(int versionCode) {
return versionCode == -1 ? -1 : versionCode / 1000;
}
}

View file

@ -0,0 +1,74 @@
package com.google.android.gms.common;
import android.content.pm.PackageInfo;
import android.content.pm.Signature;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.android.gms.common.util.Hex;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
class GoogleSignatureVerifier {
private static final String TAG = "GoogleSignatureVerifier";
/**
* SHA-1: 38918a453d07199354f8b19af05ec6562ced5788
* SHA-256: f0fd6c5b410f25cb25c3b53346c8972fae30f8ee7411df910480ad6b2d60db83
* Certificate: CN=Android, OU=Android, O=Google Inc., L=Mountain View, ST=California, C=US
*/
private static final byte[] GMS_PRIMARY_SIGNATURE_SHA256 = new byte[] {
(byte) 0xf0, (byte) 0xfd, (byte) 0x6c, (byte) 0x5b, (byte) 0x41, (byte) 0x0f, (byte) 0x25, (byte) 0xcb,
(byte) 0x25, (byte) 0xc3, (byte) 0xb5, (byte) 0x33, (byte) 0x46, (byte) 0xc8, (byte) 0x97, (byte) 0x2f,
(byte) 0xae, (byte) 0x30, (byte) 0xf8, (byte) 0xee, (byte) 0x74, (byte) 0x11, (byte) 0xdf, (byte) 0x91,
(byte) 0x04, (byte) 0x80, (byte) 0xad, (byte) 0x6b, (byte) 0x2d, (byte) 0x60, (byte) 0xdb, (byte) 0x83
};
/**
* SHA-1: 2169eddb5fbb1fdf241c262681024692c4fc1ecb
* SHA-256: 5f2391277b1dbd489000467e4c2fa6af802430080457dce2f618992e9dfb5402
* Certificate: CN=Android, OU=Android, O=Google Inc., L=Mountain View, ST=California, C=US
*/
private static final byte[] GMS_SECONDARY_SIGNATURE_SHA256 = new byte[] {
(byte) 0x5f, (byte) 0x23, (byte) 0x91, (byte) 0x27, (byte) 0x7b, (byte) 0x1d, (byte) 0xbd, (byte) 0x48,
(byte) 0x90, (byte) 0x00, (byte) 0x46, (byte) 0x7e, (byte) 0x4c, (byte) 0x2f, (byte) 0xa6, (byte) 0xaf,
(byte) 0x80, (byte) 0x24, (byte) 0x30, (byte) 0x08, (byte) 0x04, (byte) 0x57, (byte) 0xdc, (byte) 0xe2,
(byte) 0xf6, (byte) 0x18, (byte) 0x99, (byte) 0x2e, (byte) 0x9d, (byte) 0xfb, (byte) 0x54, (byte) 0x02
};
private static final byte[][] VALID_GMS_SIGNATURES = {
GMS_PRIMARY_SIGNATURE_SHA256,
GMS_SECONDARY_SIGNATURE_SHA256
};
private GoogleSignatureVerifier() {}
public static boolean isGooglePublicSignedPackage(@NonNull PackageInfo packageInfo) {
if (packageInfo.signatures != null) {
if (packageInfo.signatures.length > 1) {
Log.w(TAG, "Package has more than one signature.");
return false;
}
return verifyGoogleSignature(packageInfo.signatures[0]);
}
return false;
}
private static boolean verifyGoogleSignature(@NonNull Signature signature) {
try {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] hash = sha256.digest(signature.toByteArray());
for (byte[] valid : VALID_GMS_SIGNATURES) {
if (Arrays.equals(hash, valid)) return true;
}
Log.w(TAG, "Unrecognized GMS signature: " + Hex.bytesToStringLowercase(hash));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return false;
}
}

View file

@ -0,0 +1,36 @@
package com.google.android.gms.common;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.common.wrappers.Wrappers;
final class PlayServicesOptions {
public static final String GMS_VERSION_RESOURCE_NAME = "com.google.android.gms.version";
@Nullable
private static Integer gmsVersion;
private PlayServicesOptions() {}
public static int getVersionCodeFromResource(@NonNull Context context) {
if (gmsVersion == null) {
try {
ApplicationInfo appInfo
= Wrappers.packageManager(context)
.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
Bundle metaData = appInfo.metaData;
gmsVersion = metaData != null ? metaData.getInt(GMS_VERSION_RESOURCE_NAME, 0) : 0;
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
}
return gmsVersion;
}
}

View file

@ -0,0 +1,22 @@
package com.google.android.gms.common;
import android.content.Intent;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@Keep
public class UserRecoverableException extends Exception {
private final Intent intent;
public UserRecoverableException(@NonNull String msg, @NonNull Intent intent) {
super(msg);
this.intent = intent;
}
@NonNull
public Intent getIntent() {
return new Intent(intent);
}
}

View file

@ -0,0 +1,13 @@
package com.google.android.gms.common.annotation;
import androidx.annotation.Keep;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Keep
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR })
@Documented
public @interface KeepForSdk {
}

View file

@ -0,0 +1,151 @@
package com.google.android.gms.common.api.internal;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Application;
import android.content.ComponentCallbacks2;
import android.content.res.Configuration;
import android.os.Bundle;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.common.util.ProcessUtils;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
@Keep
public final class BackgroundDetector implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
private final AtomicBoolean inBackground = new AtomicBoolean();
private final AtomicBoolean evaluated = new AtomicBoolean();
private final List<BackgroundStateChangeListener> listeners = new CopyOnWriteArrayList<>();
private volatile boolean initialized;
private BackgroundDetector() {}
private static final class InstanceHolder {
private static final BackgroundDetector instance = new BackgroundDetector();
}
@NonNull
public static BackgroundDetector getInstance() {
return InstanceHolder.instance;
}
public static void initialize(@NonNull Application application) {
BackgroundDetector instance = getInstance();
if (instance.initialized) {
return;
}
synchronized (instance) {
if (instance.initialized) {
return;
}
instance.initialized = true;
application.registerActivityLifecycleCallbacks(instance);
application.registerComponentCallbacks(instance);
}
}
public boolean readCurrentStateIfPossible(boolean isInBackgroundDefault) {
if (!evaluated.get()) {
if (ProcessUtils.isIsolatedProcess()) {
return isInBackgroundDefault;
}
ActivityManager.RunningAppProcessInfo info =
new ActivityManager.RunningAppProcessInfo();
ActivityManager.getMyMemoryState(info);
boolean firstEval = !evaluated.getAndSet(true);
if (firstEval && info.importance > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
inBackground.set(true);
}
}
return isInBackground();
}
public boolean isInBackground() {
return inBackground.get();
}
public void addListener(@NonNull BackgroundStateChangeListener listener) {
listeners.add(listener);
if (initialized) {
listener.onBackgroundStateChanged(inBackground.get());
}
}
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
enterForeground();
}
@Override
public void onActivityStarted(@NonNull Activity activity) {}
@Override
public void onActivityResumed(@NonNull Activity activity) {
enterForeground();
}
@Override
public void onActivityPaused(@NonNull Activity activity) {}
@Override
public void onActivityStopped(@NonNull Activity activity) {}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {}
@Override
public void onTrimMemory(int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
enterBackground();
}
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {}
@Override
public void onLowMemory() {}
private void enterForeground() {
boolean stateChanged = inBackground.compareAndSet(true, false);
evaluated.set(true);
if (stateChanged) {
notifyListeners(false);
}
}
private void enterBackground() {
boolean stateChanged = inBackground.compareAndSet(false, true);
evaluated.set(true);
if (stateChanged) {
notifyListeners(true);
}
}
private void notifyListeners(boolean nowBackground) {
for (BackgroundStateChangeListener listener : listeners) {
listener.onBackgroundStateChanged(nowBackground);
}
}
public interface BackgroundStateChangeListener {
void onBackgroundStateChanged(boolean isInBackground);
}
}

View file

@ -0,0 +1,93 @@
package com.google.android.gms.common.internal;
import android.os.Bundle;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
@Keep
public final class Objects {
private Objects() {}
public static boolean equal(Object a, Object b) {
if (a == b) {
return true;
}
if (a == null || b == null) {
return false;
}
return a.equals(b);
}
public static int hashCode(Object... objects) {
return Arrays.hashCode(objects);
}
public static boolean checkBundlesEquality(Bundle a, Bundle b) {
if (a == null || b == null) {
return a == b;
}
if (a.size() != b.size()) {
return false;
}
Set<String> keys = a.keySet();
if (!keys.containsAll(b.keySet())) {
return false;
}
for (String k : keys) {
if (!equal(a.get(k), b.get(k))) {
return false;
}
}
return true;
}
public static final class ToStringHelper {
private final Object target;
private final List<String> parts;
private ToStringHelper(@NonNull Object target) {
this.target = target;
this.parts = new ArrayList<>();
}
@NonNull
public ToStringHelper add(@NonNull String name, Object value) {
parts.add(name + "=" + value);
return this;
}
@Override
@NonNull
public String toString() {
String name = target.getClass().getSimpleName();
StringBuilder sb = new StringBuilder(name.length() * 2);
sb.append(name);
sb.append('{');
for (int i = 0; i < parts.size(); i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(parts.get(i));
}
sb.append('}');
return sb.toString();
}
}
@NonNull
public static ToStringHelper toStringHelper(@NonNull Object target) {
return new ToStringHelper(target);
}
}

View file

@ -0,0 +1,200 @@
package com.google.android.gms.common.internal;
import android.os.Looper;
import android.text.TextUtils;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Objects;
@Keep
public class Preconditions {
private Preconditions() {}
public static double checkArgumentInRange(double value, double lower, double upper, @NonNull String valueName) {
if (value < lower) {
throw new IllegalArgumentException(
String.format("%s is out of range of [%f, %f] (too low)", valueName, lower, upper)
);
} else if (value > upper) {
throw new IllegalArgumentException(
String.format("%s is out of range of [%f, %f] (too high)", valueName, lower, upper)
);
}
return value;
}
public static float checkArgumentInRange(float value, float lower, float upper, @NonNull String valueName) {
if (value < lower) {
throw new IllegalArgumentException(
String.format("%s is out of range of [%f, %f] (too low)", valueName, lower, upper)
);
} else if (value > upper) {
throw new IllegalArgumentException(
String.format("%s is out of range of [%f, %f] (too high)", valueName, lower, upper)
);
}
return value;
}
public static int checkArgumentInRange(int value, int lower, int upper, @NonNull String valueName) {
if (value < lower) {
throw new IllegalArgumentException(
String.format("%s is out of range of [%d, %d] (too low)", valueName, lower, upper)
);
} else if (value > upper) {
throw new IllegalArgumentException(
String.format("%s is out of range of [%d, %d] (too high)", valueName, lower, upper)
);
}
return value;
}
public static int requireNonZero(int value) {
if (value == 0) {
throw new IllegalArgumentException("Value must not be zero");
}
return value;
}
public static int requireNonZero(int value, Object errorMessage) {
if (value == 0) {
throw new IllegalArgumentException(String.valueOf(errorMessage));
}
return value;
}
public static long checkArgumentInRange(long value, long lower, long upper, @NonNull String valueName) {
if (value < lower) {
throw new IllegalArgumentException(
String.format("%s is out of range of [%d, %d] (too low)", valueName, lower, upper)
);
} else if (value > upper) {
throw new IllegalArgumentException(
String.format("%s is out of range of [%d, %d] (too high)", valueName, lower, upper)
);
}
return value;
}
public static long requireNonZero(long value) {
if (value == 0L) {
throw new IllegalArgumentException("Value must not be zero");
}
return value;
}
public static long requireNonZero(long value, @NonNull Object errorMessage) {
if (value == 0L) {
throw new IllegalArgumentException(errorMessage.toString());
}
return value;
}
@NonNull
public static <T> T checkNotNull(@Nullable T reference) {
Objects.requireNonNull(reference);
return reference;
}
@NonNull
public static <T> T checkNotNull(@Nullable T reference, @NonNull Object errorMessage) {
Objects.requireNonNull(reference, String.valueOf(errorMessage));
return reference;
}
@NonNull
public static String checkNotEmpty(@Nullable String string) {
if (TextUtils.isEmpty(string)) {
throw new IllegalArgumentException("String must not be null or empty");
} else {
return string;
}
}
@NonNull
public static String checkNotEmpty(@Nullable String string, @NonNull Object errorMessage) {
if (TextUtils.isEmpty(string)) {
throw new IllegalArgumentException(String.valueOf(errorMessage));
} else {
return string;
}
}
public static void checkArgument(boolean expression) {
if (!expression) {
throw new IllegalArgumentException();
}
}
public static void checkArgument(boolean expression, @NonNull Object errorMessage) {
if (!expression) {
throw new IllegalArgumentException(String.valueOf(errorMessage));
}
}
public static void checkArgument(boolean expression, @NonNull String errorMessage, @NonNull Object... errorMessageArgs) {
if (!expression) {
throw new IllegalArgumentException(String.format(errorMessage, errorMessageArgs));
}
}
public static void checkMainThread() {
checkMainThread("Must be called on the main thread");
}
public static void checkMainThread(@NonNull String errorMessage) {
if (!isOnMainThread()) {
throw new IllegalStateException(errorMessage);
}
}
public static void checkNotGoogleApiHandlerThread() {
checkNotGoogleApiHandlerThread("Must not be called on GoogleApiHandler thread.");
}
public static void checkNotGoogleApiHandlerThread(@NonNull String errorMessage) {
Looper looper = Looper.myLooper();
if (looper != null) {
String threadName = looper.getThread().getName();
if ("GoogleApiHandler".equals(threadName)) {
throw new IllegalStateException(errorMessage);
}
}
}
public static void checkNotMainThread() {
checkNotMainThread("Must not be called on the main thread");
}
public static void checkNotMainThread(@NonNull String errorMessage) {
if (isOnMainThread()) {
throw new IllegalStateException(errorMessage);
}
}
public static void checkState(boolean expression) {
if (!expression) {
throw new IllegalStateException();
}
}
public static void checkState(boolean expression, @NonNull Object errorMessage) {
if (!expression) {
throw new IllegalStateException(String.valueOf(errorMessage));
}
}
public static void checkState(boolean expression, @NonNull String errorMessage, @NonNull Object... errorMessageArgs) {
if (!expression) {
throw new IllegalStateException(String.format(errorMessage, errorMessageArgs));
}
}
private static boolean isOnMainThread() {
return Looper.getMainLooper() == Looper.myLooper();
}
}

View file

@ -0,0 +1,30 @@
package com.google.android.gms.common.internal;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.R;
@Keep
public class StringResourceValueReader {
private final Resources resources;
private final String namespace;
public StringResourceValueReader(@NonNull Context context) {
resources = context.getResources();
namespace = resources.getResourcePackageName(R.integer.google_play_services_version);
}
@SuppressLint("DiscouragedApi")
@Nullable
public String getString(@NonNull String name) {
int resId = resources.getIdentifier(name, "string", namespace);
return resId == 0 ? null : resources.getString(resId);
}
}

View file

@ -0,0 +1,96 @@
package com.google.android.gms.common.stats;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import com.google.android.gms.common.wrappers.Wrappers;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@Keep
public class ConnectionTracker {
private static final String TAG = "ConnectionTracker";
private final Map<ServiceConnection, ServiceConnection> tracked = new ConcurrentHashMap<>();
private ConnectionTracker() {}
private static final class InstanceHolder {
private static final ConnectionTracker instance = new ConnectionTracker();
}
@NonNull
public static ConnectionTracker getInstance() {
return InstanceHolder.instance;
}
public boolean bindService(@NonNull Context context,
@NonNull Intent intent,
@NonNull ServiceConnection conn,
int flags)
{
return bindInternal(context, context.getClass().getName(), intent, conn, flags);
}
public void unbindService(@NonNull Context context, @NonNull ServiceConnection conn) {
ServiceConnection current = tracked.remove(conn);
try {
context.unbindService(Objects.requireNonNullElse(current, conn));
} catch (IllegalArgumentException ignored) {}
}
private boolean bindInternal(@NonNull Context context,
@NonNull String callerName,
@NonNull Intent intent,
@NonNull ServiceConnection conn,
int flags)
{
ComponentName component = intent.getComponent();
if (component != null) {
String pkg = component.getPackageName();
if (isStoppedPackage(context, pkg)) {
Log.w(TAG, "Attempted to bind to a service in a STOPPED package.");
return false;
}
}
ServiceConnection previous = tracked.putIfAbsent(conn, conn);
if (previous != null && conn != previous) {
Log.w(TAG, String.format(
"Duplicate binding with the same ServiceConnection: %s, %s, %s.",
conn, callerName, intent.getAction())
);
}
boolean bound = false;
try {
bound = context.bindService(intent, conn, flags);
} finally {
if (!bound) {
tracked.remove(conn, conn);
}
}
return bound;
}
private static boolean isStoppedPackage(Context context, String packageName) {
try {
ApplicationInfo ai =
Wrappers.packageManager(context).getApplicationInfo(packageName, 0);
return (ai.flags & ApplicationInfo.FLAG_STOPPED) != 0;
} catch (PackageManager.NameNotFoundException ignored) {
return false;
}
}
}

View file

@ -0,0 +1,42 @@
package com.google.android.gms.common.util;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.common.wrappers.Wrappers;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@Keep
public class AndroidUtilsLight {
AndroidUtilsLight() {}
@Deprecated
@Nullable
public static byte[] getPackageCertificateHashBytes(@NonNull Context context, @NonNull String packageName)
throws PackageManager.NameNotFoundException {
PackageInfo pkgInfo =
Wrappers.packageManager(context)
.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
Signature[] signatures = pkgInfo.signatures;
if (signatures != null && signatures.length == 1) {
try {
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
return sha1.digest(signatures[0].toByteArray());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
return null;
}
}

View file

@ -0,0 +1,42 @@
package com.google.android.gms.common.util;
import android.util.Base64;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@Keep
public final class Base64Utils {
private Base64Utils() {}
@NonNull
public static String encode(@NonNull byte[] data) {
return Base64.encodeToString(data, Base64.DEFAULT);
}
@NonNull
public static String encodeUrlSafe(@NonNull byte[] data) {
return Base64.encodeToString(data, Base64.URL_SAFE | Base64.NO_WRAP);
}
@NonNull
public static String encodeUrlSafeNoPadding(@NonNull byte[] data) {
return Base64.encodeToString(data, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
}
@NonNull
public static byte[] decode(@NonNull String encodedData) {
return Base64.decode(encodedData, Base64.DEFAULT);
}
@NonNull
public static byte[] decodeUrlSafe(@NonNull String encodedData) {
return Base64.decode(encodedData, Base64.URL_SAFE | Base64.NO_WRAP);
}
@NonNull
public static byte[] decodeUrlSafeNoPadding(@NonNull String encodedData) {
return Base64.decode(encodedData, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
}
}

View file

@ -0,0 +1,8 @@
package com.google.android.gms.common.util;
import androidx.annotation.Keep;
@Keep
public interface BiConsumer<T, U> {
void accept(T t, U u);
}

View file

@ -0,0 +1,14 @@
package com.google.android.gms.common.util;
import androidx.annotation.Keep;
@Keep
public interface Clock {
long currentTimeMillis();
long nanoTime();
long currentThreadTimeMillis();
long elapsedRealtime();
}

View file

@ -0,0 +1,37 @@
package com.google.android.gms.common.util;
import android.os.SystemClock;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@Keep
public class DefaultClock implements Clock {
private DefaultClock() {}
private static final class InstanceHolder {
private static final DefaultClock instance = new DefaultClock();
}
@NonNull
public static Clock getInstance() {
return InstanceHolder.instance;
}
public final long currentTimeMillis() {
return System.currentTimeMillis();
}
public final long nanoTime() {
return System.nanoTime();
}
public final long currentThreadTimeMillis() {
return SystemClock.currentThreadTimeMillis();
}
public final long elapsedRealtime() {
return SystemClock.elapsedRealtime();
}
}

View file

@ -0,0 +1,50 @@
package com.google.android.gms.common.util;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@Keep
public class Hex {
private Hex() {}
@NonNull
public static String bytesToStringLowercase(@NonNull byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
@NonNull
public static String bytesToStringUppercase(@NonNull byte[] bytes) {
return bytesToStringUppercase(bytes, false);
}
@NonNull
public static String bytesToStringUppercase(@NonNull byte[] bytes, boolean zeroTerminated) {
int len = bytes.length;
StringBuilder sb = new StringBuilder(len * 2);
for (int i = 0; i < len; i++) {
if (zeroTerminated && i == len - 1 && bytes[i] == 0) {
break;
}
sb.append(String.format("%02X", bytes[i]));
}
return sb.toString();
}
@NonNull
public static byte[] stringToBytes(String hex) throws IllegalArgumentException {
int len = hex.length();
if (len % 2 != 0) {
throw new IllegalArgumentException();
}
byte[] bytes = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
bytes[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16);
}
return bytes;
}
}

View file

@ -0,0 +1,123 @@
package com.google.android.gms.common.util;
import android.os.Build.VERSION;
import androidx.annotation.ChecksSdkIntAtLeast;
import androidx.annotation.Keep;
import androidx.core.os.BuildCompat;
@Keep
public final class PlatformVersion {
private PlatformVersion() {}
@ChecksSdkIntAtLeast(api = 11)
public static boolean isAtLeastHoneycomb() {
return true;
}
@ChecksSdkIntAtLeast(api = 12)
public static boolean isAtLeastHoneycombMR1() {
return true;
}
@ChecksSdkIntAtLeast(api = 14)
public static boolean isAtLeastIceCreamSandwich() {
return true;
}
@ChecksSdkIntAtLeast(api = 15)
public static boolean isAtLeastIceCreamSandwichMR1() {
return true;
}
@ChecksSdkIntAtLeast(api = 16)
public static boolean isAtLeastJellyBean() {
return true;
}
@ChecksSdkIntAtLeast(api = 17)
public static boolean isAtLeastJellyBeanMR1() {
return true;
}
@ChecksSdkIntAtLeast(api = 18)
public static boolean isAtLeastJellyBeanMR2() {
return true;
}
@ChecksSdkIntAtLeast(api = 19)
public static boolean isAtLeastKitKat() {
return true;
}
@ChecksSdkIntAtLeast(api = 20)
public static boolean isAtLeastKitKatWatch() {
return true;
}
@ChecksSdkIntAtLeast(api = 21)
public static boolean isAtLeastLollipop() {
return true;
}
@ChecksSdkIntAtLeast(api = 22)
public static boolean isAtLeastLollipopMR1() {
return VERSION.SDK_INT >= 22;
}
@ChecksSdkIntAtLeast(api = 23)
public static boolean isAtLeastM() {
return VERSION.SDK_INT >= 23;
}
@ChecksSdkIntAtLeast(api = 24)
public static boolean isAtLeastN() {
return VERSION.SDK_INT >= 24;
}
@ChecksSdkIntAtLeast(api = 26)
public static boolean isAtLeastO() {
return VERSION.SDK_INT >= 26;
}
@ChecksSdkIntAtLeast(api = 28)
public static boolean isAtLeastP() {
return VERSION.SDK_INT >= 28;
}
@ChecksSdkIntAtLeast(api = 29)
public static boolean isAtLeastQ() {
return VERSION.SDK_INT >= 29;
}
@ChecksSdkIntAtLeast(api = 30)
public static boolean isAtLeastR() {
return VERSION.SDK_INT >= 30;
}
@ChecksSdkIntAtLeast(api = 31)
public static boolean isAtLeastS() {
return VERSION.SDK_INT >= 31;
}
@ChecksSdkIntAtLeast(api = 32)
public static boolean isAtLeastSv2() {
return VERSION.SDK_INT >= 32;
}
@ChecksSdkIntAtLeast(api = 33)
public static boolean isAtLeastT() {
return VERSION.SDK_INT >= 33;
}
@ChecksSdkIntAtLeast(api = 34)
public static boolean isAtLeastU() {
return VERSION.SDK_INT >= 34;
}
@ChecksSdkIntAtLeast(api = 35)
public static boolean isAtLeastV() {
return BuildCompat.isAtLeastV();
}
}

View file

@ -0,0 +1,40 @@
package com.google.android.gms.common.util;
import android.os.Process;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@Keep
public class ProcessUtils {
@Nullable
private static Boolean cachedIsolated;
private ProcessUtils() {}
@NonNull
public static String getMyProcessName() {
return "";
}
public static boolean isIsolatedProcess() {
if (cachedIsolated == null) {
if (PlatformVersion.isAtLeastP()) {
cachedIsolated = Process.isIsolated();
} else {
cachedIsolated = reflectIsIsolated();
}
}
return cachedIsolated;
}
private static Boolean reflectIsIsolated() {
try {
return (Boolean) Process.class.getMethod("isIsolated").invoke(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,21 @@
package com.google.android.gms.common.util;
import android.text.TextUtils;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
@Keep
public class Strings {
private Strings() {}
@Nullable
public static String emptyToNull(String string) {
return TextUtils.isEmpty(string) ? null : string;
}
public static boolean isEmptyOrWhitespace(String string) {
return string == null || string.trim().isEmpty();
}
}

View file

@ -0,0 +1,7 @@
package com.google.android.gms.common.util;
import androidx.annotation.Keep;
@Keep
public @interface VisibleForTesting {
}

View file

@ -0,0 +1,33 @@
package com.google.android.gms.common.util.concurrent;
import android.os.Process;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
@Keep
public class NamedThreadFactory implements ThreadFactory {
private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
private final String name;
public NamedThreadFactory(@NonNull String name) {
this.name = name;
}
@Override
public Thread newThread(Runnable runnable) {
Runnable wrapper = () -> {
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
runnable.run();
};
Thread thread = defaultFactory.newThread(wrapper);
thread.setName(name);
return thread;
}
}

View file

@ -0,0 +1,77 @@
package com.google.android.gms.common.wrappers;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.core.util.Pair;
@Keep
public class PackageManagerWrapper {
private final PackageManager packageManager;
private final Context appContext;
public PackageManagerWrapper(@NonNull Context context) {
this.packageManager = context.getPackageManager();
this.appContext = context.getApplicationContext();
}
public int checkCallingOrSelfPermission(@NonNull String permission) {
return appContext.checkCallingOrSelfPermission(permission);
}
public int checkPermission(@NonNull String permission, @NonNull String packageName) {
return packageManager.checkPermission(permission, packageName);
}
public boolean isCallerInstantApp() {
return false;
}
public boolean hasSystemFeature(@NonNull String featureName) {
return packageManager.hasSystemFeature(featureName);
}
@NonNull
public ApplicationInfo getApplicationInfo(@NonNull String packageName, int flags)
throws PackageManager.NameNotFoundException
{
return packageManager.getApplicationInfo(packageName, flags);
}
@NonNull
public PackageInfo getPackageInfo(@NonNull String packageName, int flags)
throws PackageManager.NameNotFoundException
{
return packageManager.getPackageInfo(packageName, flags);
}
@NonNull
public Pair<CharSequence, Drawable> getApplicationLabelAndIcon(@NonNull String packageName)
throws PackageManager.NameNotFoundException
{
ApplicationInfo info = getApplicationInfo(packageName, 0);
CharSequence label = packageManager.getApplicationLabel(info);
Drawable icon = packageManager.getApplicationIcon(info);
return Pair.create(label, icon);
}
@NonNull
public CharSequence getApplicationLabel(@NonNull String packageName)
throws PackageManager.NameNotFoundException
{
ApplicationInfo info = getApplicationInfo(packageName, 0);
return packageManager.getApplicationLabel(info);
}
@NonNull
public PackageInstaller getPackageInstaller() {
return packageManager.getPackageInstaller();
}
}

View file

@ -0,0 +1,28 @@
package com.google.android.gms.common.wrappers;
import android.content.Context;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@Keep
public class Wrappers {
private static volatile PackageManagerWrapper packageManagerWrapper;
private Wrappers() {}
public static PackageManagerWrapper packageManager(@NonNull Context context) {
PackageManagerWrapper wrapper = packageManagerWrapper;
if (wrapper == null) {
synchronized (Wrappers.class) {
wrapper = packageManagerWrapper;
if (wrapper == null) {
wrapper = new PackageManagerWrapper(context);
packageManagerWrapper = wrapper;
}
}
}
return wrapper;
}
}

View file

@ -0,0 +1,28 @@
package com.google.android.gms.security;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException;
@Keep
public class ProviderInstaller {
public static void installIfNeeded(Context context) throws GooglePlayServicesRepairableException, GooglePlayServicesNotAvailableException {
// NO-OP
}
public static void installIfNeededAsync(Context context, ProviderInstaller.ProviderInstallListener listener) {
// NO-OP
}
public interface ProviderInstallListener {
void onProviderInstallFailed(int errorCode, @Nullable Intent recoveryIntent);
void onProviderInstalled();
}
}

View file

@ -0,0 +1,38 @@
package com.google.android.gms.stats;
import android.content.Context;
import android.os.PowerManager;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@Keep
public class WakeLock {
private final PowerManager.WakeLock pmWakeLock;
public WakeLock(@NonNull Context context, int levelAndFlags, @NonNull String wakeLockName) {
this.pmWakeLock = createWakeLock(context.getApplicationContext(), levelAndFlags, wakeLockName);
}
private static PowerManager.WakeLock createWakeLock(@NonNull Context context, int levelAndFlags, @NonNull String tag) {
final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
return pm.newWakeLock(levelAndFlags, tag);
}
public void setReferenceCounted(boolean value) {
pmWakeLock.setReferenceCounted(value);
}
public void acquire(long timeoutMillis) {
pmWakeLock.acquire(timeoutMillis);
}
public void release() {
pmWakeLock.release();
}
public boolean isHeld() {
return pmWakeLock.isHeld();
}
}

View file

@ -0,0 +1,26 @@
plugins {
id("signal-library")
}
android {
namespace = "com.google.android.gms.cloudmessaging"
buildFeatures {
buildConfig = true
}
defaultConfig {
buildConfigField("boolean", "NOTIFICATION_PAYLOAD_ENABLED", "false")
}
lint {
disable += setOf("LogNotSignal")
}
}
dependencies {
api(project(":core-gms:base"))
api(project(":core-gms:safeparcel"))
api(project(":core-gms:tasks"))
annotationProcessor(project(":core-gms:safeparcel-processor"))
}

View file

@ -0,0 +1,204 @@
package com.google.android.gms.cloudmessaging;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import androidx.annotation.IntDef;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.ArrayMap;
import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable;
import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Class;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
@Keep
@Class(creator = "CloudMessageCreator")
public final class CloudMessage extends AbstractSafeParcelable {
private static final String TAG = "CloudMessage";
public static final int PRIORITY_UNKNOWN = 0;
public static final int PRIORITY_HIGH = 1;
public static final int PRIORITY_NORMAL = 2;
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
PRIORITY_UNKNOWN,
PRIORITY_HIGH,
PRIORITY_NORMAL,
})
public @interface MessagePriority {
}
public static final Parcelable.Creator<CloudMessage> CREATOR = new CloudMessageCreator();
@Field(id = 1) final Intent intent;
private Map<String, String> dataCache;
@Constructor
public CloudMessage(@NonNull @Param(id = 1) Intent intent) {
this.intent = intent;
}
@Nullable
public String getCollapseKey() {
return intent.getStringExtra("collapse_key");
}
@NonNull
public synchronized Map<String, String> getData() {
if (dataCache == null) {
dataCache = parseExtrasToMap(intent.getExtras());
}
return dataCache;
}
@Nullable
public String getFrom() {
return intent.getStringExtra("from");
}
@NonNull
public Intent getIntent() {
return intent;
}
@Nullable
public String getMessageId() {
String msgId = intent.getStringExtra("google.message_id");
return msgId == null ? intent.getStringExtra("message_id") : msgId;
}
@Nullable
public String getMessageType() {
return intent.getStringExtra("message_type");
}
@MessagePriority
public int getOriginalPriority() {
String p = intent.getStringExtra("google.original_priority");
if (p == null) {
p = intent.getStringExtra("google.priority");
}
return parsePriority(p);
}
@MessagePriority
public int getPriority() {
String p = intent.getStringExtra("google.delivered_priority");
if (p == null) {
if ("1".equals(intent.getStringExtra("google.priority_reduced"))) {
return PRIORITY_NORMAL;
}
p = intent.getStringExtra("google.priority");
}
return parsePriority(p);
}
@Nullable
public byte[] getRawData() {
return intent.getByteArrayExtra("rawData");
}
@Nullable
public String getSenderId() {
return intent.getStringExtra("google.c.sender.id");
}
public long getSentTime() {
Bundle extras = intent.getExtras();
if (extras == null) {
return 0;
}
Object raw = extras.get("google.sent_time");
if (raw instanceof Long) {
return (Long) raw;
}
if (raw instanceof String) {
try {
return Long.parseLong((String) raw);
} catch (NumberFormatException e) {
Log.w(TAG, "Invalid sent time: " + raw);
}
}
return 0;
}
@Nullable
public String getTo() {
return intent.getStringExtra("google.to");
}
public int getTtl() {
Bundle extras = intent.getExtras();
if (extras == null) {
return 0;
}
Object raw = extras.get("google.ttl");
if (raw instanceof Integer) {
return (Integer) raw;
}
if (raw instanceof String) {
try {
return Integer.parseInt((String) raw);
} catch (NumberFormatException e) {
Log.w(TAG, "Invalid TTL: " + raw);
}
}
return 0;
}
@Nullable
Integer getProductId() {
if (!intent.hasExtra("google.product_id")) {
return null;
}
return intent.getIntExtra("google.product_id", 0);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
CloudMessageCreator.writeToParcel(this, dest, flags);
}
@MessagePriority
private static int parsePriority(@Nullable String p) {
if ("high".equals(p)) {
return PRIORITY_HIGH;
} else if ("normal".equals(p)) {
return PRIORITY_NORMAL;
} else {
return PRIORITY_UNKNOWN;
}
}
@NonNull
private static Map<String, String> parseExtrasToMap(@Nullable Bundle extras) {
ArrayMap<String, String> map = new ArrayMap<>();
if (extras == null) {
return map;
}
for (String key : extras.keySet()) {
Object value = extras.get(key);
if (!(value instanceof String)
|| key.startsWith("google.")
|| key.equals("from")
|| key.equals("message_type")
|| key.equals("collapse_key"))
{
continue;
}
map.put(key, (String) value);
}
return map;
}
}

View file

@ -0,0 +1,23 @@
package com.google.android.gms.cloudmessaging;
import android.os.Bundle;
import androidx.annotation.NonNull;
public class CloudMessageBundle {
private CloudMessageBundle() {}
@NonNull
public static Bundle forMessage(@NonNull CloudMessage message) {
Bundle bundle = new Bundle();
bundle.putString("google.message_id", message.getMessageId());
Integer productId = message.getProductId();
if (productId != null) {
bundle.putInt("google.product_id", productId);
}
return bundle;
}
}

View file

@ -0,0 +1,14 @@
package com.google.android.gms.cloudmessaging;
import androidx.annotation.Nullable;
final class CloudMessagingException extends Exception {
public CloudMessagingException(@Nullable String message, @Nullable Throwable cause) {
super(message, cause);
}
public CloudMessagingException(@Nullable String message) {
super(message);
}
}

View file

@ -0,0 +1,173 @@
package com.google.android.gms.cloudmessaging;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.google.android.gms.common.util.concurrent.NamedThreadFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Keep
public abstract class CloudMessagingReceiver extends BroadcastReceiver {
private static final String TAG = "CloudMessagingReceiver";
public static final class IntentActionKeys {
private static final String NOTIFICATION_OPEN = "com.google.firebase.messaging.NOTIFICATION_OPEN";
private static final String NOTIFICATION_DISMISS = "com.google.firebase.messaging.NOTIFICATION_DISMISS";
}
public static final class IntentKeys {
private static final String PENDING_INTENT = "pending_intent";
private static final String WRAPPED_INTENT = "wrapped_intent";
}
private static ThreadPoolExecutor singleThreadExecutor(String threadName) {
return new ThreadPoolExecutor(
0, 1, 30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new NamedThreadFactory(threadName)
);
}
private static final class ExecutorsHolder {
static final ThreadPoolExecutor dispatchExecutor
= singleThreadExecutor("firebase-iid-executor");
static final ThreadPoolExecutor ackExecutor
= singleThreadExecutor("pscm-ack-executor");
}
@Override
public final void onReceive(Context context, Intent intent) {
if (intent != null) {
BroadcastReceiver.PendingResult pendingResult = goAsync();
getBroadcastExecutor().execute(
() -> handleIntent(intent, context, isOrderedBroadcast(), pendingResult)
);
}
}
@NonNull
protected Executor getBroadcastExecutor() {
return ExecutorsHolder.dispatchExecutor;
}
@NonNull
private Executor getAckExecutor() {
return ExecutorsHolder.ackExecutor;
}
@WorkerThread
protected abstract int onMessageReceive(@NonNull Context context, @NonNull CloudMessage message);
@WorkerThread
protected void onNotificationDismissed(@NonNull Context context, @NonNull Bundle data) {}
@WorkerThread
private void handleIntent(@NonNull Intent intent,
@NonNull Context context,
boolean isOrdered,
BroadcastReceiver.PendingResult pendingResult)
{
int resultCode = 500;
Parcelable wrappedIntent = intent.getParcelableExtra(IntentKeys.WRAPPED_INTENT);
try {
if (wrappedIntent instanceof Intent) {
if (BuildConfig.NOTIFICATION_PAYLOAD_ENABLED) {
resultCode = deliverDismissNotificationAction((Intent) wrappedIntent, context);
}
} else {
if (intent.getExtras() != null) {
resultCode = deliverCloudMessage(intent, context);
}
}
} finally {
if (pendingResult != null) {
if (isOrdered) {
pendingResult.setResultCode(resultCode);
}
pendingResult.finish();
}
}
}
@WorkerThread
private int deliverCloudMessage(@NonNull Intent intent, @NonNull Context context) {
CloudMessage message = new CloudMessage(intent);
CountDownLatch ackLatch = new CountDownLatch(1);
getAckExecutor().execute(() -> {
if (TextUtils.isEmpty(message.getMessageId())) {
ackLatch.countDown();
return;
}
Bundle data = CloudMessageBundle.forMessage(message);
data.putBoolean("supports_message_handled", true);
MessengerIpcClient
.getInstance(context)
.sendOneWay(GmsOpCode.BROADCAST_ACK, data)
.addOnCompleteListener(
Runnable::run, t -> ackLatch.countDown()
);
});
int result = onMessageReceive(context, message);
try {
boolean acked = ackLatch.await(1, TimeUnit.SECONDS);
if (!acked) {
Log.w(TAG, "Message ack timed out");
}
} catch (InterruptedException e) {
Log.w(TAG, "Message ack failed: " + e);
Thread.currentThread().interrupt();
}
return result;
}
@WorkerThread
private int deliverDismissNotificationAction(@NonNull Intent intent, @NonNull Context context) {
PendingIntent pendingIntent = intent.getParcelableExtra(IntentKeys.PENDING_INTENT);
if (pendingIntent != null) {
try {
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Notification pending intent canceled");
}
}
if (!IntentActionKeys.NOTIFICATION_DISMISS.equals(intent.getAction())) {
return 500;
}
Bundle extras = intent.getExtras();
if (extras != null) {
extras.remove(IntentKeys.PENDING_INTENT);
} else {
extras = new Bundle();
}
onNotificationDismissed(context, extras);
return -1;
}
}

View file

@ -0,0 +1,21 @@
package com.google.android.gms.cloudmessaging;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
final class GmsOpCode {
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = { SEND, BROADCAST_ACK, RPC_ACK, PROXY_RETAIN, PROXY_FETCH })
@interface Code {}
static final int SEND = 1;
static final int BROADCAST_ACK = 2;
static final int RPC_ACK = 3;
static final int PROXY_RETAIN = 4;
static final int PROXY_FETCH = 5;
private GmsOpCode() {}
}

View file

@ -0,0 +1,373 @@
package com.google.android.gms.cloudmessaging;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.stats.ConnectionTracker;
import com.google.android.gms.common.util.concurrent.NamedThreadFactory;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.TaskCompletionSource;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public final class MessengerIpcClient {
private static final String TAG = "MessengerIpcClient";
private static final String ACTION_REGISTER = "com.google.android.c2dm.intent.REGISTER";
private static final long TIMEOUT_SECONDS = 30L;
@Nullable
private static volatile MessengerIpcClient instance;
private final Context context;
private final ScheduledExecutorService executor;
private final AtomicInteger nextRequestId;
private Connection connection;
private MessengerIpcClient(Context context, ScheduledExecutorService executor) {
this.context = context.getApplicationContext();
this.executor = executor;
this.nextRequestId = new AtomicInteger(1);
this.connection = new Connection();
}
public static MessengerIpcClient getInstance(Context context) {
if (instance == null) {
synchronized (MessengerIpcClient.class) {
if (instance == null) {
instance = new MessengerIpcClient(
context,
Executors.unconfigurableScheduledExecutorService(
Executors.newScheduledThreadPool(1, new NamedThreadFactory(TAG))
)
);
}
}
}
return instance;
}
public Task<Bundle> sendRequest(@GmsOpCode.Code int what, Bundle data) {
return enqueue(new TwoWayRequest(nextRequestId(), what, data));
}
public Task<Void> sendOneWay(@GmsOpCode.Code int what, Bundle data) {
return enqueue(new OneWayRequest(nextRequestId(), what, data));
}
private <T> Task<T> enqueue(Request<T> request) {
synchronized (this) {
if (!connection.enqueueRequest(request)) {
connection = new Connection();
connection.enqueueRequest(request);
}
}
return request.completion.getTask();
}
private int nextRequestId() {
return nextRequestId.getAndIncrement();
}
private class Connection implements ServiceConnection {
private enum State { IDLE, BINDING, BOUND, UNBINDING, DEAD }
private State state = State.IDLE;
private final Messenger replyMessenger;
private Messenger serviceMessenger;
private final ConnectionTracker connectionTracker = ConnectionTracker.getInstance();
private final Queue<Request<?>> pendingRequests = new ArrayDeque<>();
private final SparseArray<Request<?>> activeRequests = new SparseArray<>();
private Connection() {
replyMessenger = new Messenger(
new Handler(Looper.getMainLooper(), this::handleReply)
);
}
synchronized boolean enqueueRequest(Request<?> request) {
switch (state) {
case IDLE:
state = State.BINDING;
pendingRequests.add(request);
bindService();
break;
case BINDING:
pendingRequests.add(request);
break;
case BOUND:
pendingRequests.add(request);
sendPendingRequests();
break;
default:
return false;
}
return true;
}
private void bindService() {
Log.v(TAG, "Starting bind to GmsCore");
final Intent intent = new Intent(ACTION_REGISTER);
intent.setPackage(GoogleApiAvailability.GOOGLE_PLAY_SERVICES_PACKAGE);
try {
boolean bound = connectionTracker.bindService(context, intent, this, Context.BIND_AUTO_CREATE);
if (bound) {
executor.schedule(this::onBindTimeout, TIMEOUT_SECONDS, TimeUnit.SECONDS);
} else {
handleDisconnection("Unable to bind to service");
}
} catch (SecurityException e) {
handleDisconnection("Unable to bind to service", e);
}
}
@MainThread
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
Log.v(TAG, "Service connected");
executor.execute(() -> handleBound(binder));
}
@MainThread
@Override
public void onServiceDisconnected(ComponentName name) {
Log.v(TAG, "Service disconnected");
executor.execute(() -> handleDisconnection("Service disconnected"));
}
private synchronized void onBindTimeout() {
if (state == State.BINDING) {
handleDisconnection("Timed out while binding");
}
}
private synchronized void handleBound(IBinder binder) {
try {
if (!"android.os.IMessenger".equals(binder.getInterfaceDescriptor())) {
throw new RemoteException("Invalid interface descriptor");
}
serviceMessenger = new Messenger(binder);
state = State.BOUND;
sendPendingRequests();
} catch (RemoteException e) {
handleDisconnection("Service connection failed", e);
}
}
private synchronized void handleDisconnection(@Nullable String reason) {
handleDisconnection(reason, null);
}
private synchronized void handleDisconnection(@Nullable String reason, @Nullable Throwable cause) {
switch (state) {
case IDLE:
throw new IllegalStateException();
case BINDING:
case BOUND:
state = State.DEAD;
connectionTracker.unbindService(context, this);
failAndClearAll(reason, cause);
break;
case UNBINDING:
state = State.DEAD;
break;
}
}
private void failAndClearAll(@Nullable String reason, @Nullable Throwable cause) {
final CloudMessagingException e = new CloudMessagingException(reason, cause);
for (Request<?> pendingRequest : pendingRequests) {
pendingRequest.fail(e);
}
pendingRequests.clear();
for (int i = 0; i < activeRequests.size(); i++) {
activeRequests.valueAt(i).fail(e);
}
activeRequests.clear();
}
private void sendPendingRequests() {
executor.execute(() -> {
while (true) {
Request<?> request;
synchronized (this) {
if (state != State.BOUND) {
return;
}
request = pendingRequests.poll();
if (request == null) {
maybeUnbind();
return;
}
activeRequests.put(request.id, request);
executor.schedule(() -> timeoutRequest(request.id), TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
Message msg = Message.obtain();
msg.what = request.what;
msg.arg1 = request.id;
msg.replyTo = replyMessenger;
Bundle data = new Bundle();
data.putBoolean("oneWay", request.isOneWay());
data.putString("pkg", context.getPackageName());
data.putBundle("data", request.payload);
msg.setData(data);
try {
serviceMessenger.send(msg);
} catch (RemoteException e) {
handleDisconnection(e.getMessage());
}
}
});
}
private synchronized void timeoutRequest(int requestId) {
Request<?> r = activeRequests.get(requestId);
if (r != null) {
Log.w(TAG, "Timing out request: " + requestId);
activeRequests.remove(requestId);
r.fail(new CloudMessagingException("Timed out waiting for response"));
maybeUnbind();
}
}
private boolean handleReply(Message msg) {
final Request<?> request;
synchronized (this) {
int requestId = msg.arg1;
request = activeRequests.get(requestId);
if (request == null) {
Log.w(TAG, "Unknown request response: " + requestId);
return true;
}
activeRequests.remove(requestId);
maybeUnbind();
}
Bundle data = msg.getData();
if (data.getBoolean("unsupported", false)) {
request.fail(new CloudMessagingException("Not supported by GmsCore"));
} else {
request.onResponse(data);
}
return true;
}
private synchronized void maybeUnbind() {
if (state == State.BOUND && activeRequests.size() == 0 && pendingRequests.isEmpty()) {
Log.v(TAG, "Finished handling requests, unbinding");
state = State.UNBINDING;
connectionTracker.unbindService(context, this);
}
}
}
abstract static class Request<T> {
final int id;
final int what;
final Bundle payload;
final TaskCompletionSource<T> completion = new TaskCompletionSource<>();
Request(int id, int what, Bundle payload) {
this.id = id;
this.what = what;
this.payload = payload;
}
final void fail(CloudMessagingException e) {
completion.setException(e);
}
final void complete(T result) {
completion.setResult(result);
}
abstract boolean isOneWay();
abstract void onResponse(Bundle response);
@Override
@NonNull
public String toString() {
return "Request { what=" + what + " id=" + id + " oneWay=" + isOneWay() + "}";
}
}
static final class OneWayRequest extends Request<Void> {
OneWayRequest(int id, int what, Bundle payload) {
super(id, what, payload);
}
@Override
boolean isOneWay() {
return true;
}
@Override
void onResponse(Bundle response) {
if (response.getBoolean("ack", false)) {
complete(null);
} else {
fail(new CloudMessagingException("Invalid response to one way request"));
}
}
}
static final class TwoWayRequest extends Request<Bundle> {
TwoWayRequest(int id, int what, Bundle payload) {
super(id, what, payload);
}
@Override
boolean isOneWay() {
return false;
}
@Override
void onResponse(Bundle response) {
Bundle data = response.getBundle("data");
if (data == null) {
data = Bundle.EMPTY;
}
complete(data);
}
}
}

View file

@ -0,0 +1,37 @@
package com.google.android.gms.cloudmessaging;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import com.google.android.gms.common.wrappers.PackageManagerWrapper;
import com.google.android.gms.common.wrappers.Wrappers;
import static com.google.android.gms.common.GoogleApiAvailabilityLight.GOOGLE_PLAY_SERVICES_PACKAGE;
final class Metadata {
private static final String TAG = "Metadata";
private final PackageManagerWrapper packageManager;
private Integer gmsPackageVersion;
public Metadata(Context context) {
this.packageManager = Wrappers.packageManager(context);
}
public int getGmsPackageVersion() {
if (gmsPackageVersion == null) {
try {
PackageInfo packageInfo = packageManager.getPackageInfo(GOOGLE_PLAY_SERVICES_PACKAGE, 0);
gmsPackageVersion = packageInfo.versionCode;
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Failed to find package: " + e);
return -1;
}
}
return gmsPackageVersion;
}
}

View file

@ -0,0 +1,96 @@
package com.google.android.gms.cloudmessaging;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import java.io.IOException;
@Keep
public class Rpc {
private static final String TAG = "Rpc";
private static final String SERVICE_NOT_AVAILABLE = "SERVICE_NOT_AVAILABLE";
private static final int MIN_VERSION_FOR_MESSAGE_ACK = 233700000;
private static final int MIN_VERSION_FOR_PROXY_RETENTION = 241100000;
private final MessengerIpcClient ipcClient;
private final Metadata metadata;
public Rpc(@NonNull Context context) {
this.ipcClient = MessengerIpcClient.getInstance(context);
this.metadata = new Metadata(context);
}
@NonNull
public Task<Bundle> send(@NonNull Bundle data) {
return ipcClient
.sendRequest(GmsOpCode.SEND, data)
.continueWith(Runnable::run, task -> {
if (task.isSuccessful()) {
return task.getResult();
} else {
Exception e = task.getException();
Log.d(TAG, "Error making request: " + e);
throw new IOException(SERVICE_NOT_AVAILABLE, e);
}
});
}
@NonNull
public Task<Void> messageHandled(@NonNull CloudMessage message) {
if (metadata.getGmsPackageVersion() < MIN_VERSION_FOR_MESSAGE_ACK) {
return serviceNotAvailable();
}
Bundle response = CloudMessageBundle.forMessage(message);
return ipcClient.sendOneWay(GmsOpCode.RPC_ACK, response);
}
@NonNull
public Task<Void> setRetainProxiedNotifications(boolean retain) {
if (!BuildConfig.NOTIFICATION_PAYLOAD_ENABLED) {
return serviceNotAvailable();
}
if (metadata.getGmsPackageVersion() < MIN_VERSION_FOR_PROXY_RETENTION) {
return serviceNotAvailable();
}
Bundle bundle = new Bundle();
bundle.putBoolean("proxy_retention", retain);
return ipcClient.sendOneWay(GmsOpCode.PROXY_RETAIN, bundle);
}
@NonNull
public Task<CloudMessage> getProxiedNotificationData() {
if (!BuildConfig.NOTIFICATION_PAYLOAD_ENABLED) {
return serviceNotAvailable();
}
if (metadata.getGmsPackageVersion() < MIN_VERSION_FOR_PROXY_RETENTION) {
return serviceNotAvailable();
}
return ipcClient
.sendRequest(GmsOpCode.PROXY_FETCH, Bundle.EMPTY)
.continueWith(Runnable::run, task -> {
Bundle bundle = task.getResult();
Parcelable intent = bundle.getParcelable("notification_data");
if (!(intent instanceof Intent)) {
return null;
}
return new CloudMessage((Intent) intent);
});
}
@NonNull
private static <T> Task<T> serviceNotAvailable() {
return Tasks.forException(new IOException(SERVICE_NOT_AVAILABLE));
}
}

View file

@ -0,0 +1,18 @@
package com.google.firebase;
import androidx.annotation.Keep;
@Keep
public class FirebaseException extends Exception {
@Deprecated
protected FirebaseException() {}
public FirebaseException(String message) {
super(message);
}
public FirebaseException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,29 @@
package com.google.firebase.iid.internal;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.tasks.Task;
import java.io.IOException;
@Keep
public interface FirebaseInstanceIdInternal {
void deleteToken(@NonNull String senderId, @NonNull String scope) throws IOException;
String getId();
@Nullable
String getToken();
@NonNull
Task<String> getTokenTask();
void addNewTokenListener(NewTokenListener listener);
interface NewTokenListener {
void onNewToken(String token);
}
}

View file

@ -0,0 +1,12 @@
plugins {
java
}
dependencies {
implementation(libs.androidx.annotation)
implementation(libs.jsilver)
}
tasks.jar {
exclude("com.google.android.gms.common.internal.safeparcel/SafeParcelable.class")
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.gms.common.internal.safeparcel;
/**
* This is here for the SafeParcelProcessor to link against and is intentionally not implementing a
* Parcelable, so that it is not necessary to link in the Android framework to compile this.
*
* @hide
*/
public interface SafeParcelable {
String NULL = "SAFE_PARCELABLE_NULL_STRING";
@interface Class {
String creator();
boolean creatorIsFinal() default true;
boolean validate() default false;
boolean doNotParcelTypeDefaultValues() default false;
}
@interface Field {
int id();
String getter() default NULL;
String type() default NULL;
String defaultValue() default NULL;
String defaultValueUnchecked() default NULL;
}
@interface VersionField {
int id();
String getter() default NULL;
String type() default NULL;
}
@interface Indicator {
String getter() default NULL;
}
@interface Constructor {}
@interface Param {
int id();
}
@interface RemovedParam {
int id();
String defaultValue() default NULL;
String defaultValueUnchecked() default NULL;
}
@interface Reserved {
int[] value();
}
}

View file

@ -0,0 +1 @@
androidx.safeparcel.processor.SafeParcelProcessor,isolating

View file

@ -0,0 +1 @@
androidx.safeparcel.processor.SafeParcelProcessor

View file

@ -0,0 +1,128 @@
// THIS FILE IS AUTOGENERATED. DO NOT MODIFY.
<?cs if:creator_package ?>
package <?cs var:creator_package ?>;
<?cs /if ?>
/**
* @hide
*/
@SuppressWarnings({"unchecked", "rawtypes", "Var", "WrongConstant"})
<?cs each:annotation=annotations ?>
<?cs var:annotation ?>
<?cs /each ?>
public <?cs if:creatorIsFinal ?>final<?cs /if ?> class <?cs var:creator_name ?> implements android.os.Parcelable.Creator< <?cs var:class ?> > {
public static final int CONTENT_DESCRIPTION = 0;
public <?cs var:creator_name ?>() {
}
@Override
public <?cs var:class ?> createFromParcel(final android.os.Parcel parcel) {
final int end = com.google.android.gms.common.internal.safeparcel.SafeParcelReader.validateObjectHeader(parcel);
<?cs each:declaration=declarations ?>
<?cs var:declaration.type ?> <?cs var:declaration.var_name ?> = <?cs var:declaration.initial_value ?>;
<?cs /each ?>
while (parcel.dataPosition() < end) {
final int header = com.google.android.gms.common.internal.safeparcel.SafeParcelReader.readHeader(parcel);
switch (com.google.android.gms.common.internal.safeparcel.SafeParcelReader.getFieldId(header)) {
<?cs each:field=fields ?>
case <?cs var:field.id ?>:
<?cs if:field.is_assignment ?>
<?cs var:field.read_name ?>
= com.google.android.gms.common.internal.safeparcel.SafeParcelReader.<?cs var:field.create ?>(
parcel, header
<?cs if:field.creator ?>, <?cs var:field.creator ?><?cs /if ?>
);
<?cs else ?>
com.google.android.gms.common.internal.safeparcel.SafeParcelReader.<?cs var:field.create ?>(
parcel, header, <?cs var:field.read_name ?>
<?cs if:field.creator ?>, <?cs var:field.creator ?><?cs /if ?>
);
<?cs /if ?>
break;
<?cs /each ?>
default:
com.google.android.gms.common.internal.safeparcel.SafeParcelReader.skipUnknownField(parcel, header);
break;
}
}
com.google.android.gms.common.internal.safeparcel.SafeParcelReader.ensureAtEnd(parcel, end);
final <?cs var:class ?> obj = new <?cs var:class ?>(<?cs var:params ?>);
<?cs if:call_validateContents ?>
obj.validateContents();
<?cs /if ?>
return obj;
}
@Override
public <?cs var:class ?>[] newArray(int size) {
return new <?cs var:class ?>[size];
}
static void writeToParcel(<?cs var:class ?> obj, android.os.Parcel parcel, int flags) {
int myStart = com.google.android.gms.common.internal.safeparcel.SafeParcelWriter.beginObjectHeader(parcel);
<?cs each:field=fields ?>
<?cs if:field.write ?>
<?cs if:doNotParcelTypeDefaultValues ?>
if (!isDefault(obj.<?cs var:field.write_name ?>)) {
<?cs /if ?>
com.google.android.gms.common.internal.safeparcel.SafeParcelWriter.<?cs var:field.write ?>(parcel
, <?cs var:field.id ?>
, obj.<?cs var:field.write_name ?>
<?cs if:field.writeWithFlags ?>, flags<?cs /if ?>
<?cs if:field.hasWriteNull ?>, false<?cs /if ?>
);
<?cs if:doNotParcelTypeDefaultValues ?>
}
<?cs /if ?>
<?cs /if ?>
<?cs /each ?>
com.google.android.gms.common.internal.safeparcel.SafeParcelWriter.finishObjectHeader(parcel, myStart);
}
<?cs if:doNotParcelTypeDefaultValues ?>
public static boolean isDefault(boolean value) {
return !value;
}
public static boolean isDefault(byte value) {
return value == 0;
}
public static boolean isDefault(char value) {
return value == 0;
}
public static boolean isDefault(short value) {
return value == 0;
}
public static boolean isDefault(int value) {
return value == 0;
}
public static boolean isDefault(long value) {
return value == 0L;
}
public static boolean isDefault(float value) {
return value == 0.0f;
}
public static boolean isDefault(double value) {
return value == 0.0;
}
public static boolean isDefault(Object value) {
return value == null;
}
<?cs /if ?>
}

View file

@ -0,0 +1,96 @@
// THIS FILE IS AUTOGENERATED. DO NOT MODIFY.
<?cs if:creator_package ?>
package <?cs var:creator_package ?>;
<?cs /if ?>
/**
* @hide
*/
@SuppressWarnings({"unchecked", "rawtypes", "Var", "WrongConstant"})
<?cs each:annotation=annotations ?>
<?cs var:annotation ?>
<?cs /each ?>
public <?cs if:creatorIsFinal ?>final<?cs /if ?> class <?cs var:creator_name ?> implements android.os.Parcelable.Creator< <?cs var:class ?> > {
public static final int CONTENT_DESCRIPTION = 0;
public <?cs var:creator_name ?>() {
}
@Override
public <?cs var:class ?> createFromParcel(final android.os.Parcel parcel) {
final int end = com.google.android.gms.common.internal.safeparcel.SafeParcelReader.validateObjectHeader(parcel);
<?cs each:declaration=declarations ?><?cs
if:declaration.var_name == indicator.read_name ?>
<?cs var:declaration.type ?> <?cs var:declaration.var_name ?> = new java.util.HashSet<Integer>();<?cs
else ?>
<?cs var:declaration.type ?> <?cs var:declaration.var_name ?> = <?cs var:declaration.initial_value ?>;<?cs
/if ?>
<?cs /each ?>
while (parcel.dataPosition() < end) {
final int header = com.google.android.gms.common.internal.safeparcel.SafeParcelReader.readHeader(parcel);
switch (com.google.android.gms.common.internal.safeparcel.SafeParcelReader.getFieldId(header)) {
<?cs each:field=fields ?>
case <?cs var:field.id ?>:
<?cs if:field.is_assignment ?>
<?cs var:field.read_name ?>
= com.google.android.gms.common.internal.safeparcel.SafeParcelReader.<?cs var:field.create ?>(
parcel, header
<?cs if:field.creator ?>, <?cs var:field.creator ?><?cs /if ?>
);
<?cs else ?>
com.google.android.gms.common.internal.safeparcel.SafeParcelReader.<?cs var:field.create ?>(
parcel, header, <?cs var:field.read_name ?>
<?cs if:field.creator ?>, <?cs var:field.creator ?><?cs /if ?>
);
<?cs /if ?>
<?cs var:indicator.read_name ?>.add(<?cs var:field.id ?>);
break;
<?cs /each ?>
default:
com.google.android.gms.common.internal.safeparcel.SafeParcelReader.skipUnknownField(parcel, header);
break;
}
}
if (parcel.dataPosition() != end) {
throw new com.google.android.gms.common.internal.safeparcel.SafeParcelReader.ParseException("Overread allowed size end=" + end, parcel);
}
final <?cs var:class ?> obj = new <?cs var:class ?>(<?cs var:params ?>);
<?cs if:call_validateContents ?>
obj.validateContents();
<?cs /if ?>
return obj;
}
@Override
public <?cs var:class ?>[] newArray(int size) {
return new <?cs var:class ?>[size];
}
static void writeToParcel(<?cs var:class ?> obj, android.os.Parcel parcel, int flags) {
int myStart = com.google.android.gms.common.internal.safeparcel.SafeParcelWriter.beginObjectHeader(parcel);
java.util.Set<Integer> __setFields = obj.<?cs var:indicator.write_name ?>;
<?cs each:field=fields ?>
<?cs if:field.write ?>
if (__setFields.contains(<?cs var:field.id ?>)) {
com.google.android.gms.common.internal.safeparcel.SafeParcelWriter.<?cs var:field.write ?>(parcel
, <?cs var:field.id ?>
, obj.<?cs var:field.write_name ?>
<?cs if:field.writeWithFlags ?>, flags<?cs /if ?>
<?cs if:field.hasWriteNull ?>, true<?cs /if ?>
);
}
<?cs /if ?>
<?cs /each ?>
com.google.android.gms.common.internal.safeparcel.SafeParcelWriter.finishObjectHeader(parcel, myStart);
}
}

View file

@ -0,0 +1,7 @@
plugins {
id("signal-library")
}
android {
namespace = "com.google.android.gms.common.internal.safeparcel"
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.gms.common.internal.safeparcel;
/**
* Implements {@link SafeParcelable} and implements some default methods defined by {@link
* android.os.Parcelable}.
*
* @hide
*/
public abstract class AbstractSafeParcelable implements SafeParcelable {
/** @hide */
@Override
public final int describeContents() {
return 0;
}
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.gms.common.internal.safeparcel;
import android.os.Parcelable;
/**
* Interface for Parcelables that have the class name reflectively read as part of serialization.
* This happens when when put into an Intent or Bundle, or in some Parcel write methods.
*
* <p>This interface is needed because the errorprone checker has some limitations on detecting
* annotations (like {@code @KeepName}), where detecting inheritance is easier.
*
* @hide
*/
public interface ReflectedParcelable extends Parcelable {}

View file

@ -0,0 +1,930 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.gms.common.internal.safeparcel;
import android.app.PendingIntent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
/**
* Functions to read in a safe parcel.
*
* @hide
*/
public class SafeParcelReader {
/** class to parse the exception. */
public static class ParseException extends RuntimeException {
public ParseException(@NonNull String message, @NonNull Parcel p) {
super(message + " Parcel: pos=" + p.dataPosition() + " size=" + p.dataSize());
}
}
private SafeParcelReader() {}
/** Reads the header. */
public static int readHeader(@NonNull Parcel p) {
return p.readInt();
}
/** Gets the id for the field. */
public static int getFieldId(int header) {
return header & 0x0000ffff;
}
/** Reads the size. */
public static int readSize(@NonNull Parcel p, int header) {
if ((header & 0xffff0000) != 0xffff0000) {
return (header >> 16) & 0x0000ffff;
} else {
return p.readInt();
}
}
/** Skips the unknown field. */
public static void skipUnknownField(@NonNull Parcel p, int header) {
int size = readSize(p, header);
p.setDataPosition(p.dataPosition() + size);
}
private static void readAndEnforceSize(@NonNull Parcel p, int header, int required) {
final int size = readSize(p, header);
if (size != required) {
throw new ParseException(
"Expected size "
+ required
+ " got "
+ size
+ " (0x"
+ Integer.toHexString(size)
+ ")",
p);
}
}
private static void enforceSize(@NonNull Parcel p, int header, int size, int required) {
if (size != required) {
throw new ParseException(
"Expected size "
+ required
+ " got "
+ size
+ " (0x"
+ Integer.toHexString(size)
+ ")",
p);
}
}
/** Returns the end position of the object in the parcel. */
public static int validateObjectHeader(@NonNull Parcel p) {
final int header = readHeader(p);
final int size = readSize(p, header);
final int start = p.dataPosition();
if (getFieldId(header) != SafeParcelWriter.OBJECT_HEADER) {
throw new ParseException(
"Expected object header. Got 0x" + Integer.toHexString(header), p);
}
final int end = start + size;
if (end < start || end > p.dataSize()) {
throw new ParseException("Size read is invalid start=" + start + " end=" + end, p);
}
return end;
}
/** Reads a boolean. */
public static boolean readBoolean(@NonNull Parcel p, int header) {
readAndEnforceSize(p, header, 4);
return p.readInt() != 0;
}
/** Reads a {@link Boolean} object. */
@Nullable
public static Boolean readBooleanObject(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
if (size == 0) {
return null;
} else {
enforceSize(p, header, size, 4);
return p.readInt() != 0;
}
}
/** Reads a byte. */
public static byte readByte(@NonNull Parcel p, int header) {
readAndEnforceSize(p, header, 4);
return (byte) p.readInt();
}
/** Reads a char. */
public static char readChar(@NonNull Parcel p, int header) {
readAndEnforceSize(p, header, 4);
return (char) p.readInt();
}
/** Reads a short. */
public static short readShort(@NonNull Parcel p, int header) {
readAndEnforceSize(p, header, 4);
return (short) p.readInt();
}
/** Reads an int. */
public static int readInt(@NonNull Parcel p, int header) {
readAndEnforceSize(p, header, 4);
return p.readInt();
}
/** Reads an {@link Integer} object. */
@Nullable
public static Integer readIntegerObject(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
if (size == 0) {
return null;
} else {
enforceSize(p, header, size, 4);
return p.readInt();
}
}
/** Reads a long. */
public static long readLong(@NonNull Parcel p, int header) {
readAndEnforceSize(p, header, 8);
return p.readLong();
}
/** Reads a {@link Long} object. */
@Nullable
public static Long readLongObject(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
if (size == 0) {
return null;
} else {
enforceSize(p, header, size, 8);
return p.readLong();
}
}
/** Creates a {@link BigInteger}. */
@Nullable
public static BigInteger createBigInteger(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final byte[] val = p.createByteArray();
p.setDataPosition(pos + size);
return new BigInteger(val);
}
/** Reads a float. */
public static float readFloat(@NonNull Parcel p, int header) {
readAndEnforceSize(p, header, 4);
return p.readFloat();
}
/** Reads a {@link Float}. */
@Nullable
public static Float readFloatObject(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
if (size == 0) {
return null;
} else {
enforceSize(p, header, size, 4);
return p.readFloat();
}
}
/** Reads a double. */
public static double readDouble(@NonNull Parcel p, int header) {
readAndEnforceSize(p, header, 8);
return p.readDouble();
}
/** Reads a {@link Double}. */
@Nullable
public static Double readDoubleObject(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
if (size == 0) {
return null;
} else {
enforceSize(p, header, size, 8);
return p.readDouble();
}
}
/** Creates a {@link BigDecimal}. */
@Nullable
public static BigDecimal createBigDecimal(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final byte[] unscaledValue = p.createByteArray();
final int scale = p.readInt();
p.setDataPosition(pos + size);
return new BigDecimal(new BigInteger(unscaledValue), scale);
}
/** Creates a {@link String}. */
@Nullable
public static String createString(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final String result = p.readString();
p.setDataPosition(pos + size);
return result;
}
/** Reads an {@link IBinder}. */
@Nullable
public static IBinder readIBinder(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final IBinder result = p.readStrongBinder();
p.setDataPosition(pos + size);
return result;
}
/** Reads a {@link PendingIntent}. */
@Nullable
public static PendingIntent readPendingIntent(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final PendingIntent result = PendingIntent.readPendingIntentOrNullFromParcel(p);
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link Parcelable}. */
@Nullable
public static <T extends Parcelable> T createParcelable(
@NonNull Parcel p, int header, @NonNull Parcelable.Creator<T> creator) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final T result = creator.createFromParcel(p);
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link Bundle}. */
@Nullable
public static Bundle createBundle(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final Bundle result = p.readBundle();
p.setDataPosition(pos + size);
return result;
}
/** Creates a byte array. */
@Nullable
public static byte[] createByteArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final byte[] result = p.createByteArray();
p.setDataPosition(pos + size);
return result;
}
/** Creates a byte array array. */
@Nullable
public static byte[][] createByteArrayArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final int length = p.readInt();
final byte[][] result = new byte[length][];
for (int i = 0; i < length; i++) {
result[i] = p.createByteArray();
}
p.setDataPosition(pos + size);
return result;
}
/** Creates a boolean array array. */
@Nullable
public static boolean[] createBooleanArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final boolean[] result = p.createBooleanArray();
p.setDataPosition(pos + size);
return result;
}
/** Creates a char array. */
@Nullable
public static char[] createCharArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final char[] result = p.createCharArray();
p.setDataPosition(pos + size);
return result;
}
/** Creates an int array. */
@Nullable
public static int[] createIntArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final int[] result = p.createIntArray();
p.setDataPosition(pos + size);
return result;
}
/** Creates a long array. */
@Nullable
public static long[] createLongArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final long[] result = p.createLongArray();
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link BigInteger} array. */
@Nullable
public static BigInteger[] createBigIntegerArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final int length = p.readInt();
final BigInteger[] result = new BigInteger[length];
for (int i = 0; i < length; i++) {
result[i] = new BigInteger(p.createByteArray());
}
p.setDataPosition(pos + size);
return result;
}
/** Creates a float array. */
@Nullable
public static float[] createFloatArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final float[] result = p.createFloatArray();
p.setDataPosition(pos + size);
return result;
}
/** Creates a double array. */
@Nullable
public static double[] createDoubleArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final double[] result = p.createDoubleArray();
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link BigDecimal} array. */
@Nullable
public static BigDecimal[] createBigDecimalArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final int length = p.readInt();
final BigDecimal[] result = new BigDecimal[length];
for (int i = 0; i < length; i++) {
byte[] unscaledValue = p.createByteArray();
int scale = p.readInt();
result[i] = new BigDecimal(new BigInteger(unscaledValue), scale);
}
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link String} array. */
@Nullable
public static String[] createStringArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final String[] result = p.createStringArray();
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link IBinder} array. */
@Nullable
public static IBinder[] createIBinderArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final IBinder[] result = p.createBinderArray();
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link Boolean} list. */
@Nullable
public static ArrayList<Boolean> createBooleanList(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final ArrayList<Boolean> result = new ArrayList<Boolean>();
final int count = p.readInt();
for (int i = 0; i < count; i++) {
result.add(p.readInt() != 0 ? true : false);
}
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link Integer} list. */
@Nullable
public static ArrayList<Integer> createIntegerList(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final ArrayList<Integer> result = new ArrayList<Integer>();
final int count = p.readInt();
for (int i = 0; i < count; i++) {
result.add(p.readInt());
}
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link SparseBooleanArray}. */
@Nullable
public static SparseBooleanArray createSparseBooleanArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
SparseBooleanArray result = p.readSparseBooleanArray();
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link SparseIntArray}. */
@Nullable
public static SparseIntArray createSparseIntArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final SparseIntArray result = new SparseIntArray();
final int count = p.readInt();
for (int i = 0; i < count; i++) {
int key = p.readInt();
int value = p.readInt();
result.append(key, value);
}
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link Float} {@link SparseArray}. */
@Nullable
public static SparseArray<Float> createFloatSparseArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final SparseArray<Float> result = new SparseArray<Float>();
final int count = p.readInt();
for (int i = 0; i < count; i++) {
int key = p.readInt();
float value = p.readFloat();
result.append(key, value);
}
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link Double} {@link SparseArray}. */
@Nullable
public static SparseArray<Double> createDoubleSparseArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final SparseArray<Double> result = new SparseArray<Double>();
final int count = p.readInt();
for (int i = 0; i < count; i++) {
int key = p.readInt();
double value = p.readDouble();
result.append(key, value);
}
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link SparseLongArray}. */
@Nullable
public static SparseLongArray createSparseLongArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final SparseLongArray result = new SparseLongArray();
final int count = p.readInt();
for (int i = 0; i < count; i++) {
int key = p.readInt();
long value = p.readLong();
result.append(key, value);
}
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link String} {@link SparseArray}. */
@Nullable
public static SparseArray<String> createStringSparseArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final SparseArray<String> result = new SparseArray<String>();
final int count = p.readInt();
for (int i = 0; i < count; i++) {
int key = p.readInt();
String value = p.readString();
result.append(key, value);
}
p.setDataPosition(pos + size);
return result;
}
/** Creates a {@link Parcel} {@link SparseArray}. */
@Nullable
public static SparseArray<Parcel> createParcelSparseArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final int count = p.readInt();
final SparseArray<Parcel> result = new SparseArray<Parcel>();
for (int i = 0; i < count; i++) {
int key = p.readInt();
// read in the flag of whether this element is null
int parcelSize = p.readInt();
if (parcelSize != 0) {
// non-null
int currentDataPosition = p.dataPosition();
Parcel item = Parcel.obtain();
item.appendFrom(p, currentDataPosition, parcelSize);
result.append(key, item);
// move p's data position
p.setDataPosition(currentDataPosition + parcelSize);
} else {
// is null
result.append(key, null);
}
}
p.setDataPosition(pos + size);
return result;
}
/** Creates typed {@link SparseArray}. */
@Nullable
public static <T> SparseArray<T> createTypedSparseArray(
@NonNull Parcel p, int header, @NonNull Parcelable.Creator<T> c) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final int count = p.readInt();
final SparseArray<T> result = new SparseArray<>();
for (int i = 0; i < count; i++) {
int key = p.readInt();
T value;
if (p.readInt() != 0) {
value = c.createFromParcel(p);
} else {
value = null;
}
result.append(key, value);
}
p.setDataPosition(pos + size);
return result;
}
/** Creates {@link IBinder} {@link SparseArray}. */
@Nullable
public static SparseArray<IBinder> createIBinderSparseArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final int count = p.readInt();
final SparseArray<IBinder> result = new SparseArray<>(count);
for (int i = 0; i < count; i++) {
int key = p.readInt();
IBinder value = p.readStrongBinder();
result.append(key, value);
}
p.setDataPosition(pos + size);
return result;
}
/** Creates byte array {@link SparseArray}. */
@Nullable
public static SparseArray<byte[]> createByteArraySparseArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final int count = p.readInt();
final SparseArray<byte[]> result = new SparseArray<byte[]>(count);
for (int i = 0; i < count; i++) {
int key = p.readInt();
byte[] value = p.createByteArray();
result.append(key, value);
}
p.setDataPosition(pos + size);
return result;
}
/** Creates {@link Long} {@link ArrayList}. */
@Nullable
public static ArrayList<Long> createLongList(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final ArrayList<Long> result = new ArrayList<Long>();
final int count = p.readInt();
for (int i = 0; i < count; i++) {
result.add(p.readLong());
}
p.setDataPosition(pos + size);
return result;
}
/** Creates {@link Float} {@link ArrayList}. */
@Nullable
public static ArrayList<Float> createFloatList(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final ArrayList<Float> result = new ArrayList<Float>();
final int count = p.readInt();
for (int i = 0; i < count; i++) {
result.add(p.readFloat());
}
p.setDataPosition(pos + size);
return result;
}
/** Creates {@link Double} {@link ArrayList}. */
@Nullable
public static ArrayList<Double> createDoubleList(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final ArrayList<Double> result = new ArrayList<Double>();
final int count = p.readInt();
for (int i = 0; i < count; i++) {
result.add(p.readDouble());
}
p.setDataPosition(pos + size);
return result;
}
/** Creates {@link String} {@link ArrayList}. */
@Nullable
public static ArrayList<String> createStringList(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final ArrayList<String> result = p.createStringArrayList();
p.setDataPosition(pos + size);
return result;
}
/** Creates {@link IBinder} {@link ArrayList}. */
@Nullable
public static ArrayList<IBinder> createIBinderList(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final ArrayList<IBinder> result = p.createBinderArrayList();
p.setDataPosition(pos + size);
return result;
}
/** Creates typed array. */
@Nullable
public static <T> T[] createTypedArray(
@NonNull Parcel p, int header, @NonNull Parcelable.Creator<T> c) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final T[] result = p.createTypedArray(c);
p.setDataPosition(pos + size);
return result;
}
/** Creates typed {@link ArrayList}. */
@Nullable
public static <T> ArrayList<T> createTypedList(
@NonNull Parcel p, int header, @NonNull Parcelable.Creator<T> c) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final ArrayList<T> result = p.createTypedArrayList(c);
p.setDataPosition(pos + size);
return result;
}
/** Creates {@link Parcel}. */
@Nullable
public static Parcel createParcel(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final Parcel result = Parcel.obtain();
result.appendFrom(p, pos, size);
p.setDataPosition(pos + size);
return result;
}
/** Creates {@link Parcel} array. */
@Nullable
public static Parcel[] createParcelArray(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final int length = p.readInt();
final Parcel[] result = new Parcel[length];
for (int i = 0; i < length; i++) {
int parcelSize = p.readInt();
if (parcelSize != 0) {
int currentDataPosition = p.dataPosition();
Parcel item = Parcel.obtain();
item.appendFrom(p, currentDataPosition, parcelSize);
result[i] = item;
// move p's data position
p.setDataPosition(currentDataPosition + parcelSize);
} else {
result[i] = null;
}
}
p.setDataPosition(pos + size);
return result;
}
/** Creates {@link Parcel} {@link ArrayList}. */
@Nullable
public static ArrayList<Parcel> createParcelList(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final int length = p.readInt();
final ArrayList<Parcel> result = new ArrayList<Parcel>();
for (int i = 0; i < length; i++) {
// read in the flag of whether this element is null
int parcelSize = p.readInt();
if (parcelSize != 0) {
// non-null
int currentDataPosition = p.dataPosition();
Parcel item = Parcel.obtain();
item.appendFrom(p, currentDataPosition, parcelSize);
result.add(item);
// move p's data position
p.setDataPosition(currentDataPosition + parcelSize);
} else {
// is null
result.add(null);
}
}
p.setDataPosition(pos + size);
return result;
}
/** Reads the list. */
public static void readList(
@NonNull Parcel p,
int header,
@SuppressWarnings("rawtypes") @NonNull List list,
@Nullable ClassLoader loader) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return;
}
p.readList(list, loader);
p.setDataPosition(pos + size);
}
/** Ensures at end. */
public static void ensureAtEnd(@NonNull Parcel parcel, int end) {
if (parcel.dataPosition() != end) {
throw new ParseException("Overread allowed size end=" + end, parcel);
}
}
}

View file

@ -0,0 +1,964 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.gms.common.internal.safeparcel;
import android.app.PendingIntent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
/**
* Functions to write a safe parcel. A safe parcel consists of a sequence of header/payload bytes.
*
* <p>The header is 16 bits of size and 16 bits of field id. If the size in the header is 0xffff,
* the next 4 bytes are the size field instead.
*
* @hide
*/
public class SafeParcelWriter {
static final int OBJECT_HEADER = 0x00004f45;
private SafeParcelWriter() {}
private static void writeHeader(Parcel p, int id, int size) {
if (size >= 0x0000ffff) {
p.writeInt(0xffff0000 | id);
p.writeInt(size);
} else {
p.writeInt((size << 16) | id);
}
}
/** Returns the cookie that should be passed to endVariableData. */
private static int beginVariableData(Parcel p, int id) {
// Since we don't know the size yet, assume it might be big and always use the
// size overflow.
p.writeInt(0xffff0000 | id);
p.writeInt(0);
return p.dataPosition();
}
/**
* @param start The result of the paired beginVariableData.
*/
private static void finishVariableData(Parcel p, int start) {
int end = p.dataPosition();
int size = end - start;
// The size is one int before start.
p.setDataPosition(start - 4);
p.writeInt(size);
p.setDataPosition(end);
}
/** Begins the objects header. */
public static int beginObjectHeader(@NonNull Parcel p) {
return beginVariableData(p, OBJECT_HEADER);
}
/** Finishes the objects header. */
public static void finishObjectHeader(@NonNull Parcel p, int start) {
finishVariableData(p, start);
}
/** Writes a boolean. */
public static void writeBoolean(@NonNull Parcel p, int id, boolean val) {
writeHeader(p, id, 4);
p.writeInt(val ? 1 : 0);
}
/** Writes a {@link Boolean} object. */
public static void writeBooleanObject(
@NonNull Parcel p, int id, @Nullable Boolean val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
writeHeader(p, id, 4);
p.writeInt(val ? 1 : 0);
}
/** Writes a byte. */
public static void writeByte(@NonNull Parcel p, int id, byte val) {
writeHeader(p, id, 4);
p.writeInt(val);
}
/** Writes a char. */
public static void writeChar(@NonNull Parcel p, int id, char val) {
writeHeader(p, id, 4);
p.writeInt(val);
}
/** Writes a short. */
public static void writeShort(@NonNull Parcel p, int id, short val) {
writeHeader(p, id, 4);
p.writeInt(val);
}
/** Writes an int. */
public static void writeInt(@NonNull Parcel p, int id, int val) {
writeHeader(p, id, 4);
p.writeInt(val);
}
/** Writes an {@link Integer}. */
public static void writeIntegerObject(
@NonNull Parcel p, int id, @Nullable Integer val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
writeHeader(p, id, 4);
p.writeInt(val);
}
/** Writes a long. */
public static void writeLong(@NonNull Parcel p, int id, long val) {
writeHeader(p, id, 8);
p.writeLong(val);
}
/** Writes a {@link Long}. */
public static void writeLongObject(
@NonNull Parcel p, int id, @Nullable Long val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
writeHeader(p, id, 8);
p.writeLong(val);
}
/** Writes a {@link BigInteger}. */
public static void writeBigInteger(
@NonNull Parcel p, int id, @Nullable BigInteger val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeByteArray(val.toByteArray());
finishVariableData(p, start);
}
/** Writes a float. */
public static void writeFloat(@NonNull Parcel p, int id, float val) {
writeHeader(p, id, 4);
p.writeFloat(val);
}
/** Writes a {@link Float}. */
public static void writeFloatObject(
@NonNull Parcel p, int id, @Nullable Float val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
writeHeader(p, id, 4);
p.writeFloat(val);
}
/** Writes a double. */
public static void writeDouble(@NonNull Parcel p, int id, double val) {
writeHeader(p, id, 8);
p.writeDouble(val);
}
/** Writes a {@link Double} object. */
public static void writeDoubleObject(
@NonNull Parcel p, int id, @Nullable Double val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
writeHeader(p, id, 8);
p.writeDouble(val);
}
/** Writes a {@link BigDecimal}. */
public static void writeBigDecimal(
@NonNull Parcel p, int id, @Nullable BigDecimal val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeByteArray(val.unscaledValue().toByteArray());
p.writeInt(val.scale());
finishVariableData(p, start);
}
/** Writes a {@link String}. */
public static void writeString(
@NonNull Parcel p, int id, @Nullable String val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeString(val);
finishVariableData(p, start);
}
/** Writes a {@link IBinder}. */
public static void writeIBinder(
@NonNull Parcel p, int id, @Nullable IBinder val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
// The size of the flat_binder_object in Parcel.cpp is not actually variable
// but is not part of the CDD, so treat it as variable. It almost certainly
// won't change between processes on a given device.
int start = beginVariableData(p, id);
p.writeStrongBinder(val);
finishVariableData(p, start);
}
/** Writes a {@link Parcelable}. */
public static void writeParcelable(
@NonNull Parcel p,
int id,
@Nullable Parcelable val,
int parcelableFlags,
boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
val.writeToParcel(p, parcelableFlags);
finishVariableData(p, start);
}
/** Writes a {@link Bundle}. */
public static void writeBundle(
@NonNull Parcel p, int id, @Nullable Bundle val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeBundle(val);
finishVariableData(p, start);
}
/** Writes a byte array. */
public static void writeByteArray(
@NonNull Parcel p, int id, @Nullable byte[] buf, boolean writeNull) {
if (buf == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeByteArray(buf);
finishVariableData(p, start);
}
/** Writes a byte array array. */
public static void writeByteArrayArray(
@NonNull Parcel p, int id, @Nullable byte[][] buf, boolean writeNull) {
if (buf == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int length = buf.length;
p.writeInt(length);
for (int i = 0; i < length; i++) {
p.writeByteArray(buf[i]);
}
finishVariableData(p, start);
}
/** Writes a boolean array. */
public static void writeBooleanArray(
@NonNull Parcel p, int id, @Nullable boolean[] val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeBooleanArray(val);
finishVariableData(p, start);
}
/** Writes a char array. */
public static void writeCharArray(
@NonNull Parcel p, int id, @Nullable char[] val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeCharArray(val);
finishVariableData(p, start);
}
/** Writes an int array. */
public static void writeIntArray(
@NonNull Parcel p, int id, @Nullable int[] val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeIntArray(val);
finishVariableData(p, start);
}
/** Writes a long array. */
public static void writeLongArray(
@NonNull Parcel p, int id, @Nullable long[] val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeLongArray(val);
finishVariableData(p, start);
}
/** Writes a {@link BigInteger} array. */
public static void writeBigIntegerArray(
@NonNull Parcel p, int id, @Nullable BigInteger[] val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int length = val.length;
p.writeInt(length);
for (int i = 0; i < length; i++) {
p.writeByteArray(val[i].toByteArray());
}
finishVariableData(p, start);
}
/** Writes a float array. */
public static void writeFloatArray(
@NonNull Parcel p, int id, @Nullable float[] val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeFloatArray(val);
finishVariableData(p, start);
}
/** Writes a double array. */
public static void writeDoubleArray(
@NonNull Parcel p, int id, @Nullable double[] val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeDoubleArray(val);
finishVariableData(p, start);
}
/** Writes a {@link BigDecimal} array. */
public static void writeBigDecimalArray(
@NonNull Parcel p, int id, @Nullable BigDecimal[] val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int length = val.length;
p.writeInt(length);
for (int i = 0; i < length; i++) {
p.writeByteArray(val[i].unscaledValue().toByteArray());
p.writeInt(val[i].scale());
}
finishVariableData(p, start);
}
/** Writes a {@link String} array. */
public static void writeStringArray(
@NonNull Parcel p, int id, @Nullable String[] val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeStringArray(val);
finishVariableData(p, start);
}
/** Writes a {@link IBinder} array. */
public static void writeIBinderArray(
@NonNull Parcel p, int id, @Nullable IBinder[] val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeBinderArray(val);
finishVariableData(p, start);
}
/** Writes a boolean list. */
public static void writeBooleanList(
@NonNull Parcel p, int id, @Nullable List<Boolean> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeInt(val.get(i) ? 1 : 0);
}
finishVariableData(p, start);
}
/** Writes an {@link Integer} list. */
public static void writeIntegerList(
@NonNull Parcel p, int id, @Nullable List<Integer> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeInt(val.get(i));
}
finishVariableData(p, start);
}
/** Writes a {@link Long} list. */
public static void writeLongList(
@NonNull Parcel p, int id, @Nullable List<Long> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeLong(val.get(i));
}
finishVariableData(p, start);
}
/** Writes a {@link Float} list. */
public static void writeFloatList(
@NonNull Parcel p, int id, @Nullable List<Float> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeFloat(val.get(i));
}
finishVariableData(p, start);
}
/** Writes a {@link Double} list. */
public static void writeDoubleList(
@NonNull Parcel p, int id, @Nullable List<Double> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeDouble(val.get(i));
}
finishVariableData(p, start);
}
/** Writes a {@link String} list. */
public static void writeStringList(
@NonNull Parcel p, int id, @Nullable List<String> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeStringList(val);
finishVariableData(p, start);
}
/** Writes a {@link IBinder} list. */
public static void writeIBinderList(
@NonNull Parcel p, int id, @Nullable List<IBinder> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeBinderList(val);
finishVariableData(p, start);
}
/** Writes a typed array. */
public static <T extends Parcelable> void writeTypedArray(
@NonNull Parcel p, int id, @Nullable T[] val, int parcelableFlags, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
// We need to customize the built-in Parcel.writeTypedArray() because we need to write
// the sizes for each individual SafeParcelable objects since they can vary in size due
// to supporting missing fields.
final int length = val.length;
p.writeInt(length);
for (int i = 0; i < length; i++) {
T item = val[i];
if (item == null) {
p.writeInt(0);
} else {
writeTypedItemWithSize(p, item, parcelableFlags);
}
}
finishVariableData(p, start);
}
/** Writes a typed list. */
public static <T extends Parcelable> void writeTypedList(
@NonNull Parcel p, int id, @Nullable List<T> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
// We need to customize the built-in Parcel.writeTypedList() because we need to write
// the sizes for each individual SafeParcelable objects since they can vary in size due
// supporting missing fields.
final int length = val.size();
p.writeInt(length);
for (int i = 0; i < length; i++) {
T item = val.get(i);
if (item == null) {
p.writeInt(0);
} else {
writeTypedItemWithSize(p, item, 0);
}
}
finishVariableData(p, start);
}
/** Writes a typed item with size. */
private static <T extends Parcelable> void writeTypedItemWithSize(
Parcel p, T item, int parcelableFlags) {
// Just write a 1 as a placeholder since we don't know the exact size of item
// yet, and save the data position in Parcel p.
final int itemSizeDataPosition = p.dataPosition();
p.writeInt(1);
final int itemStartPosition = p.dataPosition();
item.writeToParcel(p, parcelableFlags);
final int currentDataPosition = p.dataPosition();
// go back and write the length in bytes
p.setDataPosition(itemSizeDataPosition);
p.writeInt(currentDataPosition - itemStartPosition);
// set the parcel data position to where it was before
p.setDataPosition(currentDataPosition);
}
/** Writes a parcel. */
public static void writeParcel(
@NonNull Parcel p, int id, @Nullable Parcel val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.appendFrom(val, 0, val.dataSize());
finishVariableData(p, start);
}
/**
* This is made to be compatible with writeTypedArray. See implementation of
* Parcel.writeTypedArray(T[] val, parcelableFlags);
*/
public static void writeParcelArray(
@NonNull Parcel p, int id, @Nullable Parcel[] val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int length = val.length;
p.writeInt(length);
for (int i = 0; i < length; i++) {
Parcel item = val[i];
if (item != null) {
p.writeInt(item.dataSize());
// custom part
p.appendFrom(item, 0, item.dataSize());
} else {
p.writeInt(0);
}
}
finishVariableData(p, start);
}
/**
* This is made to be compatible with writeTypedList. See implementation of
* Parce.writeTypedList(List<T> val).
*/
public static void writeParcelList(
@NonNull Parcel p, int id, @Nullable List<Parcel> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
Parcel item = val.get(i);
if (item != null) {
p.writeInt(item.dataSize());
// custom part
p.appendFrom(item, 0, item.dataSize());
} else {
p.writeInt(0);
}
}
finishVariableData(p, start);
}
/** Writes a {@link PendingIntent}. */
public static void writePendingIntent(
@NonNull Parcel p, int id, @Nullable PendingIntent val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
PendingIntent.writePendingIntentOrNullToParcel(val, p);
finishVariableData(p, start);
}
/** Writes a list. */
public static void writeList(
@NonNull Parcel p,
int id,
@SuppressWarnings("rawtypes") @Nullable List list,
boolean writeNull) {
if (list == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeList(list);
finishVariableData(p, start);
}
/** Writes a {@link SparseBooleanArray}. */
public static void writeSparseBooleanArray(
@NonNull Parcel p, int id, @Nullable SparseBooleanArray val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
p.writeSparseBooleanArray(val);
finishVariableData(p, start);
}
/** Writes a {@link Double} {@link SparseArray}. */
public static void writeDoubleSparseArray(
@NonNull Parcel p, int id, @Nullable SparseArray<Double> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeInt(val.keyAt(i));
p.writeDouble(val.valueAt(i));
}
finishVariableData(p, start);
}
/** Writes a {@link Float} {@link SparseArray}. */
public static void writeFloatSparseArray(
@NonNull Parcel p, int id, @Nullable SparseArray<Float> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeInt(val.keyAt(i));
p.writeFloat(val.valueAt(i));
}
finishVariableData(p, start);
}
/** Writes a {@link SparseIntArray}. */
public static void writeSparseIntArray(
@NonNull Parcel p, int id, @Nullable SparseIntArray val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeInt(val.keyAt(i));
p.writeInt(val.valueAt(i));
}
finishVariableData(p, start);
}
/** Writes a {@link SparseLongArray}. */
public static void writeSparseLongArray(
@NonNull Parcel p, int id, @Nullable SparseLongArray val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeInt(val.keyAt(i));
p.writeLong(val.valueAt(i));
}
finishVariableData(p, start);
}
/** Writes a {@link String} {@link SparseArray}. */
public static void writeStringSparseArray(
@NonNull Parcel p, int id, @Nullable SparseArray<String> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeInt(val.keyAt(i));
p.writeString(val.valueAt(i));
}
finishVariableData(p, start);
}
/** Writes a {@link Parcel} {@link SparseArray}. */
public static void writeParcelSparseArray(
@NonNull Parcel p, int id, @Nullable SparseArray<Parcel> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeInt(val.keyAt(i));
Parcel item = val.valueAt(i);
if (item != null) {
p.writeInt(item.dataSize());
// custom part
p.appendFrom(item, 0, item.dataSize());
} else {
p.writeInt(0);
}
}
finishVariableData(p, start);
}
/** Writes typed {@link SparseArray}. */
public static <T extends Parcelable> void writeTypedSparseArray(
@NonNull Parcel p, int id, @Nullable SparseArray<T> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
// We follow the same approach as writeTypedList().
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeInt(val.keyAt(i));
T item = val.valueAt(i);
if (item == null) {
p.writeInt(0);
} else {
writeTypedItemWithSize(p, item, 0);
}
}
finishVariableData(p, start);
}
/** Writes {@link IBinder} {@link SparseArray}. */
public static void writeIBinderSparseArray(
@NonNull Parcel p, int id, @Nullable SparseArray<IBinder> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeInt(val.keyAt(i));
p.writeStrongBinder(val.valueAt(i));
}
finishVariableData(p, start);
}
/** Writes byte array {@link SparseArray}. */
public static void writeByteArraySparseArray(
@NonNull Parcel p, int id, @Nullable SparseArray<byte[]> val, boolean writeNull) {
if (val == null) {
if (writeNull) {
writeHeader(p, id, 0);
}
return;
}
int start = beginVariableData(p, id);
final int size = val.size();
p.writeInt(size);
for (int i = 0; i < size; i++) {
p.writeInt(val.keyAt(i));
p.writeByteArray(val.valueAt(i));
}
finishVariableData(p, start);
}
}

View file

@ -0,0 +1,316 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.gms.common.internal.safeparcel;
import android.os.Parcel;
import android.os.Parcelable;
/**
* A SafeParcelable is a special {@link Parcelable} interface that marshalls its fields in a
* protobuf-like manner into a {@link Parcel}. The marshalling encodes a unique id for each field
* along with the size in bytes of the field. By doing this, older versions of a SafeParcelable can
* skip over unknown fields, which enables backwards compatibility. Because SafeParcelable extends a
* Parcelable, it is NOT safe for persistence.
*
* <p>To prevent the need to manually write code to marshall fields like in a Parcelable, a
* SafeParcelable implementing class is annotated with several annotations so that a generated
* "creator" class has the metadata it needs to automatically generate the boiler plate marshalling
* and unmarshalling code.
*
* <p>The main annotations are the following:
*
* <ul>
* <li>{@link Class} - This annotates the SafeParcelable implementing class and indicates the name
* of the generated "creator" class that has the boiler plate marshalling/unmarshalling code.
* You can also specify whether to call a method named validateContents() after a new instance
* of this class is constructed.
* <li>{@link VersionField} - This annotation may be on one field in the SafeParcelable to
* indicate the version number for this SafeParcelable. This is purely here for style reasons
* in case it is necessary for code to be dependent on the version of the SafeParcelable. This
* member field must be final.
* <li>{@link Field} - This annotates fields that will be marshalled and unmarshalled by the
* generated "creator" class. You must provide an integer "id" for each field. To ensure
* backwards compatibility, these field id's should never be changed. It is okay to omit field
* id's in future versions, but do not reuse old id's. Member fields annotated with {@link
* Field} may be any visibility (private, protected, etc.) and may also be final. You will
* have to specify the "getter" if member fields are not at least package visible. See details
* in {@link Field}.
* <li>{@link Constructor} - You must annotate one constructor with this annotation, which
* indicates the constructor that the "creator" class will use to construct a new instance of
* this class. Each parameter to this constructor must be annotated with the {@link Param} or
* {@link RemovedParam} annotation indicating the field id that the parameter corresponds to.
* Every {@link Param} must correspond to a {@link Field} or {@link VersionField}, and every
* {@link RemovedParam} must correspond to a {@link Reserved} field. Note that this
* constructor must have at least package visibility because the generated "creator" class
* must be able to use this constructor. (The "creator" class is generated in the same package
* as the SafeParcelable class.).
* <li>{@link Indicator} - This is an annotation on a field that keeps track of which fields are
* actually present in a Parcel that represents the marshalled version of a SafeParcelable.
* This is used in the GMS Core Apiary model class code generation.
* </ul>
*
* <p>Because a SafeParcelable extends Parcelable, you must have a public static final member named
* CREATOR and override writeToParcel() and describeContents(). Here's a typical example.
*
* <pre>
* &#64;Class(creator="MySafeParcelableCreator", validate=true)
* public class MySafeParcelable implements SafeParcelable {
* public static final Parcelable.Creator&#60;MySafeParcelable&#62; CREATOR =
* new MySafeParcelableCreator();
*
* &#64;Field(id=1)
* public final String myString;
*
* &#64;Field(id=2, getter="getInteger")
* private final int myInteger;
*
* &#64;Constructor
* MySafeParcelable(
* &#64;Param(id=1) String string,
* &#64;Param(id=2) int integer) {
* myString = string;
* myInteger = integer;
* )
*
* // Example public constructor (not used by MySafeParcelableCreator)
* public MySafeParcelable(String string, int integer) {
* myString = string;
* myInteger = integer;
* }
*
* // This is only needed if validate=true in &#64;Class annotation.
* public void validateContents() {
* // Add validation here.
* }
*
* // This getter is needed because myInteger is private, and the generated creator class
* // MySafeParcelableCreator can't access private member fields.
* int getInteger() {
* return myInteger;
* }
*
* // This is necessary because SafeParcelable extends Parcelable.
* // {@link AbstractSafeParcelable} implements this for you.
* &#64;Override
* public int describeContents() {
* return MySafeParcelableCreator.CONTENT_DESCRIPTION;
* }
*
* // This is necessary because SafeParcelable extends Parcelable.
* &#64;Override
* public void writeToParcel(Parcel out, int flags) {
* // This invokes the generated MySafeParcelableCreator class's marshalling to a Parcel.
* // In the event you need custom logic when writing to a Parcel, that logic can be
* // inserted here.
* MySafeParcelableCreator.writeToParcel(this, out, flags);
* }
* }
* </pre>
*
* @hide
*/
public interface SafeParcelable extends Parcelable {
/** @hide */
// Note: the field name and value are accessed using reflection for backwards compatibility, and
// must not be changed.
String NULL = "SAFE_PARCELABLE_NULL_STRING";
/**
* This annotates your class and specifies the name of the generated "creator" class for
* marshalling/unmarshalling a SafeParcelable to/from a {@link Parcel}. The "creator" class is
* generated in the same package as the SafeParcelable class. You can also set "validate" to
* true, which will cause the "creator" to invoke the method validateContents() on your class
* after constructing an instance.
*/
@SuppressWarnings("JavaLangClash")
@interface Class {
/**
* Simple name of the generated "creator" class generated in the same package as the
* SafeParceable.
*/
String creator();
/** Whether the generated "creator" class is final. */
boolean creatorIsFinal() default true;
/**
* When set to true, invokes the validateContents() method in this SafeParcelable object
* after constructing a new instance.
*/
boolean validate() default false;
/**
* When set to true, it will not write type default values to the Parcel.
*
* <p>boolean: false byte/char/short/int/long: 0 float: 0.0f double: 0.0 Objects/arrays:
* null
*
* <p>Cannot be used with Field(defaultValue)
*/
boolean doNotParcelTypeDefaultValues() default false;
}
/** Use this annotation on members that you wish to be marshalled in the SafeParcelable. */
@interface Field {
/**
* Valid values for id are between 1 and 65535. This field id is marshalled into a Parcel .
* To maintain backwards compatibility, never reuse old id's. It is okay to no longer use
* old id's and add new ones in subsequent versions of a SafeParcelable.
*/
int id();
/**
* This specifies the name of the getter method for retrieving the value of this field. This
* must be specified for fields that do not have at least package visibility because the
* "creator" class will be unable to access the value when attempting to marshall this
* field. The getter method should take no parameters and return the type of this field
* (unless overridden by the "type" attribute below).
*/
String getter() default NULL;
/**
* For advanced uses, this specifies the type for the field when marshalling and
* unmarshalling by the "creator" class to be something different than the declared type of
* the member variable. This is useful if you want to incorporate an object that is not
* SafeParcelable (or a system Parcelable object). Be sure to enter the fully qualified name
* for the class (i.e., android.os.Bundle and not Bundle). For example,
*
* <pre>
* &#64;Class(creator="MyAdvancedCreator")
* public class MyAdvancedSafeParcelable implements SafeParcelable {
* public static final Parcelable.Creator&#60;MyAdvancedSafeParcelable&#62; CREATOR =
* new MyAdvancedCreator();
*
* &#64;Field(id=1, getter="getObjectAsBundle", type="android.os.Bundle")
* private final MyCustomObject myObject;
*
* &#64;Constructor
* MyAdvancedSafeParcelable(
* &#64;Param(id=1) Bundle objectAsBundle) {
* myObject = myConvertFromBundleToObject(objectAsBundle);
* }
*
* Bundle getObjectAsBundle() {
* // The code here can convert your custom object to one that can be parcelled.
* return myConvertFromObjectToBundle(myObject);
* }
*
* ...
* }
* </pre>
*/
String type() default NULL;
/**
* This can be used to specify the default value for primitive types (e.g., boolean, int,
* long), primitive type object wrappers (e.g., Boolean, Integer, Long) and String in the
* case a value for a field was not explicitly set in the marshalled Parcel. This performs
* compile-time checks for the type of the field and inserts the appropriate quotes or
* double quotes around strings and chars or removes them completely for booleans and
* numbers. To insert a generic string for initializing field, use {@link
* #defaultValueUnchecked()}. You can specify at most one of {@link #defaultValue()} or
* {@link #defaultValueUnchecked()}. For example,
*
* <pre>
* &#64;Field(id=2, defaultValue="true")
* boolean myBoolean;
*
* &#64;Field(id=3, defaultValue="13")
* Integer myInteger;
*
* &#64;Field(id=4, defaultValue="foo")
* String myString;
* </pre>
*/
String defaultValue() default NULL;
/**
* This can be used to specify the default value for any object and the string value is
* literally added to the generated creator class code unchecked. You can specify at most
* one of {@link #defaultValue()} or {@link #defaultValueUnchecked()}. You must fully
* qualify any classes you reference within the string. For example,
*
* <pre>
* &#64;Field(id=2, defaultValueUnchecked="new android.os.Bundle()")
* Bundle myBundle;
* </pre>
*/
String defaultValueUnchecked() default NULL;
}
/**
* There may be exactly one member annotated with VersionField, which represents the version of
* this safe parcelable. The attributes are the same as those of {@link Field}. Note you can use
* any type you want for your version field, although most people use int's.
*/
@interface VersionField {
int id();
String getter() default NULL;
String type() default NULL;
}
/**
* Use this to indicate the member field that holds whether a field was set or not. The member
* field type currently supported is a HashSet&#60;Integer&#62; which is the set of safe
* parcelable field id's that have been explicitly set.
*
* <p>This annotation should also be used to annotate one of the parameters to the constructor
* annotated with &#64;Constructor. Note that this annotation should either be present on
* exactly one member field and one constructor parameter or left out completely.
*/
@interface Indicator {
String getter() default NULL;
}
/**
* Use this to indicate the constructor that the creator should use. The constructor annotated
* with this must be package or public visibility, so that the generated "creator" class can
* invoke this.
*/
@interface Constructor {}
/**
* Use this on each parameter passed in to the Constructor to indicate to which field id each
* formal parameter corresponds.
*/
@interface Param {
int id();
}
/**
* Use this on a parameter passed in to the Constructor to indicate that a removed field should
* be read on construction. If the field is not present when read, the default value will be
* used instead.
*/
@interface RemovedParam {
int id();
String defaultValue() default NULL;
String defaultValueUnchecked() default NULL;
}
/**
* Use this to mark tombstones for removed {@link Field Fields} or {@link VersionField
* VersionFields}.
*/
@interface Reserved {
int[] value();
}
}

View file

@ -0,0 +1,11 @@
plugins {
id("signal-library")
}
android {
namespace = "com.google.android.gms.tasks"
}
dependencies {
implementation(project(":core-gms:base"))
}

View file

@ -0,0 +1,13 @@
package com.google.android.gms.tasks;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@Keep
public abstract class CancellationToken {
public abstract boolean isCancellationRequested();
@NonNull
public abstract CancellationToken onCanceledRequested(@NonNull OnTokenCanceledListener listener);
}

View file

@ -0,0 +1,9 @@
package com.google.android.gms.tasks;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@Keep
public interface Continuation<TResult, TContinuationResult> {
TContinuationResult then(@NonNull Task<TResult> task) throws Exception;
}

View file

@ -0,0 +1,35 @@
package com.google.android.gms.tasks;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@Keep
public final class DuplicateTaskCompletionException extends IllegalStateException {
private DuplicateTaskCompletionException(String message, @Nullable Exception cause) {
super(message, cause);
}
@NonNull
public static IllegalStateException of(@NonNull Task<?> task) {
if (!task.isComplete()) {
return new IllegalStateException("Task is not complete.");
}
final String description;
if (task.getException() != null) {
description = "failure";
} else if (task.isSuccessful()) {
description = "success";
} else if (task.isCanceled()) {
description = "cancellation";
} else {
description = "unknown state";
}
return new DuplicateTaskCompletionException(
"Already completed: " + description, task.getException()
);
}
}

View file

@ -0,0 +1,8 @@
package com.google.android.gms.tasks;
import androidx.annotation.Keep;
@Keep
public interface OnCanceledListener {
void onCanceled();
}

View file

@ -0,0 +1,8 @@
package com.google.android.gms.tasks;
import androidx.annotation.Keep;
@Keep
public interface OnCompleteListener<TResult> {
void onComplete(Task<TResult> task);
}

View file

@ -0,0 +1,9 @@
package com.google.android.gms.tasks;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@Keep
public interface OnFailureListener {
void onFailure(@NonNull Exception e);
}

View file

@ -0,0 +1,8 @@
package com.google.android.gms.tasks;
import androidx.annotation.Keep;
@Keep
public interface OnSuccessListener<TResult> {
void onSuccess(TResult result);
}

View file

@ -0,0 +1,8 @@
package com.google.android.gms.tasks;
import androidx.annotation.Keep;
@Keep
public interface OnTokenCanceledListener {
void onCanceled();
}

View file

@ -0,0 +1,10 @@
package com.google.android.gms.tasks;
import androidx.annotation.Keep;
@Keep
public class RuntimeExecutionException extends RuntimeException {
public RuntimeExecutionException(Throwable cause) {
super(cause);
}
}

View file

@ -0,0 +1,10 @@
package com.google.android.gms.tasks;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@Keep
public interface SuccessContinuation<TResult, TContinuationResult> {
@NonNull
Task<TContinuationResult> then(TResult result) throws Exception;
}

View file

@ -0,0 +1,104 @@
package com.google.android.gms.tasks;
import android.app.Activity;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.concurrent.Executor;
@Keep
public abstract class Task<TResult> {
@NonNull
public Task<TResult> addOnCanceledListener(@NonNull OnCanceledListener listener) {
throw new UnsupportedOperationException("addOnCanceledListener is not implemented.");
}
@NonNull
public Task<TResult> addOnCanceledListener(@NonNull Activity activity, @NonNull OnCanceledListener listener) {
throw new UnsupportedOperationException("addOnCanceledListener is not implemented.");
}
@NonNull
public Task<TResult> addOnCanceledListener(@NonNull Executor executor, @NonNull OnCanceledListener listener) {
throw new UnsupportedOperationException("addOnCanceledListener is not implemented.");
}
@NonNull
public Task<TResult> addOnCompleteListener(@NonNull OnCompleteListener<TResult> listener) {
throw new UnsupportedOperationException("addOnCompleteListener is not implemented");
}
@NonNull
public Task<TResult> addOnCompleteListener(@NonNull Activity activity, @NonNull OnCompleteListener<TResult> listener) {
throw new UnsupportedOperationException("addOnCompleteListener is not implemented");
}
@NonNull
public Task<TResult> addOnCompleteListener(@NonNull Executor executor, @NonNull OnCompleteListener<TResult> listener) {
throw new UnsupportedOperationException("addOnCompleteListener is not implemented");
}
@NonNull
public abstract Task<TResult> addOnFailureListener(@NonNull OnFailureListener listener);
@NonNull
public abstract Task<TResult> addOnFailureListener(@NonNull Activity activity, @NonNull OnFailureListener listener);
@NonNull
public abstract Task<TResult> addOnFailureListener(@NonNull Executor executor, @NonNull OnFailureListener listener);
@NonNull
public abstract Task<TResult> addOnSuccessListener(@NonNull OnSuccessListener<? super TResult> listener);
@NonNull
public abstract Task<TResult> addOnSuccessListener(@NonNull Activity activity, @NonNull OnSuccessListener<? super TResult> listener);
@NonNull
public abstract Task<TResult> addOnSuccessListener(@NonNull Executor executor, @NonNull OnSuccessListener<? super TResult> listener);
@NonNull
public <TContinuationResult> Task<TContinuationResult> continueWith(@NonNull Continuation<TResult, TContinuationResult> continuation) {
throw new UnsupportedOperationException("continueWith is not implemented");
}
@NonNull
public <TContinuationResult> Task<TContinuationResult> continueWith(@NonNull Executor executor, @NonNull Continuation<TResult, TContinuationResult> continuation) {
throw new UnsupportedOperationException("continueWith is not implemented");
}
@NonNull
public <TContinuationResult> Task<TContinuationResult> continueWithTask(@NonNull Continuation<TResult, Task<TContinuationResult>> continuation) {
throw new UnsupportedOperationException("continueWithTask is not implemented");
}
@NonNull
public <TContinuationResult> Task<TContinuationResult> continueWithTask(@NonNull Executor executor, @NonNull Continuation<TResult, Task<TContinuationResult>> continuation) {
throw new UnsupportedOperationException("continueWithTask is not implemented");
}
public abstract @Nullable Exception getException();
public abstract TResult getResult();
public abstract <X extends Throwable> TResult getResult(@NonNull Class<X> exceptionType) throws X;
public abstract boolean isCanceled();
public abstract boolean isComplete();
public abstract boolean isSuccessful();
@NonNull
public <TContinuationResult> Task<TContinuationResult> onSuccessTask(@NonNull SuccessContinuation<TResult, TContinuationResult> successContinuation) {
throw new UnsupportedOperationException("onSuccessTask is not implemented");
}
@NonNull
public <TContinuationResult> Task<TContinuationResult> onSuccessTask(@NonNull Executor executor, @NonNull SuccessContinuation<TResult, TContinuationResult> successContinuation) {
throw new UnsupportedOperationException("onSuccessTask is not implemented");
}
}

View file

@ -0,0 +1,51 @@
package com.google.android.gms.tasks;
import androidx.annotation.Keep;
import org.microg.gms.tasks.TaskImpl;
@Keep
public class TaskCompletionSource<TResult> {
private final TaskImpl<TResult> task = new TaskImpl<>();
public TaskCompletionSource() {}
public TaskCompletionSource(CancellationToken token) {
token.onCanceledRequested(() -> {
try {
task.cancel();
} catch (DuplicateTaskCompletionException ignored) {}
});
}
public Task<TResult> getTask() {
return task;
}
public void setException(Exception e) {
task.setException(e);
}
public void setResult(TResult result) {
task.setResult(result);
}
public boolean trySetException(Exception e) {
try {
setException(e);
} catch (DuplicateTaskCompletionException ignored) {
return false;
}
return true;
}
public boolean trySetResult(TResult result) {
try {
setResult(result);
} catch (DuplicateTaskCompletionException ignored) {
return false;
}
return true;
}
}

View file

@ -0,0 +1,27 @@
package com.google.android.gms.tasks;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.Keep;
import java.util.concurrent.Executor;
@Keep
public class TaskExecutors {
private TaskExecutors() {}
public static final Executor MAIN_THREAD = new Executor() {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable runnable) {
if (Looper.getMainLooper() == Looper.myLooper()) {
runnable.run();
} else {
handler.post(runnable);
}
}
};
}

View file

@ -0,0 +1,148 @@
package com.google.android.gms.tasks;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import com.google.android.gms.common.internal.Preconditions;
import org.microg.gms.tasks.CancellationTokenImpl;
import org.microg.gms.tasks.TaskImpl;
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;
@Keep
public final class Tasks {
private Tasks() {}
public static <TResult> TResult await(@NonNull Task<TResult> task)
throws ExecutionException, InterruptedException
{
Preconditions.checkNotMainThread();
Preconditions.checkNotGoogleApiHandlerThread();
Preconditions.checkNotNull(task, "Task must not be null");
if (!task.isComplete()) {
CountDownLatch latch = new CountDownLatch(1);
task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown());
latch.await();
}
return getResult(task);
}
static <TResult> TResult await(@NonNull Task<TResult> task, long timeout, @NonNull TimeUnit unit)
throws ExecutionException, InterruptedException, TimeoutException
{
Preconditions.checkNotMainThread();
Preconditions.checkNotGoogleApiHandlerThread();
Preconditions.checkNotNull(task, "Task must not be null");
Preconditions.checkNotNull(unit, "TimeUnit must not be null");
if (!task.isComplete()) {
CountDownLatch latch = new CountDownLatch(1);
task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown());
boolean completed = latch.await(timeout, unit);
if (!completed) {
throw new TimeoutException("Timed out waiting for Task");
}
}
return getResult(task);
}
@Deprecated
@NonNull
public static <TResult> Task<TResult> call(@NonNull Callable<TResult> callable) {
return call(TaskExecutors.MAIN_THREAD, callable);
}
@Deprecated
@NonNull
public static <TResult> Task<TResult> call(@NonNull Executor executor, @NonNull Callable<TResult> callable) {
Preconditions.checkNotNull(executor, "Executor must not be null");
Preconditions.checkNotNull(callable, "Callback must not be null");
TaskCompletionSource<TResult> taskSource = new TaskCompletionSource<>();
executor.execute(() -> {
try {
TResult result = callable.call();
taskSource.setResult(result);
} catch (Exception e) {
taskSource.setException(e);
} catch (Throwable t) {
taskSource.setException(new RuntimeException(t));
}
});
return taskSource.getTask();
}
@NonNull
public static <TResult> Task<TResult> forCanceled() {
TaskImpl<TResult> task = new TaskImpl<>();
task.cancel();
return task;
}
public static <TResult> Task<TResult> forException(Exception e) {
TaskImpl<TResult> task = new TaskImpl<>();
task.setException(e);
return task;
}
@NonNull
public static <TResult> Task<TResult> forResult(TResult result) {
TaskImpl<TResult> task = new TaskImpl<>();
task.setResult(result);
return task;
}
private static <TResult> TResult getResult(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());
}
@NonNull
public static <T> Task<T> withTimeout(@NonNull Task<T> task, long timeout, @NonNull TimeUnit unit) {
Preconditions.checkNotNull(task, "Task must not be null");
Preconditions.checkArgument(timeout > 0L, "Timeout must be positive");
Preconditions.checkNotNull(unit, "TimeUnit must not be null");
final CancellationTokenImpl cancellationToken = new CancellationTokenImpl();
final TaskCompletionSource<T> taskCompletionSource = new TaskCompletionSource<>(cancellationToken);
final Handler handler = new Handler(Looper.getMainLooper());
final Runnable timeoutRunnable = () ->
taskCompletionSource.trySetException(new TimeoutException());
handler.postDelayed(timeoutRunnable, unit.toMillis(timeout));
task.addOnCompleteListener(completedTask -> {
handler.removeCallbacks(timeoutRunnable);
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) {
}
}
}