Merge "AudioService: fix A2DP device check when making device unavailable"
diff --git a/Android.bp b/Android.bp
index b349ce2..787b48b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -807,9 +807,11 @@
filegroup {
name: "dataloader_aidl",
srcs: [
+ "core/java/android/content/pm/DataLoaderParamsParcel.aidl",
+ "core/java/android/content/pm/FileSystemControlParcel.aidl",
"core/java/android/content/pm/IDataLoaderStatusListener.aidl",
- "core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl",
- "core/java/android/os/incremental/NamedParcelFileDescriptor.aidl",
+ "core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl",
+ "core/java/android/content/pm/NamedParcelFileDescriptor.aidl",
],
path: "core/java",
}
diff --git a/CleanSpec.mk b/CleanSpec.mk
index f94de29..b84e715 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -257,6 +257,8 @@
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java/com/google/android/mms)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/*-service.jar)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/service-statsd.jar)
+$(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_aidl-cpp-source/)
+$(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_manager_aidl-cpp-source/)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
# ******************************************************************
diff --git a/cmds/svc/src/com/android/commands/svc/DataCommand.java b/cmds/svc/src/com/android/commands/svc/DataCommand.java
index b4dbd1d..35510cf 100644
--- a/cmds/svc/src/com/android/commands/svc/DataCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/DataCommand.java
@@ -16,16 +16,12 @@
package com.android.commands.svc;
-/**
- * @deprecated Please use adb shell cmd phone data enabled/disable instead.
- */
-@Deprecated
+import android.os.ServiceManager;
+import android.os.RemoteException;
+import android.content.Context;
+import com.android.internal.telephony.ITelephony;
+
public class DataCommand extends Svc.Command {
-
- private static final String DECPRECATED_MESSAGE =
- "adb shell svc data enable/disable is deprecated;"
- + "please use adb shell cmd phone data enable/disable instead.";
-
public DataCommand() {
super("data");
}
@@ -37,10 +33,36 @@
public String longHelp() {
return shortHelp() + "\n"
+ "\n"
- + DECPRECATED_MESSAGE;
+ + "usage: svc data [enable|disable]\n"
+ + " Turn mobile data on or off.\n\n";
}
public void run(String[] args) {
- System.err.println(DECPRECATED_MESSAGE);
+ boolean validCommand = false;
+ if (args.length >= 2) {
+ boolean flag = false;
+ if ("enable".equals(args[1])) {
+ flag = true;
+ validCommand = true;
+ } else if ("disable".equals(args[1])) {
+ flag = false;
+ validCommand = true;
+ }
+ if (validCommand) {
+ ITelephony phoneMgr
+ = ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
+ try {
+ if (flag) {
+ phoneMgr.enableDataConnectivity();
+ } else
+ phoneMgr.disableDataConnectivity();
+ }
+ catch (RemoteException e) {
+ System.err.println("Mobile data operation failed: " + e);
+ }
+ return;
+ }
+ }
+ System.err.println(longHelp());
}
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 795f51a..d74802c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2097,6 +2097,113 @@
return new TaskSnapshot[size];
}
};
+
+ /** Builder for a {@link TaskSnapshot} object */
+ public static final class Builder {
+ private long mId;
+ private ComponentName mTopActivity;
+ private GraphicBuffer mSnapshot;
+ private ColorSpace mColorSpace;
+ private int mOrientation;
+ private Rect mContentInsets;
+ private boolean mReducedResolution;
+ private float mScaleFraction;
+ private boolean mIsRealSnapshot;
+ private int mWindowingMode;
+ private int mSystemUiVisibility;
+ private boolean mIsTranslucent;
+ private int mPixelFormat;
+
+ public Builder setId(long id) {
+ mId = id;
+ return this;
+ }
+
+ public Builder setTopActivityComponent(ComponentName name) {
+ mTopActivity = name;
+ return this;
+ }
+
+ public Builder setSnapshot(GraphicBuffer buffer) {
+ mSnapshot = buffer;
+ return this;
+ }
+
+ public Builder setColorSpace(ColorSpace colorSpace) {
+ mColorSpace = colorSpace;
+ return this;
+ }
+
+ public Builder setOrientation(int orientation) {
+ mOrientation = orientation;
+ return this;
+ }
+
+ public Builder setContentInsets(Rect contentInsets) {
+ mContentInsets = contentInsets;
+ return this;
+ }
+
+ public Builder setReducedResolution(boolean reducedResolution) {
+ mReducedResolution = reducedResolution;
+ return this;
+ }
+
+ public float getScaleFraction() {
+ return mScaleFraction;
+ }
+
+ public Builder setScaleFraction(float scaleFraction) {
+ mScaleFraction = scaleFraction;
+ return this;
+ }
+
+ public Builder setIsRealSnapshot(boolean realSnapshot) {
+ mIsRealSnapshot = realSnapshot;
+ return this;
+ }
+
+ public Builder setWindowingMode(int windowingMode) {
+ mWindowingMode = windowingMode;
+ return this;
+ }
+
+ public Builder setSystemUiVisibility(int systemUiVisibility) {
+ mSystemUiVisibility = systemUiVisibility;
+ return this;
+ }
+
+ public Builder setIsTranslucent(boolean isTranslucent) {
+ mIsTranslucent = isTranslucent;
+ return this;
+ }
+
+ public int getPixelFormat() {
+ return mPixelFormat;
+ }
+
+ public Builder setPixelFormat(int pixelFormat) {
+ mPixelFormat = pixelFormat;
+ return this;
+ }
+
+ public TaskSnapshot build() {
+ return new TaskSnapshot(
+ mId,
+ mTopActivity,
+ mSnapshot,
+ mColorSpace,
+ mOrientation,
+ mContentInsets,
+ mReducedResolution,
+ mScaleFraction,
+ mIsRealSnapshot,
+ mWindowingMode,
+ mSystemUiVisibility,
+ mIsTranslucent);
+
+ }
+ }
}
/** @hide */
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParams.java b/core/java/android/content/pm/DataLoaderParams.java
similarity index 82%
rename from core/java/android/os/incremental/IncrementalDataLoaderParams.java
rename to core/java/android/content/pm/DataLoaderParams.java
index 701f1cc..b163861 100644
--- a/core/java/android/os/incremental/IncrementalDataLoaderParams.java
+++ b/core/java/android/content/pm/DataLoaderParams.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.os.incremental;
+package android.content.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,12 +29,12 @@
* Hide for now.
* @hide
*/
-public class IncrementalDataLoaderParams {
- @NonNull private final IncrementalDataLoaderParamsParcel mData;
+public class DataLoaderParams {
+ @NonNull private final DataLoaderParamsParcel mData;
- public IncrementalDataLoaderParams(@NonNull String url, @NonNull String packageName,
+ public DataLoaderParams(@NonNull String url, @NonNull String packageName,
@Nullable Map<String, ParcelFileDescriptor> namedFds) {
- IncrementalDataLoaderParamsParcel data = new IncrementalDataLoaderParamsParcel();
+ DataLoaderParamsParcel data = new DataLoaderParamsParcel();
data.staticArgs = url;
data.packageName = packageName;
if (namedFds == null || namedFds.isEmpty()) {
@@ -52,7 +52,7 @@
mData = data;
}
- public IncrementalDataLoaderParams(@NonNull IncrementalDataLoaderParamsParcel data) {
+ public DataLoaderParams(@NonNull DataLoaderParamsParcel data) {
mData = data;
}
@@ -70,7 +70,7 @@
return mData.packageName;
}
- public final @NonNull IncrementalDataLoaderParamsParcel getData() {
+ public final @NonNull DataLoaderParamsParcel getData() {
return mData;
}
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl b/core/java/android/content/pm/DataLoaderParamsParcel.aidl
similarity index 85%
rename from core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
rename to core/java/android/content/pm/DataLoaderParamsParcel.aidl
index cd988dc..3316398 100644
--- a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
+++ b/core/java/android/content/pm/DataLoaderParamsParcel.aidl
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package android.os.incremental;
+package android.content.pm;
-import android.os.incremental.NamedParcelFileDescriptor;
+import android.content.pm.NamedParcelFileDescriptor;
/**
* Class for holding data loader configuration parameters.
* @hide
*/
-parcelable IncrementalDataLoaderParamsParcel {
+parcelable DataLoaderParamsParcel {
@utf8InCpp String packageName;
@utf8InCpp String staticArgs;
NamedParcelFileDescriptor[] dynamicArgs;
diff --git a/core/java/android/content/pm/FileSystemControlParcel.aidl b/core/java/android/content/pm/FileSystemControlParcel.aidl
new file mode 100644
index 0000000..f00feae
--- /dev/null
+++ b/core/java/android/content/pm/FileSystemControlParcel.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 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 android.content.pm;
+
+import android.content.pm.IPackageInstallerSessionFileSystemConnector;
+import android.os.incremental.IncrementalFileSystemControlParcel;
+
+/**
+ * Wraps info needed for DataLoader to provide data.
+ * @hide
+ */
+parcelable FileSystemControlParcel {
+ // Incremental FS control descriptors.
+ @nullable IncrementalFileSystemControlParcel incremental;
+ // Callback-based installation connector.
+ @nullable IPackageInstallerSessionFileSystemConnector callback;
+}
diff --git a/core/java/android/content/pm/IDataLoader.aidl b/core/java/android/content/pm/IDataLoader.aidl
index 60cc9ba9..c65bd6a 100644
--- a/core/java/android/content/pm/IDataLoader.aidl
+++ b/core/java/android/content/pm/IDataLoader.aidl
@@ -30,5 +30,4 @@
void start(in List<InstallationFile> fileInfos);
void stop();
void destroy();
- void onFileCreated(long inode, in byte[] metadata);
}
diff --git a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl b/core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl
similarity index 76%
copy from core/java/android/os/incremental/NamedParcelFileDescriptor.aidl
copy to core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl
index 038ced1..4b2f29e 100644
--- a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl
@@ -14,15 +14,11 @@
* limitations under the License.
*/
-package android.os.incremental;
+package android.content.pm;
import android.os.ParcelFileDescriptor;
-/**
- * A named ParcelFileDescriptor.
- * @hide
- */
-parcelable NamedParcelFileDescriptor {
- @utf8InCpp String name;
- ParcelFileDescriptor fd;
+/** {@hide} */
+interface IPackageInstallerSessionFileSystemConnector {
+ void writeData(String name, long offsetBytes, long lengthBytes, in ParcelFileDescriptor fd);
}
diff --git a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl b/core/java/android/content/pm/NamedParcelFileDescriptor.aidl
similarity index 95%
rename from core/java/android/os/incremental/NamedParcelFileDescriptor.aidl
rename to core/java/android/content/pm/NamedParcelFileDescriptor.aidl
index 038ced1..68dd5f5 100644
--- a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl
+++ b/core/java/android/content/pm/NamedParcelFileDescriptor.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.os.incremental;
+package android.content.pm;
import android.os.ParcelFileDescriptor;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 898631e..218c876 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -50,8 +50,6 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.os.incremental.IncrementalDataLoaderParams;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArraySet;
@@ -1459,7 +1457,7 @@
/** {@hide} */
public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
/** {@hide} */
- public IncrementalDataLoaderParams incrementalParams;
+ public DataLoaderParams incrementalParams;
/** TODO(b/146080380): add a class name to make it fully compatible with ComponentName.
* {@hide} */
public String dataLoaderPackageName;
@@ -1496,10 +1494,10 @@
isMultiPackage = source.readBoolean();
isStaged = source.readBoolean();
requiredInstalledVersionCode = source.readLong();
- IncrementalDataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
- IncrementalDataLoaderParamsParcel.class.getClassLoader());
+ DataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
+ DataLoaderParamsParcel.class.getClassLoader());
if (dataLoaderParamsParcel != null) {
- incrementalParams = new IncrementalDataLoaderParams(
+ incrementalParams = new DataLoaderParams(
dataLoaderParamsParcel);
}
dataLoaderPackageName = source.readString();
@@ -1863,7 +1861,7 @@
* {@hide}
*/
@RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
- public void setIncrementalParams(@NonNull IncrementalDataLoaderParams incrementalParams) {
+ public void setIncrementalParams(@NonNull DataLoaderParams incrementalParams) {
this.incrementalParams = incrementalParams;
}
diff --git a/core/java/android/os/incremental/IIncrementalManager.aidl b/core/java/android/os/incremental/IIncrementalManager.aidl
index f84d7ef..17a310a 100644
--- a/core/java/android/os/incremental/IIncrementalManager.aidl
+++ b/core/java/android/os/incremental/IIncrementalManager.aidl
@@ -16,8 +16,8 @@
package android.os.incremental;
-import android.os.incremental.IncrementalFileSystemControlParcel;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
+import android.content.pm.FileSystemControlParcel;
+import android.content.pm.DataLoaderParamsParcel;
import android.content.pm.IDataLoaderStatusListener;
/**
@@ -27,8 +27,8 @@
*/
interface IIncrementalManager {
boolean prepareDataLoader(int mountId,
- in IncrementalFileSystemControlParcel control,
- in IncrementalDataLoaderParamsParcel params,
+ in FileSystemControlParcel control,
+ in DataLoaderParamsParcel params,
in IDataLoaderStatusListener listener);
boolean startDataLoader(int mountId);
void showHealthBlockedUI(int mountId);
diff --git a/core/java/android/os/incremental/IIncrementalManagerNative.aidl b/core/java/android/os/incremental/IIncrementalManagerNative.aidl
index d9c7c6b..14215b1 100644
--- a/core/java/android/os/incremental/IIncrementalManagerNative.aidl
+++ b/core/java/android/os/incremental/IIncrementalManagerNative.aidl
@@ -16,7 +16,7 @@
package android.os.incremental;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
+import android.content.pm.DataLoaderParamsParcel;
/** @hide */
interface IIncrementalManagerNative {
@@ -32,7 +32,7 @@
* Opens or creates a storage given a target path and data loader params. Returns the storage ID.
*/
int openStorage(in @utf8InCpp String path);
- int createStorage(in @utf8InCpp String path, in IncrementalDataLoaderParamsParcel params, int createMode);
+ int createStorage(in @utf8InCpp String path, in DataLoaderParamsParcel params, int createMode);
int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
/**
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 5bd0748..7987efd 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -35,6 +35,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.DataLoaderParams;
import android.content.pm.InstallationFile;
import android.os.IVold;
import android.os.RemoteException;
@@ -82,12 +83,12 @@
public IncrementalFileStorages(@NonNull String packageName,
@NonNull File stageDir,
@NonNull IncrementalManager incrementalManager,
- @NonNull IncrementalDataLoaderParams incrementalDataLoaderParams) {
+ @NonNull DataLoaderParams dataLoaderParams) {
mPackageName = packageName;
mStageDir = stageDir;
mIncrementalManager = incrementalManager;
- if (incrementalDataLoaderParams.getPackageName().equals("local")) {
- final String incrementalPath = incrementalDataLoaderParams.getStaticArgs();
+ if (dataLoaderParams.getPackageName().equals("local")) {
+ final String incrementalPath = dataLoaderParams.getStaticArgs();
mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
mDefaultDir = incrementalPath;
return;
@@ -97,7 +98,7 @@
return;
}
mDefaultStorage = mIncrementalManager.createStorage(mDefaultDir,
- incrementalDataLoaderParams,
+ dataLoaderParams,
IncrementalManager.CREATE_MODE_CREATE
| IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false);
}
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index c30f558..c722287 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
+import android.content.pm.DataLoaderParams;
import android.os.RemoteException;
import android.system.ErrnoException;
import android.system.Os;
@@ -104,7 +105,7 @@
*/
@Nullable
public IncrementalStorage createStorage(@NonNull String path,
- @NonNull IncrementalDataLoaderParams params, @CreateMode int createMode,
+ @NonNull DataLoaderParams params, @CreateMode int createMode,
boolean autoStartDataLoader) {
try {
final int id = mNativeService.createStorage(path, params.getData(), createMode);
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
new file mode 100644
index 0000000..373e1e5
--- /dev/null
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2019 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 android.service.dataloader;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.DataLoaderParamsParcel;
+import android.content.pm.FileSystemControlParcel;
+import android.content.pm.IDataLoader;
+import android.content.pm.IDataLoaderStatusListener;
+import android.content.pm.IPackageInstallerSessionFileSystemConnector;
+import android.content.pm.InstallationFile;
+import android.content.pm.NamedParcelFileDescriptor;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.ExceptionUtils;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * The base class for implementing data loader service to control data loaders. Expecting
+ * Incremental Service to bind to a children class of this.
+ *
+ * @hide
+ *
+ * Hide for now, should be @SystemApi
+ * TODO(b/136132412): update with latest API design
+ */
+public abstract class DataLoaderService extends Service {
+ private static final String TAG = "IncrementalDataLoaderService";
+ private final DataLoaderBinderService mBinder = new DataLoaderBinderService();
+
+ public static final int DATA_LOADER_READY =
+ IDataLoaderStatusListener.DATA_LOADER_READY;
+ public static final int DATA_LOADER_NOT_READY =
+ IDataLoaderStatusListener.DATA_LOADER_NOT_READY;
+ public static final int DATA_LOADER_RUNNING =
+ IDataLoaderStatusListener.DATA_LOADER_RUNNING;
+ public static final int DATA_LOADER_STOPPED =
+ IDataLoaderStatusListener.DATA_LOADER_STOPPED;
+ public static final int DATA_LOADER_SLOW_CONNECTION =
+ IDataLoaderStatusListener.DATA_LOADER_SLOW_CONNECTION;
+ public static final int DATA_LOADER_NO_CONNECTION =
+ IDataLoaderStatusListener.DATA_LOADER_NO_CONNECTION;
+ public static final int DATA_LOADER_CONNECTION_OK =
+ IDataLoaderStatusListener.DATA_LOADER_CONNECTION_OK;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"DATA_LOADER_"}, value = {
+ DATA_LOADER_READY,
+ DATA_LOADER_NOT_READY,
+ DATA_LOADER_RUNNING,
+ DATA_LOADER_STOPPED,
+ DATA_LOADER_SLOW_CONNECTION,
+ DATA_LOADER_NO_CONNECTION,
+ DATA_LOADER_CONNECTION_OK
+ })
+ public @interface DataLoaderStatus {
+ }
+
+ /**
+ * Managed DataLoader interface. Each instance corresponds to a single Incremental File System
+ * instance.
+ */
+ public abstract static class DataLoader {
+ /**
+ * A virtual constructor used to do simple initialization. Not ready to serve any data yet.
+ * All heavy-lifting has to be done in onStart.
+ *
+ * @param params Data loader configuration parameters.
+ * @param connector IncFS API wrapper.
+ * @param listener Used for reporting internal state to IncrementalService.
+ * @return True if initialization of a Data Loader was successful. False will be reported to
+ * IncrementalService and can cause an unmount of an IFS instance.
+ */
+ public abstract boolean onCreate(@NonNull DataLoaderParams params,
+ @NonNull FileSystemConnector connector,
+ @NonNull StatusListener listener);
+
+ /**
+ * Start the data loader. After this method returns data loader is considered to be ready to
+ * receive callbacks from IFS, supply data via connector and send status updates via
+ * callbacks.
+ *
+ * @return True if Data Loader was able to start. False will be reported to
+ * IncrementalService and can cause an unmount of an IFS instance.
+ */
+ public abstract boolean onStart();
+
+ /**
+ * Stop the data loader. Use to stop any additional threads and free up resources. Data
+ * loader is not longer responsible for supplying data. Start/Stop pair can be called
+ * multiple times e.g. if IFS detects corruption and data needs to be re-loaded.
+ */
+ public abstract void onStop();
+
+ /**
+ * Virtual destructor. Use to cleanup all internal state. After this method returns, the
+ * data loader can no longer use connector or callbacks. For any additional operations with
+ * this instance of IFS a new DataLoader will be created using createDataLoader method.
+ */
+ public abstract void onDestroy();
+ }
+
+ /**
+ * DataLoader factory method.
+ *
+ * @return An instance of a DataLoader.
+ */
+ public abstract @Nullable DataLoader onCreateDataLoader();
+
+ /**
+ * @hide
+ */
+ public final @NonNull IBinder onBind(@NonNull Intent intent) {
+ return (IBinder) mBinder;
+ }
+
+ private class DataLoaderBinderService extends IDataLoader.Stub {
+ private int mId;
+
+ @Override
+ public void create(int id, @NonNull Bundle options,
+ @NonNull IDataLoaderStatusListener listener)
+ throws IllegalArgumentException, RuntimeException {
+ mId = id;
+ final DataLoaderParamsParcel params = options.getParcelable("params");
+ if (params == null) {
+ throw new IllegalArgumentException("Must specify Incremental data loader params");
+ }
+ final FileSystemControlParcel control =
+ options.getParcelable("control");
+ if (control == null) {
+ throw new IllegalArgumentException("Must specify Incremental control parcel");
+ }
+ mStatusListener = listener;
+ try {
+ if (!nativeCreateDataLoader(id, control, params, listener)) {
+ Slog.e(TAG, "Failed to create native loader for " + mId);
+ }
+ } catch (Exception ex) {
+ destroy();
+ throw new RuntimeException(ex);
+ } finally {
+ // Closing FDs.
+ if (control.incremental.cmd != null) {
+ try {
+ control.incremental.cmd.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to close IncFs CMD file descriptor " + e);
+ }
+ }
+ if (control.incremental.log != null) {
+ try {
+ control.incremental.log.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to close IncFs LOG file descriptor " + e);
+ }
+ }
+ NamedParcelFileDescriptor[] fds = params.dynamicArgs;
+ for (NamedParcelFileDescriptor nfd : fds) {
+ try {
+ nfd.fd.close();
+ } catch (IOException e) {
+ Slog.e(TAG,
+ "Failed to close DynamicArgs parcel file descriptor " + e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void start(List<InstallationFile> fileInfos) {
+ if (!nativeStartDataLoader(mId)) {
+ Slog.e(TAG, "Failed to start loader: loader not found for " + mId);
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (!nativeStopDataLoader(mId)) {
+ Slog.w(TAG, "Failed to stop loader: loader not found for " + mId);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ if (!nativeDestroyDataLoader(mId)) {
+ Slog.w(TAG, "Failed to destroy loader: loader not found for " + mId);
+ }
+ }
+ }
+
+ /**
+ *
+ * Used by the DataLoaderService implementations.
+ *
+ * @hide
+ *
+ * TODO(b/136132412) Should be @SystemApi
+ */
+ public static final class FileSystemConnector {
+ /**
+ * Creates a wrapper for an installation session connector.
+ * @hide
+ */
+ FileSystemConnector(IPackageInstallerSessionFileSystemConnector connector) {
+ mConnector = connector;
+ }
+
+ /**
+ * Write data to an installation file from an arbitrary FD.
+ *
+ * @param name name of file previously added to the installation session.
+ * @param offsetBytes offset into the file to begin writing at, or 0 to
+ * start at the beginning of the file.
+ * @param lengthBytes total size of the file being written, used to
+ * preallocate the underlying disk space, or -1 if unknown.
+ * The system may clear various caches as needed to allocate
+ * this space.
+ * @param incomingFd FD to read bytes from.
+ * @throws IOException if trouble opening the file for writing, such as
+ * lack of disk space or unavailable media.
+ */
+ public void writeData(String name, long offsetBytes, long lengthBytes,
+ ParcelFileDescriptor incomingFd) throws IOException {
+ try {
+ mConnector.writeData(name, offsetBytes, lengthBytes, incomingFd);
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private final IPackageInstallerSessionFileSystemConnector mConnector;
+ }
+
+ /**
+ * Wrapper for native reporting DataLoader statuses.
+ * @hide
+ * TODO(b/136132412) Should be @SystemApi
+ */
+ public static final class StatusListener {
+ /**
+ * Creates a wrapper for a native instance.
+ * @hide
+ */
+ StatusListener(long nativeInstance) {
+ mNativeInstance = nativeInstance;
+ }
+
+ /**
+ * Report the status of DataLoader. Used for system-wide notifications e.g., disabling
+ * applications which rely on this data loader to function properly.
+ *
+ * @param status status to report.
+ * @return True if status was reported successfully.
+ */
+ public boolean onStatusChanged(@DataLoaderStatus int status) {
+ return nativeReportStatus(mNativeInstance, status);
+ }
+
+ private final long mNativeInstance;
+ }
+
+ private IDataLoaderStatusListener mStatusListener = null;
+
+ /* Native methods */
+ private native boolean nativeCreateDataLoader(int storageId,
+ @NonNull FileSystemControlParcel control,
+ @NonNull DataLoaderParamsParcel params,
+ IDataLoaderStatusListener listener);
+
+ private native boolean nativeStartDataLoader(int storageId);
+
+ private native boolean nativeStopDataLoader(int storageId);
+
+ private native boolean nativeDestroyDataLoader(int storageId);
+
+ private static native boolean nativeReportStatus(long nativeInstance, int status);
+}
diff --git a/core/java/android/service/incremental/IncrementalDataLoaderService.java b/core/java/android/service/incremental/IncrementalDataLoaderService.java
deleted file mode 100644
index c4a06c8..0000000
--- a/core/java/android/service/incremental/IncrementalDataLoaderService.java
+++ /dev/null
@@ -1,563 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.service.incremental;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Service;
-import android.content.Intent;
-import android.content.pm.IDataLoader;
-import android.content.pm.IDataLoaderStatusListener;
-import android.content.pm.InstallationFile;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.incremental.IncrementalDataLoaderParams;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
-import android.os.incremental.IncrementalFileSystemControlParcel;
-import android.os.incremental.NamedParcelFileDescriptor;
-import android.util.Slog;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Collection;
-import java.util.List;
-
-
-/**
- * The base class for implementing data loader service to control data loaders. Expecting
- * Incremental Service to bind to a children class of this.
- *
- * @hide
- *
- * Hide for now, should be @SystemApi
- * TODO(b/136132412): update with latest API design
- */
-public abstract class IncrementalDataLoaderService extends Service {
- private static final String TAG = "IncrementalDataLoaderService";
- private final DataLoaderBinderService mBinder = new DataLoaderBinderService();
-
- public static final int DATA_LOADER_READY =
- IDataLoaderStatusListener.DATA_LOADER_READY;
- public static final int DATA_LOADER_NOT_READY =
- IDataLoaderStatusListener.DATA_LOADER_NOT_READY;
- public static final int DATA_LOADER_RUNNING =
- IDataLoaderStatusListener.DATA_LOADER_RUNNING;
- public static final int DATA_LOADER_STOPPED =
- IDataLoaderStatusListener.DATA_LOADER_STOPPED;
- public static final int DATA_LOADER_SLOW_CONNECTION =
- IDataLoaderStatusListener.DATA_LOADER_SLOW_CONNECTION;
- public static final int DATA_LOADER_NO_CONNECTION =
- IDataLoaderStatusListener.DATA_LOADER_NO_CONNECTION;
- public static final int DATA_LOADER_CONNECTION_OK =
- IDataLoaderStatusListener.DATA_LOADER_CONNECTION_OK;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"DATA_LOADER_"}, value = {
- DATA_LOADER_READY,
- DATA_LOADER_NOT_READY,
- DATA_LOADER_RUNNING,
- DATA_LOADER_STOPPED,
- DATA_LOADER_SLOW_CONNECTION,
- DATA_LOADER_NO_CONNECTION,
- DATA_LOADER_CONNECTION_OK
- })
- public @interface DataLoaderStatus {
- }
-
- /**
- * Incremental FileSystem block size.
- **/
- public static final int BLOCK_SIZE = 4096;
-
- /**
- * Data compression types
- */
- public static final int COMPRESSION_NONE = 0;
- public static final int COMPRESSION_LZ4 = 1;
-
- /**
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({COMPRESSION_NONE, COMPRESSION_LZ4})
- public @interface CompressionType {
- }
-
- /**
- * Managed DataLoader interface. Each instance corresponds to a single Incremental File System
- * instance.
- */
- public abstract static class DataLoader {
- /**
- * A virtual constructor used to do simple initialization. Not ready to serve any data yet.
- * All heavy-lifting has to be done in onStart.
- *
- * @param params Data loader configuration parameters.
- * @param connector IncFS API wrapper.
- * @param listener Used for reporting internal state to IncrementalService.
- * @return True if initialization of a Data Loader was successful. False will be reported to
- * IncrementalService and can cause an unmount of an IFS instance.
- */
- public abstract boolean onCreate(@NonNull IncrementalDataLoaderParams params,
- @NonNull FileSystemConnector connector,
- @NonNull StatusListener listener);
-
- /**
- * Start the data loader. After this method returns data loader is considered to be ready to
- * receive callbacks from IFS, supply data via connector and send status updates via
- * callbacks.
- *
- * @return True if Data Loader was able to start. False will be reported to
- * IncrementalService and can cause an unmount of an IFS instance.
- */
- public abstract boolean onStart();
-
- /**
- * Stop the data loader. Use to stop any additional threads and free up resources. Data
- * loader is not longer responsible for supplying data. Start/Stop pair can be called
- * multiple times e.g. if IFS detects corruption and data needs to be re-loaded.
- */
- public abstract void onStop();
-
- /**
- * Virtual destructor. Use to cleanup all internal state. After this method returns, the
- * data loader can no longer use connector or callbacks. For any additional operations with
- * this instance of IFS a new DataLoader will be created using createDataLoader method.
- */
- public abstract void onDestroy();
-
- /**
- * IFS reports a pending read each time the page needs to be loaded, e.g. missing.
- *
- * @param pendingReads array of blocks to load.
- *
- * TODO(b/136132412): avoid using collections
- */
- public abstract void onPendingReads(
- @NonNull Collection<FileSystemConnector.PendingReadInfo> pendingReads);
-
- /**
- * IFS tracks all reads and reports them using onPageReads.
- *
- * @param reads array of blocks.
- *
- * TODO(b/136132412): avoid using collections
- */
- public abstract void onPageReads(@NonNull Collection<FileSystemConnector.ReadInfo> reads);
-
- /**
- * IFS informs data loader that a new file has been created.
- * <p>
- * This can be used to prepare the data loader before it starts loading data. For example,
- * the data loader can keep a list of newly created files, so that it knows what files to
- * download from the server.
- *
- * @param inode The inode value of the new file.
- * @param metadata The metadata of the new file.
- */
- public abstract void onFileCreated(long inode, byte[] metadata);
- }
-
- /**
- * DataLoader factory method.
- *
- * @return An instance of a DataLoader.
- */
- public abstract @Nullable DataLoader onCreateDataLoader();
-
- /**
- * @hide
- */
- public final @NonNull IBinder onBind(@NonNull Intent intent) {
- return (IBinder) mBinder;
- }
-
- private class DataLoaderBinderService extends IDataLoader.Stub {
- private int mId;
-
- @Override
- public void create(int id, @NonNull Bundle options,
- @NonNull IDataLoaderStatusListener listener)
- throws IllegalArgumentException, RuntimeException {
- mId = id;
- final IncrementalDataLoaderParamsParcel params = options.getParcelable("params");
- if (params == null) {
- throw new IllegalArgumentException("Must specify Incremental data loader params");
- }
- final IncrementalFileSystemControlParcel control =
- options.getParcelable("control");
- if (control == null) {
- throw new IllegalArgumentException("Must specify Incremental control parcel");
- }
- mStatusListener = listener;
- try {
- if (!nativeCreateDataLoader(id, control, params, listener)) {
- Slog.e(TAG, "Failed to create native loader for " + mId);
- }
- } catch (Exception ex) {
- destroy();
- throw new RuntimeException(ex);
- } finally {
- // Closing FDs.
- if (control.cmd != null) {
- try {
- control.cmd.close();
- } catch (IOException e) {
- Slog.e(TAG, "Failed to close IncFs CMD file descriptor " + e);
- }
- }
- if (control.log != null) {
- try {
- control.log.close();
- } catch (IOException e) {
- Slog.e(TAG, "Failed to close IncFs LOG file descriptor " + e);
- }
- }
- NamedParcelFileDescriptor[] fds = params.dynamicArgs;
- for (NamedParcelFileDescriptor nfd : fds) {
- try {
- nfd.fd.close();
- } catch (IOException e) {
- Slog.e(TAG,
- "Failed to close DynamicArgs parcel file descriptor " + e);
- }
- }
- }
- }
-
- @Override
- public void start(List<InstallationFile> fileInfos) {
- if (!nativeStartDataLoader(mId)) {
- Slog.e(TAG, "Failed to start loader: loader not found for " + mId);
- }
- }
-
- @Override
- public void stop() {
- if (!nativeStopDataLoader(mId)) {
- Slog.w(TAG, "Failed to stop loader: loader not found for " + mId);
- }
- }
-
- @Override
- public void destroy() {
- if (!nativeDestroyDataLoader(mId)) {
- Slog.w(TAG, "Failed to destroy loader: loader not found for " + mId);
- }
- }
-
- @Override
- // TODO(b/136132412): remove this
- public void onFileCreated(long inode, byte[] metadata) {
- if (!nativeOnFileCreated(mId, inode, metadata)) {
- Slog.w(TAG, "Failed to handle onFileCreated for storage:" + mId
- + " inode:" + inode);
- }
- }
- }
-
- /**
- * IncFs API wrapper for writing pages and getting page missing info. Non-hidden methods are
- * expected to be called by the IncrementalDataLoaderService implemented by developers.
- *
- * @hide
- *
- * TODO(b/136132412) Should be @SystemApi
- */
- public static final class FileSystemConnector {
- /**
- * Defines a block address. A block is the unit of data chunk that IncFs operates with.
- *
- * @hide
- */
- public static class BlockAddress {
- /**
- * Linux inode uniquely identifies file within a single IFS instance.
- */
- private final long mFileIno;
- /**
- * Index of a 4K block within a file.
- */
- private final int mBlockIndex;
-
- public BlockAddress(long fileIno, int blockIndex) {
- this.mFileIno = fileIno;
- this.mBlockIndex = blockIndex;
- }
-
- public long getFileIno() {
- return mFileIno;
- }
-
- public int getBlockIndex() {
- return mBlockIndex;
- }
- }
-
- /**
- * A block is the unit of data chunk that IncFs operates with.
- *
- * @hide
- */
- public static class Block extends BlockAddress {
- /**
- * Data content of the block.
- */
- private final @NonNull byte[] mDataBytes;
-
- public Block(long fileIno, int blockIndex, @NonNull byte[] dataBytes) {
- super(fileIno, blockIndex);
- this.mDataBytes = dataBytes;
- }
- }
-
- /**
- * Defines a page/block inside a file.
- */
- public static class DataBlock extends Block {
- /**
- * Compression type of the data block.
- */
- private final @CompressionType int mCompressionType;
-
- public DataBlock(long fileIno, int blockIndex, @NonNull byte[] dataBytes,
- @CompressionType int compressionType) {
- super(fileIno, blockIndex, dataBytes);
- this.mCompressionType = compressionType;
- }
- }
-
- /**
- * Defines a hash block for a certain file. A hash block index is the index in an array of
- * hashes which is the 1-d representation of the hash tree. One DataBlock might be
- * associated with multiple HashBlocks.
- */
- public static class HashBlock extends Block {
- public HashBlock(long fileIno, int blockIndex, @NonNull byte[] dataBytes) {
- super(fileIno, blockIndex, dataBytes);
- }
- }
-
- /**
- * Information about a page that is pending to be read.
- */
- public static class PendingReadInfo extends BlockAddress {
- PendingReadInfo(long fileIno, int blockIndex) {
- super(fileIno, blockIndex);
- }
- }
-
- /**
- * Information about a page that is read.
- */
- public static class ReadInfo extends BlockAddress {
- /**
- * A monotonically increasing read timestamp.
- */
- private final long mTimePoint;
- /**
- * Number of blocks read starting from blockIndex.
- */
- private final int mBlockCount;
-
- ReadInfo(long timePoint, long fileIno, int firstBlockIndex, int blockCount) {
- super(fileIno, firstBlockIndex);
- this.mTimePoint = timePoint;
- this.mBlockCount = blockCount;
- }
-
- public long getTimePoint() {
- return mTimePoint;
- }
-
- public int getBlockCount() {
- return mBlockCount;
- }
- }
-
- /**
- * Defines the dynamic information about an IncFs file.
- */
- public static class FileInfo {
- /**
- * BitSet to show if any block is available at each block index.
- */
- private final @NonNull
- byte[] mBlockBitmap;
-
- /**
- * @hide
- */
- public FileInfo(@NonNull byte[] blockBitmap) {
- this.mBlockBitmap = blockBitmap;
- }
- }
-
- /**
- * Creates a wrapper for a native instance.
- */
- FileSystemConnector(long nativeInstance) {
- mNativeInstance = nativeInstance;
- }
-
- /**
- * Checks whether a range in a file if loaded.
- *
- * @param node inode of the file.
- * @param start The starting offset of the range.
- * @param end The ending offset of the range.
- * @return True if the file is fully loaded.
- */
- public boolean isFileRangeLoaded(long node, long start, long end) {
- return nativeIsFileRangeLoadedNode(mNativeInstance, node, start, end);
- }
-
- /**
- * Gets the metadata of a file.
- *
- * @param node inode of the file.
- * @return The metadata object.
- */
- @NonNull
- public byte[] getFileMetadata(long node) throws IOException {
- final byte[] metadata = nativeGetFileMetadataNode(mNativeInstance, node);
- if (metadata == null || metadata.length == 0) {
- throw new IOException(
- "IncrementalFileSystem failed to obtain metadata for node: " + node);
- }
- return metadata;
- }
-
- /**
- * Gets the dynamic information of a file, such as page bitmaps. Can be used to get missing
- * page indices by the FileSystemConnector.
- *
- * @param node inode of the file.
- * @return Dynamic file info.
- */
- @NonNull
- public FileInfo getDynamicFileInfo(long node) throws IOException {
- final byte[] blockBitmap = nativeGetFileInfoNode(mNativeInstance, node);
- if (blockBitmap == null || blockBitmap.length == 0) {
- throw new IOException(
- "IncrementalFileSystem failed to obtain dynamic file info for node: "
- + node);
- }
- return new FileInfo(blockBitmap);
- }
-
- /**
- * Writes a page's data and/or hashes.
- *
- * @param dataBlocks the DataBlock objects that contain data block index and data bytes.
- * @param hashBlocks the HashBlock objects that contain hash indices and hash bytes.
- *
- * TODO(b/136132412): change API to avoid dynamic allocation of data block objects
- */
- public void writeMissingData(@NonNull DataBlock[] dataBlocks,
- @Nullable HashBlock[] hashBlocks) throws IOException {
- if (!nativeWriteMissingData(mNativeInstance, dataBlocks, hashBlocks)) {
- throw new IOException("IncrementalFileSystem failed to write missing data.");
- }
- }
-
- /**
- * Writes the signer block of a file. Expecting the connector to call this when it got
- * signing data from data loader.
- *
- * @param node the file to be written to.
- * @param signerData the raw signer data byte array.
- */
- public void writeSignerData(long node, @NonNull byte[] signerData)
- throws IOException {
- if (!nativeWriteSignerDataNode(mNativeInstance, node, signerData)) {
- throw new IOException(
- "IncrementalFileSystem failed to write signer data of node " + node);
- }
- }
-
- private final long mNativeInstance;
- }
-
- /**
- * Wrapper for native reporting DataLoader statuses.
- *
- * @hide
- *
- * TODO(b/136132412) Should be @SystemApi
- */
- public static final class StatusListener {
- /**
- * Creates a wrapper for a native instance.
- *
- * @hide
- */
- StatusListener(long nativeInstance) {
- mNativeInstance = nativeInstance;
- }
-
- /**
- * Report the status of DataLoader. Used for system-wide notifications e.g., disabling
- * applications which rely on this data loader to function properly.
- *
- * @param status status to report.
- * @return True if status was reported successfully.
- */
- public boolean onStatusChanged(@DataLoaderStatus int status) {
- return nativeReportStatus(mNativeInstance, status);
- }
-
- private final long mNativeInstance;
- }
-
- private IDataLoaderStatusListener mStatusListener = null;
-
- /* Native methods */
- private native boolean nativeCreateDataLoader(int storageId,
- @NonNull IncrementalFileSystemControlParcel control,
- @NonNull IncrementalDataLoaderParamsParcel params,
- IDataLoaderStatusListener listener);
-
- private native boolean nativeStartDataLoader(int storageId);
-
- private native boolean nativeStopDataLoader(int storageId);
-
- private native boolean nativeDestroyDataLoader(int storageId);
-
- private static native boolean nativeOnFileCreated(int storageId,
- long inode, byte[] metadata);
-
- private static native boolean nativeIsFileRangeLoadedNode(
- long nativeInstance, long node, long start, long end);
-
- private static native boolean nativeWriteMissingData(
- long nativeInstance, FileSystemConnector.DataBlock[] dataBlocks,
- FileSystemConnector.HashBlock[] hashBlocks);
-
- private static native boolean nativeWriteSignerDataNode(
- long nativeInstance, long node, byte[] signerData);
-
- private static native byte[] nativeGetFileMetadataNode(
- long nativeInstance, long node);
-
- private static native byte[] nativeGetFileInfoNode(
- long nativeInstance, long node);
-
- private static native boolean nativeReportStatus(long nativeInstance, int status);
-}
diff --git a/core/jni/android_service_DataLoaderService.cpp b/core/jni/android_service_DataLoaderService.cpp
index 4c0f55f..381b386 100644
--- a/core/jni/android_service_DataLoaderService.cpp
+++ b/core/jni/android_service_DataLoaderService.cpp
@@ -16,83 +16,18 @@
#define LOG_TAG "dataloader-jni"
-#include <vector>
-
#include "core_jni_helpers.h"
#include "dataloader_ndk.h"
-#include "jni.h"
namespace android {
namespace {
-struct JniIds {
- jfieldID dataBlockFileIno;
- jfieldID dataBlockBlockIndex;
- jfieldID dataBlockDataBytes;
- jfieldID dataBlockCompressionType;
-
- JniIds(JNIEnv* env) {
- const auto dataBlock =
- FindClassOrDie(env,
- "android/service/incremental/"
- "IncrementalDataLoaderService$FileSystemConnector$DataBlock");
- dataBlockFileIno = GetFieldIDOrDie(env, dataBlock, "mFileIno", "J");
- dataBlockBlockIndex =
- GetFieldIDOrDie(env, dataBlock, "mBlockIndex", "I");
- dataBlockDataBytes = GetFieldIDOrDie(env, dataBlock, "mDataBytes", "[B");
- dataBlockCompressionType =
- GetFieldIDOrDie(env, dataBlock, "mCompressionType", "I");
- }
-};
-
-const JniIds& jniIds(JNIEnv* env) {
- static const JniIds ids(env);
- return ids;
-}
-
-class ScopedJniArrayCritical {
-public:
- ScopedJniArrayCritical(JNIEnv* env, jarray array) : mEnv(env), mArr(array) {
- mPtr = array ? env->GetPrimitiveArrayCritical(array, nullptr) : nullptr;
- }
- ~ScopedJniArrayCritical() {
- if (mPtr) {
- mEnv->ReleasePrimitiveArrayCritical(mArr, mPtr, 0);
- mPtr = nullptr;
- }
- }
-
- ScopedJniArrayCritical(const ScopedJniArrayCritical&) = delete;
- void operator=(const ScopedJniArrayCritical&) = delete;
-
- ScopedJniArrayCritical(ScopedJniArrayCritical&& other)
- : mEnv(other.mEnv),
- mArr(std::exchange(mArr, nullptr)),
- mPtr(std::exchange(mPtr, nullptr)) {}
- ScopedJniArrayCritical& operator=(ScopedJniArrayCritical&& other) {
- mEnv = other.mEnv;
- mArr = std::exchange(other.mArr, nullptr);
- mPtr = std::exchange(other.mPtr, nullptr);
- return *this;
- }
-
- void* ptr() const { return mPtr; }
- jsize size() const { return mArr ? mEnv->GetArrayLength(mArr) : 0; }
-
-private:
- JNIEnv* mEnv;
- jarray mArr;
- void* mPtr;
-};
-
static jboolean nativeCreateDataLoader(JNIEnv* env,
jobject thiz,
jint storageId,
jobject control,
jobject params,
jobject callback) {
- ALOGE("nativeCreateDataLoader: %p/%d, %d, %p, %p, %p", thiz,
- env->GetObjectRefType(thiz), storageId, params, control, callback);
return DataLoaderService_OnCreate(env, thiz,
storageId, control, params, callback);
}
@@ -100,130 +35,22 @@
static jboolean nativeStartDataLoader(JNIEnv* env,
jobject thiz,
jint storageId) {
- ALOGE("nativeStartDataLoader: %p/%d, %d", thiz, env->GetObjectRefType(thiz),
- storageId);
return DataLoaderService_OnStart(storageId);
}
static jboolean nativeStopDataLoader(JNIEnv* env,
jobject thiz,
jint storageId) {
- ALOGE("nativeStopDataLoader: %p/%d, %d", thiz, env->GetObjectRefType(thiz),
- storageId);
return DataLoaderService_OnStop(storageId);
}
static jboolean nativeDestroyDataLoader(JNIEnv* env,
jobject thiz,
jint storageId) {
- ALOGE("nativeDestroyDataLoader: %p/%d, %d", thiz,
- env->GetObjectRefType(thiz), storageId);
return DataLoaderService_OnDestroy(storageId);
}
-static jboolean nativeOnFileCreated(JNIEnv* env,
- jobject thiz,
- jint storageId,
- jlong inode,
- jbyteArray metadata) {
- ALOGE("nativeOnFileCreated: %p/%d, %d", thiz,
- env->GetObjectRefType(thiz), storageId);
- return DataLoaderService_OnFileCreated(storageId, inode, metadata);
-}
-
-static jboolean nativeIsFileRangeLoadedNode(JNIEnv* env,
- jobject clazz,
- jlong self,
- jlong node,
- jlong start,
- jlong end) {
- // TODO(b/136132412): implement this
- return JNI_FALSE;
-}
-
-static jboolean nativeWriteMissingData(JNIEnv* env,
- jobject clazz,
- jlong self,
- jobjectArray data_block,
- jobjectArray hash_blocks) {
- const auto& jni = jniIds(env);
- auto length = env->GetArrayLength(data_block);
- std::vector<incfs_new_data_block> instructions(length);
-
- // May not call back into Java after even a single jniArrayCritical, so
- // let's collect the Java pointers to byte buffers first and lock them in
- // memory later.
-
- std::vector<jbyteArray> blockBuffers(length);
- for (int i = 0; i != length; ++i) {
- auto& inst = instructions[i];
- auto jniBlock = env->GetObjectArrayElement(data_block, i);
- inst.file_ino = env->GetLongField(jniBlock, jni.dataBlockFileIno);
- inst.block_index = env->GetIntField(jniBlock, jni.dataBlockBlockIndex);
- blockBuffers[i] = (jbyteArray)env->GetObjectField(
- jniBlock, jni.dataBlockDataBytes);
- inst.compression = (incfs_compression_alg)env->GetIntField(
- jniBlock, jni.dataBlockCompressionType);
- }
-
- std::vector<ScopedJniArrayCritical> jniScopedArrays;
- jniScopedArrays.reserve(length);
- for (int i = 0; i != length; ++i) {
- auto buffer = blockBuffers[i];
- jniScopedArrays.emplace_back(env, buffer);
- auto& inst = instructions[i];
- inst.data = (uint64_t)jniScopedArrays.back().ptr();
- inst.data_len = jniScopedArrays.back().size();
- }
-
- auto connector = (DataLoaderFilesystemConnectorPtr)self;
- if (auto err = DataLoader_FilesystemConnector_writeBlocks(
- connector, instructions.data(), length);
- err < 0) {
- jniScopedArrays.clear();
- return JNI_FALSE;
- }
-
- return JNI_TRUE;
-}
-
-static jboolean nativeWriteSignerDataNode(JNIEnv* env,
- jobject clazz,
- jlong self,
- jstring relative_path,
- jbyteArray signer_data) {
- // TODO(b/136132412): implement this
- return JNI_TRUE;
-}
-
-static jbyteArray nativeGetFileMetadataNode(JNIEnv* env,
- jobject clazz,
- jlong self,
- jlong inode) {
- auto connector = (DataLoaderFilesystemConnectorPtr)self;
- std::vector<char> metadata(INCFS_MAX_FILE_ATTR_SIZE);
- size_t size = metadata.size();
- if (DataLoader_FilesystemConnector_getRawMetadata(connector, inode,
- metadata.data(), &size) < 0) {
- size = 0;
- }
- metadata.resize(size);
-
- auto buffer = env->NewByteArray(metadata.size());
- env->SetByteArrayRegion(buffer, 0, metadata.size(),
- (jbyte*)metadata.data());
- return buffer;
-}
-
-static jbyteArray nativeGetFileInfoNode(JNIEnv* env,
- jobject clazz,
- jlong self,
- jlong inode) {
- // TODO(b/136132412): implement this
- return nullptr;
-}
-
static jboolean nativeReportStatus(JNIEnv* env,
jobject clazz,
jlong self,
@@ -235,34 +62,21 @@
static const JNINativeMethod dlc_method_table[] = {
{"nativeCreateDataLoader",
- "(ILandroid/os/incremental/IncrementalFileSystemControlParcel;"
- "Landroid/os/incremental/IncrementalDataLoaderParamsParcel;"
+ "(ILandroid/content/pm/FileSystemControlParcel;"
+ "Landroid/content/pm/DataLoaderParamsParcel;"
"Landroid/content/pm/IDataLoaderStatusListener;)Z",
(void*)nativeCreateDataLoader},
{"nativeStartDataLoader", "(I)Z", (void*)nativeStartDataLoader},
{"nativeStopDataLoader", "(I)Z", (void*)nativeStopDataLoader},
{"nativeDestroyDataLoader", "(I)Z", (void*)nativeDestroyDataLoader},
- {"nativeIsFileRangeLoadedNode", "(JJJJ)Z",
- (void*)nativeIsFileRangeLoadedNode},
- {"nativeWriteMissingData",
- "(J[Landroid/service/incremental/"
- "IncrementalDataLoaderService$FileSystemConnector$DataBlock;[Landroid/service/incremental/"
- "IncrementalDataLoaderService$FileSystemConnector$HashBlock;)Z",
- (void*)nativeWriteMissingData},
- {"nativeWriteSignerDataNode", "(JJ[B)Z",
- (void*)nativeWriteSignerDataNode},
- {"nativeGetFileMetadataNode", "(JJ)[B",
- (void*)nativeGetFileMetadataNode},
- {"nativeGetFileInfoNode", "(JJ)[B", (void*)nativeGetFileInfoNode},
{"nativeReportStatus", "(JI)Z", (void*)nativeReportStatus},
- {"nativeOnFileCreated", "(IJ[B)Z", (void*)nativeOnFileCreated},
};
} // namespace
int register_android_service_DataLoaderService(JNIEnv* env) {
return jniRegisterNativeMethods(env,
- "android/service/incremental/IncrementalDataLoaderService",
+ "android/service/dataloader/DataLoaderService",
dlc_method_table, NELEM(dlc_method_table));
}
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index e17a617..37aca08 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -20,6 +20,7 @@
#include <jni.h>
#include <media/MediaMetricsItem.h>
#include <nativehelper/JNIHelp.h>
+#include <variant>
#include "android_media_MediaMetricsJNI.h"
#include "android_os_Parcel.h"
@@ -74,6 +75,23 @@
}
template<>
+ void put(jstring keyName, const std::string& value) {
+ env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value.c_str()));
+ }
+
+ template<>
+ void put(jstring keyName, const std::pair<int64_t, int64_t>& value) {
+ ; // rate is currently ignored
+ }
+
+ template<>
+ void put(jstring keyName, const std::monostate& value) {
+ ; // none is currently ignored
+ }
+
+ // string char * helpers
+
+ template<>
void put(jstring keyName, const char * const& value) {
env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value));
}
@@ -83,11 +101,6 @@
env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value));
}
- template<>
- void put(jstring keyName, const std::pair<int64_t, int64_t>& value) {
- ; // rate is currently ignored
- }
-
// We allow both jstring and non-jstring variants.
template<typename T>
void put(const char *keyName, const T& value) {
diff --git a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
index 1f88114..bd5b795 100644
--- a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
+++ b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
@@ -16,10 +16,10 @@
package com.android.incremental.nativeadb;
-import android.service.incremental.IncrementalDataLoaderService;
+import android.service.dataloader.DataLoaderService;
/** This code is used for testing only. */
-public class NativeAdbDataLoaderService extends IncrementalDataLoaderService {
+public class NativeAdbDataLoaderService extends DataLoaderService {
public static final String TAG = "NativeAdbDataLoaderService";
static {
System.loadLibrary("nativeadbdataloaderservice_jni");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
index a1cfb54..f0a003f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
@@ -20,9 +20,12 @@
import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_PENDING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
-import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FILTERING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_IDLE;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_GROUP_FILTERING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_RENDER_FILTERING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_RESETTING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_SORTING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_TRANSFORMING;
@@ -63,16 +66,17 @@
private final SystemClock mSystemClock;
private final NotifLog mNotifLog;
- private final List<ListEntry> mNotifList = new ArrayList<>();
+ private List<ListEntry> mNotifList = new ArrayList<>();
+ private List<ListEntry> mNewNotifList = new ArrayList<>();
private final PipelineState mPipelineState = new PipelineState();
private final Map<String, GroupEntry> mGroups = new ArrayMap<>();
private Collection<NotificationEntry> mAllEntries = Collections.emptyList();
- private final List<ListEntry> mNewEntries = new ArrayList<>();
private int mIterationCount = 0;
- private final List<NotifFilter> mNotifFilters = new ArrayList<>();
+ private final List<NotifFilter> mNotifPreGroupFilters = new ArrayList<>();
private final List<NotifPromoter> mNotifPromoters = new ArrayList<>();
+ private final List<NotifFilter> mNotifPreRenderFilters = new ArrayList<>();
private final List<NotifComparator> mNotifComparators = new ArrayList<>();
private SectionsProvider mSectionsProvider = new DefaultSectionsProvider();
@@ -138,12 +142,21 @@
}
@Override
- public void addFilter(NotifFilter filter) {
+ public void addPreGroupFilter(NotifFilter filter) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
- mNotifFilters.add(filter);
- filter.setInvalidationListener(this::onFilterInvalidated);
+ mNotifPreGroupFilters.add(filter);
+ filter.setInvalidationListener(this::onPreGroupFilterInvalidated);
+ }
+
+ @Override
+ public void addPreRenderFilter(NotifFilter filter) {
+ Assert.isMainThread();
+ mPipelineState.requireState(STATE_IDLE);
+
+ mNotifPreRenderFilters.add(filter);
+ filter.setInvalidationListener(this::onPreRenderFilterInvalidated);
}
@Override
@@ -202,15 +215,15 @@
}
};
- private void onFilterInvalidated(NotifFilter filter) {
+ private void onPreGroupFilterInvalidated(NotifFilter filter) {
Assert.isMainThread();
- mNotifLog.log(NotifEvent.FILTER_INVALIDATED, String.format(
+ mNotifLog.log(NotifEvent.PRE_GROUP_FILTER_INVALIDATED, String.format(
"Filter \"%s\" invalidated; pipeline state is %d",
filter.getName(),
mPipelineState.getState()));
- rebuildListIfBefore(STATE_FILTERING);
+ rebuildListIfBefore(STATE_PRE_GROUP_FILTERING);
}
private void onPromoterInvalidated(NotifPromoter filter) {
@@ -235,6 +248,17 @@
rebuildListIfBefore(STATE_SORTING);
}
+ private void onPreRenderFilterInvalidated(NotifFilter filter) {
+ Assert.isMainThread();
+
+ mNotifLog.log(NotifEvent.PRE_RENDER_FILTER_INVALIDATED, String.format(
+ "Filter \"%s\" invalidated; pipeline state is %d",
+ filter.getName(),
+ mPipelineState.getState()));
+
+ rebuildListIfBefore(STATE_PRE_RENDER_FILTERING);
+ }
+
private void onNotifComparatorInvalidated(NotifComparator comparator) {
Assert.isMainThread();
@@ -247,6 +271,17 @@
}
/**
+ * Points mNotifList to the list stored in mNewNotifList.
+ * Reuses the (emptied) mNotifList as mNewNotifList.
+ */
+ private void applyNewNotifList() {
+ mNotifList.clear();
+ List<ListEntry> emptyList = mNotifList;
+ mNotifList = mNewNotifList;
+ mNewNotifList = emptyList;
+ }
+
+ /**
* The core algorithm of the pipeline. See the top comment in {@link NotifListBuilder} for
* details on our contracts with other code.
*
@@ -261,35 +296,47 @@
mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
mPipelineState.setState(STATE_BUILD_STARTED);
- // Step 1: Filtering and initial grouping
- // Filter out any notifs that shouldn't be shown right now and cluster any that are part of
- // a group
- mPipelineState.incrementTo(STATE_FILTERING);
- mNotifList.clear();
- mNewEntries.clear();
- filterAndGroup(mAllEntries, mNotifList, mNewEntries);
- pruneIncompleteGroups(mNotifList, mNewEntries);
+ // Step 1: Reset notification states
+ mPipelineState.incrementTo(STATE_RESETTING);
+ resetNotifs();
- // Step 2: Group transforming
+ // Step 2: Filter out any notifications that shouldn't be shown right now
+ mPipelineState.incrementTo(STATE_PRE_GROUP_FILTERING);
+ filterNotifs(mAllEntries, mNotifList, mNotifPreGroupFilters);
+
+ // Step 3: Group notifications with the same group key and set summaries
+ mPipelineState.incrementTo(STATE_GROUPING);
+ groupNotifs(mNotifList, mNewNotifList);
+ applyNewNotifList();
+ pruneIncompleteGroups(mNotifList);
+
+ // Step 4: Group transforming
// Move some notifs out of their groups and up to top-level (mostly used for heads-upping)
- dispatchOnBeforeTransformGroups(mReadOnlyNotifList, mNewEntries);
+ dispatchOnBeforeTransformGroups(mReadOnlyNotifList);
mPipelineState.incrementTo(STATE_TRANSFORMING);
promoteNotifs(mNotifList);
- pruneIncompleteGroups(mNotifList, mNewEntries);
+ pruneIncompleteGroups(mNotifList);
- // Step 3: Sort
+ // Step 5: Sort
// Assign each top-level entry a section, then sort the list by section and then within
// section by our list of custom comparators
dispatchOnBeforeSort(mReadOnlyNotifList);
mPipelineState.incrementTo(STATE_SORTING);
sortList();
- // Step 4: Lock in our group structure and log anything that's changed since the last run
+ // Step 6: Filter out entries after pre-group filtering, grouping, promoting and sorting
+ // Now filters can see grouping information to determine whether to filter or not
+ mPipelineState.incrementTo(STATE_PRE_RENDER_FILTERING);
+ filterNotifs(mNotifList, mNewNotifList, mNotifPreRenderFilters);
+ applyNewNotifList();
+ pruneIncompleteGroups(mNotifList);
+
+ // Step 7: Lock in our group structure and log anything that's changed since the last run
mPipelineState.incrementTo(STATE_FINALIZING);
logParentingChanges();
freeEmptyGroups();
- // Step 5: Dispatch the new list, first to any listeners and then to the view layer
+ // Step 6: Dispatch the new list, first to any listeners and then to the view layer
mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n"
+ dumpList(mNotifList));
dispatchOnBeforeRenderList(mReadOnlyNotifList);
@@ -297,20 +344,14 @@
mOnRenderListListener.onRenderList(mReadOnlyNotifList);
}
- // Step 6: We're done!
+ // Step 7: We're done!
mNotifLog.log(NotifEvent.LIST_BUILD_COMPLETE,
"Notif list build #" + mIterationCount + " completed");
mPipelineState.setState(STATE_IDLE);
mIterationCount++;
}
- private void filterAndGroup(
- Collection<NotificationEntry> entries,
- List<ListEntry> out,
- List<ListEntry> newlyVisibleEntries) {
-
- long now = mSystemClock.uptimeMillis();
-
+ private void resetNotifs() {
for (GroupEntry group : mGroups.values()) {
group.setPreviousParent(group.getParent());
group.setParent(null);
@@ -318,22 +359,57 @@
group.setSummary(null);
}
- for (NotificationEntry entry : entries) {
+ for (NotificationEntry entry : mAllEntries) {
entry.setPreviousParent(entry.getParent());
entry.setParent(null);
- // See if we should filter out this notification
- boolean shouldFilterOut = applyFilters(entry, now);
- if (shouldFilterOut) {
- continue;
- }
-
if (entry.mFirstAddedIteration == -1) {
entry.mFirstAddedIteration = mIterationCount;
- newlyVisibleEntries.add(entry);
}
+ }
- // Otherwise, group it
+ mNotifList.clear();
+ }
+
+ private void filterNotifs(Collection<? extends ListEntry> entries,
+ List<ListEntry> out, List<NotifFilter> filters) {
+ final long now = mSystemClock.uptimeMillis();
+ for (ListEntry entry : entries) {
+ if (entry instanceof GroupEntry) {
+ final GroupEntry groupEntry = (GroupEntry) entry;
+
+ // apply filter on its summary
+ final NotificationEntry summary = groupEntry.getRepresentativeEntry();
+ if (applyFilters(summary, now, filters)) {
+ groupEntry.setSummary(null);
+ annulAddition(summary);
+ }
+
+ // apply filter on its children
+ final List<NotificationEntry> children = groupEntry.getRawChildren();
+ for (int j = children.size() - 1; j >= 0; j--) {
+ final NotificationEntry child = children.get(j);
+ if (applyFilters(child, now, filters)) {
+ children.remove(child);
+ annulAddition(child);
+ }
+ }
+
+ out.add(groupEntry);
+ } else {
+ if (applyFilters((NotificationEntry) entry, now, filters)) {
+ annulAddition(entry);
+ } else {
+ out.add(entry);
+ }
+ }
+ }
+ }
+
+ private void groupNotifs(List<ListEntry> entries, List<ListEntry> out) {
+ for (ListEntry listEntry : entries) {
+ // since grouping hasn't happened yet, all notifs are NotificationEntries
+ NotificationEntry entry = (NotificationEntry) listEntry;
if (entry.getSbn().isGroup()) {
final String topLevelKey = entry.getSbn().getGroupKey();
@@ -341,7 +417,6 @@
if (group == null) {
group = new GroupEntry(topLevelKey);
group.mFirstAddedIteration = mIterationCount;
- newlyVisibleEntries.add(group);
mGroups.put(topLevelKey, group);
}
if (group.getParent() == null) {
@@ -367,9 +442,9 @@
if (entry.getSbn().getPostTime()
> existingSummary.getSbn().getPostTime()) {
group.setSummary(entry);
- annulAddition(existingSummary, out, newlyVisibleEntries);
+ annulAddition(existingSummary, out);
} else {
- annulAddition(entry, out, newlyVisibleEntries);
+ annulAddition(entry, out);
}
}
} else {
@@ -411,10 +486,7 @@
}
}
- private void pruneIncompleteGroups(
- List<ListEntry> shadeList,
- List<ListEntry> newlyVisibleEntries) {
-
+ private void pruneIncompleteGroups(List<ListEntry> shadeList) {
for (int i = 0; i < shadeList.size(); i++) {
final ListEntry tle = shadeList.get(i);
@@ -431,7 +503,7 @@
shadeList.add(summary);
group.setSummary(null);
- annulAddition(group, shadeList, newlyVisibleEntries);
+ annulAddition(group, shadeList);
} else if (group.getSummary() == null
|| children.size() < MIN_CHILDREN_FOR_GROUP) {
@@ -444,7 +516,7 @@
if (group.getSummary() != null) {
final NotificationEntry summary = group.getSummary();
group.setSummary(null);
- annulAddition(summary, shadeList, newlyVisibleEntries);
+ annulAddition(summary, shadeList);
}
for (int j = 0; j < children.size(); j++) {
@@ -454,7 +526,7 @@
}
children.clear();
- annulAddition(group, shadeList, newlyVisibleEntries);
+ annulAddition(group, shadeList);
}
}
}
@@ -468,10 +540,7 @@
* Before calling this method, the entry must already have been removed from its parent. If
* it's a group, its summary must be null and its children must be empty.
*/
- private void annulAddition(
- ListEntry entry,
- List<ListEntry> shadeList,
- List<ListEntry> newlyVisibleEntries) {
+ private void annulAddition(ListEntry entry, List<ListEntry> shadeList) {
// This function does very little, but if any of its assumptions are violated (and it has a
// lot of them), it will put the system into an inconsistent state. So we check all of them
@@ -508,13 +577,18 @@
}
}
+ annulAddition(entry);
+
+ }
+
+ /**
+ * Erases bookkeeping traces stored on an entry when it is removed from the notif list.
+ * This can happen if the entry is removed from a group that was broken up or if the entry was
+ * filtered out during any of the filtering steps.
+ */
+ private void annulAddition(ListEntry entry) {
entry.setParent(null);
if (entry.mFirstAddedIteration == mIterationCount) {
- if (!newlyVisibleEntries.remove(entry)) {
- throw new IllegalStateException("Cannot late-filter entry " + entry.getKey() + " "
- + entry + " from " + newlyVisibleEntries + " "
- + entry.mFirstAddedIteration);
- }
entry.mFirstAddedIteration = -1;
}
}
@@ -606,8 +680,8 @@
return cmp;
};
- private boolean applyFilters(NotificationEntry entry, long now) {
- NotifFilter filter = findRejectingFilter(entry, now);
+ private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
+ NotifFilter filter = findRejectingFilter(entry, now, filters);
if (filter != entry.mExcludingFilter) {
if (entry.mExcludingFilter == null) {
@@ -637,9 +711,12 @@
return filter != null;
}
- @Nullable private NotifFilter findRejectingFilter(NotificationEntry entry, long now) {
- for (int i = 0; i < mNotifFilters.size(); i++) {
- NotifFilter filter = mNotifFilters.get(i);
+ @Nullable private static NotifFilter findRejectingFilter(NotificationEntry entry, long now,
+ List<NotifFilter> filters) {
+ final int size = filters.size();
+
+ for (int i = 0; i < size; i++) {
+ NotifFilter filter = filters.get(i);
if (filter.shouldFilterOut(entry, now)) {
return filter;
}
@@ -691,12 +768,9 @@
}
}
- private void dispatchOnBeforeTransformGroups(
- List<ListEntry> entries,
- List<ListEntry> newlyVisibleEntries) {
+ private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
- mOnBeforeTransformGroupsListeners.get(i)
- .onBeforeTransformGroups(entries, newlyVisibleEntries);
+ mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index b68cb0d..5e7dd98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -55,7 +55,7 @@
public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
- notifListBuilder.addFilter(mNotifFilter);
+ notifListBuilder.addPreGroupFilter(mNotifFilter);
}
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
index 378599b..ee841c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
@@ -83,7 +83,7 @@
mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged);
// filter out foreground service notifications that aren't necessary anymore
- notifListBuilder.addFilter(mNotifFilter);
+ notifListBuilder.addPreGroupFilter(mNotifFilter);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 232246e..9312c22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -85,7 +85,7 @@
@Override
public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
setupInvalidateNotifListCallbacks();
- notifListBuilder.addFilter(mNotifFilter);
+ notifListBuilder.addPreRenderFilter(mNotifFilter);
}
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@@ -131,9 +131,8 @@
}
}
- // ... neither this notification nor its summary have high enough priority
+ // ... neither this notification nor its group have high enough priority
// to be shown on the lockscreen
- // TODO: grouping hasn't happened yet (b/145134683)
if (entry.getParent() != null) {
final GroupEntry parent = entry.getParent();
if (priorityExceedsLockscreenShowingThreshold(parent)) {
@@ -152,11 +151,10 @@
}
if (NotificationUtils.useNewInterruptionModel(mContext)
&& hideSilentNotificationsOnLockscreen()) {
- // TODO: make sure in the NewNotifPipeline that entry.isHighPriority() has been
- // correctly updated before reaching this point (b/145134683)
return entry.isHighPriority();
} else {
- return !entry.getRepresentativeEntry().getRanking().isAmbient();
+ return entry.getRepresentativeEntry() != null
+ && !entry.getRepresentativeEntry().getRanking().isAmbient();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 24e7a79..0751aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -46,7 +46,7 @@
public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
mStatusBarStateController.addCallback(mStatusBarStateCallback);
- notifListBuilder.addFilter(mNotifFilter);
+ notifListBuilder.addPreGroupFilter(mNotifFilter);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
index 15d3b92..7580924 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
@@ -59,11 +59,12 @@
public interface NotifListBuilder {
/**
- * Registers a filter with the pipeline. Filters are called on each notification in the order
- * that they were registered. If any filter returns true, the notification is removed from the
- * pipeline (and no other filters are called on that notif).
+ * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters
+ * are called on each notification in the order that they were registered. If any filter
+ * returns true, the notification is removed from the pipeline (and no other filters are
+ * called on that notif).
*/
- void addFilter(NotifFilter filter);
+ void addPreGroupFilter(NotifFilter filter);
/**
* Registers a promoter with the pipeline. Promoters are able to promote child notifications to
@@ -91,6 +92,15 @@
void setComparators(List<NotifComparator> comparators);
/**
+ * Registers a filter with the pipeline to filter right before rendering the list (after
+ * pre-group filtering, grouping, promoting and sorting occurs). Filters are
+ * called on each notification in the order that they were registered. If any filter returns
+ * true, the notification is removed from the pipeline (and no other filters are called on that
+ * notif).
+ */
+ void addPreRenderFilter(NotifFilter filter);
+
+ /**
* Called after notifications have been filtered and after the initial grouping has been
* performed but before NotifPromoters have had a chance to promote children out of groups.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
index 170ff48..d7a0815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
@@ -33,8 +33,6 @@
* @param list The current filtered and grouped list of (top-level) entries. Note that this is
* a live view into the current notif list and will change as the list moves through
* the pipeline.
- * @param newlyVisibleEntries The list of all entries (both top-level and children) who have
- * been added to the list for the first time.
*/
- void onBeforeTransformGroups(List<ListEntry> list, List<ListEntry> newlyVisibleEntries);
+ void onBeforeTransformGroups(List<ListEntry> list);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
index ad4bbd9..85f828d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
@@ -78,18 +78,24 @@
public static final int STATE_IDLE = 0;
public static final int STATE_BUILD_PENDING = 1;
public static final int STATE_BUILD_STARTED = 2;
- public static final int STATE_FILTERING = 3;
- public static final int STATE_TRANSFORMING = 4;
- public static final int STATE_SORTING = 5;
- public static final int STATE_FINALIZING = 6;
+ public static final int STATE_RESETTING = 3;
+ public static final int STATE_PRE_GROUP_FILTERING = 4;
+ public static final int STATE_GROUPING = 5;
+ public static final int STATE_TRANSFORMING = 6;
+ public static final int STATE_SORTING = 7;
+ public static final int STATE_PRE_RENDER_FILTERING = 8;
+ public static final int STATE_FINALIZING = 9;
@IntDef(prefix = { "STATE_" }, value = {
STATE_IDLE,
STATE_BUILD_PENDING,
STATE_BUILD_STARTED,
- STATE_FILTERING,
+ STATE_RESETTING,
+ STATE_PRE_GROUP_FILTERING,
+ STATE_GROUPING,
STATE_TRANSFORMING,
STATE_SORTING,
+ STATE_PRE_RENDER_FILTERING,
STATE_FINALIZING,
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
index 685eac8..e6189ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
@@ -20,8 +20,8 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
/**
- * Pluggable for participating in notif filtering. See
- * {@link NotifListBuilder#addFilter(NotifFilter)}.
+ * Pluggable for participating in notif filtering.
+ * See {@link NotifListBuilder#addPreGroupFilter} and {@link NotifListBuilder#addPreRenderFilter}.
*/
public abstract class NotifFilter extends Pluggable<NotifFilter> {
protected NotifFilter(String name) {
@@ -34,7 +34,11 @@
* This doesn't necessarily mean that your filter will get called on every notification,
* however. If another filter returns true before yours, we'll skip straight to the next notif.
*
- * @param entry The entry in question
+ * @param entry The entry in question.
+ * If this filter is registered via {@link NotifListBuilder#addPreGroupFilter},
+ * this entry will not have any grouping nor sorting information.
+ * If this filter is registered via {@link NotifListBuilder#addPreRenderFilter},
+ * this entry will have grouping and sorting information.
* @param now A timestamp in SystemClock.uptimeMillis that represents "now" for the purposes of
* pipeline execution. This value will be the same for all pluggable calls made
* during this pipeline run, giving pluggables a stable concept of "now" to compare
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
index 3b06220..c18af80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -88,13 +88,14 @@
START_BUILD_LIST,
DISPATCH_FINAL_LIST,
LIST_BUILD_COMPLETE,
- FILTER_INVALIDATED,
+ PRE_GROUP_FILTER_INVALIDATED,
PROMOTER_INVALIDATED,
SECTIONS_PROVIDER_INVALIDATED,
COMPARATOR_INVALIDATED,
PARENT_CHANGED,
FILTER_CHANGED,
PROMOTER_CHANGED,
+ PRE_RENDER_FILTER_INVALIDATED,
// NotificationEntryManager events:
NOTIF_ADDED,
@@ -127,6 +128,7 @@
"ParentChanged",
"FilterChanged",
"PromoterChanged",
+ "FinalFilterInvalidated",
// NEM event labels:
"NotifAdded",
@@ -152,14 +154,15 @@
public static final int START_BUILD_LIST = 2;
public static final int DISPATCH_FINAL_LIST = 3;
public static final int LIST_BUILD_COMPLETE = 4;
- public static final int FILTER_INVALIDATED = 5;
+ public static final int PRE_GROUP_FILTER_INVALIDATED = 5;
public static final int PROMOTER_INVALIDATED = 6;
public static final int SECTIONS_PROVIDER_INVALIDATED = 7;
public static final int COMPARATOR_INVALIDATED = 8;
public static final int PARENT_CHANGED = 9;
public static final int FILTER_CHANGED = 10;
public static final int PROMOTER_CHANGED = 11;
- private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 12;
+ public static final int PRE_RENDER_FILTER_INVALIDATED = 12;
+ private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 13;
/**
* Events related to {@link NotificationEntryManager}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
index f751f30..9f90396 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -335,7 +336,7 @@
// GIVEN a notification that is initially added to the list
PackageFilter filter = new PackageFilter(PACKAGE_2);
filter.setEnabled(false);
- mListBuilder.addFilter(filter);
+ mListBuilder.addPreGroupFilter(filter);
addNotif(0, PACKAGE_1);
addNotif(1, PACKAGE_2);
@@ -372,24 +373,54 @@
notif(2)
);
- // THEN the list of newly visible entries doesn't contain the summary or the group
- assertEquals(
- Arrays.asList(
- mEntrySet.get(0),
- mEntrySet.get(2)),
- listener.newlyVisibleEntries
- );
-
// THEN the summary has a null parent and an unset firstAddedIteration
assertNull(mEntrySet.get(1).getParent());
assertEquals(-1, mEntrySet.get(1).mFirstAddedIteration);
}
@Test
- public void testNotifsAreFiltered() {
+ public void testPreGroupNotifsAreFiltered() {
+ // GIVEN a PreGroupNotifFilter and PreRenderFilter that filters out the same package
+ NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_2));
+ NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_2));
+ mListBuilder.addPreGroupFilter(preGroupFilter);
+ mListBuilder.addPreRenderFilter(preRenderFilter);
+
+ // WHEN the pipeline is kicked off on a list of notifs
+ addNotif(0, PACKAGE_1);
+ addNotif(1, PACKAGE_2);
+ addNotif(2, PACKAGE_3);
+ addNotif(3, PACKAGE_2);
+ dispatchBuild();
+
+ // THEN the preGroupFilter is called on each notif in the original set
+ verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
+ verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
+ verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
+ verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
+
+ // THEN the preRenderFilter is only called on the notifications not already filtered out
+ verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
+ verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
+ verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
+ verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
+
+ // THEN the final list doesn't contain any filtered-out notifs
+ verifyBuiltList(
+ notif(0),
+ notif(2)
+ );
+
+ // THEN each filtered notif records the NotifFilter that did it
+ assertEquals(preGroupFilter, mEntrySet.get(1).mExcludingFilter);
+ assertEquals(preGroupFilter, mEntrySet.get(3).mExcludingFilter);
+ }
+
+ @Test
+ public void testPreRenderNotifsAreFiltered() {
// GIVEN a NotifFilter that filters out a specific package
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
- mListBuilder.addFilter(filter1);
+ mListBuilder.addPreRenderFilter(filter1);
// WHEN the pipeline is kicked off on a list of notifs
addNotif(0, PACKAGE_1);
@@ -420,8 +451,8 @@
// GIVEN two notif filters
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
- mListBuilder.addFilter(filter1);
- mListBuilder.addFilter(filter2);
+ mListBuilder.addPreGroupFilter(filter1);
+ mListBuilder.addPreGroupFilter(filter2);
// WHEN the pipeline is kicked off on a list of notifs
addNotif(0, PACKAGE_1);
@@ -521,7 +552,7 @@
public void testNotifsAreSectioned() {
// GIVEN a filter that removes all PACKAGE_4 notifs and a SectionsProvider that divides
// notifs based on package name
- mListBuilder.addFilter(new PackageFilter(PACKAGE_4));
+ mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4));
final SectionsProvider sectionsProvider = spy(new PackageSectioner());
mListBuilder.setSectionsProvider(sectionsProvider);
@@ -595,17 +626,19 @@
@Test
public void testListenersAndPluggablesAreFiredInOrder() {
// GIVEN a bunch of registered listeners and pluggables
- NotifFilter filter = spy(new PackageFilter(PACKAGE_1));
+ NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
NotifPromoter promoter = spy(new IdPromoter(3));
PackageSectioner sectioner = spy(new PackageSectioner());
NotifComparator comparator = spy(new HypeComparator(PACKAGE_4));
- mListBuilder.addFilter(filter);
+ NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5));
+ mListBuilder.addPreGroupFilter(preGroupFilter);
mListBuilder.addOnBeforeTransformGroupsListener(mOnBeforeTransformGroupsListener);
mListBuilder.addPromoter(promoter);
mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener);
mListBuilder.setComparators(Collections.singletonList(comparator));
mListBuilder.setSectionsProvider(sectioner);
mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
+ mListBuilder.addPreRenderFilter(preRenderFilter);
// WHEN a few new notifs are added
addNotif(0, PACKAGE_1);
@@ -619,25 +652,28 @@
// THEN the pluggables and listeners are called in order
InOrder inOrder = inOrder(
- filter,
+ preGroupFilter,
mOnBeforeTransformGroupsListener,
promoter,
mOnBeforeSortListener,
sectioner,
comparator,
+ preRenderFilter,
mOnBeforeRenderListListener,
mOnRenderListListener);
- inOrder.verify(filter, atLeastOnce())
+ inOrder.verify(preGroupFilter, atLeastOnce())
.shouldFilterOut(any(NotificationEntry.class), anyLong());
inOrder.verify(mOnBeforeTransformGroupsListener)
- .onBeforeTransformGroups(anyList(), anyList());
+ .onBeforeTransformGroups(anyList());
inOrder.verify(promoter, atLeastOnce())
.shouldPromoteToTopLevel(any(NotificationEntry.class));
inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
inOrder.verify(sectioner, atLeastOnce()).getSection(any(ListEntry.class));
inOrder.verify(comparator, atLeastOnce())
.compare(any(ListEntry.class), any(ListEntry.class));
+ inOrder.verify(preRenderFilter, atLeastOnce())
+ .shouldFilterOut(any(NotificationEntry.class), anyLong());
inOrder.verify(mOnBeforeRenderListListener).onBeforeRenderList(anyList());
inOrder.verify(mOnRenderListListener).onRenderList(anyList());
}
@@ -650,7 +686,7 @@
SectionsProvider sectionsProvider = new PackageSectioner();
NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
- mListBuilder.addFilter(packageFilter);
+ mListBuilder.addPreGroupFilter(packageFilter);
mListBuilder.addPromoter(idPromoter);
mListBuilder.setSectionsProvider(sectionsProvider);
mListBuilder.setComparators(Collections.singletonList(hypeComparator));
@@ -686,9 +722,9 @@
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_5));
NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
NotifFilter filter3 = spy(new PackageFilter(PACKAGE_5));
- mListBuilder.addFilter(filter1);
- mListBuilder.addFilter(filter2);
- mListBuilder.addFilter(filter3);
+ mListBuilder.addPreGroupFilter(filter1);
+ mListBuilder.addPreGroupFilter(filter2);
+ mListBuilder.addPreGroupFilter(filter3);
// GIVEN the SystemClock is set to a particular time:
mSystemClock.setUptimeMillis(47);
@@ -708,13 +744,13 @@
}
@Test
- public void testNewlyAddedEntries() {
+ public void testGroupTransformEntries() {
// GIVEN a registered OnBeforeTransformGroupsListener
RecordingOnBeforeTransformGroupsListener listener =
spy(new RecordingOnBeforeTransformGroupsListener());
mListBuilder.addOnBeforeTransformGroupsListener(listener);
- // Given some new notifs
+ // GIVEN some new notifs
addNotif(0, PACKAGE_1);
addGroupChild(1, PACKAGE_2, GROUP_1);
addGroupSummary(2, PACKAGE_2, GROUP_1);
@@ -742,27 +778,18 @@
mEntrySet.get(0),
mBuiltList.get(1),
mEntrySet.get(4)
- ),
- Arrays.asList(
- mEntrySet.get(0),
- mEntrySet.get(1),
- mBuiltList.get(1),
- mEntrySet.get(2),
- mEntrySet.get(3),
- mEntrySet.get(4),
- mEntrySet.get(5)
)
);
}
@Test
- public void testNewlyAddedEntriesOnSecondRun() {
+ public void testGroupTransformEntriesOnSecondRun() {
// GIVEN a registered OnBeforeTransformGroupsListener
RecordingOnBeforeTransformGroupsListener listener =
spy(new RecordingOnBeforeTransformGroupsListener());
mListBuilder.addOnBeforeTransformGroupsListener(listener);
- // Given some notifs that have already been added (two of which are in malformed groups)
+ // GIVEN some notifs that have already been added (two of which are in malformed groups)
addNotif(0, PACKAGE_1);
addGroupChild(1, PACKAGE_2, GROUP_1);
addGroupChild(2, PACKAGE_3, GROUP_2);
@@ -798,13 +825,6 @@
mEntrySet.get(1),
mBuiltList.get(2),
mEntrySet.get(7)
- ),
- Arrays.asList(
- mBuiltList.get(2),
- mEntrySet.get(4),
- mEntrySet.get(5),
- mEntrySet.get(6),
- mEntrySet.get(7)
)
);
}
@@ -841,19 +861,18 @@
}
@Test(expected = IllegalStateException.class)
- public void testOutOfOrderFilterInvalidationThrows() {
- // GIVEN a NotifFilter that gets invalidated during the grouping stage
+ public void testOutOfOrderPreGroupFilterInvalidationThrows() {
+ // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage
NotifFilter filter = new PackageFilter(PACKAGE_5);
- OnBeforeTransformGroupsListener listener =
- (list, newlyVisibleEntries) -> filter.invalidateList();
- mListBuilder.addFilter(filter);
+ OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
+ mListBuilder.addPreGroupFilter(filter);
mListBuilder.addOnBeforeTransformGroupsListener(listener);
// WHEN we try to run the pipeline and the filter is invalidated
addNotif(0, PACKAGE_1);
dispatchBuild();
- // Then an exception is thrown
+ // THEN an exception is thrown
}
@Test(expected = IllegalStateException.class)
@@ -869,7 +888,7 @@
addNotif(0, PACKAGE_1);
dispatchBuild();
- // Then an exception is thrown
+ // THEN an exception is thrown
}
@Test(expected = IllegalStateException.class)
@@ -885,7 +904,37 @@
addNotif(0, PACKAGE_1);
dispatchBuild();
- // Then an exception is thrown
+ // THEN an exception is thrown
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOutOfOrderPreRenderFilterInvalidationThrows() {
+ // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage
+ NotifFilter filter = new PackageFilter(PACKAGE_5);
+ OnBeforeRenderListListener listener = (list) -> filter.invalidateList();
+ mListBuilder.addPreRenderFilter(filter);
+ mListBuilder.addOnBeforeRenderListListener(listener);
+
+ // WHEN we try to run the pipeline and the PreRenderFilter is invalidated
+ addNotif(0, PACKAGE_1);
+ dispatchBuild();
+
+ // THEN an exception is thrown
+ }
+
+ @Test
+ public void testInOrderPreRenderFilter() {
+ // GIVEN a PreRenderFilter that gets invalidated during the grouping stage
+ NotifFilter filter = new PackageFilter(PACKAGE_5);
+ OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
+ mListBuilder.addPreRenderFilter(filter);
+ mListBuilder.addOnBeforeTransformGroupsListener(listener);
+
+ // WHEN we try to run the pipeline and the filter is invalidated
+ addNotif(0, PACKAGE_1);
+ dispatchBuild();
+
+ // THEN no exception thrown
}
/**
@@ -1177,13 +1226,9 @@
private static class RecordingOnBeforeTransformGroupsListener
implements OnBeforeTransformGroupsListener {
- public List<ListEntry> newlyVisibleEntries;
@Override
- public void onBeforeTransformGroups(List<ListEntry> list,
- List<ListEntry> newlyVisibleEntries) {
- this.newlyVisibleEntries = newlyVisibleEntries;
- }
+ public void onBeforeTransformGroups(List<ListEntry> list) { }
}
private static final String PACKAGE_1 = "com.test1";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
index 2c4361f..ea6c70a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
@@ -85,7 +85,7 @@
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
mDeviceProvisionedCoordinator.attach(null, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+ verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
mDeviceProvisionedFilter = filterCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
index b046b30..01bca0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
@@ -85,7 +85,7 @@
ArgumentCaptor.forClass(NotifLifetimeExtender.class);
mForegroundCoordinator.attach(mNotifCollection, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+ verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
verify(mNotifCollection, times(1)).addNotificationLifetimeExtender(
lifetimeExtenderCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
index f385178..979b8a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -86,7 +86,7 @@
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
mKeyguardCoordinator.attach(null, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+ verify(mNotifListBuilder, times(1)).addPreRenderFilter(filterCaptor.capture());
mKeyguardFilter = filterCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index 6b46045..d3b16c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -63,7 +63,7 @@
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
mRankingCoordinator.attach(null, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+ verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
mRankingFilter = filterCaptor.getValue();
}
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 5e659b6..c5409f8 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -29,6 +29,10 @@
import com.android.server.pm.UserManagerService;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* The base class for services running in the system process. Override and implement
* the lifecycle event callback methods as needed.
@@ -164,6 +168,25 @@
}
/**
+ * Helper method used to dump which users are {@link #onStartUser(UserInfo) supported}.
+ */
+ protected void dumpSupportedUsers(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final List<UserInfo> allUsers = UserManager.get(mContext).getUsers();
+ final List<Integer> supportedUsers = new ArrayList<>(allUsers.size());
+ for (UserInfo user : allUsers) {
+ supportedUsers.add(user.id);
+ }
+ if (allUsers.isEmpty()) {
+ pw.print(prefix); pw.println("No supported users");
+ } else {
+ final int size = supportedUsers.size();
+ pw.print(prefix); pw.print(size); pw.print(" supported user");
+ if (size > 1) pw.print("s");
+ pw.print(": "); pw.println(supportedUsers);
+ }
+ }
+
+ /**
* @deprecated subclasses should extend {@link #onStartUser(UserInfo)} instead (which by default
* calls this method).
*/
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerService.java b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
index 1b1a292..789551b 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerService.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.DataLoaderManager;
+import android.content.pm.DataLoaderParamsParcel;
+import android.content.pm.FileSystemControlParcel;
import android.content.pm.IDataLoader;
import android.content.pm.IDataLoaderStatusListener;
import android.os.Bundle;
@@ -27,8 +29,6 @@
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.incremental.IIncrementalManager;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
-import android.os.incremental.IncrementalFileSystemControlParcel;
import android.util.Slog;
import java.io.FileDescriptor;
@@ -80,8 +80,8 @@
* Finds data loader service provider and binds to it. This requires PackageManager.
*/
@Override
- public boolean prepareDataLoader(int mountId, IncrementalFileSystemControlParcel control,
- IncrementalDataLoaderParamsParcel params,
+ public boolean prepareDataLoader(int mountId, FileSystemControlParcel control,
+ DataLoaderParamsParcel params,
IDataLoaderStatusListener listener) {
Bundle dataLoaderParams = new Bundle();
dataLoaderParams.putCharSequence("packageName", params.packageName);
@@ -138,10 +138,6 @@
Slog.e(TAG, "Failed to retrieve data loader for ID=" + mountId);
return;
}
- try {
- dataLoader.onFileCreated(inode, metadata);
- } catch (RemoteException ex) {
- }
}
@Override
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
index d35e806..6a8434a 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
@@ -24,6 +24,7 @@
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.pm.DataLoaderParams;
import android.content.pm.InstallationFile;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
@@ -31,7 +32,6 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.ShellCommand;
-import android.os.incremental.IncrementalDataLoaderParams;
import android.util.Slog;
import java.io.FileDescriptor;
@@ -111,7 +111,7 @@
pw.println("File names and sizes don't match.");
return ERROR_DATA_LOADER_INIT;
}
- final IncrementalDataLoaderParams params = new IncrementalDataLoaderParams(
+ final DataLoaderParams params = new DataLoaderParams(
"", LOADER_PACKAGE_NAME, dataLoaderDynamicArgs);
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index d71ffb7..58f6ba2 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -762,6 +762,7 @@
if (mUpdatingPackageNames != null) {
pw.print("Packages being updated: "); pw.println(mUpdatingPackageNames);
}
+ dumpSupportedUsers(pw, prefix);
if (mServiceNameResolver != null) {
pw.print(prefix); pw.print("Name resolver: ");
mServiceNameResolver.dumpShort(pw); pw.println();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 286d291..dfdc29c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -57,6 +57,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageInstallerSession;
+import android.content.pm.IPackageInstallerSessionFileSystemConnector;
import android.content.pm.InstallationFile;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -1003,6 +1004,21 @@
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
+ private class FileSystemConnector extends IPackageInstallerSessionFileSystemConnector.Stub {
+ @Override
+ public void writeData(String name, long offsetBytes, long lengthBytes,
+ ParcelFileDescriptor incomingFd) {
+ if (incomingFd == null) {
+ throw new IllegalArgumentException("incomingFd can't be null");
+ }
+ try {
+ doWriteInternal(name, offsetBytes, lengthBytes, incomingFd);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
+ }
+ }
+
private class ChildStatusIntentReceiver {
private final SparseIntArray mChildSessionsRemaining;
private final IntentSender mStatusReceiver;
@@ -2405,7 +2421,7 @@
return;
}
- FilesystemConnector connector = new FilesystemConnector();
+ FileSystemConnector connector = new FileSystemConnector();
FileInfo[] addedFiles = mFiles.stream().filter(
file -> sAddedFilter.accept(new File(file.name))).toArray(FileInfo[]::new);
@@ -2430,19 +2446,11 @@
}
}
- // TODO(b/146080380): implement DataLoader using Incremental infrastructure.
- class FilesystemConnector {
- void writeData(FileInfo fileInfo, long offset, long lengthBytes,
- ParcelFileDescriptor incomingFd) throws IOException {
- doWriteInternal(fileInfo.name, offset, lengthBytes, incomingFd);
- }
- }
-
static class DataLoader {
private ParcelFileDescriptor mInFd = null;
- private FilesystemConnector mConnector = null;
+ private FileSystemConnector mConnector = null;
- void onCreate(FilesystemConnector connector) throws IOException {
+ void onCreate(FileSystemConnector connector) throws IOException {
mConnector = connector;
}
@@ -2460,14 +2468,14 @@
return false;
}
ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(mInFd.getFileDescriptor());
- mConnector.writeData(fileInfo, 0, fileInfo.lengthBytes, inFd);
+ mConnector.writeData(fileInfo.name, 0, fileInfo.lengthBytes, inFd);
} else {
File localFile = new File(filePath);
ParcelFileDescriptor incomingFd = null;
try {
incomingFd = ParcelFileDescriptor.open(localFile,
ParcelFileDescriptor.MODE_READ_ONLY);
- mConnector.writeData(fileInfo, 0, localFile.length(), incomingFd);
+ mConnector.writeData(fileInfo.name, 0, localFile.length(), incomingFd);
} finally {
IoUtils.closeQuietly(incomingFd);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5fabdb6..3515cb7 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -84,7 +84,6 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.IntArray;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -1510,7 +1509,7 @@
public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
checkManageUsersPermission("update users");
if (hasUserRestriction(UserManager.DISALLOW_SET_USER_ICON, userId)) {
- Log.w(LOG_TAG, "Cannot set user icon. DISALLOW_SET_USER_ICON is enabled.");
+ Slog.w(LOG_TAG, "Cannot set user icon. DISALLOW_SET_USER_ICON is enabled.");
return;
}
mLocalService.setUserIcon(userId, bitmap);
@@ -1558,7 +1557,7 @@
return ParcelFileDescriptor.open(
new File(iconPath), ParcelFileDescriptor.MODE_READ_ONLY);
} catch (FileNotFoundException e) {
- Log.e(LOG_TAG, "Couldn't find icon file", e);
+ Slog.e(LOG_TAG, "Couldn't find icon file", e);
}
return null;
}
@@ -1656,7 +1655,7 @@
}
}
if (DBG) {
- Log.d(LOG_TAG, "setDevicePolicyUserRestrictions: "
+ Slog.d(LOG_TAG, "setDevicePolicyUserRestrictions: "
+ " originatingUserId=" + originatingUserId
+ " global=" + global + (globalChanged ? " (changed)" : "")
+ " local=" + local + (localChanged ? " (changed)" : "")
@@ -1718,7 +1717,7 @@
@GuardedBy("mRestrictionsLock")
private void invalidateEffectiveUserRestrictionsLR(@UserIdInt int userId) {
if (DBG) {
- Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
+ Slog.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
}
mCachedEffectiveUserRestrictions.remove(userId);
}
@@ -1940,7 +1939,7 @@
try {
mAppOpsService.setUserRestrictions(effective, mUserRestriconToken, userId);
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
+ Slog.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
}
}
});
@@ -2012,7 +2011,7 @@
try {
runningUsers = ActivityManager.getService().getRunningUserIds();
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to access ActivityManagerService");
+ Slog.w(LOG_TAG, "Unable to access ActivityManagerService");
return;
}
// Then re-calculate the effective restrictions and apply, only for running users.
@@ -2596,7 +2595,7 @@
}
}
} catch (Resources.NotFoundException e) {
- Log.e(LOG_TAG, "Couldn't find resource: config_defaultFirstUserRestrictions", e);
+ Slog.e(LOG_TAG, "Couldn't find resource: config_defaultFirstUserRestrictions", e);
}
if (!restrictions.isEmpty()) {
@@ -3056,7 +3055,7 @@
? UserManager.DISALLOW_ADD_MANAGED_PROFILE
: UserManager.DISALLOW_ADD_USER;
if (hasUserRestriction(restriction, UserHandle.getCallingUserId())) {
- Log.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled.");
+ Slog.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled.");
return null;
}
return createUserInternalUnchecked(name, userType, flags, parentId,
@@ -3115,7 +3114,7 @@
DeviceStorageMonitorInternal dsm = LocalServices
.getService(DeviceStorageMonitorInternal.class);
if (dsm.isMemoryLow()) {
- Log.w(LOG_TAG, "Cannot add user. Not enough space on disk.");
+ Slog.w(LOG_TAG, "Cannot add user. Not enough space on disk.");
return null;
}
@@ -3138,36 +3137,36 @@
if (parent == null) return null;
}
if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) {
- Log.e(LOG_TAG, "Cannot add more users of type " + userType
+ Slog.e(LOG_TAG, "Cannot add more users of type " + userType
+ ". Maximum number of that type already exists.");
return null;
}
// TODO(b/142482943): Perhaps let the following code apply to restricted users too.
if (isProfile && !canAddMoreProfilesToUser(userType, parentId, false)) {
- Log.e(LOG_TAG, "Cannot add more profiles of type " + userType
+ Slog.e(LOG_TAG, "Cannot add more profiles of type " + userType
+ " for user " + parentId);
return null;
}
if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) {
// If we're not adding a guest/demo user or a profile and the 'user limit' has
// been reached, cannot add a user.
- Log.e(LOG_TAG, "Cannot add user. Maximum user limit is reached.");
+ Slog.e(LOG_TAG, "Cannot add user. Maximum user limit is reached.");
return null;
}
// In legacy mode, restricted profile's parent can only be the owner user
if (isRestricted && !UserManager.isSplitSystemUser()
&& (parentId != UserHandle.USER_SYSTEM)) {
- Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
+ Slog.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
return null;
}
if (isRestricted && UserManager.isSplitSystemUser()) {
if (parent == null) {
- Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
+ Slog.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
+ "specified");
return null;
}
if (!parent.info.canHaveProfile()) {
- Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
+ Slog.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
+ "created for the specified parent user id " + parentId);
return null;
}
@@ -3318,7 +3317,7 @@
+ Integer.toHexString(preCreatedUser.flags) + ").");
return null;
}
- Log.i(LOG_TAG, "Reusing pre-created user " + preCreatedUser.id + " of type "
+ Slog.i(LOG_TAG, "Reusing pre-created user " + preCreatedUser.id + " of type "
+ userType + " and bestowing on it flags " + UserInfo.flagsToString(flags));
preCreatedUser.name = name;
preCreatedUser.flags = newFlags;
@@ -3483,7 +3482,7 @@
checkManageUsersPermission("Only the system can remove users");
if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
UserManager.DISALLOW_REMOVE_USER, false)) {
- Log.w(LOG_TAG, "Cannot remove user. DISALLOW_REMOVE_USER is enabled.");
+ Slog.w(LOG_TAG, "Cannot remove user. DISALLOW_REMOVE_USER is enabled.");
return false;
}
@@ -3535,7 +3534,7 @@
String restriction = isManagedProfile
? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER;
if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
- Log.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
+ Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
return false;
}
return removeUserUnchecked(userId);
@@ -3553,25 +3552,25 @@
final UserData userData;
int currentUser = ActivityManager.getCurrentUser();
if (currentUser == userId) {
- Log.w(LOG_TAG, "Current user cannot be removed.");
+ Slog.w(LOG_TAG, "Current user cannot be removed.");
return false;
}
synchronized (mPackagesLock) {
synchronized (mUsersLock) {
userData = mUsers.get(userId);
if (userId == UserHandle.USER_SYSTEM) {
- Log.e(LOG_TAG, "System user cannot be removed.");
+ Slog.e(LOG_TAG, "System user cannot be removed.");
return false;
}
if (userData == null) {
- Log.e(LOG_TAG, String.format(
+ Slog.e(LOG_TAG, String.format(
"Cannot remove user %d, invalid user id provided.", userId));
return false;
}
if (mRemovingUserIds.get(userId)) {
- Log.e(LOG_TAG, String.format(
+ Slog.e(LOG_TAG, String.format(
"User %d is already scheduled for removal.", userId));
return false;
}
@@ -3591,7 +3590,7 @@
try {
mAppOpsService.removeUser(userId);
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
+ Slog.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
}
// TODO(b/142482943): Send some sort of broadcast for profiles even if non-managed?
@@ -3616,7 +3615,7 @@
}
});
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Failed to stop user during removal.", e);
+ Slog.w(LOG_TAG, "Failed to stop user during removal.", e);
return false;
}
return res == ActivityManager.USER_OP_SUCCESS;
@@ -3828,7 +3827,7 @@
readEntry(restrictions, values, parser);
}
} catch (IOException|XmlPullParserException e) {
- Log.w(LOG_TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
+ Slog.w(LOG_TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
} finally {
IoUtils.closeQuietly(fis);
}
@@ -4862,8 +4861,8 @@
}
private static void debug(String message) {
- Log.d(LOG_TAG, message +
- (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, " ") : ""));
+ Slog.d(LOG_TAG, message
+ + (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, " ") : ""));
}
/** @see #getMaxUsersOfTypePerParent(UserTypeDetails) */
diff --git a/services/core/java/com/android/server/utils/quota/Categorizer.java b/services/core/java/com/android/server/utils/quota/Categorizer.java
new file mode 100644
index 0000000..bf24991
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/Categorizer.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 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.android.server.utils.quota;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Identifies the {@link Category} that each UPTC belongs in.
+ *
+ * @see Uptc
+ */
+public interface Categorizer {
+ /**
+ * Return the {@link Category} that this UPTC belongs to.
+ *
+ * @see Uptc
+ */
+ @NonNull
+ Category getCategory(int userId, @NonNull String packageName, @Nullable String tag);
+}
diff --git a/services/core/java/com/android/server/utils/quota/Category.java b/services/core/java/com/android/server/utils/quota/Category.java
new file mode 100644
index 0000000..d8459d7
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/Category.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 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.android.server.utils.quota;
+
+import android.annotation.NonNull;
+import android.util.proto.ProtoOutputStream;
+import android.util.quota.CategoryProto;
+
+/**
+ * A category as defined by the (system) client. Categories are used to put UPTCs in different
+ * groups. A sample group of Categories could be the various App Standby buckets or foreground vs
+ * background.
+ *
+ * @see Uptc
+ */
+public final class Category {
+ @NonNull
+ private final String mName;
+
+ private final int mHash;
+
+ /** Construct a new Category with the specified name. */
+ public Category(@NonNull String name) {
+ mName = name;
+ mHash = name.hashCode();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other instanceof Category) {
+ return this.mName.equals(((Category) other).mName);
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return mHash;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "Category{" + mName + "}";
+ }
+
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(CategoryProto.NAME, mName);
+ proto.end(token);
+ }
+}
diff --git a/services/core/java/com/android/server/utils/quota/QuotaChangeListener.java b/services/core/java/com/android/server/utils/quota/QuotaChangeListener.java
new file mode 100644
index 0000000..b673050
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/QuotaChangeListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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.android.server.utils.quota;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Listener that is notified when a UPTC goes in and out of quota.
+ *
+ * @see Uptc
+ */
+public interface QuotaChangeListener {
+ /**
+ * Called when the UPTC reaches its quota or comes back into quota.
+ *
+ * @see Uptc
+ */
+ void onQuotaStateChanged(int userId, @NonNull String packageName, @Nullable String tag);
+}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index dee9e9f..4de36b8b 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -89,6 +89,14 @@
@VisibleForTesting
static final int SNAPSHOT_MODE_NONE = 2;
+ /**
+ * Constant for <code>scaleFactor</code> when calling {@link #snapshotTask} which is
+ * interpreted as using the most appropriate scale ratio for the system.
+ * This may yield a smaller ratio on low memory devices.
+ */
+ @VisibleForTesting
+ static final float SNAPSHOT_SCALE_AUTO = -1f;
+
private final WindowManagerService mService;
private final TaskSnapshotCache mCache;
@@ -260,6 +268,84 @@
});
}
+ /**
+ * Validates the state of the Task is appropriate to capture a snapshot, collects
+ * information from the task and populates the builder.
+ *
+ * @param task the task to capture
+ * @param scaleFraction the scale fraction between 0-1.0, or {@link #SNAPSHOT_SCALE_AUTO}
+ * to automatically select
+ * @param pixelFormat the desired pixel format, or {@link PixelFormat#UNKNOWN} to
+ * automatically select
+ * @param builder the snapshot builder to populate
+ *
+ * @return true if the state of the task is ok to proceed
+ */
+ private boolean prepareTaskSnapshot(Task task, float scaleFraction, int pixelFormat,
+ TaskSnapshot.Builder builder) {
+ if (!mService.mPolicy.isScreenOn()) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
+ }
+ return false;
+ }
+ final ActivityRecord activity = findAppTokenForSnapshot(task);
+ if (activity == null) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task);
+ }
+ return false;
+ }
+ if (activity.hasCommittedReparentToAnimationLeash()) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + activity);
+ }
+ return false;
+ }
+
+ final WindowState mainWindow = activity.findMainWindow();
+ if (mainWindow == null) {
+ Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + task);
+ return false;
+ }
+
+ builder.setIsRealSnapshot(true);
+ builder.setId(System.currentTimeMillis());
+ builder.setContentInsets(getInsets(mainWindow));
+
+ final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
+
+ if (scaleFraction == SNAPSHOT_SCALE_AUTO) {
+ builder.setScaleFraction(isLowRamDevice
+ ? mPersister.getReducedScale()
+ : mFullSnapshotScale);
+ builder.setReducedResolution(isLowRamDevice);
+ } else {
+ builder.setScaleFraction(scaleFraction);
+ builder.setReducedResolution(scaleFraction < 1.0f);
+ }
+
+ final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
+ final boolean isShowWallpaper = (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) != 0;
+
+ if (pixelFormat == PixelFormat.UNKNOWN) {
+ pixelFormat = mPersister.use16BitFormat() && activity.fillsParent()
+ && !(isWindowTranslucent && isShowWallpaper)
+ ? PixelFormat.RGB_565
+ : PixelFormat.RGBA_8888;
+ }
+
+ final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat)
+ && (!activity.fillsParent() || isWindowTranslucent);
+
+ builder.setTopActivityComponent(activity.mActivityComponent);
+ builder.setIsTranslucent(isTranslucent);
+ builder.setOrientation(activity.getTask().getConfiguration().orientation);
+ builder.setWindowingMode(task.getWindowingMode());
+ builder.setSystemUiVisibility(getSystemUiVisibility(task));
+ return true;
+ }
+
@Nullable
SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task,
float scaleFraction) {
@@ -288,63 +374,31 @@
return screenshotBuffer;
}
- @Nullable private TaskSnapshot snapshotTask(Task task) {
- if (!mService.mPolicy.isScreenOn()) {
- if (DEBUG_SCREENSHOT) {
- Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
- }
+ @Nullable
+ TaskSnapshot snapshotTask(Task task) {
+ return snapshotTask(task, SNAPSHOT_SCALE_AUTO, PixelFormat.UNKNOWN);
+ }
+
+ @Nullable
+ TaskSnapshot snapshotTask(Task task, float scaleFraction, int pixelFormat) {
+ TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
+
+ if (!prepareTaskSnapshot(task, scaleFraction, pixelFormat, builder)) {
+ // Failed some pre-req. Has been logged.
return null;
}
- final ActivityRecord activity = findAppTokenForSnapshot(task);
- if (activity == null) {
- if (DEBUG_SCREENSHOT) {
- Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task);
- }
- return null;
- }
- if (activity.hasCommittedReparentToAnimationLeash()) {
- if (DEBUG_SCREENSHOT) {
- Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + activity);
- }
- return null;
- }
-
- final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
- final float scaleFraction = isLowRamDevice
- ? mPersister.getReducedScale()
- : mFullSnapshotScale;
-
- final WindowState mainWindow = activity.findMainWindow();
- if (mainWindow == null) {
- Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + task);
- return null;
- }
- final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
- final boolean isShowWallpaper = (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) != 0;
- final int pixelFormat = mPersister.use16BitFormat() && activity.fillsParent()
- && !(isWindowTranslucent && isShowWallpaper)
- ? PixelFormat.RGB_565
- : PixelFormat.RGBA_8888;
final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
- createTaskSnapshot(task, scaleFraction, pixelFormat);
+ createTaskSnapshot(task, builder.getScaleFraction(),
+ builder.getPixelFormat());
if (screenshotBuffer == null) {
- if (DEBUG_SCREENSHOT) {
- Slog.w(TAG_WM, "Failed to take screenshot for " + task);
- }
+ // Failed to acquire image. Has been logged.
return null;
}
- final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat)
- && (!activity.fillsParent() || isWindowTranslucent);
- return new TaskSnapshot(
- System.currentTimeMillis() /* id */,
- activity.mActivityComponent, screenshotBuffer.getGraphicBuffer(),
- screenshotBuffer.getColorSpace(),
- activity.getTask().getConfiguration().orientation,
- getInsets(mainWindow), isLowRamDevice /* reduced */, scaleFraction /* scale */,
- true /* isRealSnapshot */, task.getWindowingMode(), getSystemUiVisibility(task),
- isTranslucent);
+ builder.setSnapshot(screenshotBuffer.getGraphicBuffer());
+ builder.setColorSpace(screenshotBuffer.getColorSpace());
+ return builder.build();
}
private boolean shouldDisableSnapshots() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 7174e5c..e17e0d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -23,9 +23,20 @@
import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_REAL;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
+import android.view.View;
import androidx.test.filters.SmallTest;
@@ -33,6 +44,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
/**
* Test class for {@link TaskSnapshotController}.
@@ -110,4 +122,57 @@
assertEquals(SNAPSHOT_MODE_APP_THEME,
mWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask()));
}
+
+ @Test
+ public void testSnapshotBuilder() {
+ final GraphicBuffer buffer = Mockito.mock(GraphicBuffer.class);
+ final ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
+ final long id = 1234L;
+ final ComponentName activityComponent = new ComponentName("package", ".Class");
+ final int windowingMode = WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+ final int systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN;
+ final int pixelFormat = PixelFormat.RGBA_8888;
+ final int orientation = Configuration.ORIENTATION_PORTRAIT;
+ final float scaleFraction = 0.25f;
+ final Rect contentInsets = new Rect(1, 2, 3, 4);
+
+ try {
+ ActivityManager.TaskSnapshot.Builder builder =
+ new ActivityManager.TaskSnapshot.Builder();
+ builder.setId(id);
+ builder.setTopActivityComponent(activityComponent);
+ builder.setSystemUiVisibility(systemUiVisibility);
+ builder.setWindowingMode(windowingMode);
+ builder.setColorSpace(sRGB);
+ builder.setReducedResolution(true);
+ builder.setOrientation(orientation);
+ builder.setContentInsets(contentInsets);
+ builder.setIsTranslucent(true);
+ builder.setScaleFraction(0.25f);
+ builder.setSnapshot(buffer);
+ builder.setIsRealSnapshot(true);
+ builder.setPixelFormat(pixelFormat);
+
+ // Not part of TaskSnapshot itself, used in screenshot process
+ assertEquals(pixelFormat, builder.getPixelFormat());
+
+ ActivityManager.TaskSnapshot snapshot = builder.build();
+ assertEquals(id, snapshot.getId());
+ assertEquals(activityComponent, snapshot.getTopActivityComponent());
+ assertEquals(systemUiVisibility, snapshot.getSystemUiVisibility());
+ assertEquals(windowingMode, snapshot.getWindowingMode());
+ assertEquals(sRGB, snapshot.getColorSpace());
+ assertTrue(snapshot.isReducedResolution());
+ assertEquals(orientation, snapshot.getOrientation());
+ assertEquals(contentInsets, snapshot.getContentInsets());
+ assertTrue(snapshot.isTranslucent());
+ assertEquals(scaleFraction, builder.getScaleFraction(), 0.001f);
+ assertSame(buffer, snapshot.getSnapshot());
+ assertTrue(snapshot.isRealSnapshot());
+ } finally {
+ if (buffer != null) {
+ buffer.destroy();
+ }
+ }
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 6132cc2..404346f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1291,6 +1291,7 @@
pw.println(" mCurUser: " + mCurUser);
pw.println(" mCurUserUnlocked: " + mCurUserUnlocked);
pw.println(" mCurUserSupported: " + mCurUserSupported);
+ dumpSupportedUsers(pw, " ");
if (mImpl == null) {
pw.println(" (No active implementation)");
return;
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
deleted file mode 100644
index c16eafb..0000000
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
+++ /dev/null
@@ -1,521 +0,0 @@
-/*
- * Copyright (C) 2012 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.android.internal.telephony.gsm;
-
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.res.Resources;
-import android.telephony.CbGeoUtils;
-import android.telephony.CbGeoUtils.Circle;
-import android.telephony.CbGeoUtils.Geometry;
-import android.telephony.CbGeoUtils.LatLng;
-import android.telephony.CbGeoUtils.Polygon;
-import android.telephony.Rlog;
-import android.telephony.SmsCbLocation;
-import android.telephony.SmsCbMessage;
-import android.telephony.SubscriptionManager;
-import android.util.Pair;
-
-import com.android.internal.R;
-import com.android.internal.telephony.GsmAlphabet;
-import com.android.internal.telephony.SmsConstants;
-import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
-import com.android.internal.telephony.gsm.SmsCbHeader.DataCodingScheme;
-
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is
- * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases.
- */
-public class GsmSmsCbMessage {
- private static final String TAG = GsmSmsCbMessage.class.getSimpleName();
-
- private static final char CARRIAGE_RETURN = 0x0d;
-
- private static final int PDU_BODY_PAGE_LENGTH = 82;
-
- /** Utility class with only static methods. */
- private GsmSmsCbMessage() { }
-
- /**
- * Get built-in ETWS primary messages by category. ETWS primary message does not contain text,
- * so we have to show the pre-built messages to the user.
- *
- * @param context Device context
- * @param category ETWS message category defined in SmsCbConstants
- * @return ETWS text message in string. Return an empty string if no match.
- */
- private static String getEtwsPrimaryMessage(Context context, int category) {
- final Resources r = context.getResources();
- switch (category) {
- case ETWS_WARNING_TYPE_EARTHQUAKE:
- return r.getString(R.string.etws_primary_default_message_earthquake);
- case ETWS_WARNING_TYPE_TSUNAMI:
- return r.getString(R.string.etws_primary_default_message_tsunami);
- case ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
- return r.getString(R.string.etws_primary_default_message_earthquake_and_tsunami);
- case ETWS_WARNING_TYPE_TEST_MESSAGE:
- return r.getString(R.string.etws_primary_default_message_test);
- case ETWS_WARNING_TYPE_OTHER_EMERGENCY:
- return r.getString(R.string.etws_primary_default_message_others);
- default:
- return "";
- }
- }
-
- /**
- * Create a new SmsCbMessage object from a header object plus one or more received PDUs.
- *
- * @param pdus PDU bytes
- * @slotIndex slotIndex for which received sms cb message
- */
- public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
- SmsCbLocation location, byte[][] pdus, int slotIndex)
- throws IllegalArgumentException {
- SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
- Context.TELEPHONY_SUBSCRIPTION_SERVICE);
- int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- int[] subIds = sm.getSubscriptionIds(slotIndex);
- if (subIds != null && subIds.length > 0) {
- subId = subIds[0];
- }
-
- long receivedTimeMillis = System.currentTimeMillis();
- if (header.isEtwsPrimaryNotification()) {
- // ETSI TS 23.041 ETWS Primary Notification message
- // ETWS primary message only contains 4 fields including serial number,
- // message identifier, warning type, and warning security information.
- // There is no field for the content/text so we get the text from the resources.
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(),
- header.getSerialNumber(), location, header.getServiceCategory(), null,
- getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
- SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
- header.getCmasInfo(), 0, null /* geometries */, receivedTimeMillis, slotIndex,
- subId);
- } else if (header.isUmtsFormat()) {
- // UMTS format has only 1 PDU
- byte[] pdu = pdus[0];
- Pair<String, String> cbData = parseUmtsBody(header, pdu);
- String language = cbData.first;
- String body = cbData.second;
- int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
- : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
- int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
- int wacDataOffset = SmsCbHeader.PDU_HEADER_LENGTH
- + 1 // number of pages
- + (PDU_BODY_PAGE_LENGTH + 1) * nrPages; // cb data
-
- // Has Warning Area Coordinates information
- List<Geometry> geometries = null;
- int maximumWaitingTimeSec = 255;
- if (pdu.length > wacDataOffset) {
- try {
- Pair<Integer, List<Geometry>> wac = parseWarningAreaCoordinates(pdu,
- wacDataOffset);
- maximumWaitingTimeSec = wac.first;
- geometries = wac.second;
- } catch (Exception ex) {
- // Catch the exception here, the message will be considered as having no WAC
- // information which means the message will be broadcasted directly.
- Rlog.e(TAG, "Can't parse warning area coordinates, ex = " + ex.toString());
- }
- }
-
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
- header.getGeographicalScope(), header.getSerialNumber(), location,
- header.getServiceCategory(), language, body, priority,
- header.getEtwsInfo(), header.getCmasInfo(), maximumWaitingTimeSec, geometries,
- receivedTimeMillis, slotIndex, subId);
- } else {
- String language = null;
- StringBuilder sb = new StringBuilder();
- for (byte[] pdu : pdus) {
- Pair<String, String> p = parseGsmBody(header, pdu);
- language = p.first;
- sb.append(p.second);
- }
- int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
- : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
-
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
- header.getGeographicalScope(), header.getSerialNumber(), location,
- header.getServiceCategory(), language, sb.toString(), priority,
- header.getEtwsInfo(), header.getCmasInfo(), 0, null /* geometries */,
- receivedTimeMillis, slotIndex, subId);
- }
- }
-
- /**
- * Parse WEA Handset Action Message(WHAM) a.k.a geo-fencing trigger message.
- *
- * WEA Handset Action Message(WHAM) is a cell broadcast service message broadcast by the network
- * to direct devices to perform a geo-fencing check on selected alerts.
- *
- * WEA Handset Action Message(WHAM) requirements from ATIS-0700041 section 4
- * 1. The Warning Message contents of a WHAM shall be in Cell Broadcast(CB) data format as
- * defined in TS 23.041.
- * 2. The Warning Message Contents of WHAM shall be limited to one CB page(max 20 referenced
- * WEA messages).
- * 3. The broadcast area for a WHAM shall be the union of the broadcast areas of the referenced
- * WEA message.
- * @param pdu cell broadcast pdu, including the header
- * @return {@link GeoFencingTriggerMessage} instance
- */
- public static GeoFencingTriggerMessage createGeoFencingTriggerMessage(byte[] pdu) {
- try {
- // Header length + 1(number of page). ATIS-0700041 define the number of page of
- // geo-fencing trigger message is 1.
- int whamOffset = SmsCbHeader.PDU_HEADER_LENGTH + 1;
-
- BitStreamReader bitReader = new BitStreamReader(pdu, whamOffset);
- int type = bitReader.read(4);
- int length = bitReader.read(7);
- // Skip the remained 5 bits
- bitReader.skip();
-
- int messageIdentifierCount = (length - 2) * 8 / 32;
- List<CellBroadcastIdentity> cbIdentifiers = new ArrayList<>();
- for (int i = 0; i < messageIdentifierCount; i++) {
- // Both messageIdentifier and serialNumber are 16 bits integers.
- // ATIS-0700041 Section 5.1.6
- int messageIdentifier = bitReader.read(16);
- int serialNumber = bitReader.read(16);
- cbIdentifiers.add(new CellBroadcastIdentity(messageIdentifier, serialNumber));
- }
- return new GeoFencingTriggerMessage(type, cbIdentifiers);
- } catch (Exception ex) {
- Rlog.e(TAG, "create geo-fencing trigger failed, ex = " + ex.toString());
- return null;
- }
- }
-
- /**
- * Parse the broadcast area and maximum wait time from the Warning Area Coordinates TLV.
- *
- * @param pdu Warning Area Coordinates TLV.
- * @param wacOffset the offset of Warning Area Coordinates TLV.
- * @return a pair with the first element is maximum wait time and the second is the broadcast
- * area. The default value of the maximum wait time is 255 which means use the device default
- * value.
- */
- private static Pair<Integer, List<Geometry>> parseWarningAreaCoordinates(
- byte[] pdu, int wacOffset) {
- // little-endian
- int wacDataLength = ((pdu[wacOffset + 1] & 0xff) << 8) | (pdu[wacOffset] & 0xff);
- int offset = wacOffset + 2;
-
- if (offset + wacDataLength > pdu.length) {
- throw new IllegalArgumentException("Invalid wac data, expected the length of pdu at"
- + "least " + offset + wacDataLength + ", actual is " + pdu.length);
- }
-
- BitStreamReader bitReader = new BitStreamReader(pdu, offset);
-
- int maximumWaitTimeSec = SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET;
-
- List<Geometry> geo = new ArrayList<>();
- int remainedBytes = wacDataLength;
- while (remainedBytes > 0) {
- int type = bitReader.read(4);
- int length = bitReader.read(10);
- remainedBytes -= length;
- // Skip the 2 remained bits
- bitReader.skip();
-
- switch (type) {
- case CbGeoUtils.GEO_FENCING_MAXIMUM_WAIT_TIME:
- maximumWaitTimeSec = bitReader.read(8);
- break;
- case CbGeoUtils.GEOMETRY_TYPE_POLYGON:
- List<LatLng> latLngs = new ArrayList<>();
- // Each coordinate is represented by 44 bits integer.
- // ATIS-0700041 5.2.4 Coordinate coding
- int n = (length - 2) * 8 / 44;
- for (int i = 0; i < n; i++) {
- latLngs.add(getLatLng(bitReader));
- }
- // Skip the padding bits
- bitReader.skip();
- geo.add(new Polygon(latLngs));
- break;
- case CbGeoUtils.GEOMETRY_TYPE_CIRCLE:
- LatLng center = getLatLng(bitReader);
- // radius = (wacRadius / 2^6). The unit of wacRadius is km, we use meter as the
- // distance unit during geo-fencing.
- // ATIS-0700041 5.2.5 radius coding
- double radius = (bitReader.read(20) * 1.0 / (1 << 6)) * 1000.0;
- geo.add(new Circle(center, radius));
- break;
- default:
- throw new IllegalArgumentException("Unsupported geoType = " + type);
- }
- }
- return new Pair(maximumWaitTimeSec, geo);
- }
-
- /**
- * The coordinate is (latitude, longitude), represented by a 44 bits integer.
- * The coding is defined in ATIS-0700041 5.2.4
- * @param bitReader
- * @return coordinate (latitude, longitude)
- */
- private static LatLng getLatLng(BitStreamReader bitReader) {
- // wacLatitude = floor(((latitude + 90) / 180) * 2^22)
- // wacLongitude = floor(((longitude + 180) / 360) * 2^22)
- int wacLat = bitReader.read(22);
- int wacLng = bitReader.read(22);
-
- // latitude = wacLatitude * 180 / 2^22 - 90
- // longitude = wacLongitude * 360 / 2^22 -180
- return new LatLng((wacLat * 180.0 / (1 << 22)) - 90, (wacLng * 360.0 / (1 << 22) - 180));
- }
-
- /**
- * Parse and unpack the UMTS body text according to the encoding in the data coding scheme.
- *
- * @param header the message header to use
- * @param pdu the PDU to decode
- * @return a pair of string containing the language and body of the message in order
- */
- private static Pair<String, String> parseUmtsBody(SmsCbHeader header, byte[] pdu) {
- // Payload may contain multiple pages
- int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
- String language = header.getDataCodingSchemeStructedData().language;
-
- if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
- * nrPages) {
- throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
- + nrPages + " pages");
- }
-
- StringBuilder sb = new StringBuilder();
-
- for (int i = 0; i < nrPages; i++) {
- // Each page is 82 bytes followed by a length octet indicating
- // the number of useful octets within those 82
- int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
- int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
-
- if (length > PDU_BODY_PAGE_LENGTH) {
- throw new IllegalArgumentException("Page length " + length
- + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
- }
-
- Pair<String, String> p = unpackBody(pdu, offset, length,
- header.getDataCodingSchemeStructedData());
- language = p.first;
- sb.append(p.second);
- }
- return new Pair(language, sb.toString());
-
- }
-
- /**
- * Parse and unpack the GSM body text according to the encoding in the data coding scheme.
- * @param header the message header to use
- * @param pdu the PDU to decode
- * @return a pair of string containing the language and body of the message in order
- */
- private static Pair<String, String> parseGsmBody(SmsCbHeader header, byte[] pdu) {
- // Payload is one single page
- int offset = SmsCbHeader.PDU_HEADER_LENGTH;
- int length = pdu.length - offset;
- return unpackBody(pdu, offset, length, header.getDataCodingSchemeStructedData());
- }
-
- /**
- * Unpack body text from the pdu using the given encoding, position and length within the pdu.
- *
- * @param pdu The pdu
- * @param offset Position of the first byte to unpack
- * @param length Number of bytes to unpack
- * @param dcs data coding scheme
- * @return a Pair of Strings containing the language and body of the message
- */
- private static Pair<String, String> unpackBody(byte[] pdu, int offset, int length,
- DataCodingScheme dcs) {
- String body = null;
-
- String language = dcs.language;
- switch (dcs.encoding) {
- case SmsConstants.ENCODING_7BIT:
- body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
-
- if (dcs.hasLanguageIndicator && body != null && body.length() > 2) {
- // Language is two GSM characters followed by a CR.
- // The actual body text is offset by 3 characters.
- language = body.substring(0, 2);
- body = body.substring(3);
- }
- break;
-
- case SmsConstants.ENCODING_16BIT:
- if (dcs.hasLanguageIndicator && pdu.length >= offset + 2) {
- // Language is two GSM characters.
- // The actual body text is offset by 2 bytes.
- language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
- offset += 2;
- length -= 2;
- }
-
- try {
- body = new String(pdu, offset, (length & 0xfffe), "utf-16");
- } catch (UnsupportedEncodingException e) {
- // Apparently it wasn't valid UTF-16.
- throw new IllegalArgumentException("Error decoding UTF-16 message", e);
- }
- break;
-
- default:
- break;
- }
-
- if (body != null) {
- // Remove trailing carriage return
- for (int i = body.length() - 1; i >= 0; i--) {
- if (body.charAt(i) != CARRIAGE_RETURN) {
- body = body.substring(0, i + 1);
- break;
- }
- }
- } else {
- body = "";
- }
-
- return new Pair<String, String>(language, body);
- }
-
- /** A class use to facilitate the processing of bits stream data. */
- private static final class BitStreamReader {
- /** The bits stream represent by a bytes array. */
- private final byte[] mData;
-
- /** The offset of the current byte. */
- private int mCurrentOffset;
-
- /**
- * The remained bits of the current byte which have not been read. The most significant
- * will be read first, so the remained bits are always the least significant bits.
- */
- private int mRemainedBit;
-
- /**
- * Constructor
- * @param data bit stream data represent by byte array.
- * @param offset the offset of the first byte.
- */
- BitStreamReader(byte[] data, int offset) {
- mData = data;
- mCurrentOffset = offset;
- mRemainedBit = 8;
- }
-
- /**
- * Read the first {@code count} bits.
- * @param count the number of bits need to read
- * @return {@code bits} represent by an 32-bits integer, therefore {@code count} must be no
- * greater than 32.
- */
- public int read(int count) throws IndexOutOfBoundsException {
- int val = 0;
- while (count > 0) {
- if (count >= mRemainedBit) {
- val <<= mRemainedBit;
- val |= mData[mCurrentOffset] & ((1 << mRemainedBit) - 1);
- count -= mRemainedBit;
- mRemainedBit = 8;
- ++mCurrentOffset;
- } else {
- val <<= count;
- val |= (mData[mCurrentOffset] & ((1 << mRemainedBit) - 1))
- >> (mRemainedBit - count);
- mRemainedBit -= count;
- count = 0;
- }
- }
- return val;
- }
-
- /**
- * Skip the current bytes if the remained bits is less than 8. This is useful when
- * processing the padding or reserved bits.
- */
- public void skip() {
- if (mRemainedBit < 8) {
- mRemainedBit = 8;
- ++mCurrentOffset;
- }
- }
- }
-
- /**
- * Part of a GSM SMS cell broadcast message which may trigger geo-fencing logic.
- * @hide
- */
- public static final class GeoFencingTriggerMessage {
- /**
- * Indicate the list of active alerts share their warning area coordinates which means the
- * broadcast area is the union of the broadcast areas of the active alerts in this list.
- */
- public static final int TYPE_ACTIVE_ALERT_SHARE_WAC = 2;
-
- public final int type;
- public final List<CellBroadcastIdentity> cbIdentifiers;
-
- GeoFencingTriggerMessage(int type, @NonNull List<CellBroadcastIdentity> cbIdentifiers) {
- this.type = type;
- this.cbIdentifiers = cbIdentifiers;
- }
-
- /**
- * Whether the trigger message indicates that the broadcast areas are shared between all
- * active alerts.
- * @return true if broadcast areas are to be shared
- */
- boolean shouldShareBroadcastArea() {
- return type == TYPE_ACTIVE_ALERT_SHARE_WAC;
- }
-
- static final class CellBroadcastIdentity {
- public final int messageIdentifier;
- public final int serialNumber;
- CellBroadcastIdentity(int messageIdentifier, int serialNumber) {
- this.messageIdentifier = messageIdentifier;
- this.serialNumber = serialNumber;
- }
- }
-
- @Override
- public String toString() {
- String identifiers = cbIdentifiers.stream()
- .map(cbIdentifier ->String.format("(msgId = %d, serial = %d)",
- cbIdentifier.messageIdentifier, cbIdentifier.serialNumber))
- .collect(Collectors.joining(","));
- return "triggerType=" + type + " identifiers=" + identifiers;
- }
- }
-}