list, Bundle bundle, IAssetModuleServiceCallback callback) {
- Log.d(TAG, "Method (cancelDownloads) called but not implement by packageName -> " + packageName);
- }
- };
-
- @Override
- public IBinder onBind(Intent intent) {
- Log.d(TAG, "onBind");
- return service.asBinder();
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- Log.d(TAG, "onUnbind");
- requested.clear();
- return super.onUnbind(intent);
- }
-}
\ No newline at end of file
diff --git a/vending-app/src/main/java/com/google/android/finsky/externalreferrer/GetInstallReferrerService.java b/vending-app/src/main/java/com/google/android/finsky/externalreferrer/GetInstallReferrerService.java
index fd67908a0c..a59ad44f9f 100644
--- a/vending-app/src/main/java/com/google/android/finsky/externalreferrer/GetInstallReferrerService.java
+++ b/vending-app/src/main/java/com/google/android/finsky/externalreferrer/GetInstallReferrerService.java
@@ -18,9 +18,8 @@ public class GetInstallReferrerService extends Service {
// https://developer.android.com/google/play/installreferrer/igetinstallreferrerservice
@Override
public Bundle getInstallReferrer(Bundle request) throws RemoteException {
- String packageName = request.getString("package_name");
Bundle result = new Bundle();
- result.putString("install_referrer", "https://play.google.com/store/apps/details?utm_source=google-play&utm_medium=organic&id="+packageName);
+ result.putString("install_referrer", "utm_source=google-play&utm_medium=organic");
result.putLong("referrer_click_timestamp_seconds", 0);
result.putLong("referrer_click_timestamp_server_seconds", 0);
result.putLong("install_begin_timestamp_seconds", 0);
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetLocation.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetLocation.java
new file mode 100644
index 0000000000..4ec647459b
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetLocation.java
@@ -0,0 +1,36 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks;
+
+import com.google.android.play.core.assetpacks.model.AssetPackStorageMethod;
+
+/**
+ * Location of a single asset, belonging to an asset pack.
+ *
+ * If the AssetPackStorageMethod for the pack is {@link AssetPackStorageMethod#APK_ASSETS}, this will be the path to the
+ * APK containing the asset, the offset of the asset inside the APK and the size of the asset. The asset file will be
+ * uncompressed, unless `bundletool` has been explicitly configured to compress the asset pack.
+ *
+ * If the AssetPackStorageMethod for the pack is {@link AssetPackStorageMethod#STORAGE_FILES}, this will be the path to
+ * the specific asset, the offset will be 0 and the size will be the size of the asset file. The asset file will be
+ * uncompressed.
+ */
+public abstract class AssetLocation {
+ /**
+ * Returns the file offset where the asset starts, in bytes.
+ */
+ public abstract long offset();
+
+ /**
+ * Returns the path to the file containing the asset.
+ */
+ public abstract String path();
+
+ /**
+ * Returns the size of the asset, in bytes.
+ */
+ public abstract long size();
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackException.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackException.java
new file mode 100644
index 0000000000..090c592fcb
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackException.java
@@ -0,0 +1,40 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks;
+
+import com.google.android.gms.common.api.ApiException;
+import com.google.android.gms.common.api.Status;
+import com.google.android.play.core.assetpacks.model.AssetPackErrorCode;
+import org.microg.gms.common.Hide;
+
+/**
+ * An exception indicating something went wrong with the Asset Delivery API.
+ *
+ * See {@link #getErrorCode()} for the specific problem.
+ */
+public class AssetPackException extends ApiException {
+ @Hide
+ public AssetPackException(@AssetPackErrorCode int errorCode) {
+ super(new Status(errorCode, "Asset Pack Download Error(" + errorCode + ")"));
+ }
+
+ /**
+ * Returns an error code value from {@link AssetPackErrorCode}.
+ */
+ @AssetPackErrorCode
+ public int getErrorCode() {
+ return super.getStatusCode();
+ }
+
+ /**
+ * Returns the error code resulting from the operation. The value is one of the constants in {@link AssetPackErrorCode}.
+ * getStatusCode() is unsupported by AssetPackException, please use getErrorCode() instead.
+ */
+ @Override
+ public int getStatusCode() {
+ return super.getStatusCode();
+ }
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackLocation.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackLocation.java
new file mode 100644
index 0000000000..abf6e6e71d
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackLocation.java
@@ -0,0 +1,46 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks;
+
+import androidx.annotation.Nullable;
+import com.google.android.play.core.assetpacks.model.AssetPackStorageMethod;
+
+/**
+ * Location of an asset pack on the device.
+ */
+public abstract class AssetPackLocation {
+ /**
+ * Returns the file path to the folder containing the pack's assets, if the storage method is
+ * {@link AssetPackStorageMethod#STORAGE_FILES}.
+ *
+ * The files found at this path should not be modified.
+ *
+ * If the storage method is {@link AssetPackStorageMethod#APK_ASSETS}, this method will return {@code null}. To access assets
+ * from packs installed as APKs, use Asset Manager.
+ */
+ @Nullable
+ public abstract String assetsPath();
+
+ /**
+ * Returns whether the pack is installed as an APK or extracted into a folder on the filesystem.
+ *
+ * @return a value from {@link AssetPackStorageMethod}
+ */
+ @AssetPackStorageMethod
+ public abstract int packStorageMethod();
+
+ /**
+ * Returns the file path to the folder containing the extracted asset pack, if the storage method is
+ * {@link AssetPackStorageMethod#STORAGE_FILES}.
+ *
+ * The files found at this path should not be modified.
+ *
+ * If the storage method is {@link AssetPackStorageMethod#APK_ASSETS}, this method will return {@code null}. To access assets
+ * from packs installed as APKs, use Asset Manager.
+ */
+ @Nullable
+ public abstract String path();
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManager.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManager.java
new file mode 100644
index 0000000000..c4c23ae1c3
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManager.java
@@ -0,0 +1,194 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks;
+
+import android.app.Activity;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.IntentSenderRequest;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.android.gms.tasks.Task;
+import com.google.android.play.core.assetpacks.model.AssetPackStatus;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Manages downloads of asset packs.
+ */
+public interface AssetPackManager {
+
+ /**
+ * Requests to cancel the download of the specified asset packs.
+ *
+ * Note: Only active downloads can be canceled.
+ *
+ * @return The new state for all specified packs.
+ */
+ AssetPackStates cancel(@NonNull List packNames);
+
+ /**
+ * Unregisters all listeners previously added using {@link #registerListener}.
+ */
+ void clearListeners();
+
+ /**
+ * Requests to download the specified asset packs.
+ *
+ * This method will fail if the app is not in the foreground.
+ *
+ * @return the state of all specified pack names
+ */
+ Task fetch(List packNames);
+
+ /**
+ * [advanced API] Returns the location of an asset in a pack, or {@code null} if the asset is not present in the given pack.
+ *
+ * You don't need to use this API for common use-cases: you can use the standard File API for accessing assets from
+ * asset packs that were extracted into the filesystem; and you can use Android's AssetManager API to access assets
+ * from packs that were installed as APKs.
+ *
+ * This API is useful for game engines that don't use Asset Manager and for developers that want a unified method to
+ * access assets, independently from the delivery mode.
+ */
+ @Nullable
+ AssetLocation getAssetLocation(@NonNull String packName, @NonNull String assetPath);
+
+ /**
+ * Returns the location of the specified asset pack on the device or {@code null} if this pack is not downloaded.
+ *
+ * The files found at this path should not be modified.
+ */
+ @Nullable
+ AssetPackLocation getPackLocation(@NonNull String packName);
+
+ /**
+ * Returns the location of all installed asset packs as a mapping from the asset pack name to an {@link AssetPackLocation}.
+ *
+ * The files found at these paths should not be modified.
+ */
+ Map getPackLocations();
+
+ /**
+ * Requests download state or details for the specified asset packs.
+ *
+ * Do not use this method to determine whether an asset pack is downloaded. Instead use {@link #getPackLocation}.
+ */
+ Task getPackStates(List packNames);
+
+ /**
+ * Registers a listener that will be notified of changes to the state of pack downloads for this app. Listeners should be
+ * subsequently unregistered using {@link #unregisterListener}.
+ */
+ void registerListener(@NonNull AssetPackStateUpdateListener listener);
+
+ /**
+ * Deletes the specified asset pack from the internal storage of the app.
+ *
+ * Use this method to delete asset packs instead of deleting files manually. This ensures that the Asset Pack will not be
+ * re-downloaded during an app update.
+ *
+ * If the asset pack is currently being downloaded or installed, this method does not cancel the process. For this case,
+ * use {@link #cancel} instead.
+ *
+ * @return A task that will be successful only if files were successfully deleted.
+ */
+
+ Task removePack(@NonNull String packName);
+
+ /**
+ * Shows a confirmation dialog to resume all pack downloads that are currently in the
+ * {@link AssetPackStatus#WAITING_FOR_WIFI} state. If the user accepts the dialog, packs are downloaded over cellular data.
+ *
+ * The status of an asset pack is set to {@link AssetPackStatus#WAITING_FOR_WIFI} if the user is currently not on a Wi-Fi
+ * connection and the asset pack is large or the user has set their download preference in the Play Store to only
+ * download apps over Wi-Fi. By showing this dialog, your app can ask the user if they accept downloading the asset
+ * pack over cellular data instead of waiting for Wi-Fi.
+ *
+ * The confirmation activity returns one of the following values:
+ *
+ * - {@link Activity#RESULT_OK Activity#RESULT_OK} if the user accepted.
+ *
- {@link Activity#RESULT_CANCELED Activity#RESULT_CANCELED} if the user denied or the dialog has been closed in any other way (e.g.
+ * backpress).
+ *
+ *
+ * @param activityResultLauncher an activityResultLauncher to launch the confirmation dialog.
+ * @return whether the confirmation dialog has been started.
+ * @deprecated This API has been deprecated in favor of {@link #showConfirmationDialog(ActivityResultLauncher)}.
+ */
+ @Deprecated
+ boolean showCellularDataConfirmation(@NonNull ActivityResultLauncher activityResultLauncher);
+
+ /**
+ * Shows a confirmation dialog to resume all pack downloads that are currently in the
+ * {@link AssetPackStatus#WAITING_FOR_WIFI} state. If the user accepts the dialog, packs are downloaded over cellular data.
+ *
+ * The status of an asset pack is set to {@link AssetPackStatus#WAITING_FOR_WIFI} if the user is currently not on a Wi-Fi
+ * connection and the asset pack is large or the user has set their download preference in the Play Store to only
+ * download apps over Wi-Fi. By showing this dialog, your app can ask the user if they accept downloading the asset
+ * pack over cellular data instead of waiting for Wi-Fi.
+ *
+ * @param activity the activity on top of which the confirmation dialog is displayed. Use your current
+ * activity for this.
+ * @return A {@link Task} that completes once the dialog has been accepted, denied or closed. A successful task
+ * result contains one of the following values:
+ *
+ * - {@link Activity#RESULT_OK Activity#RESULT_OK} if the user accepted.
+ *
- {@link Activity#RESULT_CANCELED Activity#RESULT_CANCELED} if the user denied or the dialog has been closed in any other way (e.g.
+ * backpress).
+ *
+ * @deprecated This API has been deprecated in favor of {@link #showConfirmationDialog(Activity)}.
+ */
+ @Deprecated
+ Task showCellularDataConfirmation(@NonNull Activity activity);
+
+ /**
+ * Shows a dialog that asks the user for consent to download packs that are currently in either the
+ * {@link AssetPackStatus#REQUIRES_USER_CONFIRMATION} state or the {@link AssetPackStatus#WAITING_FOR_WIFI} state.
+ *
+ * If the app has not been installed by Play, an update may be triggered to ensure that a valid version is installed. This
+ * will cause the app to restart and all asset requests to be cancelled. These assets should be requested again after the
+ * app restarts.
+ *
+ * The confirmation activity returns one of the following values:
+ *
+ * - {@link Activity#RESULT_OK Activity#RESULT_OK} if the user accepted.
+ *
- {@link Activity#RESULT_CANCELED Activity#RESULT_CANCELED} if the user denied or the dialog has been closed in any other way (e.g.
+ * backpress).
+ *
+ *
+ * @param activityResultLauncher an activityResultLauncher to launch the confirmation dialog.
+ * @return whether the confirmation dialog has been started.
+ */
+ boolean showConfirmationDialog(@NonNull ActivityResultLauncher activityResultLauncher);
+
+ /**
+ * Shows a dialog that asks the user for consent to download packs that are currently in either the
+ * {@link AssetPackStatus#REQUIRES_USER_CONFIRMATION} state or the {@link AssetPackStatus#WAITING_FOR_WIFI} state.
+ *
+ * If the app has not been installed by Play, an update may be triggered to ensure that a valid version is installed. This
+ * will cause the app to restart and all asset requests to be cancelled. These assets should be requested again after the
+ * app restarts.
+ *
+ * @param activity the activity on top of which the confirmation dialog is displayed. Use your current
+ * activity for this.
+ * @return A {@link Task} that completes once the dialog has been accepted, denied or closed. A successful task
+ * result contains one of the following values:
+ *
+ * - {@link Activity#RESULT_OK Activity#RESULT_OK} if the user accepted.
+ *
- {@link Activity#RESULT_CANCELED Activity#RESULT_CANCELED} if the user denied or the dialog has been closed in any other way (e.g.
+ * backpress).
+ *
+ */
+ Task showConfirmationDialog(@NonNull Activity activity);
+
+ /**
+ * Unregisters a listener previously added using {@link #registerListener}.
+ */
+ void unregisterListener(@NonNull AssetPackStateUpdateListener listener);
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManagerFactory.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManagerFactory.java
new file mode 100644
index 0000000000..272d90efa5
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManagerFactory.java
@@ -0,0 +1,27 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks;
+
+import android.content.Context;
+import androidx.annotation.NonNull;
+
+/**
+ * Creates instances of {@link AssetPackManager}.
+ */
+public final class AssetPackManagerFactory {
+ private AssetPackManagerFactory() {
+ }
+
+ /**
+ * Creates an instance of {@link AssetPackManager}.
+ *
+ * @param applicationContext a fully initialized application context
+ */
+ @NonNull
+ public static AssetPackManager getInstance(Context applicationContext) {
+ return new AssetPackManagerImpl();
+ }
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManagerImpl.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManagerImpl.java
new file mode 100644
index 0000000000..4ee68d8c9e
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManagerImpl.java
@@ -0,0 +1,104 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks;
+
+import android.app.Activity;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.IntentSenderRequest;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import com.google.android.gms.tasks.Task;
+import com.google.android.play.core.assetpacks.model.AssetPackStatus;
+import org.microg.gms.common.Hide;
+
+import java.util.List;
+import java.util.Map;
+
+@Hide
+public class AssetPackManagerImpl implements AssetPackManager {
+ @Override
+ public AssetPackStates cancel(@NonNull List packNames) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearListeners() {
+ }
+
+ @Override
+ public Task fetch(List packNames) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Nullable
+ @Override
+ public AssetLocation getAssetLocation(@NonNull String packName, @NonNull String assetPath) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Nullable
+ @Override
+ public AssetPackLocation getPackLocation(@NonNull String packName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Map getPackLocations() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Task getPackStates(List packNames) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void registerListener(@NonNull AssetPackStateUpdateListener listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Task removePack(@NonNull String packName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean showCellularDataConfirmation(@NonNull ActivityResultLauncher activityResultLauncher) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Task showCellularDataConfirmation(@NonNull Activity activity) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean showConfirmationDialog(@NonNull ActivityResultLauncher activityResultLauncher) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Task showConfirmationDialog(@NonNull Activity activity) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unregisterListener(@NonNull AssetPackStateUpdateListener listener) {
+
+ }
+
+ public @AssetPackStatus int getLocalStatus(String packName, int remoteStatus) {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getTransferProgressPercentage(String packName) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getInstalledVersionTag(String packName) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackServiceClient.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackServiceClient.java
new file mode 100644
index 0000000000..321e86ea8a
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackServiceClient.java
@@ -0,0 +1,366 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import com.google.android.gms.tasks.Task;
+import com.google.android.gms.tasks.TaskCompletionSource;
+import com.google.android.play.core.assetpacks.model.AssetPackErrorCode;
+import com.google.android.play.core.assetpacks.model.AssetPackStatus;
+import com.google.android.play.core.assetpacks.protocol.*;
+import org.microg.gms.common.Hide;
+
+import java.util.*;
+
+@Hide
+public class AssetPackServiceClient {
+ private static final String TAG = "AssetPackServiceClient";
+ private List> pendingCalls = new ArrayList<>();
+ private Context context;
+ private AssetPackManagerImpl assetPackManager;
+
+ private interface PendingCall {
+ void execute(IAssetModuleService service, TaskCompletionSource completionSource) throws Exception;
+ }
+
+ private Task execute(PendingCall pendingCall) {
+ TaskCompletionSource completionSource = new TaskCompletionSource<>();
+ pendingCalls.add(completionSource);
+ try {
+ pendingCall.execute(null, completionSource);
+ } catch (Exception e) {
+ completionSource.trySetException(e);
+ }
+ Task task = completionSource.getTask();
+ task.addOnCompleteListener(ignored -> pendingCalls.remove(completionSource));
+ return task;
+ }
+
+ private Bundle getOptionsBundle() {
+ Bundle options = new Bundle();
+ // TODO
+ BundleKeys.put(options, BundleKeys.PLAY_CORE_VERSION_CODE, 20202);
+ BundleKeys.put(options, BundleKeys.SUPPORTED_COMPRESSION_FORMATS, new ArrayList<>(Arrays.asList(CompressionFormat.UNSPECIFIED, CompressionFormat.BROTLI)));
+ BundleKeys.put(options, BundleKeys.SUPPORTED_PATCH_FORMATS, new ArrayList<>(Arrays.asList(PatchFormat.PATCH_GDIFF, PatchFormat.GZIPPED_GDIFF)));
+ return options;
+ }
+
+ private ArrayList getModuleNameBundles(List packNames) {
+ ArrayList moduleNameBundles = new ArrayList<>();
+ for (String packName : packNames) {
+ Bundle arg = new Bundle();
+ BundleKeys.put(arg, BundleKeys.MODULE_NAME, packName);
+ moduleNameBundles.add(arg);
+ }
+ return moduleNameBundles;
+ }
+
+ private Bundle getInstalledAssetModulesBundle(Map installedAssetModules) {
+ Bundle installedAssetModulesBundle = getOptionsBundle();
+ ArrayList installedAssetModuleBundles = new ArrayList<>();
+ for (String moduleName : installedAssetModules.keySet()) {
+ Bundle installedAssetModuleBundle = new Bundle();
+ BundleKeys.put(installedAssetModuleBundle, BundleKeys.INSTALLED_ASSET_MODULE_NAME, moduleName);
+ BundleKeys.put(installedAssetModuleBundle, BundleKeys.INSTALLED_ASSET_MODULE_VERSION, installedAssetModules.get(moduleName));
+ installedAssetModuleBundles.add(installedAssetModuleBundle);
+ }
+ BundleKeys.put(installedAssetModulesBundle, BundleKeys.INSTALLED_ASSET_MODULE, installedAssetModuleBundles);
+ return installedAssetModulesBundle;
+ }
+
+ private Bundle getSessionIdentifierBundle(int sessionId) {
+ Bundle sessionIdentifierBundle = new Bundle();
+ BundleKeys.put(sessionIdentifierBundle, BundleKeys.SESSION_ID, sessionId);
+ return sessionIdentifierBundle;
+ }
+
+ private Bundle getModuleIdentifierBundle(int sessionId, String moduleName) {
+ Bundle moduleIdentifierBundle = getSessionIdentifierBundle(sessionId);
+ BundleKeys.put(moduleIdentifierBundle, BundleKeys.MODULE_NAME, moduleName);
+ return moduleIdentifierBundle;
+ }
+
+ private Bundle getChunkIdentifierBundle(int sessionId, String moduleName, String sliceId, int chunkNumber) {
+ Bundle chunkIdentifierBundle = getModuleIdentifierBundle(sessionId, moduleName);
+ BundleKeys.put(chunkIdentifierBundle, BundleKeys.SLICE_ID, sliceId);
+ BundleKeys.put(chunkIdentifierBundle, BundleKeys.CHUNK_NUMBER, chunkNumber);
+ return chunkIdentifierBundle;
+ }
+
+ public Task getChunkFileDescriptor(int sessionId, String moduleName, String sliceId, int chunkNumber) {
+ return execute((service, completionSource) -> {
+ service.getChunkFileDescriptor(context.getPackageName(), getChunkIdentifierBundle(sessionId, moduleName, sliceId, chunkNumber), getOptionsBundle(), new BaseCallback(completionSource) {
+ @Override
+ public void onGetChunkFileDescriptor(ParcelFileDescriptor chunkFileDescriptor) {
+ completionSource.trySetResult(chunkFileDescriptor);
+ }
+ });
+ });
+ }
+
+ public Task getPackStates(List packNames, Map installedAssetModules) {
+ return execute((service, completionSource) -> {
+ service.requestDownloadInfo(context.getPackageName(), getModuleNameBundles(packNames), getInstalledAssetModulesBundle(installedAssetModules), new BaseCallback(completionSource) {
+ @Override
+ public void onRequestDownloadInfo(Bundle bundle, Bundle bundle2) {
+ completionSource.trySetResult(AssetPackStatesImpl.fromBundle(bundle, assetPackManager));
+ }
+ });
+ });
+ }
+
+ public Task startDownload(List packNames, Map installedAssetModules) {
+ Task task = execute((service, completionSource) -> {
+ service.startDownload(context.getPackageName(), getModuleNameBundles(packNames), getInstalledAssetModulesBundle(installedAssetModules), new BaseCallback(completionSource) {
+ @Override
+ public void onStartDownload(int status, Bundle bundle) {
+ completionSource.trySetResult(AssetPackStatesImpl.fromBundle(bundle, assetPackManager, true));
+ }
+ });
+ });
+ task.addOnSuccessListener(ignored -> keepAlive());
+ return task;
+ }
+
+ public Task> syncPacks(Map installedAssetModules) {
+ return execute((service, completionSource) -> {
+ service.getSessionStates(context.getPackageName(), getInstalledAssetModulesBundle(installedAssetModules), new BaseCallback(completionSource) {
+ @Override
+ public void onGetSessionStates(List list) {
+ ArrayList packNames = new ArrayList<>();
+ for (Bundle bundle : list) {
+ Collection packStates = AssetPackStatesImpl.fromBundle(bundle, assetPackManager, true).packStates().values();
+ if (!packStates.isEmpty()) {
+ AssetPackState state = packStates.iterator().next();
+ switch (state.status()) {
+ case AssetPackStatus.PENDING:
+ case AssetPackStatus.DOWNLOADING:
+ case AssetPackStatus.TRANSFERRING:
+ case AssetPackStatus.WAITING_FOR_WIFI:
+ case AssetPackStatus.REQUIRES_USER_CONFIRMATION:
+ packNames.add(state.name());
+ }
+ }
+ }
+ completionSource.trySetResult(packNames);
+ }
+ });
+ });
+ }
+
+ public void cancelDownloads(List packNames) {
+ execute((service, completionSource) -> {
+ service.cancelDownloads(context.getPackageName(), getModuleNameBundles(packNames), getOptionsBundle(), new BaseCallback(completionSource) {
+ @Override
+ public void onCancelDownloads() {
+ completionSource.trySetResult(null);
+ }
+ });
+ });
+ }
+
+ public void keepAlive() {
+ // TODO
+ }
+
+ public void notifyChunkTransferred(int sessionId, String moduleName, String sliceId, int chunkNumber) {
+ execute((service, completionSource) -> {
+ service.notifyChunkTransferred(context.getPackageName(), getChunkIdentifierBundle(sessionId, moduleName, sliceId, chunkNumber), getOptionsBundle(), new BaseCallback(completionSource) {
+ @Override
+ public void onNotifyChunkTransferred(int sessionId, String moduleName, String sliceId, int chunkNumber) {
+ completionSource.trySetResult(null);
+ }
+ });
+ });
+ }
+
+ public void notifyModuleCompleted(int sessionId, String moduleName) {
+ notifyModuleCompleted(sessionId, moduleName, 10);
+ }
+
+ public void notifyModuleCompleted(int sessionId, String moduleName, int maxRetries) {
+ execute((service, completionSource) -> {
+ service.notifyModuleCompleted(context.getPackageName(), getModuleIdentifierBundle(sessionId, moduleName), getOptionsBundle(), new BaseCallback(completionSource) {
+ @Override
+ public void onError(int errorCode) {
+ if (maxRetries > 0) {
+ notifyModuleCompleted(sessionId, moduleName, maxRetries - 1);
+ }
+ }
+ });
+ });
+ }
+
+ public void notifySessionFailed(int sessionId) {
+ execute((service, completionSource) -> {
+ service.notifySessionFailed(context.getPackageName(), getSessionIdentifierBundle(sessionId), getOptionsBundle(), new BaseCallback(completionSource) {
+ @Override
+ public void onNotifySessionFailed(int sessionId) {
+ completionSource.trySetResult(null);
+ }
+ });
+ });
+ }
+
+ public void removePack(String packName) {
+ execute((service, completionSource) -> {
+ service.removeModule(context.getPackageName(), getModuleIdentifierBundle(0, packName), getOptionsBundle(), new BaseCallback(completionSource) {
+ @Override
+ public void onRemoveModule() {
+ completionSource.trySetResult(null);
+ }
+ });
+ });
+ }
+
+ private static class BaseCallback extends IAssetModuleServiceCallback.Stub {
+ @NonNull
+ private final TaskCompletionSource> completionSource;
+
+ public BaseCallback(@NonNull TaskCompletionSource> completionSource) {
+ this.completionSource = completionSource;
+ }
+
+ @Override
+ public void onStartDownload(int sessionId, Bundle bundle) {
+ Log.i(TAG, "onStartDownload(" + sessionId + ")");
+ onStartDownload(sessionId);
+ }
+
+ public void onStartDownload(int sessionId) {
+ completionSource.trySetException(new Exception("Unexpected callback: onStartDownload"));
+ }
+
+ @Override
+ public void onCancelDownload(int status, Bundle bundle) {
+ Log.i(TAG, "onCancelDownload(" + status + ")");
+ onCancelDownload(status);
+ }
+
+ public void onCancelDownload(int status) {
+ completionSource.trySetException(new Exception("Unexpected callback: onCancelDownload"));
+ }
+
+ @Override
+ public void onGetSession(int status, Bundle bundle) {
+ Log.i(TAG, "onGetSession(" + status + ")");
+ onGetSession(status);
+ }
+
+ public void onGetSession(int status) {
+ completionSource.trySetException(new Exception("Unexpected callback: onGetSession"));
+ }
+
+ @Override
+ public void onGetSessionStates(List list) {
+ completionSource.trySetException(new Exception("Unexpected callback: onGetSessionStates"));
+ }
+
+ @Override
+ public void onNotifyChunkTransferred(Bundle bundle, Bundle bundle2) {
+ int sessionId = BundleKeys.get(bundle, BundleKeys.SESSION_ID, 0);
+ String moduleName = BundleKeys.get(bundle, BundleKeys.MODULE_NAME);
+ String sliceId = BundleKeys.get(bundle, BundleKeys.SLICE_ID);
+ int chunkNumber = BundleKeys.get(bundle, BundleKeys.CHUNK_NUMBER, 0);
+ Log.i(TAG, "onNotifyChunkTransferred(" + sessionId + ", " + moduleName + ", " + sliceId + ", " + chunkNumber + ")");
+ onNotifyChunkTransferred(sessionId, moduleName, sliceId, chunkNumber);
+ }
+
+ public void onNotifyChunkTransferred(int sessionId, String moduleName, String sliceId, int chunkNumber) {
+ completionSource.trySetException(new Exception("Unexpected callback: onNotifyChunkTransferred"));
+ }
+
+ @Override
+ public void onError(Bundle bundle) {
+ int errorCode = BundleKeys.get(bundle, BundleKeys.ERROR_CODE, AssetPackErrorCode.INTERNAL_ERROR);
+ onError(errorCode);
+ }
+
+ public void onError(int errorCode) {
+ completionSource.trySetException(new AssetPackException(errorCode));
+ }
+
+ @Override
+ public void onNotifyModuleCompleted(Bundle bundle, Bundle bundle2) {
+ int sessionId = BundleKeys.get(bundle, BundleKeys.SESSION_ID, 0);
+ String moduleName = BundleKeys.get(bundle, BundleKeys.MODULE_NAME);
+ Log.i(TAG, "onNotifyModuleCompleted(" + sessionId + ", " + moduleName + ")");
+ onNotifyModuleCompleted(sessionId, moduleName);
+ }
+
+ public void onNotifyModuleCompleted(int sessionId, String moduleName) {
+ completionSource.trySetException(new Exception("Unexpected callback: onNotifyModuleCompleted"));
+ }
+
+ @Override
+ public void onNotifySessionFailed(Bundle bundle) {
+ int sessionId = BundleKeys.get(bundle, BundleKeys.SESSION_ID, 0);
+ Log.i(TAG, "onNotifySessionFailed(" + sessionId + ")");
+ onNotifySessionFailed(sessionId);
+ }
+
+ public void onNotifySessionFailed(int sessionId) {
+ completionSource.trySetException(new Exception("Unexpected callback: onNotifySessionFailed"));
+ }
+
+ @Override
+ public void onKeepAlive(Bundle bundle, Bundle bundle2) {
+ boolean keepAlive = BundleKeys.get(bundle, BundleKeys.KEEP_ALIVE, false);
+ Log.i(TAG, "onKeepAlive(" + keepAlive + ")");
+ onKeepAlive(keepAlive);
+ }
+
+ public void onKeepAlive(boolean keepAlive) {
+ completionSource.trySetException(new Exception("Unexpected callback: onKeepAlive"));
+ }
+
+ @Override
+ public void onGetChunkFileDescriptor(Bundle bundle, Bundle bundle2) {
+ ParcelFileDescriptor chunkFileDescriptor = BundleKeys.get(bundle, BundleKeys.CHUNK_FILE_DESCRIPTOR);
+ Log.i(TAG, "onGetChunkFileDescriptor(...)");
+ onGetChunkFileDescriptor(chunkFileDescriptor);
+ }
+
+ public void onGetChunkFileDescriptor(ParcelFileDescriptor chunkFileDescriptor) {
+ completionSource.trySetException(new Exception("Unexpected callback: onGetChunkFileDescriptor"));
+ }
+
+ @Override
+ public void onRequestDownloadInfo(Bundle bundle, Bundle bundle2) {
+ Log.i(TAG, "onRequestDownloadInfo()");
+ onRequestDownloadInfo();
+ }
+
+ public void onRequestDownloadInfo() {
+ completionSource.trySetException(new Exception("Unexpected callback: onRequestDownloadInfo"));
+ }
+
+ @Override
+ public void onRemoveModule(Bundle bundle, Bundle bundle2) {
+ Log.i(TAG, "onRemoveModule()");
+ onRemoveModule();
+ }
+
+ public void onRemoveModule() {
+ completionSource.trySetException(new Exception("Unexpected callback: onRemoveModule"));
+ }
+
+ @Override
+ public void onCancelDownloads(Bundle bundle) {
+ Log.i(TAG, "onCancelDownload()");
+ onCancelDownloads();
+ }
+
+ public void onCancelDownloads() {
+ completionSource.trySetException(new Exception("Unexpected callback: onCancelDownloads"));
+ }
+ }
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackState.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackState.java
new file mode 100644
index 0000000000..c7028e9666
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackState.java
@@ -0,0 +1,65 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks;
+
+import com.google.android.play.core.assetpacks.model.AssetPackErrorCode;
+import com.google.android.play.core.assetpacks.model.AssetPackStatus;
+import com.google.android.play.core.assetpacks.model.AssetPackUpdateAvailability;
+
+/**
+ * The state of an individual asset pack.
+ */
+public abstract class AssetPackState {
+ public abstract String availableVersionTag();
+
+ /**
+ * Returns the total number of bytes already downloaded for the pack.
+ */
+ public abstract long bytesDownloaded();
+
+ /**
+ * Returns the error code for the pack, if Play has failed to download the pack. Returns
+ * {@link AssetPackErrorCode#NO_ERROR} if the download was successful or is in progress or has not been attempted.
+ *
+ * @return A value from {@link AssetPackErrorCode}.
+ */
+ @AssetPackErrorCode
+ public abstract int errorCode();
+
+ public abstract String installedVersionTag();
+
+ /**
+ * Returns the name of the pack.
+ */
+ public abstract String name();
+
+ /**
+ * Returns the download status of the pack.
+ *
+ * If the pack has never been requested before its status is {@link AssetPackStatus#UNKNOWN}.
+ *
+ * @return a value from {@link AssetPackStatus}
+ */
+ @AssetPackStatus
+ public abstract int status();
+
+ /**
+ * Returns the total size of the pack in bytes.
+ */
+ public abstract long totalBytesToDownload();
+
+ /**
+ * Returns the percentage of the asset pack already transferred to the app.
+ *
+ * This value is only defined when the status is {@link AssetPackStatus#TRANSFERRING}.
+ *
+ * @return a value between 0 and 100 inclusive.
+ */
+ public abstract int transferProgressPercentage();
+
+ @AssetPackUpdateAvailability
+ public abstract int updateAvailability();
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStateImpl.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStateImpl.java
new file mode 100644
index 0000000000..18e1c81ee5
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStateImpl.java
@@ -0,0 +1,130 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks;
+
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import com.google.android.play.core.assetpacks.model.AssetPackErrorCode;
+import com.google.android.play.core.assetpacks.model.AssetPackStatus;
+import com.google.android.play.core.assetpacks.model.AssetPackUpdateAvailability;
+import com.google.android.play.core.assetpacks.protocol.BundleKeys;
+import org.microg.gms.common.Hide;
+import org.microg.gms.utils.ToStringHelper;
+
+@Hide
+public class AssetPackStateImpl extends AssetPackState {
+ private final String name;
+ private final @AssetPackStatus int status;
+ private final @AssetPackErrorCode int errorCode;
+ private final long bytesDownloaded;
+ private final long totalBytesToDownload;
+ private final int transferProgressPercentage;
+ @AssetPackUpdateAvailability
+ private final int updateAvailability;
+ private final String availableVersionTag;
+ private final String installedVersionTag;
+
+ public AssetPackStateImpl(String name, @AssetPackStatus int status, @AssetPackErrorCode int errorCode, long bytesDownloaded, long totalBytesToDownload, int transferProgressPercentage, @AssetPackUpdateAvailability int updateAvailability, String availableVersionTag, String installedVersionTag) {
+ this.name = name;
+ this.status = status;
+ this.errorCode = errorCode;
+ this.bytesDownloaded = bytesDownloaded;
+ this.totalBytesToDownload = totalBytesToDownload;
+ this.transferProgressPercentage = transferProgressPercentage;
+ this.updateAvailability = updateAvailability;
+ this.availableVersionTag = availableVersionTag;
+ this.installedVersionTag = installedVersionTag;
+ }
+
+ @NonNull
+ public static AssetPackState fromBundle(Bundle bundle, @NonNull String name, AssetPackManagerImpl assetPackManager) {
+ return fromBundle(bundle, name, assetPackManager, false);
+ }
+
+ @NonNull
+ public static AssetPackState fromBundle(Bundle bundle, @NonNull String name, AssetPackManagerImpl assetPackManager, boolean ignoreLocalStatus) {
+ @AssetPackStatus int remoteStatus = BundleKeys.get(bundle, BundleKeys.STATUS, name, 0);
+ @AssetPackStatus int status = ignoreLocalStatus ? remoteStatus : assetPackManager.getLocalStatus(name, remoteStatus);
+ @AssetPackErrorCode int errorCode = BundleKeys.get(bundle, BundleKeys.ERROR_CODE, name, 0);
+ long bytesDownloaded = BundleKeys.get(bundle, BundleKeys.BYTES_DOWNLOADED, name, 0L);
+ long totalBytesToDownload = BundleKeys.get(bundle, BundleKeys.TOTAL_BYTES_TO_DOWNLOAD, name, 0L);
+ int transferProgressPercentage = assetPackManager.getTransferProgressPercentage(name);
+ long packVersion = BundleKeys.get(bundle, BundleKeys.PACK_VERSION, name, 0L);
+ long packBaseVersion = BundleKeys.get(bundle, BundleKeys.PACK_BASE_VERSION, name, 0L);
+ int appVersionCode = BundleKeys.get(bundle, BundleKeys.APP_VERSION_CODE, 0);
+ String availableVersionTag = BundleKeys.get(bundle, BundleKeys.PACK_VERSION_TAG, name, Integer.toString(appVersionCode));
+ String installedVersionTag = assetPackManager.getInstalledVersionTag(name);
+ int updateAvailability = AssetPackUpdateAvailability.UPDATE_NOT_AVAILABLE;
+ if (status == AssetPackStatus.COMPLETED && packBaseVersion != 0 && packBaseVersion != packVersion) {
+ updateAvailability = AssetPackUpdateAvailability.UPDATE_AVAILABLE;
+ }
+ return new AssetPackStateImpl(name, status, errorCode, bytesDownloaded, totalBytesToDownload, transferProgressPercentage, updateAvailability, availableVersionTag, installedVersionTag);
+ }
+
+ @Override
+ public String availableVersionTag() {
+ return availableVersionTag;
+ }
+
+ @Override
+ public long bytesDownloaded() {
+ return bytesDownloaded;
+ }
+
+ @Override
+ @AssetPackErrorCode
+ public int errorCode() {
+ return errorCode;
+ }
+
+ @Override
+ public String installedVersionTag() {
+ return installedVersionTag;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ @AssetPackStatus
+ public int status() {
+ return status;
+ }
+
+ @Override
+ public long totalBytesToDownload() {
+ return totalBytesToDownload;
+ }
+
+ @Override
+ public int transferProgressPercentage() {
+ return transferProgressPercentage;
+ }
+
+ @Override
+ @AssetPackUpdateAvailability
+ public int updateAvailability() {
+ return updateAvailability;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return ToStringHelper.name("AssetPackState")
+ .field("name", name)
+ .field("status", status)
+ .field("errorCode", errorCode)
+ .field("bytesDownloaded", bytesDownloaded)
+ .field("totalBytesToDownload", totalBytesToDownload)
+ .field("transferProgressPercentage", transferProgressPercentage)
+ .field("updateAvailability", updateAvailability)
+ .field("availableVersionTag", availableVersionTag)
+ .field("installedVersionTag", installedVersionTag)
+ .end();
+ }
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStateUpdateListener.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStateUpdateListener.java
new file mode 100644
index 0000000000..1193f59aed
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStateUpdateListener.java
@@ -0,0 +1,14 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks;
+
+import com.google.android.play.core.listener.StateUpdateListener;
+
+/**
+ * Listener that may be registered for updates on the state of the download of asset packs.
+ */
+public interface AssetPackStateUpdateListener extends StateUpdateListener {
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStates.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStates.java
new file mode 100644
index 0000000000..2fc5332c17
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStates.java
@@ -0,0 +1,23 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks;
+
+import java.util.Map;
+
+/**
+ * Contains the state for all requested packs.
+ */
+public abstract class AssetPackStates {
+ /**
+ * Returns a map from a pack's name to its state.
+ */
+ public abstract Map packStates();
+
+ /**
+ * Returns total size of all requested packs in bytes.
+ */
+ public abstract long totalBytes();
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStatesImpl.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStatesImpl.java
new file mode 100644
index 0000000000..445a1cc993
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStatesImpl.java
@@ -0,0 +1,65 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks;
+
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import com.google.android.play.core.assetpacks.protocol.BundleKeys;
+import org.microg.gms.common.Hide;
+import org.microg.gms.utils.ToStringHelper;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+@Hide
+public class AssetPackStatesImpl extends AssetPackStates {
+ private final long totalBytes;
+ @NonNull
+ private final Map packStates;
+
+ public AssetPackStatesImpl(long totalBytes, @NonNull Map packStates) {
+ this.totalBytes = totalBytes;
+ this.packStates = packStates;
+ }
+
+ public static AssetPackStates fromBundle(@NonNull Bundle bundle, @NonNull AssetPackManagerImpl assetPackManager) {
+ return fromBundle(bundle, assetPackManager, false);
+ }
+
+ @NonNull
+ public static AssetPackStates fromBundle(@NonNull Bundle bundle, @NonNull AssetPackManagerImpl assetPackManager, boolean ignoreLocalStatus) {
+ long totalBytes = BundleKeys.get(bundle, BundleKeys.TOTAL_BYTES_TO_DOWNLOAD, 0L);
+ ArrayList packNames = BundleKeys.get(bundle, BundleKeys.PACK_NAMES);
+ Map packStates = new HashMap<>();
+ if (packNames != null) {
+ for (String packName : packNames) {
+ packStates.put(packName, AssetPackStateImpl.fromBundle(bundle, packName, assetPackManager, ignoreLocalStatus));
+ }
+ }
+ return new AssetPackStatesImpl(totalBytes, packStates);
+ }
+
+ @Override
+ @NonNull
+ public Map packStates() {
+ return packStates;
+ }
+
+ @Override
+ public long totalBytes() {
+ return totalBytes;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return ToStringHelper.name("AssetPackStates")
+ .field("totalBytes", totalBytes)
+ .field("packStates", packStates)
+ .end();
+ }
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackErrorCode.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackErrorCode.java
new file mode 100644
index 0000000000..65f8b294e9
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackErrorCode.java
@@ -0,0 +1,85 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks.model;
+
+import android.app.Activity;
+import androidx.annotation.IntDef;
+import com.google.android.play.core.assetpacks.AssetPackManager;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Error codes for the download of an asset pack.
+ */
+@Target({ElementType.TYPE_USE})
+@Retention(RetentionPolicy.CLASS)
+@IntDef({AssetPackErrorCode.NO_ERROR, AssetPackErrorCode.APP_UNAVAILABLE, AssetPackErrorCode.PACK_UNAVAILABLE, AssetPackErrorCode.INVALID_REQUEST, AssetPackErrorCode.DOWNLOAD_NOT_FOUND, AssetPackErrorCode.API_NOT_AVAILABLE, AssetPackErrorCode.NETWORK_ERROR, AssetPackErrorCode.ACCESS_DENIED, AssetPackErrorCode.INSUFFICIENT_STORAGE, AssetPackErrorCode.APP_NOT_OWNED, AssetPackErrorCode.PLAY_STORE_NOT_FOUND, AssetPackErrorCode.NETWORK_UNRESTRICTED, AssetPackErrorCode.CONFIRMATION_NOT_REQUIRED, AssetPackErrorCode.UNRECOGNIZED_INSTALLATION, AssetPackErrorCode.INTERNAL_ERROR})
+public @interface AssetPackErrorCode {
+ int NO_ERROR = 0;
+ /**
+ * The requesting app is unavailable.
+ */
+ int APP_UNAVAILABLE = -1;
+ /**
+ * The requested asset pack isn't available.
+ *
+ * This can happen if the asset pack wasn't included in the Android App Bundle that was published to the Play Store.
+ */
+ int PACK_UNAVAILABLE = -2;
+ /**
+ * The request is invalid.
+ */
+ int INVALID_REQUEST = -3;
+ /**
+ * The requested download isn't found.
+ */
+ int DOWNLOAD_NOT_FOUND = -4;
+ /**
+ * The Asset Delivery API isn't available.
+ */
+ int API_NOT_AVAILABLE = -5;
+ /**
+ * Network error. Unable to obtain the asset pack details.
+ */
+ int NETWORK_ERROR = -6;
+ /**
+ * Download not permitted under the current device circumstances (e.g. in background).
+ */
+ int ACCESS_DENIED = -7;
+ /**
+ * Asset pack download failed due to insufficient storage.
+ */
+ int INSUFFICIENT_STORAGE = -10;
+ /**
+ * The Play Store app is either not installed or not the official version.
+ */
+ int PLAY_STORE_NOT_FOUND = -11;
+ /**
+ * Returned if {@link AssetPackManager#showCellularDataConfirmation(Activity)} is called but no asset packs are
+ * waiting for Wi-Fi.
+ */
+ int NETWORK_UNRESTRICTED = -12;
+ /**
+ * The app isn't owned by any user on this device. An app is "owned" if it has been installed via the Play Store.
+ */
+ int APP_NOT_OWNED = -13;
+ /**
+ * Returned if {@link AssetPackManager#showConfirmationDialog(Activity)} is called but no asset packs require user
+ * confirmation.
+ */
+ int CONFIRMATION_NOT_REQUIRED = -14;
+ /**
+ * The installed app version is not recognized by Play. This can happen if the app was not installed by Play.
+ */
+ int UNRECOGNIZED_INSTALLATION = -15;
+ /**
+ * Unknown error downloading an asset pack.
+ */
+ int INTERNAL_ERROR = -100;
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackStatus.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackStatus.java
new file mode 100644
index 0000000000..d6f7d2c174
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackStatus.java
@@ -0,0 +1,71 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks.model;
+
+import android.app.Activity;
+import androidx.annotation.IntDef;
+import com.google.android.play.core.assetpacks.AssetPackManager;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Status of the download of an asset pack.
+ */
+@Target({ElementType.TYPE_USE})
+@Retention(RetentionPolicy.CLASS)
+@IntDef({AssetPackStatus.UNKNOWN, AssetPackStatus.PENDING, AssetPackStatus.DOWNLOADING, AssetPackStatus.TRANSFERRING, AssetPackStatus.COMPLETED, AssetPackStatus.FAILED, AssetPackStatus.CANCELED, AssetPackStatus.WAITING_FOR_WIFI, AssetPackStatus.NOT_INSTALLED, AssetPackStatus.REQUIRES_USER_CONFIRMATION})
+public @interface AssetPackStatus {
+ /**
+ * The asset pack state is unknown.
+ */
+ int UNKNOWN = 0;
+ /**
+ * The asset pack download is pending and will be processed soon.
+ */
+ int PENDING = 1;
+ /**
+ * The asset pack download is in progress.
+ */
+ int DOWNLOADING = 2;
+ /**
+ * The asset pack is being decompressed and copied (or patched) to the app's internal storage.
+ */
+ int TRANSFERRING = 3;
+ /**
+ * The asset pack download and transfer is complete; the assets are available to the app.
+ */
+ int COMPLETED = 4;
+ /**
+ * The asset pack download or transfer has failed.
+ */
+ int FAILED = 5;
+ /**
+ * The asset pack download has been canceled by the user through the Play Store or the download notification.
+ */
+ int CANCELED = 6;
+ /**
+ * The asset pack download is waiting for Wi-Fi to become available before proceeding.
+ *
+ * The app can ask the user to download a session that is waiting for Wi-Fi over cellular data by using
+ * {@link AssetPackManager#showCellularDataConfirmation(Activity)}.
+ */
+ int WAITING_FOR_WIFI = 7;
+ /**
+ * The asset pack is not installed.
+ */
+ int NOT_INSTALLED = 8;
+ /**
+ * The asset pack requires user consent to be downloaded.
+ *
+ * This can happen if the current app version was not installed by Play.
+ *
+ * If the asset pack is also waiting for Wi-Fi, this state takes precedence.
+ */
+ int REQUIRES_USER_CONFIRMATION = 9;
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackStorageMethod.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackStorageMethod.java
new file mode 100644
index 0000000000..a3f94502db
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackStorageMethod.java
@@ -0,0 +1,34 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks.model;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Method used to store an asset pack.
+ */
+@Target({ElementType.TYPE_USE})
+@Retention(RetentionPolicy.CLASS)
+@IntDef({AssetPackStorageMethod.STORAGE_FILES, AssetPackStorageMethod.APK_ASSETS})
+public @interface AssetPackStorageMethod {
+ /**
+ * The asset pack is extracted into a folder containing individual asset files.
+ *
+ * Assets contained by this asset pack can be accessed via standard File APIs.
+ */
+ int STORAGE_FILES = 0;
+ /**
+ * The asset pack is installed as APKs containing asset files.
+ *
+ * Assets contained by this asset pack can be accessed via Asset Manager.
+ */
+ int APK_ASSETS = 1;
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackUpdateAvailability.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackUpdateAvailability.java
new file mode 100644
index 0000000000..faff47315f
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackUpdateAvailability.java
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks.model;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE_USE})
+@Retention(RetentionPolicy.CLASS)
+@IntDef({AssetPackUpdateAvailability.UNKNOWN, AssetPackUpdateAvailability.UPDATE_NOT_AVAILABLE, AssetPackUpdateAvailability.UPDATE_AVAILABLE})
+public @interface AssetPackUpdateAvailability {
+ int UNKNOWN = 0;
+ int UPDATE_NOT_AVAILABLE = 1;
+ int UPDATE_AVAILABLE = 2;
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/BroadcastConstants.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/BroadcastConstants.java
new file mode 100644
index 0000000000..399d566c73
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/BroadcastConstants.java
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks.protocol;
+
+import org.microg.gms.common.Hide;
+
+@Hide
+public class BroadcastConstants {
+ public static String ACTION_SESSION_UPDATE = "com.google.android.play.core.assetpacks.receiver.ACTION_SESSION_UPDATE";
+ public static String EXTRA_SESSION_STATE = "com.google.android.play.core.assetpacks.receiver.EXTRA_SESSION_STATE";
+ public static String EXTRA_FLAGS = "com.google.android.play.core.FLAGS";
+ public static String KEY_USING_EXTRACTOR_STREAM = "usingExtractorStream";
+}
diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/BundleKeys.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/BundleKeys.java
new file mode 100644
index 0000000000..0abba2f3b8
--- /dev/null
+++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/BundleKeys.java
@@ -0,0 +1,448 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.play.core.assetpacks.protocol;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.os.BundleCompat;
+import org.microg.gms.common.Hide;
+
+import java.util.ArrayList;
+
+@Hide
+public final class BundleKeys {
+ public static RootKey APP_VERSION_CODE = new RootKey.Int("app_version_code");
+ public static RootKey CHUNK_NUMBER = new RootKey.Int("chunk_number");
+ public static RootKey CHUNK_FILE_DESCRIPTOR = new RootKey.Parcelable<>("chunk_file_descriptor", ParcelFileDescriptor.class);
+ public static RootKey KEEP_ALIVE = new RootKey.Bool("keep_alive");
+ public static RootKey MODULE_NAME = new RootKey.String("module_name");
+ public static RootKey SLICE_ID = new RootKey.String("slice_id");
+ public static RootKey> PACK_NAMES = new RootKey.StringArrayList("pack_names");
+
+ // OptionsBundle
+ public static RootKey PLAY_CORE_VERSION_CODE = new RootKey.Int("playcore_version_code");
+ public static RootKey> SUPPORTED_COMPRESSION_FORMATS = new RootKey.IntArrayList("supported_compression_formats");
+ public static RootKey> SUPPORTED_PATCH_FORMATS = new RootKey.IntArrayList("supported_patch_formats");
+
+ // InstalledAssetModulesBundle
+ public static RootKey> INSTALLED_ASSET_MODULE = new RootKey.ParcelableArrayList<>("installed_asset_module", Bundle.class);
+ public static RootKey INSTALLED_ASSET_MODULE_NAME = new RootKey.String("installed_asset_module_name");
+ public static RootKey INSTALLED_ASSET_MODULE_VERSION = new RootKey.Long("installed_asset_module_version");
+
+ public static RootAndPackKey SESSION_ID = new RootAndPackKey.Int("session_id");
+ public static RootAndPackKey STATUS = new RootAndPackKey.Int("status");
+ public static RootAndPackKey ERROR_CODE = new RootAndPackKey.Int("error_code");
+ public static RootAndPackKey BYTES_DOWNLOADED = new RootAndPackKey.Long("bytes_downloaded");
+ public static RootAndPackKey TOTAL_BYTES_TO_DOWNLOAD = new RootAndPackKey.Long("total_bytes_to_download");
+
+ public static PackKey PACK_VERSION = new PackKey.Long("pack_version");
+ public static PackKey PACK_BASE_VERSION = new PackKey.Long("pack_base_version");
+ public static PackKey PACK_VERSION_TAG = new PackKey.String("pack_version_tag");
+ public static PackKey> SLICE_IDS = new PackKey.StringArrayList("slice_ids");
+
+ public static SliceKey> CHUNK_INTENTS = new SliceKey.ParcelableArrayList<>("chunk_intents", Intent.class);
+ public static SliceKey<@CompressionFormat Integer> COMPRESSION_FORMAT = new SliceKey.Int("compression_format");
+ public static SliceKey<@PatchFormat Integer> PATCH_FORMAT = new SliceKey.Int("patch_format");
+ public static SliceKey UNCOMPRESSED_HASH_SHA256 = new SliceKey.String("uncompressed_hash_sha256");
+ public static SliceKey UNCOMPRESSED_SIZE = new SliceKey.Long("uncompressed_size");
+
+ private BundleKeys() {
+ }
+
+ @Nullable
+ public static T get(Bundle bundle, @NonNull RootKey key) {
+ return key.get(bundle, key.baseKey());
+ }
+
+ public static T get(Bundle bundle, @NonNull RootKey key, T def) {
+ return key.get(bundle, key.baseKey(), def);
+ }
+
+ public static void put(Bundle bundle, @NonNull RootKey key, T value) {
+ key.put(bundle, key.baseKey(), value);
+ }
+
+ @Nullable
+ public static T get(Bundle bundle, @NonNull PackKey key, String packName) {
+ return key.get(bundle, packKey(packName, key.baseKey()));
+ }
+
+ public static T get(Bundle bundle, @NonNull PackKey key, String packName, T def) {
+ return key.get(bundle, packKey(packName, key.baseKey()), def);
+ }
+
+ public static void put(Bundle bundle, @NonNull PackKey key, String packName, T value) {
+ key.put(bundle, packKey(packName, key.baseKey()), value);
+ }
+
+ @Nullable
+ public static T get(Bundle bundle, @NonNull SliceKey key, String packName, String sliceId) {
+ return key.get(bundle, sliceKey(packName, sliceId, key.baseKey()));
+ }
+
+ public static T get(Bundle bundle, @NonNull SliceKey key, String packName, String sliceId, T def) {
+ return key.get(bundle, sliceKey(packName, sliceId, key.baseKey()), def);
+ }
+
+ public static void put(Bundle bundle, @NonNull SliceKey key, String packName, String sliceId, T value) {
+ key.put(bundle, sliceKey(packName, sliceId, key.baseKey()), value);
+ }
+
+ @NonNull
+ private static String packKey(String packName, String baseKey) {
+ return baseKey + ":" + packName;
+ }
+
+ @NonNull
+ private static String sliceKey(String packName, String sliceId, String baseKey) {
+ return baseKey + ":" + packName + ":" + sliceId;
+ }
+
+ public interface TypedBundleKey {
+ @NonNull
+ java.lang.String baseKey();
+
+ @Nullable
+ T get(@NonNull Bundle bundle, @NonNull java.lang.String key);
+
+ T get(@NonNull Bundle bundle, @NonNull java.lang.String key, T def);
+
+ void put(@NonNull Bundle bundle, @NonNull java.lang.String key, T value);
+
+ abstract class Base implements TypedBundleKey {
+ @NonNull
+ public final java.lang.String baseKey;
+
+ public Base(@NonNull java.lang.String baseKey) {
+ this.baseKey = baseKey;
+ }
+
+ @NonNull
+ @Override
+ public java.lang.String baseKey() {
+ return baseKey;
+ }
+ }
+
+ class Int extends Base {
+
+ public Int(@NonNull java.lang.String key) {
+ super(key);
+ }
+
+ @Override
+ public Integer get(@NonNull Bundle bundle, @NonNull java.lang.String key) {
+ return bundle.getInt(key);
+ }
+
+ @Override
+ public Integer get(@NonNull Bundle bundle, @NonNull java.lang.String key, Integer def) {
+ return bundle.getInt(key, def);
+ }
+
+ @Override
+ public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, Integer value) {
+ bundle.putInt(key, value);
+ }
+ }
+
+ class Long extends Base {
+
+ public Long(@NonNull java.lang.String key) {
+ super(key);
+ }
+
+ @Override
+ public java.lang.Long get(@NonNull Bundle bundle, @NonNull java.lang.String key) {
+ return bundle.getLong(key);
+ }
+
+ @Override
+ public java.lang.Long get(@NonNull Bundle bundle, @NonNull java.lang.String key, java.lang.Long def) {
+ return bundle.getLong(key, def);
+ }
+
+ @Override
+ public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, java.lang.Long value) {
+ bundle.putLong(key, value);
+ }
+ }
+
+ class Bool extends Base {
+
+ public Bool(@NonNull java.lang.String key) {
+ super(key);
+ }
+
+ @Override
+ public Boolean get(@NonNull Bundle bundle, @NonNull java.lang.String key) {
+ return bundle.getBoolean(key);
+ }
+
+ @Override
+ public Boolean get(@NonNull Bundle bundle, @NonNull java.lang.String key, Boolean def) {
+ return bundle.getBoolean(key, def);
+ }
+
+ @Override
+ public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, Boolean value) {
+ bundle.putBoolean(key, value);
+ }
+ }
+
+ class String extends Base {
+
+ public String(@NonNull java.lang.String key) {
+ super(key);
+ }
+
+ @Override
+ public java.lang.String get(@NonNull Bundle bundle, @NonNull java.lang.String key) {
+ return bundle.getString(key);
+ }
+
+ @Override
+ public java.lang.String get(@NonNull Bundle bundle, @NonNull java.lang.String key, java.lang.String def) {
+ return bundle.getString(key, def);
+ }
+
+ @Override
+ public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, java.lang.String value) {
+ bundle.putString(key, value);
+ }
+ }
+
+ class Parcelable extends Base {
+ @NonNull
+ private final Class tClass;
+
+ public Parcelable(@NonNull java.lang.String key, @NonNull Class tClass) {
+ super(key);
+ this.tClass = tClass;
+ }
+
+ @Override
+ public T get(@NonNull Bundle bundle, @NonNull java.lang.String key) {
+ return BundleCompat.getParcelable(bundle, key, tClass);
+ }
+
+ @Override
+ public T get(@NonNull Bundle bundle, @NonNull java.lang.String key, T def) {
+ if (bundle.containsKey(key)) {
+ return BundleCompat.getParcelable(bundle, key, tClass);
+ } else {
+ return def;
+ }
+ }
+
+ @Override
+ public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, T value) {
+ bundle.putParcelable(key, value);
+ }
+ }
+
+ class StringArrayList extends Base> {
+ public StringArrayList(@NonNull java.lang.String key) {
+ super(key);
+ }
+
+ @Override
+ public ArrayList get(@NonNull Bundle bundle, @NonNull java.lang.String key) {
+ return bundle.getStringArrayList(key);
+ }
+
+ @Override
+ public ArrayList get(@NonNull Bundle bundle, @NonNull java.lang.String key, ArrayList def) {
+ if (bundle.containsKey(key)) {
+ return bundle.getStringArrayList(key);
+ } else {
+ return def;
+ }
+ }
+
+ @Override
+ public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, ArrayList value) {
+ bundle.putStringArrayList(key, value);
+ }
+ }
+
+ class IntArrayList extends Base> {
+ public IntArrayList(@NonNull java.lang.String key) {
+ super(key);
+ }
+
+ @Override
+ public ArrayList get(@NonNull Bundle bundle, @NonNull java.lang.String key) {
+ return bundle.getIntegerArrayList(key);
+ }
+
+ @Override
+ public ArrayList get(@NonNull Bundle bundle, @NonNull java.lang.String key, ArrayList def) {
+ if (bundle.containsKey(key)) {
+ return bundle.getIntegerArrayList(key);
+ } else {
+ return def;
+ }
+ }
+
+ @Override
+ public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, ArrayList value) {
+ bundle.putIntegerArrayList(key, value);
+ }
+ }
+
+ class ParcelableArrayList extends Base> {
+ @NonNull
+ private final Class tClass;
+
+ public ParcelableArrayList(@NonNull java.lang.String key, @NonNull Class tClass) {
+ super(key);
+ this.tClass = tClass;
+ }
+
+ @Override
+ public ArrayList get(@NonNull Bundle bundle, @NonNull java.lang.String key) {
+ return BundleCompat.getParcelableArrayList(bundle, key, tClass);
+ }
+
+ @Override
+ public ArrayList get(@NonNull Bundle bundle, @NonNull java.lang.String key, ArrayList def) {
+ if (bundle.containsKey(key)) {
+ return BundleCompat.getParcelableArrayList(bundle, key, tClass);
+ } else {
+ return def;
+ }
+ }
+
+ @Override
+ public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, ArrayList value) {
+ bundle.putParcelableArrayList(key, value);
+ }
+ }
+ }
+
+
+ public interface PackKey extends TypedBundleKey {
+ class Int extends TypedBundleKey.Int implements PackKey {
+ public Int(@NonNull java.lang.String key) {
+ super(key);
+ }
+ }
+
+ class Long extends TypedBundleKey.Long implements PackKey {
+ public Long(@NonNull java.lang.String key) {
+ super(key);
+ }
+ }
+
+ class String extends TypedBundleKey.String implements PackKey {
+ public String(@NonNull java.lang.String key) {
+ super(key);
+ }
+ }
+
+ class StringArrayList extends TypedBundleKey.StringArrayList implements PackKey> {
+ public StringArrayList(@NonNull java.lang.String key) {
+ super(key);
+ }
+ }
+ }
+
+ public interface SliceKey extends TypedBundleKey {
+ class Int extends TypedBundleKey.Int implements SliceKey {
+ public Int(@NonNull java.lang.String key) {
+ super(key);
+ }
+ }
+
+ class Long extends TypedBundleKey.Long implements SliceKey {
+ public Long(@NonNull java.lang.String key) {
+ super(key);
+ }
+ }
+
+ class String extends TypedBundleKey.String implements SliceKey {
+ public String(@NonNull java.lang.String key) {
+ super(key);
+ }
+ }
+
+ class ParcelableArrayList extends TypedBundleKey.ParcelableArrayList implements SliceKey> {
+ public ParcelableArrayList(@NonNull java.lang.String key, @NonNull Class tClass) {
+ super(key, tClass);
+ }
+ }
+ }
+
+ public interface RootKey extends TypedBundleKey {
+ class Int extends TypedBundleKey.Int implements RootKey {
+ public Int(@NonNull java.lang.String key) {
+ super(key);
+ }
+ }
+
+ class Long extends TypedBundleKey.Long implements RootKey {
+ public Long(@NonNull java.lang.String key) {
+ super(key);
+ }
+ }
+
+ class Bool extends TypedBundleKey.Bool implements RootKey {
+ public Bool(@NonNull java.lang.String key) {
+ super(key);
+ }
+ }
+
+ class String extends TypedBundleKey.String implements RootKey {
+ public String(@NonNull java.lang.String key) {
+ super(key);
+ }
+ }
+
+ class Parcelable extends TypedBundleKey.Parcelable