Repo cloned
This commit is contained in:
commit
496ae75f58
7988 changed files with 1451097 additions and 0 deletions
22
core-gms/base/build.gradle.kts
Normal file
22
core-gms/base/build.gradle.kts
Normal 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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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\" />";
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.google.android.gms.common.util;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public @interface VisibleForTesting {
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
26
core-gms/cloud-messaging/build.gradle.kts
Normal file
26
core-gms/cloud-messaging/build.gradle.kts
Normal 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"))
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
12
core-gms/safeparcel-processor/build.gradle.kts
Normal file
12
core-gms/safeparcel-processor/build.gradle.kts
Normal 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")
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
androidx.safeparcel.processor.SafeParcelProcessor,isolating
|
||||
|
|
@ -0,0 +1 @@
|
|||
androidx.safeparcel.processor.SafeParcelProcessor
|
||||
|
|
@ -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 ?>
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
7
core-gms/safeparcel/build.gradle.kts
Normal file
7
core-gms/safeparcel/build.gradle.kts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
plugins {
|
||||
id("signal-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.google.android.gms.common.internal.safeparcel"
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
* @Class(creator="MySafeParcelableCreator", validate=true)
|
||||
* public class MySafeParcelable implements SafeParcelable {
|
||||
* public static final Parcelable.Creator<MySafeParcelable> CREATOR =
|
||||
* new MySafeParcelableCreator();
|
||||
*
|
||||
* @Field(id=1)
|
||||
* public final String myString;
|
||||
*
|
||||
* @Field(id=2, getter="getInteger")
|
||||
* private final int myInteger;
|
||||
*
|
||||
* @Constructor
|
||||
* MySafeParcelable(
|
||||
* @Param(id=1) String string,
|
||||
* @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 @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.
|
||||
* @Override
|
||||
* public int describeContents() {
|
||||
* return MySafeParcelableCreator.CONTENT_DESCRIPTION;
|
||||
* }
|
||||
*
|
||||
* // This is necessary because SafeParcelable extends Parcelable.
|
||||
* @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>
|
||||
* @Class(creator="MyAdvancedCreator")
|
||||
* public class MyAdvancedSafeParcelable implements SafeParcelable {
|
||||
* public static final Parcelable.Creator<MyAdvancedSafeParcelable> CREATOR =
|
||||
* new MyAdvancedCreator();
|
||||
*
|
||||
* @Field(id=1, getter="getObjectAsBundle", type="android.os.Bundle")
|
||||
* private final MyCustomObject myObject;
|
||||
*
|
||||
* @Constructor
|
||||
* MyAdvancedSafeParcelable(
|
||||
* @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>
|
||||
* @Field(id=2, defaultValue="true")
|
||||
* boolean myBoolean;
|
||||
*
|
||||
* @Field(id=3, defaultValue="13")
|
||||
* Integer myInteger;
|
||||
*
|
||||
* @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>
|
||||
* @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<Integer> 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 @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();
|
||||
}
|
||||
}
|
||||
11
core-gms/tasks/build.gradle.kts
Normal file
11
core-gms/tasks/build.gradle.kts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
plugins {
|
||||
id("signal-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.google.android.gms.tasks"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core-gms:base"))
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.google.android.gms.tasks;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface OnCanceledListener {
|
||||
void onCanceled();
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.google.android.gms.tasks;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface OnCompleteListener<TResult> {
|
||||
void onComplete(Task<TResult> task);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.google.android.gms.tasks;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface OnSuccessListener<TResult> {
|
||||
void onSuccess(TResult result);
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.google.android.gms.tasks;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface OnTokenCanceledListener {
|
||||
void onCanceled();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
240
core-gms/tasks/src/main/java/org/microg/gms/tasks/TaskImpl.java
Normal file
240
core-gms/tasks/src/main/java/org/microg/gms/tasks/TaskImpl.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue