Merge "Adding proto for QuotaTracker dumps."
diff --git a/Android.bp b/Android.bp
index 21552695..15188ec 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1660,6 +1660,8 @@
"core/java/android/util/LocalLog.java",
"core/java/android/util/TimeUtils.java",
"core/java/com/android/internal/os/SomeArgs.java",
+ "core/java/com/android/internal/util/AsyncChannel.java",
+ "core/java/com/android/internal/util/BitwiseInputStream.java",
"core/java/com/android/internal/util/FastXmlSerializer.java",
"core/java/com/android/internal/util/HexDump.java",
"core/java/com/android/internal/util/IState.java",
diff --git a/api/current.txt b/api/current.txt
index 6ddd847..52dd1a1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22653,6 +22653,7 @@
method public void onConfigureWindow(android.view.Window, boolean, boolean);
method public android.view.View onCreateCandidatesView();
method public android.view.View onCreateExtractTextView();
+ method @Nullable public android.view.inputmethod.InlineSuggestionsRequest onCreateInlineSuggestionsRequest();
method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface();
method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
method public android.view.View onCreateInputView();
@@ -22669,6 +22670,7 @@
method public void onFinishInput();
method public void onFinishInputView(boolean);
method public void onInitializeInterface();
+ method public boolean onInlineSuggestionsResponse(@NonNull android.view.inputmethod.InlineSuggestionsResponse);
method public boolean onKeyDown(int, android.view.KeyEvent);
method public boolean onKeyLongPress(int, android.view.KeyEvent);
method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
@@ -36025,7 +36027,8 @@
method @WorkerThread public long getCacheSizeBytes(@NonNull java.util.UUID) throws java.io.IOException;
method public String getMountedObbPath(String);
method @NonNull public android.os.storage.StorageVolume getPrimaryStorageVolume();
- method @Nullable public android.os.storage.StorageVolume getStorageVolume(java.io.File);
+ method @NonNull public java.util.List<android.os.storage.StorageVolume> getRecentStorageVolumes();
+ method @Nullable public android.os.storage.StorageVolume getStorageVolume(@NonNull java.io.File);
method @NonNull public android.os.storage.StorageVolume getStorageVolume(@NonNull android.net.Uri);
method @NonNull public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
method @NonNull public java.util.UUID getUuidForPath(@NonNull java.io.File) throws java.io.IOException;
@@ -36051,6 +36054,8 @@
method @NonNull public android.content.Intent createOpenDocumentTreeIntent();
method public int describeContents();
method public String getDescription(android.content.Context);
+ method @Nullable public java.io.File getDirectory();
+ method @Nullable public String getMediaStoreVolumeName();
method public String getState();
method @Nullable public String getUuid();
method public boolean isEmulated();
@@ -38721,6 +38726,7 @@
method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context);
method public static android.net.Uri getMediaScannerUri();
method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri);
+ method @NonNull public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
method public static boolean getRequireOriginal(@NonNull android.net.Uri);
method @NonNull public static String getVersion(@NonNull android.content.Context);
method @NonNull public static String getVersion(@NonNull android.content.Context, @NonNull String);
@@ -38986,6 +38992,7 @@
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
method @Deprecated public static android.net.Uri getContentUri(String);
+ method @Deprecated @NonNull public static android.util.Size getKindSize(int);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
@@ -39068,6 +39075,7 @@
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
method @Deprecated public static android.net.Uri getContentUri(String);
+ method @Deprecated @NonNull public static android.util.Size getKindSize(int);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
field @Deprecated public static final String DATA = "_data";
@@ -41574,7 +41582,8 @@
method @NonNull public java.util.List<android.service.autofill.FillContext> getFillContexts();
method public int getFlags();
method public int getId();
- method public void writeToParcel(android.os.Parcel, int);
+ method @Nullable public android.view.inputmethod.InlineSuggestionsRequest getInlineSuggestionsRequest();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillRequest> CREATOR;
field public static final int FLAG_COMPATIBILITY_MODE_REQUEST = 2; // 0x2
field public static final int FLAG_MANUAL_REQUEST = 1; // 0x1
@@ -41591,6 +41600,7 @@
public static final class FillResponse.Builder {
ctor public FillResponse.Builder();
method @NonNull public android.service.autofill.FillResponse.Builder addDataset(@Nullable android.service.autofill.Dataset);
+ method @NonNull public android.service.autofill.FillResponse.Builder addInlineSuggestionSlice(@NonNull android.app.slice.Slice);
method @NonNull public android.service.autofill.FillResponse build();
method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long);
method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
@@ -52801,6 +52811,7 @@
public static final class WindowInsetsAnimationCallback.InsetsAnimation {
ctor public WindowInsetsAnimationCallback.InsetsAnimation(int, @Nullable android.view.animation.Interpolator, long);
+ method @FloatRange(from=0.0f, to=1.0f) public float getAlpha();
method public long getDurationMillis();
method @FloatRange(from=0.0f, to=1.0f) public float getFraction();
method public float getInterpolatedFraction();
@@ -52817,6 +52828,7 @@
public interface WindowInsetsAnimationController {
method public void finish(boolean);
+ method public float getCurrentAlpha();
method @FloatRange(from=0.0f, to=1.0f) public float getCurrentFraction();
method @NonNull public android.graphics.Insets getCurrentInsets();
method @NonNull public android.graphics.Insets getHiddenStateInsets();
diff --git a/api/removed.txt b/api/removed.txt
index 4e8e325..8b30d0a 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -436,10 +436,8 @@
}
public final class MediaStore {
- method @Deprecated @NonNull public static android.net.Uri createPending(@NonNull android.content.Context, @NonNull android.provider.MediaStore.PendingParams);
method @Deprecated @NonNull public static java.util.Set<java.lang.String> getAllVolumeNames(@NonNull android.content.Context);
method @Deprecated public static boolean getIncludePending(@NonNull android.net.Uri);
- method @Deprecated @NonNull public static android.provider.MediaStore.PendingSession openPending(@NonNull android.content.Context, @NonNull android.net.Uri);
method @Deprecated @NonNull public static android.net.Uri setIncludeTrashed(@NonNull android.net.Uri);
method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri);
method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri, long);
@@ -473,22 +471,6 @@
field @Deprecated public static final String GROUP_ID = "group_id";
}
- @Deprecated public static class MediaStore.PendingParams {
- ctor public MediaStore.PendingParams(@NonNull android.net.Uri, @NonNull String, @NonNull String);
- method public void setDownloadUri(@Nullable android.net.Uri);
- method public void setRefererUri(@Nullable android.net.Uri);
- method public void setRelativePath(@Nullable String);
- }
-
- @Deprecated public static class MediaStore.PendingSession implements java.lang.AutoCloseable {
- method public void abandon();
- method public void close();
- method public void notifyProgress(@IntRange(from=0, to=100) int);
- method @NonNull public android.os.ParcelFileDescriptor open() throws java.io.FileNotFoundException;
- method @NonNull public java.io.OutputStream openOutputStream() throws java.io.FileNotFoundException;
- method @NonNull public android.net.Uri publish();
- }
-
public static interface MediaStore.Video.VideoColumns extends android.provider.MediaStore.MediaColumns {
field public static final String ALBUM = "album";
field public static final String ARTIST = "artist";
diff --git a/api/system-current.txt b/api/system-current.txt
index 8d55a33..1108927 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6365,6 +6365,7 @@
}
public class Environment {
+ method @NonNull public static java.util.Collection<java.io.File> getInternalMediaDirectories();
method @NonNull public static java.io.File getOdmDirectory();
method @NonNull public static java.io.File getOemDirectory();
method @NonNull public static java.io.File getProductDirectory();
@@ -7253,8 +7254,8 @@
}
public final class MediaStore {
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
+ method @NonNull public static android.net.Uri scanFile(@NonNull android.content.ContentResolver, @NonNull java.io.File);
+ method public static void scanVolume(@NonNull android.content.ContentResolver, @NonNull String);
}
public abstract class SearchIndexableData {
@@ -9608,6 +9609,7 @@
public final class SmsManager {
method public boolean disableCellBroadcastRange(int, int, int);
method public boolean enableCellBroadcastRange(int, int, int);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc();
method public void sendMultipartTextMessage(@NonNull String, @NonNull String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 1c6bce0..219258e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -697,6 +697,7 @@
public abstract class Context {
method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public java.io.File getCrateDir(@NonNull String);
method public abstract android.view.Display getDisplay();
method public abstract int getDisplayId();
method public android.os.UserHandle getUser();
@@ -2467,14 +2468,9 @@
}
public final class MediaStore {
- method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
- method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
- method @NonNull public static java.io.File getVolumePath(@NonNull String) throws java.io.FileNotFoundException;
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
- method public static android.net.Uri scanFile(android.content.Context, java.io.File);
- method public static android.net.Uri scanFileFromShell(android.content.Context, java.io.File);
- method public static void scanVolume(android.content.Context, java.io.File);
- method public static void waitForIdle(android.content.Context);
+ method @NonNull public static android.net.Uri scanFile(@NonNull android.content.ContentResolver, @NonNull java.io.File);
+ method public static void scanVolume(@NonNull android.content.ContentResolver, @NonNull String);
+ method public static void waitForIdle(@NonNull android.content.ContentResolver);
}
public final class Settings {
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 484f823..afff614 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -146,6 +146,7 @@
"libprotoutil",
"libservices",
"libstatslog",
+ "libstatsmetadata",
"libstatssocket",
"libsysutils",
"libtimestats_proto",
@@ -153,6 +154,63 @@
],
}
+// ================
+// libstatsmetadata
+// ================
+
+genrule {
+ name: "atoms_info.h",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --atomsInfoHeader $(genDir)/atoms_info.h",
+ out: [
+ "atoms_info.h",
+ ],
+}
+
+genrule {
+ name: "atoms_info.cpp",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --atomsInfoCpp $(genDir)/atoms_info.cpp",
+ out: [
+ "atoms_info.cpp",
+ ],
+}
+
+cc_library_shared {
+ name: "libstatsmetadata",
+ host_supported: true,
+ generated_sources: [
+ "atoms_info.cpp",
+ ],
+ generated_headers: [
+ "atoms_info.h",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ export_generated_headers: [
+ "atoms_info.h",
+ ],
+ shared_libs: [
+ "libcutils",
+ "libstatslog",
+ ],
+ target: {
+ android: {
+ shared_libs: [
+ "libutils",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libutils",
+ ],
+ },
+ },
+}
+
+
// =========
// statsd
// =========
diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp
index 84a0607..a545fc5 100644
--- a/cmds/statsd/src/FieldValue.cpp
+++ b/cmds/statsd/src/FieldValue.cpp
@@ -18,8 +18,8 @@
#include "Log.h"
#include "FieldValue.h"
#include "HashableDimensionKey.h"
+#include "atoms_info.h"
#include "math.h"
-#include "statslog.h"
namespace android {
namespace os {
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 98d41c2..3481814 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -27,6 +27,7 @@
#include <utils/SystemClock.h>
#include "android-base/stringprintf.h"
+#include "atoms_info.h"
#include "external/StatsPullerManager.h"
#include "guardrail/StatsdStats.h"
#include "metrics/CountMetricProducer.h"
diff --git a/cmds/statsd/src/external/PowerStatsPuller.cpp b/cmds/statsd/src/external/PowerStatsPuller.cpp
index b142cac..dc69b78 100644
--- a/cmds/statsd/src/external/PowerStatsPuller.cpp
+++ b/cmds/statsd/src/external/PowerStatsPuller.cpp
@@ -22,6 +22,7 @@
#include <vector>
#include "PowerStatsPuller.h"
+#include "statslog.h"
#include "stats_log_util.h"
using android::hardware::hidl_vec;
diff --git a/cmds/statsd/src/external/puller_util.cpp b/cmds/statsd/src/external/puller_util.cpp
index 53fa630..031c437 100644
--- a/cmds/statsd/src/external/puller_util.cpp
+++ b/cmds/statsd/src/external/puller_util.cpp
@@ -18,8 +18,8 @@
#include "Log.h"
#include "StatsPullerManager.h"
+#include "atoms_info.h"
#include "puller_util.h"
-#include "statslog.h"
namespace android {
namespace os {
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 23d2ace..564b9ee 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -16,7 +16,7 @@
#pragma once
#include "config/ConfigKey.h"
-#include "statslog.h"
+#include "atoms_info.h"
#include <gtest/gtest_prod.h>
#include <log/log_time.h>
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 464cec3..088f607 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -23,6 +23,7 @@
#include <utils/SystemClock.h>
#include "CountMetricProducer.h"
+#include "atoms_info.h"
#include "condition/CombinationConditionTracker.h"
#include "condition/SimpleConditionTracker.h"
#include "guardrail/StatsdStats.h"
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 9131802..2ad8217 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -22,6 +22,7 @@
#include <inttypes.h>
+#include "atoms_info.h"
#include "condition/CombinationConditionTracker.h"
#include "condition/SimpleConditionTracker.h"
#include "condition/StateConditionTracker.h"
@@ -36,7 +37,6 @@
#include "metrics/ValueMetricProducer.h"
#include "state/StateManager.h"
#include "stats_util.h"
-#include "statslog.h"
using std::set;
using std::string;
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
index e65325a..7453370 100644
--- a/cmds/statsd/src/state/StateTracker.h
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -15,7 +15,7 @@
*/
#pragma once
-#include <statslog.h>
+#include <atoms_info.h>
#include <utils/RefBase.h>
#include "HashableDimensionKey.h"
#include "logd/LogEvent.h"
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
index 0a86363..f3e9433 100644
--- a/cmds/statsd/src/stats_log_util.h
+++ b/cmds/statsd/src/stats_log_util.h
@@ -19,9 +19,9 @@
#include <android/util/ProtoOutputStream.h>
#include "FieldValue.h"
#include "HashableDimensionKey.h"
+#include "atoms_info.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "guardrail/StatsdStats.h"
-#include "statslog.h"
namespace android {
namespace os {
diff --git a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
index e91fb0d..6abdfa3 100644
--- a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
@@ -24,6 +24,7 @@
#include <log/log.h>
#include "src/external/GpuStatsPuller.h"
+#include "statslog.h"
#ifdef __ANDROID__
diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
index 5c9636f..5b7a30d 100644
--- a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
@@ -18,6 +18,7 @@
#define LOG_TAG "SurfaceflingerStatsPuller_test"
#include "src/external/SurfaceflingerStatsPuller.h"
+#include "statslog.h"
#include <gtest/gtest.h>
#include <log/log.h>
diff --git a/cmds/statsd/tests/external/puller_util_test.cpp b/cmds/statsd/tests/external/puller_util_test.cpp
index 266ea35..6730828 100644
--- a/cmds/statsd/tests/external/puller_util_test.cpp
+++ b/cmds/statsd/tests/external/puller_util_test.cpp
@@ -17,6 +17,7 @@
#include <gtest/gtest.h>
#include <stdio.h>
#include <vector>
+#include "statslog.h"
#include "../metrics/metrics_test_helper.h"
#ifdef __ANDROID__
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 46f88d5..155e93f 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -99,6 +99,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteOrder;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -251,6 +252,8 @@
@GuardedBy("mSync")
private File mFilesDir;
@GuardedBy("mSync")
+ private File mCratesDir;
+ @GuardedBy("mSync")
private File mNoBackupFilesDir;
@GuardedBy("mSync")
private File mCacheDir;
@@ -702,6 +705,24 @@
}
@Override
+ public File getCrateDir(@NonNull String crateId) {
+ Preconditions.checkArgument(FileUtils.isValidExtFilename(crateId), "invalidated crateId");
+ final Path cratesRootPath = getDataDir().toPath().resolve("crates");
+ final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
+ .toAbsolutePath().normalize();
+
+ synchronized (mSync) {
+ if (mCratesDir == null) {
+ mCratesDir = cratesRootPath.toFile();
+ }
+ ensurePrivateDirExists(mCratesDir);
+ }
+
+ File cratedDir = absoluteNormalizedCratePath.toFile();
+ return ensurePrivateDirExists(cratedDir);
+ }
+
+ @Override
public File getNoBackupFilesDir() {
synchronized (mSync) {
if (mNoBackupFilesDir == null) {
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 80c9ba2..eb50581 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1314,8 +1314,8 @@
// TODO: DownloadProvider.update() should take care of updating corresponding
// MediaProvider entries.
- MediaStore.scanFile(context, before);
- MediaStore.scanFile(context, after);
+ MediaStore.scanFile(mResolver, before);
+ MediaStore.scanFile(mResolver, after);
final ContentValues values = new ContentValues();
values.put(Downloads.Impl.COLUMN_TITLE, displayName);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index a1305da..ce21db3 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -152,6 +152,8 @@
import android.os.health.SystemHealthManager;
import android.os.image.DynamicSystemManager;
import android.os.image.IDynamicSystemService;
+import android.os.incremental.IIncrementalManagerNative;
+import android.os.incremental.IncrementalManager;
import android.os.storage.StorageManager;
import android.permission.PermissionControllerManager;
import android.permission.PermissionManager;
@@ -1225,6 +1227,20 @@
Context.DATA_LOADER_MANAGER_SERVICE);
return new DataLoaderManager(IDataLoaderManager.Stub.asInterface(b));
}});
+ //TODO(b/136132412): refactor this: 1) merge IIncrementalManager.aidl and
+ //IIncrementalManagerNative.aidl, 2) implement the binder interface in
+ //IncrementalManagerService.java, 3) use JNI to call native functions
+ registerService(Context.INCREMENTAL_SERVICE, IncrementalManager.class,
+ new CachedServiceFetcher<IncrementalManager>() {
+ @Override
+ public IncrementalManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.INCREMENTAL_SERVICE);
+ if (b == null) {
+ return null;
+ }
+ return new IncrementalManager(
+ IIncrementalManagerNative.Stub.asInterface(b));
+ }});
//CHECKSTYLE:ON IndentationCheck
sInitializing = true;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d084449..24b5061 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1060,6 +1060,31 @@
public abstract File getFilesDir();
/**
+ * Returns the absolute path to the directory that is related to the crate on the filesystem.
+ * <p>
+ * The crateId require a validated file name. It can't contain any "..", ".",
+ * {@link File#separatorChar} etc..
+ * </p>
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * </p>
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path.
+ *</p>
+ *
+ * @param crateId the relative validated file name under {@link Context#getDataDir()}/crates
+ * @return the crate directory file.
+ * @hide
+ */
+ @NonNull
+ @TestApi
+ public File getCrateDir(@NonNull String crateId) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Returns the absolute path to the directory on the filesystem similar to
* {@link #getFilesDir()}. The difference is that files placed under this
* directory will be excluded from automatic backup to remote storage. See
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index d6442e2..d1b5135 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -252,6 +252,16 @@
return mBase.getFilesDir();
}
+ /**
+ * {@inheritDoc Context#getCrateDir()}
+ * @hide
+ */
+ @NonNull
+ @Override
+ public File getCrateDir(@NonNull String cratedId) {
+ return mBase.getCrateDir(cratedId);
+ }
+
@Override
public File getNoBackupFilesDir() {
return mBase.getNoBackupFilesDir();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 94af541..0d1f404 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1513,16 +1513,6 @@
public static final int DELETE_DONT_KILL_APP = 0x00000008;
/**
- * Flag parameter for {@link #deletePackage} to indicate that any
- * contributed media should also be deleted during this uninstall. The
- * meaning of "contributed" means it won't automatically be deleted when the
- * app is uninstalled.
- *
- * @hide
- */
- public static final int DELETE_CONTRIBUTED_MEDIA = 0x00000010;
-
- /**
* Flag parameter for {@link #deletePackage} to indicate that package deletion
* should be chatty.
*
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 48d8867..39c1dac 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -823,6 +823,7 @@
switch (token.toUpperCase(Locale.US)) {
case "COLLATE": case "ASC": case "DESC":
case "BINARY": case "RTRIM": case "NOCASE":
+ case "LOCALIZED": case "UNICODE":
return;
}
throw new IllegalArgumentException("Invalid token " + token);
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index a47f601..62f0196 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -19,6 +19,7 @@
import android.annotation.BinderThread;
import android.annotation.MainThread;
import android.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -28,6 +29,7 @@
import android.os.ResultReceiver;
import android.util.Log;
import android.view.InputChannel;
+import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
@@ -39,6 +41,7 @@
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodSession;
@@ -72,6 +75,7 @@
private static final int DO_SHOW_SOFT_INPUT = 60;
private static final int DO_HIDE_SOFT_INPUT = 70;
private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
+ private static final int DO_CREATE_INLINE_SUGGESTIONS_REQUEST = 90;
final WeakReference<AbstractInputMethodService> mTarget;
final Context mContext;
@@ -225,6 +229,11 @@
case DO_CHANGE_INPUTMETHOD_SUBTYPE:
inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
return;
+ case DO_CREATE_INLINE_SUGGESTIONS_REQUEST:
+ SomeArgs args = (SomeArgs) msg.obj;
+ inputMethod.onCreateInlineSuggestionsRequest((ComponentName) args.arg1,
+ (AutofillId) args.arg2, (IInlineSuggestionsRequestCallback) args.arg3);
+ return;
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -267,6 +276,15 @@
@BinderThread
@Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName, AutofillId autofillId,
+ IInlineSuggestionsRequestCallback cb) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOOO(DO_CREATE_INLINE_SUGGESTIONS_REQUEST, componentName,
+ autofillId, cb));
+ }
+
+ @BinderThread
+ @Override
public void bindInput(InputBinding binding) {
if (mIsUnbindIssued != null) {
Log.e(TAG, "bindInput must be paired with unbindInput.");
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 156bcfe..7da7dc1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -23,6 +23,8 @@
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.AnyThread;
@@ -35,6 +37,7 @@
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.Dialog;
+import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -47,6 +50,8 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.provider.Settings;
@@ -70,11 +75,14 @@
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
+import android.view.autofill.AutofillId;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
@@ -91,11 +99,14 @@
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.Collections;
/**
@@ -436,6 +447,14 @@
final Insets mTmpInsets = new Insets();
final int[] mTmpLocation = new int[2];
+ @Nullable
+ private InlineSuggestionsRequestInfo mInlineSuggestionsRequestInfo = null;
+
+ @Nullable
+ private InlineSuggestionsResponseCallbackImpl mInlineSuggestionsResponseCallback = null;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
+
final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
onComputeInsets(mTmpInsets);
if (isExtractViewShown()) {
@@ -495,6 +514,18 @@
/**
* {@inheritDoc}
+ * @hide
+ */
+ @MainThread
+ @Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()");
+ handleOnCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+ }
+
+ /**
+ * {@inheritDoc}
*/
@MainThread
@Override
@@ -670,6 +701,103 @@
}
}
+ // TODO(b/137800469): Add detailed docs explaining the inline suggestions process.
+ /**
+ * Returns an {@link InlineSuggestionsRequest} to be sent to Autofill.
+ *
+ * <p>Should be implemented by subclasses.</p>
+ */
+ public @Nullable InlineSuggestionsRequest onCreateInlineSuggestionsRequest() {
+ return null;
+ }
+
+ /**
+ * Called when Autofill responds back with {@link InlineSuggestionsResponse} containing
+ * inline suggestions.
+ *
+ * <p>Should be implemented by subclasses.</p>
+ *
+ * @param response {@link InlineSuggestionsResponse} passed back by Autofill.
+ * @return Whether the IME will use and render the inline suggestions.
+ */
+ public boolean onInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) {
+ return false;
+ }
+
+ /**
+ * Returns whether inline suggestions are enabled on this service.
+ *
+ * TODO(b/137800469): check XML for value.
+ */
+ private boolean isInlineSuggestionsEnabled() {
+ return true;
+ }
+
+ /**
+ * Sends an {@link InlineSuggestionsRequest} obtained from
+ * {@link #onCreateInlineSuggestionsRequest()} to the current Autofill Session through
+ * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
+ */
+ private void makeInlineSuggestionsRequest() {
+ if (mInlineSuggestionsRequestInfo == null) {
+ Log.w(TAG, "makeInlineSuggestionsRequest() called with null requestInfo cache");
+ return;
+ }
+
+ final IInlineSuggestionsRequestCallback requestCallback =
+ mInlineSuggestionsRequestInfo.mCallback;
+ try {
+ final InlineSuggestionsRequest request = onCreateInlineSuggestionsRequest();
+ if (request == null) {
+ Log.w(TAG, "onCreateInlineSuggestionsRequest() returned null request");
+ requestCallback.onInlineSuggestionsUnsupported();
+ } else {
+ if (mInlineSuggestionsResponseCallback == null) {
+ mInlineSuggestionsResponseCallback =
+ new InlineSuggestionsResponseCallbackImpl(this,
+ mInlineSuggestionsRequestInfo.mComponentName,
+ mInlineSuggestionsRequestInfo.mFocusedId);
+ }
+ requestCallback.onInlineSuggestionsRequest(request,
+ mInlineSuggestionsResponseCallback);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
+ }
+ }
+
+ private void handleOnCreateInlineSuggestionsRequest(@NonNull ComponentName componentName,
+ @NonNull AutofillId autofillId, @NonNull IInlineSuggestionsRequestCallback callback) {
+ mInlineSuggestionsRequestInfo = new InlineSuggestionsRequestInfo(componentName, autofillId,
+ callback);
+
+ if (!isInlineSuggestionsEnabled()) {
+ try {
+ callback.onInlineSuggestionsUnsupported();
+ } catch (RemoteException e) {
+ Log.w(TAG, "handleMakeInlineSuggestionsRequest() RemoteException:" + e);
+ }
+ return;
+ }
+
+ if (!mInputStarted) {
+ Log.w(TAG, "onStartInput() not called yet");
+ return;
+ }
+
+ makeInlineSuggestionsRequest();
+ }
+
+ private void handleOnInlineSuggestionsResponse(@NonNull ComponentName componentName,
+ @NonNull AutofillId autofillId, @NonNull InlineSuggestionsResponse response) {
+ if (!mInlineSuggestionsRequestInfo.validate(componentName)) {
+ Log.d(TAG, "Response component=" + componentName + " differs from request component="
+ + mInlineSuggestionsRequestInfo.mComponentName + ", ignoring response");
+ return;
+ }
+ onInlineSuggestionsResponse(response);
+ }
+
private void notifyImeHidden() {
setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
onPreRenderedWindowVisibilityChanged(false /* setVisible */);
@@ -688,6 +816,63 @@
}
/**
+ * Internal implementation of {@link IInlineSuggestionsResponseCallback}.
+ */
+ private static final class InlineSuggestionsResponseCallbackImpl
+ extends IInlineSuggestionsResponseCallback.Stub {
+ private final WeakReference<InputMethodService> mInputMethodService;
+
+ private final ComponentName mRequestComponentName;
+ private final AutofillId mRequestAutofillId;
+
+ private InlineSuggestionsResponseCallbackImpl(InputMethodService inputMethodService,
+ ComponentName componentName, AutofillId autofillId) {
+ mInputMethodService = new WeakReference<>(inputMethodService);
+ mRequestComponentName = componentName;
+ mRequestAutofillId = autofillId;
+ }
+
+ @Override
+ public void onInlineSuggestionsResponse(InlineSuggestionsResponse response)
+ throws RemoteException {
+ final InputMethodService service = mInputMethodService.get();
+ if (service != null) {
+ service.mHandler.sendMessage(obtainMessage(
+ InputMethodService::handleOnInlineSuggestionsResponse, service,
+ mRequestComponentName, mRequestAutofillId, response));
+ }
+ }
+ }
+
+ /**
+ * Information about incoming requests from Autofill Frameworks for inline suggestions.
+ */
+ private static final class InlineSuggestionsRequestInfo {
+ final ComponentName mComponentName;
+ final AutofillId mFocusedId;
+ final IInlineSuggestionsRequestCallback mCallback;
+
+ InlineSuggestionsRequestInfo(ComponentName componentName, AutofillId focusedId,
+ IInlineSuggestionsRequestCallback callback) {
+ this.mComponentName = componentName;
+ this.mFocusedId = focusedId;
+ this.mCallback = callback;
+ }
+
+ /**
+ * Returns whether the cached {@link ComponentName} matches the passed in activity.
+ */
+ public boolean validate(ComponentName componentName) {
+ final boolean result = componentName.equals(mComponentName);
+ if (!result) {
+ Log.d(TAG, "Cached request info ComponentName=" + mComponentName
+ + " differs from received ComponentName=" + componentName);
+ }
+ return result;
+ }
+ }
+
+ /**
* Concrete implementation of
* {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
* all of the standard behavior for an input method session.
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index a92237b..61da5e6 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -34,6 +34,8 @@
import android.util.Log;
import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.LinkedList;
/**
@@ -528,6 +530,22 @@
}
/**
+ * Return locations where media files (such as ringtones, notification
+ * sounds, or alarm sounds) may be located on internal storage. These are
+ * typically indexed under {@link MediaStore#VOLUME_INTERNAL}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static @NonNull Collection<File> getInternalMediaDirectories() {
+ final ArrayList<File> res = new ArrayList<>();
+ res.add(new File(Environment.getRootDirectory(), "media"));
+ res.add(new File(Environment.getOemDirectory(), "media"));
+ res.add(new File(Environment.getProductDirectory(), "media"));
+ return res;
+ }
+
+ /**
* Return the primary shared/external storage directory. This directory may
* not currently be accessible if it has been mounted by the user on their
* computer, has been removed from the device, or some other problem has
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 62603fe..2e9f27e 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -264,6 +264,8 @@
public static final int FLAG_REAL_STATE = 1 << 9;
/** {@hide} */
public static final int FLAG_INCLUDE_INVISIBLE = 1 << 10;
+ /** {@hide} */
+ public static final int FLAG_INCLUDE_RECENT = 1 << 11;
/** {@hide} */
public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM;
@@ -1125,7 +1127,7 @@
* Return the {@link StorageVolume} that contains the given file, or
* {@code null} if none.
*/
- public @Nullable StorageVolume getStorageVolume(File file) {
+ public @Nullable StorageVolume getStorageVolume(@NonNull File file) {
return getStorageVolume(getVolumeList(), file);
}
@@ -1140,7 +1142,7 @@
return getPrimaryStorageVolume();
default:
for (StorageVolume vol : getStorageVolumes()) {
- if (Objects.equals(vol.getNormalizedUuid(), volumeName)) {
+ if (Objects.equals(vol.getMediaStoreVolumeName(), volumeName)) {
return vol;
}
}
@@ -1201,12 +1203,13 @@
}
/**
- * Return the list of shared/external storage volumes available to the
- * current user. This includes both the primary shared storage device and
- * any attached external volumes including SD cards and USB drives.
- *
- * @see Environment#getExternalStorageDirectory()
- * @see StorageVolume#createAccessIntent(String)
+ * Return the list of shared/external storage volumes currently available to
+ * the calling user.
+ * <p>
+ * These storage volumes are actively attached to the device, but may be in
+ * any mount state, as returned by {@link StorageVolume#getState()}. Returns
+ * both the primary shared storage device and any attached external volumes,
+ * including SD cards and USB drives.
*/
public @NonNull List<StorageVolume> getStorageVolumes() {
final ArrayList<StorageVolume> res = new ArrayList<>();
@@ -1216,6 +1219,22 @@
}
/**
+ * Return the list of shared/external storage volumes both currently and
+ * recently available to the calling user.
+ * <p>
+ * Recently available storage volumes are likely to reappear in the future,
+ * so apps are encouraged to preserve any indexed metadata related to these
+ * volumes to optimize user experiences.
+ */
+ public @NonNull List<StorageVolume> getRecentStorageVolumes() {
+ final ArrayList<StorageVolume> res = new ArrayList<>();
+ Collections.addAll(res,
+ getVolumeList(mContext.getUserId(),
+ FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_RECENT));
+ return res;
+ }
+
+ /**
* Return the primary shared/external storage volume available to the
* current user. This volume is the same storage device returned by
* {@link Environment#getExternalStorageDirectory()} and
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index aefe843..560d617 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -29,6 +29,7 @@
import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.DocumentsContract;
+import android.provider.MediaStore;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
@@ -173,7 +174,7 @@
* @return the mount path
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
@TestApi
public String getPath() {
return mPath.toString();
@@ -190,12 +191,35 @@
}
/** {@hide} */
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
public File getPathFile() {
return mPath;
}
/**
+ * Returns the directory where this volume is currently mounted.
+ * <p>
+ * Direct filesystem access via this path has significant emulation
+ * overhead, and apps are instead strongly encouraged to interact with media
+ * on storage volumes via the {@link MediaStore} APIs.
+ * <p>
+ * This directory does not give apps any additional access beyond what they
+ * already have via {@link MediaStore}.
+ *
+ * @return directory where this volume is mounted, or {@code null} if the
+ * volume is not currently mounted.
+ */
+ public @Nullable File getDirectory() {
+ switch (mState) {
+ case Environment.MEDIA_MOUNTED:
+ case Environment.MEDIA_MOUNTED_READ_ONLY:
+ return mPath;
+ default:
+ return null;
+ }
+ }
+
+ /**
* Returns a user-visible description of the volume.
*
* @return the volume description
@@ -265,6 +289,24 @@
return mFsUuid;
}
+ /**
+ * Return the volume name that can be used to interact with this storage
+ * device through {@link MediaStore}.
+ *
+ * @return opaque volume name, or {@code null} if this volume is not indexed
+ * by {@link MediaStore}.
+ * @see android.provider.MediaStore.Audio.Media#getContentUri(String)
+ * @see android.provider.MediaStore.Video.Media#getContentUri(String)
+ * @see android.provider.MediaStore.Images.Media#getContentUri(String)
+ */
+ public @Nullable String getMediaStoreVolumeName() {
+ if (isPrimary()) {
+ return MediaStore.VOLUME_EXTERNAL_PRIMARY;
+ } else {
+ return getNormalizedUuid();
+ }
+ }
+
/** {@hide} */
public static @Nullable String normalizeUuid(@Nullable String fsUuid) {
return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
diff --git a/core/java/android/os/storage/VolumeRecord.java b/core/java/android/os/storage/VolumeRecord.java
index 1a794eb..99b45d6 100644
--- a/core/java/android/os/storage/VolumeRecord.java
+++ b/core/java/android/os/storage/VolumeRecord.java
@@ -17,14 +17,18 @@
package android.os.storage;
import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.util.DebugUtils;
import android.util.TimeUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import java.io.File;
import java.util.Locale;
import java.util.Objects;
@@ -92,6 +96,27 @@
return (userFlags & USER_FLAG_SNOOZED) != 0;
}
+ public StorageVolume buildStorageVolume(Context context) {
+ final String id = "unknown:" + fsUuid;
+ final File userPath = new File("/dev/null");
+ final File internalPath = new File("/dev/null");
+ final boolean primary = false;
+ final boolean removable = true;
+ final boolean emulated = false;
+ final boolean allowMassStorage = false;
+ final long maxFileSize = 0;
+ final UserHandle user = new UserHandle(UserHandle.USER_NULL);
+ final String envState = Environment.MEDIA_UNKNOWN;
+
+ String description = nickname;
+ if (description == null) {
+ description = context.getString(android.R.string.unknownName);
+ }
+
+ return new StorageVolume(id, userPath, internalPath, description, primary, removable,
+ emulated, allowMassStorage, maxFileSize, user, fsUuid, envState);
+ }
+
public void dump(IndentingPrintWriter pw) {
pw.println("VolumeRecord:");
pw.increaseIndent();
diff --git a/core/java/android/provider/BaseColumns.java b/core/java/android/provider/BaseColumns.java
index 00c9e72..b216e2b 100644
--- a/core/java/android/provider/BaseColumns.java
+++ b/core/java/android/provider/BaseColumns.java
@@ -16,13 +16,11 @@
package android.provider;
-import android.database.Cursor;
-
public interface BaseColumns {
/**
* The unique ID for a row.
*/
- @Column(Cursor.FIELD_TYPE_INTEGER)
+ // @Column(Cursor.FIELD_TYPE_INTEGER)
public static final String _ID = "_id";
/**
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 701ba91..63204d3 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -21,17 +21,15 @@
import android.annotation.CurrentTimeSecondsLong;
import android.annotation.DurationMillisLong;
import android.annotation.IntDef;
-import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
-import android.app.AppGlobals;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ContentProviderClient;
@@ -45,39 +43,28 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageDecoder;
-import android.graphics.Point;
import android.graphics.PostProcessor;
import android.media.ExifInterface;
-import android.media.MediaFile;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Environment;
-import android.os.FileUtils;
import android.os.OperationCanceledException;
-import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
-import android.os.storage.VolumeInfo;
-import android.os.storage.VolumeRecord;
-import android.service.media.CameraPrewarmService;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
+import android.util.Size;
import libcore.util.HexEncoding;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -162,9 +149,6 @@
/** {@hide} */
public static final String SCAN_VOLUME_CALL = "scan_volume";
/** {@hide} */
- public static final String SUICIDE_CALL = "suicide";
-
- /** {@hide} */
public static final String CREATE_WRITE_REQUEST_CALL = "create_write_request";
/** {@hide} */
public static final String CREATE_TRASH_REQUEST_CALL = "create_trash_request";
@@ -173,42 +157,19 @@
/** {@hide} */
public static final String CREATE_DELETE_REQUEST_CALL = "create_delete_request";
- /**
- * Extra used with {@link #SCAN_FILE_CALL} or {@link #SCAN_VOLUME_CALL} to indicate that
- * the file path originated from shell.
- *
- * {@hide}
- */
- public static final String EXTRA_ORIGINATED_FROM_SHELL =
- "android.intent.extra.originated_from_shell";
-
- /**
- * The method name used by the media scanner and mtp to tell the media provider to
- * rescan and reclassify that have become unhidden because of renaming folders or
- * removing nomedia files
- * @hide
- */
- @Deprecated
- public static final String UNHIDE_CALL = "unhide";
-
- /**
- * The method name used by the media scanner service to reload all localized ringtone titles due
- * to a locale change.
- * @hide
- */
- public static final String RETRANSLATE_CALL = "update_titles";
/** {@hide} */
public static final String GET_VERSION_CALL = "get_version";
+
/** {@hide} */
public static final String GET_DOCUMENT_URI_CALL = "get_document_uri";
/** {@hide} */
public static final String GET_MEDIA_URI_CALL = "get_media_uri";
/** {@hide} */
- public static final String GET_CONTRIBUTED_MEDIA_CALL = "get_contributed_media";
+ public static final String EXTRA_URI = "uri";
/** {@hide} */
- public static final String DELETE_CONTRIBUTED_MEDIA_CALL = "delete_contributed_media";
+ public static final String EXTRA_URI_PERMISSIONS = "uriPermissions";
/** {@hide} */
public static final String EXTRA_CLIP_DATA = "clip_data";
@@ -391,10 +352,10 @@
* service.
* <p>
* This meta-data should reference the fully qualified class name of the prewarm service
- * extending {@link CameraPrewarmService}.
+ * extending {@code CameraPrewarmService}.
* <p>
* The prewarm service will get bound and receive a prewarm signal
- * {@link CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent.
+ * {@code CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent.
* An application implementing a prewarm service should do the absolute minimum amount of work
* to initialize the camera in order to reduce startup time in likely case that shortly after a
* camera launch intent would be sent.
@@ -775,197 +736,6 @@
}
/**
- * Create a new pending media item using the given parameters. Pending items
- * are expected to have a short lifetime, and owners should either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
- * pending item within a few hours after first creating it.
- *
- * @return token which can be passed to {@link #openPending(Context, Uri)}
- * to work with this pending item.
- * @see MediaColumns#IS_PENDING
- * @see MediaStore#setIncludePending(Uri)
- * @see MediaStore#createPending(Context, PendingParams)
- * @removed
- */
- @Deprecated
- public static @NonNull Uri createPending(@NonNull Context context,
- @NonNull PendingParams params) {
- return context.getContentResolver().insert(params.insertUri, params.insertValues);
- }
-
- /**
- * Open a pending media item to make progress on it. You can open a pending
- * item multiple times before finally calling either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()}.
- *
- * @param uri token which was previously returned from
- * {@link #createPending(Context, PendingParams)}.
- * @removed
- */
- @Deprecated
- public static @NonNull PendingSession openPending(@NonNull Context context, @NonNull Uri uri) {
- return new PendingSession(context, uri);
- }
-
- /**
- * Parameters that describe a pending media item.
- *
- * @removed
- */
- @Deprecated
- public static class PendingParams {
- /** {@hide} */
- public final Uri insertUri;
- /** {@hide} */
- public final ContentValues insertValues;
-
- /**
- * Create parameters that describe a pending media item.
- *
- * @param insertUri the {@code content://} Uri where this pending item
- * should be inserted when finally published. For example, to
- * publish an image, use
- * {@link MediaStore.Images.Media#getContentUri(String)}.
- */
- public PendingParams(@NonNull Uri insertUri, @NonNull String displayName,
- @NonNull String mimeType) {
- this.insertUri = Objects.requireNonNull(insertUri);
- final long now = System.currentTimeMillis() / 1000;
- this.insertValues = new ContentValues();
- this.insertValues.put(MediaColumns.DISPLAY_NAME, Objects.requireNonNull(displayName));
- this.insertValues.put(MediaColumns.MIME_TYPE, Objects.requireNonNull(mimeType));
- this.insertValues.put(MediaColumns.DATE_ADDED, now);
- this.insertValues.put(MediaColumns.DATE_MODIFIED, now);
- this.insertValues.put(MediaColumns.IS_PENDING, 1);
- this.insertValues.put(MediaColumns.DATE_EXPIRES,
- (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
- }
-
- public void setRelativePath(@Nullable String relativePath) {
- if (relativePath == null) {
- this.insertValues.remove(MediaColumns.RELATIVE_PATH);
- } else {
- this.insertValues.put(MediaColumns.RELATIVE_PATH, relativePath);
- }
- }
-
- /**
- * Optionally set the Uri from where the file has been downloaded. This is used
- * for files being added to {@link Downloads} table.
- *
- * @see DownloadColumns#DOWNLOAD_URI
- */
- public void setDownloadUri(@Nullable Uri downloadUri) {
- if (downloadUri == null) {
- this.insertValues.remove(DownloadColumns.DOWNLOAD_URI);
- } else {
- this.insertValues.put(DownloadColumns.DOWNLOAD_URI, downloadUri.toString());
- }
- }
-
- /**
- * Optionally set the Uri indicating HTTP referer of the file. This is used for
- * files being added to {@link Downloads} table.
- *
- * @see DownloadColumns#REFERER_URI
- */
- public void setRefererUri(@Nullable Uri refererUri) {
- if (refererUri == null) {
- this.insertValues.remove(DownloadColumns.REFERER_URI);
- } else {
- this.insertValues.put(DownloadColumns.REFERER_URI, refererUri.toString());
- }
- }
- }
-
- /**
- * Session actively working on a pending media item. Pending items are
- * expected to have a short lifetime, and owners should either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
- * pending item within a few hours after first creating it.
- *
- * @removed
- */
- @Deprecated
- public static class PendingSession implements AutoCloseable {
- /** {@hide} */
- private final Context mContext;
- /** {@hide} */
- private final Uri mUri;
-
- /** {@hide} */
- public PendingSession(Context context, Uri uri) {
- mContext = Objects.requireNonNull(context);
- mUri = Objects.requireNonNull(uri);
- }
-
- /**
- * Open the underlying file representing this media item. When a media
- * item is successfully completed, you should
- * {@link ParcelFileDescriptor#close()} and then {@link #publish()} it.
- *
- * @see #notifyProgress(int)
- */
- public @NonNull ParcelFileDescriptor open() throws FileNotFoundException {
- return mContext.getContentResolver().openFileDescriptor(mUri, "rw");
- }
-
- /**
- * Open the underlying file representing this media item. When a media
- * item is successfully completed, you should
- * {@link OutputStream#close()} and then {@link #publish()} it.
- *
- * @see #notifyProgress(int)
- */
- public @NonNull OutputStream openOutputStream() throws FileNotFoundException {
- return mContext.getContentResolver().openOutputStream(mUri);
- }
-
- /**
- * Notify of current progress on this pending media item. Gallery
- * applications may choose to surface progress information of this
- * pending item.
- *
- * @param progress a percentage between 0 and 100.
- */
- public void notifyProgress(@IntRange(from = 0, to = 100) int progress) {
- final Uri withProgress = mUri.buildUpon()
- .appendQueryParameter(PARAM_PROGRESS, Integer.toString(progress)).build();
- mContext.getContentResolver().notifyChange(withProgress, null, 0);
- }
-
- /**
- * When this media item is successfully completed, call this method to
- * publish and make the final item visible to the user.
- *
- * @return the final {@code content://} Uri representing the newly
- * published media.
- */
- public @NonNull Uri publish() {
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.IS_PENDING, 0);
- values.putNull(MediaColumns.DATE_EXPIRES);
- mContext.getContentResolver().update(mUri, values, null, null);
- return mUri;
- }
-
- /**
- * When this media item has failed to be completed, call this method to
- * destroy the pending item record and any data related to it.
- */
- public void abandon() {
- mContext.getContentResolver().delete(mUri, null, null);
- }
-
- @Override
- public void close() {
- // No resources to close, but at least we can inform people that no
- // progress is being actively made.
- notifyProgress(-1);
- }
- }
-
- /**
* Mark the given item as being "trashed", meaning it should be deleted at
* some point in the future. This is a more gentle operation than simply
* calling {@link ContentResolver#delete(Uri, String, String[])}, which
@@ -1701,34 +1471,22 @@
return ContentUris.withAppendedId(getContentUri(volumeName), rowId);
}
- /**
- * For use only by the MTP implementation.
- * @hide
- */
+ /** {@hide} */
@UnsupportedAppUsage
- public static Uri getMtpObjectsUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("object").build();
+ public static Uri getMtpObjectsUri(@NonNull String volumeName) {
+ return MediaStore.Files.getContentUri(volumeName);
}
- /**
- * For use only by the MTP implementation.
- * @hide
- */
+ /** {@hide} */
@UnsupportedAppUsage
- public static final Uri getMtpObjectsUri(String volumeName,
- long fileId) {
- return ContentUris.withAppendedId(getMtpObjectsUri(volumeName), fileId);
+ public static final Uri getMtpObjectsUri(@NonNull String volumeName, long fileId) {
+ return MediaStore.Files.getContentUri(volumeName, fileId);
}
- /**
- * Used to implement the MTP GetObjectReferences and SetObjectReferences commands.
- * @hide
- */
+ /** {@hide} */
@UnsupportedAppUsage
- public static final Uri getMtpReferencesUri(String volumeName,
- long fileId) {
- return getMtpObjectsUri(volumeName, fileId).buildUpon().appendPath("references")
- .build();
+ public static final Uri getMtpReferencesUri(@NonNull String volumeName, long fileId) {
+ return MediaStore.Files.getContentUri(volumeName, fileId);
}
/**
@@ -1851,9 +1609,21 @@
public static final int FULL_SCREEN_KIND = 2;
public static final int MICRO_KIND = 3;
- public static final Point MINI_SIZE = new Point(512, 384);
- public static final Point FULL_SCREEN_SIZE = new Point(1024, 786);
- public static final Point MICRO_SIZE = new Point(96, 96);
+ public static final Size MINI_SIZE = new Size(512, 384);
+ public static final Size FULL_SCREEN_SIZE = new Size(1024, 786);
+ public static final Size MICRO_SIZE = new Size(96, 96);
+
+ public static @NonNull Size getKindSize(int kind) {
+ if (kind == ThumbnailConstants.MICRO_KIND) {
+ return ThumbnailConstants.MICRO_SIZE;
+ } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
+ return ThumbnailConstants.FULL_SCREEN_SIZE;
+ } else if (kind == ThumbnailConstants.MINI_KIND) {
+ return ThumbnailConstants.MINI_SIZE;
+ } else {
+ throw new IllegalArgumentException("Unsupported kind: " + kind);
+ }
+ }
}
/**
@@ -1957,22 +1727,22 @@
}
}
- /** {@hide} */
+ /**
+ * @deprecated since this method doesn't have a {@link Context}, we can't
+ * find the actual {@link StorageVolume} for the given path, so
+ * only a vague guess is returned. Callers should use
+ * {@link StorageManager#getStorageVolume(File)} instead.
+ * @hide
+ */
+ @Deprecated
public static @NonNull String getVolumeName(@NonNull File path) {
- if (FileUtils.contains(Environment.getStorageDirectory(), path)) {
- final StorageManager sm = AppGlobals.getInitialApplication()
- .getSystemService(StorageManager.class);
- final StorageVolume sv = sm.getStorageVolume(path);
- if (sv != null) {
- if (sv.isPrimary()) {
- return VOLUME_EXTERNAL_PRIMARY;
- } else {
- return checkArgumentVolumeName(sv.getNormalizedUuid());
- }
- }
- throw new IllegalStateException("Unknown volume at " + path);
+ // Ideally we'd find the relevant StorageVolume, but we don't have a
+ // Context to obtain it from, so the best we can do is assume
+ if (path.getAbsolutePath()
+ .startsWith(Environment.getStorageDirectory().getAbsolutePath())) {
+ return MediaStore.VOLUME_EXTERNAL;
} else {
- return VOLUME_INTERNAL;
+ return MediaStore.VOLUME_INTERNAL;
}
}
@@ -1985,7 +1755,7 @@
/**
* Currently outstanding thumbnail requests that can be cancelled.
*/
- @GuardedBy("sPending")
+ // @GuardedBy("sPending")
private static ArrayMap<Uri, CancellationSignal> sPending = new ArrayMap<>();
/**
@@ -1997,16 +1767,7 @@
@Deprecated
static @Nullable Bitmap getThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri,
int kind, @Nullable BitmapFactory.Options opts) {
- final Point size;
- if (kind == ThumbnailConstants.MICRO_KIND) {
- size = ThumbnailConstants.MICRO_SIZE;
- } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
- size = ThumbnailConstants.FULL_SCREEN_SIZE;
- } else if (kind == ThumbnailConstants.MINI_KIND) {
- size = ThumbnailConstants.MINI_SIZE;
- } else {
- throw new IllegalArgumentException("Unsupported kind: " + kind);
- }
+ final Size size = ThumbnailConstants.getKindSize(kind);
CancellationSignal signal = null;
synchronized (sPending) {
@@ -2018,7 +1779,7 @@
}
try {
- return cr.loadThumbnail(uri, Point.convert(size), signal);
+ return cr.loadThumbnail(uri, size, signal);
} catch (IOException e) {
Log.w(TAG, "Failed to obtain thumbnail for " + uri, e);
return null;
@@ -2215,26 +1976,14 @@
@Deprecated
public static final String insertImage(ContentResolver cr, String imagePath,
String name, String description) throws FileNotFoundException {
- final File file = new File(imagePath);
- final String mimeType = MediaFile.getMimeTypeForFile(imagePath);
-
- if (TextUtils.isEmpty(name)) name = "Image";
- final PendingParams params = new PendingParams(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, name, mimeType);
-
- final Context context = AppGlobals.getInitialApplication();
- final Uri pendingUri = createPending(context, params);
- try (PendingSession session = openPending(context, pendingUri)) {
- try (InputStream in = new FileInputStream(file);
- OutputStream out = session.openOutputStream()) {
- FileUtils.copy(in, out);
- }
- return session.publish().toString();
- } catch (Exception e) {
- Log.w(TAG, "Failed to insert image", e);
- context.getContentResolver().delete(pendingUri, null, null);
- return null;
+ final Bitmap source;
+ try {
+ source = ImageDecoder
+ .decodeBitmap(ImageDecoder.createSource(new File(imagePath)));
+ } catch (IOException e) {
+ throw new FileNotFoundException(e.getMessage());
}
+ return insertImage(cr, source, name, description);
}
/**
@@ -2251,22 +2000,34 @@
* control over lifecycle.
*/
@Deprecated
- public static final String insertImage(ContentResolver cr, Bitmap source,
- String title, String description) {
+ public static final String insertImage(ContentResolver cr, Bitmap source, String title,
+ String description) {
if (TextUtils.isEmpty(title)) title = "Image";
- final PendingParams params = new PendingParams(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, title, "image/jpeg");
- final Context context = AppGlobals.getInitialApplication();
- final Uri pendingUri = createPending(context, params);
- try (PendingSession session = openPending(context, pendingUri)) {
- try (OutputStream out = session.openOutputStream()) {
+ final long now = System.currentTimeMillis();
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.DISPLAY_NAME, title);
+ values.put(MediaColumns.MIME_TYPE, "image/jpeg");
+ values.put(MediaColumns.DATE_ADDED, now / 1000);
+ values.put(MediaColumns.DATE_MODIFIED, now / 1000);
+ values.put(MediaColumns.DATE_EXPIRES, (now + DateUtils.DAY_IN_MILLIS) / 1000);
+ values.put(MediaColumns.IS_PENDING, 1);
+
+ final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ try {
+ try (OutputStream out = cr.openOutputStream(uri)) {
source.compress(Bitmap.CompressFormat.JPEG, 90, out);
}
- return session.publish().toString();
+
+ // Everything went well above, publish it!
+ values.clear();
+ values.put(MediaColumns.IS_PENDING, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ cr.update(uri, values, null, null);
+ return uri.toString();
} catch (Exception e) {
Log.w(TAG, "Failed to insert image", e);
- context.getContentResolver().delete(pendingUri, null, null);
+ cr.delete(uri, null, null);
return null;
}
}
@@ -2526,6 +2287,14 @@
public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND;
/**
+ * Return the typical {@link Size} (in pixels) used internally when
+ * the given thumbnail kind is requested.
+ */
+ public static @NonNull Size getKindSize(int kind) {
+ return ThumbnailConstants.getKindSize(kind);
+ }
+
+ /**
* The blob raw data of thumbnail
*
* @deprecated this column never existed internally, and could never
@@ -3780,6 +3549,14 @@
public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND;
/**
+ * Return the typical {@link Size} (in pixels) used internally when
+ * the given thumbnail kind is requested.
+ */
+ public static @NonNull Size getKindSize(int kind) {
+ return ThumbnailConstants.getKindSize(kind);
+ }
+
+ /**
* The width of the thumbnal
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
@@ -3811,54 +3588,44 @@
*/
public static @NonNull Set<String> getExternalVolumeNames(@NonNull Context context) {
final StorageManager sm = context.getSystemService(StorageManager.class);
- final Set<String> volumeNames = new ArraySet<>();
- for (VolumeInfo vi : sm.getVolumes()) {
- if (vi.isVisibleForUser(UserHandle.myUserId()) && vi.isMountedReadable()) {
- if (vi.isPrimary()) {
- volumeNames.add(VOLUME_EXTERNAL_PRIMARY);
- } else {
- volumeNames.add(vi.getNormalizedFsUuid());
+ final Set<String> res = new ArraySet<>();
+ for (StorageVolume sv : sm.getStorageVolumes()) {
+ switch (sv.getState()) {
+ case Environment.MEDIA_MOUNTED:
+ case Environment.MEDIA_MOUNTED_READ_ONLY: {
+ final String volumeName = sv.getMediaStoreVolumeName();
+ if (volumeName != null) {
+ res.add(volumeName);
+ }
+ break;
}
}
}
- return volumeNames;
+ return res;
}
/**
- * Return list of all specific volume names that have recently been part of
+ * Return list of all recent volume names that have been part of
* {@link #VOLUME_EXTERNAL}.
* <p>
- * This includes both currently mounted volumes <em>and</em> recently
- * mounted (but currently unmounted) volumes. Any indexed metadata for these
- * volumes is preserved to optimize the speed of remounting at a later time.
- *
- * @hide
+ * These volume names are not currently mounted, but they're likely to
+ * reappear in the future, so apps are encouraged to preserve any indexed
+ * metadata related to these volumes to optimize user experiences.
+ * <p>
+ * Each specific volume name can be passed to APIs like
+ * {@link MediaStore.Images.Media#getContentUri(String)} to interact with
+ * media on that storage device.
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
public static @NonNull Set<String> getRecentExternalVolumeNames(@NonNull Context context) {
final StorageManager sm = context.getSystemService(StorageManager.class);
-
- // We always have primary storage
- final Set<String> volumeNames = new ArraySet<>();
- volumeNames.add(VOLUME_EXTERNAL_PRIMARY);
-
- final long lastWeek = System.currentTimeMillis() - DateUtils.WEEK_IN_MILLIS;
- for (VolumeRecord rec : sm.getVolumeRecords()) {
- // Skip volumes without valid UUIDs
- if (TextUtils.isEmpty(rec.fsUuid)) continue;
-
- final VolumeInfo vi = sm.findVolumeByUuid(rec.fsUuid);
- if (vi != null && vi.isVisibleForUser(UserHandle.myUserId())
- && vi.isMountedReadable()) {
- // We're mounted right now
- volumeNames.add(rec.getNormalizedFsUuid());
- } else if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) {
- // We're not mounted right now, but we've been seen recently
- volumeNames.add(rec.getNormalizedFsUuid());
+ final Set<String> res = new ArraySet<>();
+ for (StorageVolume sv : sm.getRecentStorageVolumes()) {
+ final String volumeName = sv.getMediaStoreVolumeName();
+ if (volumeName != null) {
+ res.add(volumeName);
}
}
- return volumeNames;
+ return res;
}
/**
@@ -3911,97 +3678,6 @@
}
/**
- * Return path where the given specific volume is mounted. Not valid for
- * {@link #VOLUME_INTERNAL} or {@link #VOLUME_EXTERNAL}, since those are
- * broad collections that cover many paths.
- *
- * @hide
- */
- @TestApi
- public static @NonNull File getVolumePath(@NonNull String volumeName)
- throws FileNotFoundException {
- final StorageManager sm = AppGlobals.getInitialApplication()
- .getSystemService(StorageManager.class);
- return getVolumePath(sm.getVolumes(), volumeName);
- }
-
- /** {@hide} */
- public static @NonNull File getVolumePath(@NonNull List<VolumeInfo> volumes,
- @NonNull String volumeName) throws FileNotFoundException {
- if (TextUtils.isEmpty(volumeName)) {
- throw new IllegalArgumentException();
- }
-
- switch (volumeName) {
- case VOLUME_INTERNAL:
- case VOLUME_EXTERNAL:
- throw new FileNotFoundException(volumeName + " has no associated path");
- }
-
- final boolean wantPrimary = VOLUME_EXTERNAL_PRIMARY.equals(volumeName);
- for (VolumeInfo volume : volumes) {
- final boolean matchPrimary = wantPrimary
- && volume.isPrimary();
- final boolean matchSecondary = !wantPrimary
- && Objects.equals(volume.getNormalizedFsUuid(), volumeName);
- if (matchPrimary || matchSecondary) {
- final File path = volume.getPathForUser(UserHandle.myUserId());
- if (path != null) {
- return path;
- }
- }
- }
- throw new FileNotFoundException("Failed to find path for " + volumeName);
- }
-
- /**
- * Return paths that should be scanned for the given volume.
- *
- * @hide
- */
- @TestApi
- @SystemApi
- @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
- public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
- throws FileNotFoundException {
- if (TextUtils.isEmpty(volumeName)) {
- throw new IllegalArgumentException();
- }
-
- final Context context = AppGlobals.getInitialApplication();
- final UserManager um = context.getSystemService(UserManager.class);
-
- final ArrayList<File> res = new ArrayList<>();
- if (VOLUME_INTERNAL.equals(volumeName)) {
- addCanonicalFile(res, new File(Environment.getRootDirectory(), "media"));
- addCanonicalFile(res, new File(Environment.getOemDirectory(), "media"));
- addCanonicalFile(res, new File(Environment.getProductDirectory(), "media"));
- } else if (VOLUME_EXTERNAL.equals(volumeName)) {
- for (String exactVolume : getExternalVolumeNames(context)) {
- addCanonicalFile(res, getVolumePath(exactVolume));
- }
- if (um.isDemoUser()) {
- addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
- }
- } else {
- addCanonicalFile(res, getVolumePath(volumeName));
- if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName) && um.isDemoUser()) {
- addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
- }
- }
- return res;
- }
-
- private static void addCanonicalFile(List<File> list, File file) {
- try {
- list.add(file.getCanonicalFile());
- } catch (IOException e) {
- Log.w(TAG, "Failed to resolve " + file + ": " + e);
- list.add(file);
- }
- }
-
- /**
* Uri for querying the state of the media scanner.
*/
public static Uri getMediaScannerUri() {
@@ -4084,10 +3760,10 @@
try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
final Bundle in = new Bundle();
- in.putParcelable(DocumentsContract.EXTRA_URI, mediaUri);
- in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
+ in.putParcelable(EXTRA_URI, mediaUri);
+ in.putParcelableArrayList(EXTRA_URI_PERMISSIONS, new ArrayList<>(uriPermissions));
final Bundle out = client.call(GET_DOCUMENT_URI_CALL, null, in);
- return out.getParcelable(DocumentsContract.EXTRA_URI);
+ return out.getParcelable(EXTRA_URI);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -4114,134 +3790,43 @@
try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
final Bundle in = new Bundle();
- in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
- in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
+ in.putParcelable(EXTRA_URI, documentUri);
+ in.putParcelableArrayList(EXTRA_URI_PERMISSIONS, new ArrayList<>(uriPermissions));
final Bundle out = client.call(GET_MEDIA_URI_CALL, null, in);
- return out.getParcelable(DocumentsContract.EXTRA_URI);
+ return out.getParcelable(EXTRA_URI);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
+ /** @hide */
+ @TestApi
+ public static void waitForIdle(@NonNull ContentResolver resolver) {
+ resolver.call(AUTHORITY, WAIT_FOR_IDLE_CALL, null, null);
+ }
+
/**
- * Calculate size of media contributed by given package under the calling
- * user. The meaning of "contributed" means it won't automatically be
- * deleted when the app is uninstalled.
+ * Perform a blocking scan of the given {@link File}, returning the
+ * {@link Uri} of the scanned file.
*
* @hide
*/
+ @SystemApi
@TestApi
- @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
- public static @BytesLong long getContributedMediaSize(Context context, String packageName,
- UserHandle user) throws IOException {
- final UserManager um = context.getSystemService(UserManager.class);
- if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
- try {
- final ContentResolver resolver = context
- .createPackageContextAsUser(packageName, 0, user).getContentResolver();
- final Bundle in = new Bundle();
- in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
- final Bundle out = resolver.call(AUTHORITY, GET_CONTRIBUTED_MEDIA_CALL, null, in);
- return out.getLong(Intent.EXTRA_INDEX);
- } catch (Exception e) {
- throw new IOException(e);
- }
- } else {
- throw new IOException("User " + user + " must be unlocked and running");
- }
+ @SuppressLint("StreamFiles")
+ public static @NonNull Uri scanFile(@NonNull ContentResolver resolver, @NonNull File file) {
+ final Bundle out = resolver.call(AUTHORITY, SCAN_FILE_CALL, file.getAbsolutePath(), null);
+ return out.getParcelable(Intent.EXTRA_STREAM);
}
/**
- * Delete all media contributed by given package under the calling user. The
- * meaning of "contributed" means it won't automatically be deleted when the
- * app is uninstalled.
+ * Perform a blocking scan of the given storage volume.
*
* @hide
*/
+ @SystemApi
@TestApi
- @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
- public static void deleteContributedMedia(Context context, String packageName,
- UserHandle user) throws IOException {
- final UserManager um = context.getSystemService(UserManager.class);
- if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
- try {
- final ContentResolver resolver = context
- .createPackageContextAsUser(packageName, 0, user).getContentResolver();
- final Bundle in = new Bundle();
- in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
- resolver.call(AUTHORITY, DELETE_CONTRIBUTED_MEDIA_CALL, null, in);
- } catch (Exception e) {
- throw new IOException(e);
- }
- } else {
- throw new IOException("User " + user + " must be unlocked and running");
- }
- }
-
- /** @hide */
- @TestApi
- public static void waitForIdle(Context context) {
- final ContentResolver resolver = context.getContentResolver();
- try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
- client.call(WAIT_FOR_IDLE_CALL, null, null);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- /** @hide */
- public static void suicide(Context context) {
- final ContentResolver resolver = context.getContentResolver();
- try (ContentProviderClient client = resolver
- .acquireUnstableContentProviderClient(AUTHORITY)) {
- client.call(SUICIDE_CALL, null, null);
- } catch (Exception ignored) {
- }
- }
-
- /** @hide */
- @TestApi
- public static Uri scanFile(Context context, File file) {
- return scan(context, SCAN_FILE_CALL, file, false);
- }
-
- /** @hide */
- @TestApi
- public static Uri scanFileFromShell(Context context, File file) {
- return scan(context, SCAN_FILE_CALL, file, true);
- }
-
- /** @hide */
- @TestApi
- public static void scanVolume(Context context, File file) {
- scan(context, SCAN_VOLUME_CALL, file, false);
- }
-
- /** @hide */
- public static Uri scanFile(ContentProviderClient client, File file) {
- return scan(client, SCAN_FILE_CALL, file, false);
- }
-
- /** @hide */
- private static Uri scan(Context context, String method, File file,
- boolean originatedFromShell) {
- final ContentResolver resolver = context.getContentResolver();
- try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
- return scan(client, method, file, originatedFromShell);
- }
- }
-
- /** @hide */
- private static Uri scan(ContentProviderClient client, String method, File file,
- boolean originatedFromShell) {
- try {
- final Bundle in = new Bundle();
- in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file));
- in.putBoolean(EXTRA_ORIGINATED_FROM_SHELL, originatedFromShell);
- final Bundle out = client.call(method, null, in);
- return out.getParcelable(Intent.EXTRA_STREAM);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
+ public static void scanVolume(@NonNull ContentResolver resolver, @NonNull String volumeName) {
+ resolver.call(AUTHORITY, SCAN_VOLUME_CALL, volumeName, null);
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e73a74f..f4e2329 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13653,13 +13653,6 @@
public static final String KERNEL_CPU_THREAD_READER = "kernel_cpu_thread_reader";
/**
- * Default user id to boot into. They map to user ids, for example, 10, 11, 12.
- *
- * @hide
- */
- public static final String DEFAULT_USER_ID_TO_BOOT_INTO = "default_boot_into_user_id";
-
- /**
* Persistent user id that is last logged in to.
*
* They map to user ids, for example, 10, 11, 12.
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index e53ebad..72e9ad0 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -23,6 +23,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
+import android.view.inputmethod.InlineSuggestionsRequest;
import com.android.internal.util.DataClass;
import com.android.internal.util.Preconditions;
@@ -116,20 +117,34 @@
*/
private final @RequestFlags int mFlags;
+ /**
+ * Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
+ * with this request.
+ *
+ * TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
+ *
+ * @return the suggestionspec
+ */
+ private final @Nullable InlineSuggestionsRequest mInlineSuggestionsRequest;
+
private void onConstructed() {
Preconditions.checkCollectionElementsNotNull(mFillContexts, "contexts");
}
- // Code below generated by codegen v1.0.0.
+ // Code below generated by codegen v1.0.14.
//
// DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/FillRequest.java
//
- // CHECKSTYLE:OFF Generated code
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
/** @hide */
@IntDef(flag = true, prefix = "FLAG_", value = {
@@ -184,6 +199,11 @@
*
* @return any combination of {@link #FLAG_MANUAL_REQUEST} and
* {@link #FLAG_COMPATIBILITY_MODE_REQUEST}.
+ * @param inlineSuggestionsRequest
+ * Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
+ * with this request.
+ *
+ * TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
* @hide
*/
@DataClass.Generated.Member
@@ -191,7 +211,8 @@
int id,
@NonNull List<FillContext> fillContexts,
@Nullable Bundle clientState,
- @RequestFlags int flags) {
+ @RequestFlags int flags,
+ @Nullable InlineSuggestionsRequest inlineSuggestionsRequest) {
this.mId = id;
this.mFillContexts = fillContexts;
com.android.internal.util.AnnotationValidations.validate(
@@ -203,6 +224,7 @@
mFlags,
FLAG_MANUAL_REQUEST
| FLAG_COMPATIBILITY_MODE_REQUEST);
+ this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
onConstructed();
}
@@ -256,6 +278,19 @@
return mFlags;
}
+ /**
+ * Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
+ * with this request.
+ *
+ * TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
+ *
+ * @return the suggestionspec
+ */
+ @DataClass.Generated.Member
+ public @Nullable InlineSuggestionsRequest getInlineSuggestionsRequest() {
+ return mInlineSuggestionsRequest;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -266,29 +301,63 @@
"id = " + mId + ", " +
"fillContexts = " + mFillContexts + ", " +
"clientState = " + mClientState + ", " +
- "flags = " + requestFlagsToString(mFlags) +
+ "flags = " + requestFlagsToString(mFlags) + ", " +
+ "inlineSuggestionsRequest = " + mInlineSuggestionsRequest +
" }";
}
@Override
@DataClass.Generated.Member
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
if (mClientState != null) flg |= 0x4;
+ if (mInlineSuggestionsRequest != null) flg |= 0x10;
dest.writeByte(flg);
dest.writeInt(mId);
dest.writeParcelableList(mFillContexts, flags);
if (mClientState != null) dest.writeBundle(mClientState);
dest.writeInt(mFlags);
+ if (mInlineSuggestionsRequest != null) dest.writeTypedObject(mInlineSuggestionsRequest, flags);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ FillRequest(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int id = in.readInt();
+ List<FillContext> fillContexts = new ArrayList<>();
+ in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
+ Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
+ int flags = in.readInt();
+ InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
+
+ this.mId = id;
+ this.mFillContexts = fillContexts;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFillContexts);
+ this.mClientState = clientState;
+ this.mFlags = flags;
+
+ Preconditions.checkFlagsArgument(
+ mFlags,
+ FLAG_MANUAL_REQUEST
+ | FLAG_COMPATIBILITY_MODE_REQUEST);
+ this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+
+ onConstructed();
+ }
+
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<FillRequest> CREATOR
= new Parcelable.Creator<FillRequest>() {
@@ -298,31 +367,21 @@
}
@Override
- @SuppressWarnings({"unchecked", "RedundantCast"})
- public FillRequest createFromParcel(Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- byte flg = in.readByte();
- int id = in.readInt();
- List<FillContext> fillContexts = new ArrayList<>();
- in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
- Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
- int flags = in.readInt();
- return new FillRequest(
- id,
- fillContexts,
- clientState,
- flags);
+ public FillRequest createFromParcel(@NonNull Parcel in) {
+ return new FillRequest(in);
}
};
@DataClass.Generated(
- time = 1565152134349L,
- codegenVersion = "1.0.0",
+ time = 1575928271155L,
+ codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
- inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
+
+ //@formatter:on
+ // End of generated code
+
}
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index c99fe61..02a6390 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -25,6 +25,7 @@
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.Activity;
+import android.app.slice.Slice;
import android.content.IntentSender;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
@@ -86,6 +87,7 @@
private int mRequestId;
private final @Nullable UserData mUserData;
private final @Nullable int[] mCancelIds;
+ private final @Nullable ParceledListSlice<Slice> mInlineSuggestionSlices;
private FillResponse(@NonNull Builder builder) {
mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
@@ -103,6 +105,8 @@
mRequestId = INVALID_REQUEST_ID;
mUserData = builder.mUserData;
mCancelIds = builder.mCancelIds;
+ mInlineSuggestionSlices = (builder.mInlineSuggestionSlices != null)
+ ? new ParceledListSlice<>(builder.mInlineSuggestionSlices) : null;
}
/** @hide */
@@ -195,6 +199,11 @@
return mCancelIds;
}
+ /** @hide */
+ public List<Slice> getInlineSuggestionSlices() {
+ return (mInlineSuggestionSlices != null) ? mInlineSuggestionSlices.getList() : null;
+ }
+
/**
* Builder for {@link FillResponse} objects. You must to provide at least
* one dataset or set an authentication intent with a presentation view.
@@ -215,6 +224,7 @@
private boolean mDestroyed;
private UserData mUserData;
private int[] mCancelIds;
+ private ArrayList<Slice> mInlineSuggestionSlices;
/**
* Triggers a custom UI before before autofilling the screen with any data set in this
@@ -570,6 +580,20 @@
}
/**
+ * TODO(b/137800469): add javadoc
+ */
+ @NonNull
+ public Builder addInlineSuggestionSlice(@NonNull Slice inlineSuggestionSlice) {
+ throwIfDestroyed();
+ throwIfAuthenticationCalled();
+ if (mInlineSuggestionSlices == null) {
+ mInlineSuggestionSlices = new ArrayList<>();
+ }
+ mInlineSuggestionSlices.add(inlineSuggestionSlice);
+ return this;
+ }
+
+ /**
* Builds a new {@link FillResponse} instance.
*
* @throws IllegalStateException if any of the following conditions occur:
@@ -670,7 +694,9 @@
if (mCancelIds != null) {
builder.append(", mCancelIds=").append(mCancelIds.length);
}
-
+ if (mInlineSuggestionSlices != null) {
+ builder.append(", inlinedSuggestions=").append(mInlineSuggestionSlices.getList());
+ }
return builder.append("]").toString();
}
@@ -699,7 +725,7 @@
parcel.writeParcelableArray(mFieldClassificationIds, flags);
parcel.writeInt(mFlags);
parcel.writeIntArray(mCancelIds);
-
+ parcel.writeParcelable(mInlineSuggestionSlices, flags);
parcel.writeInt(mRequestId);
}
@@ -755,6 +781,16 @@
final int[] cancelIds = parcel.createIntArray();
builder.setCancelTargetIds(cancelIds);
+ final ParceledListSlice<Slice> parceledInlineSuggestionSlices =
+ parcel.readParcelable(null);
+ if (parceledInlineSuggestionSlices != null) {
+ final List<Slice> inlineSuggestionSlices = parceledInlineSuggestionSlices.getList();
+ final int size = inlineSuggestionSlices.size();
+ for (int i = 0; i < size; i++) {
+ builder.addInlineSuggestionSlice(inlineSuggestionSlices.get(i));
+ }
+ }
+
final FillResponse response = builder.build();
response.setRequestId(parcel.readInt());
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 7ea4f30..cdfd397 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -68,22 +68,26 @@
private final InsetsController mController;
private final WindowInsetsAnimationCallback.InsetsAnimation mAnimation;
private final Rect mFrame;
+ private final boolean mFade;
private Insets mCurrentInsets;
private Insets mPendingInsets;
private float mPendingFraction;
private boolean mFinished;
private boolean mCancelled;
private boolean mShownOnFinish;
+ private float mCurrentAlpha;
+ private float mPendingAlpha;
@VisibleForTesting
public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame,
InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetsType int types,
Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier,
- InsetsController controller, long durationMs) {
+ InsetsController controller, long durationMs, boolean fade) {
mConsumers = consumers;
mListener = listener;
mTypes = types;
+ mFade = fade;
mTransactionApplierSupplier = transactionApplierSupplier;
mController = controller;
mInitialInsetsState = new InsetsState(state, true /* copySources */);
@@ -100,6 +104,7 @@
mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes,
InsetsController.INTERPOLATOR, durationMs);
+ mAnimation.setAlpha(getCurrentAlpha());
mController.dispatchAnimationStarted(mAnimation,
new AnimationBounds(mHiddenInsets, mShownInsets));
}
@@ -120,6 +125,11 @@
}
@Override
+ public float getCurrentAlpha() {
+ return mCurrentAlpha;
+ }
+
+ @Override
@InsetsType public int getTypes() {
return mTypes;
}
@@ -136,6 +146,7 @@
}
mPendingFraction = sanitize(fraction);
mPendingInsets = sanitize(insets);
+ mPendingAlpha = 1 - sanitize(alpha);
mController.scheduleApplyChangeInsets();
}
@@ -148,17 +159,24 @@
return false;
}
final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
+ final Float alphaOffset = 1 - mPendingAlpha;
ArrayList<SurfaceParams> params = new ArrayList<>();
- updateLeashesForSide(ISIDE_LEFT, offset.left, mPendingInsets.left, params, state);
- updateLeashesForSide(ISIDE_TOP, offset.top, mPendingInsets.top, params, state);
- updateLeashesForSide(ISIDE_RIGHT, offset.right, mPendingInsets.right, params, state);
- updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mPendingInsets.bottom, params, state);
- updateLeashesForSide(ISIDE_FLOATING, 0 /* offset */, 0 /* inset */, params, state);
+ updateLeashesForSide(ISIDE_LEFT, offset.left, mShownInsets.left, mPendingInsets.left,
+ params, state, alphaOffset);
+ updateLeashesForSide(ISIDE_TOP, offset.top, mShownInsets.top, mPendingInsets.top, params,
+ state, alphaOffset);
+ updateLeashesForSide(ISIDE_RIGHT, offset.right, mShownInsets.right, mPendingInsets.right,
+ params, state, alphaOffset);
+ updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mShownInsets.bottom,
+ mPendingInsets.bottom, params, state, alphaOffset);
+ updateLeashesForSide(ISIDE_FLOATING, 0 /* offset */, 0 /* inset */, 0 /* maxInset */,
+ params, state, alphaOffset);
SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get();
applier.scheduleApply(params.toArray(new SurfaceParams[params.size()]));
mCurrentInsets = mPendingInsets;
mAnimation.setFraction(mPendingFraction);
+ mCurrentAlpha = 1 - alphaOffset;
if (mFinished) {
mController.notifyFinished(this, mShownOnFinish);
}
@@ -233,7 +251,7 @@
}
private void updateLeashesForSide(@InternalInsetsSide int side, int offset, int inset,
- ArrayList<SurfaceParams> surfaceParams, InsetsState state) {
+ int maxInset, ArrayList<SurfaceParams> surfaceParams, InsetsState state, Float alpha) {
ArraySet<InsetsSourceConsumer> items = mSideSourceMap.get(side);
if (items == null) {
return;
@@ -257,7 +275,9 @@
// If the system is controlling the insets source, the leash can be null.
if (leash != null) {
- surfaceParams.add(new SurfaceParams(leash, 1f /* alpha */, mTmpMatrix,
+ // TODO: use a better interpolation for fade.
+ alpha = mFade ? ((float) maxInset / inset * 0.3f + 0.7f) : alpha;
+ surfaceParams.add(new SurfaceParams(leash, alpha, mTmpMatrix,
null /* windowCrop */, 0 /* layer */, 0f /* cornerRadius*/,
side == ISIDE_FLOATING ? consumer.isVisible() : inset != 0 /* visible */));
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8870311..5563d62 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -361,12 +361,12 @@
listener.onCancelled();
return;
}
- controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs);
+ controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */);
}
private void controlAnimationUnchecked(@InsetsType int types,
WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
- long durationMs) {
+ long durationMs, boolean fade) {
if (types == 0) {
// nothing to animate.
return;
@@ -397,7 +397,7 @@
final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
frame, mState, listener, typesReady,
- () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this, durationMs);
+ () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this, durationMs, fade);
mAnimationControls.add(controller);
}
@@ -588,7 +588,8 @@
// Show/hide animations always need to be relative to the display frame, in order that shown
// and hidden state insets are correct.
controlAnimationUnchecked(
- types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs());
+ types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
+ true /* fade */);
}
private void hideDirectly(@InsetsType int types) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2d0b954..44f211a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16185,7 +16185,7 @@
* by the most recent call to {@link #measure(int, int)}. This result is a bit mask
* as defined by {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
* This should be used during measurement and layout calculations only. Use
- * {@link #getHeight()} to see how wide a view is after layout.
+ * {@link #getHeight()} to see how high a view is after layout.
*
* @return The measured height of this view as a bit mask.
*/
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e3f0da1..df6f3b8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -211,13 +211,6 @@
* @see #USE_NEW_INSETS_PROPERTY
* @hide
*/
- public static int sNewInsetsMode =
- SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, 0);
-
- /**
- * @see #USE_NEW_INSETS_PROPERTY
- * @hide
- */
public static final int NEW_INSETS_MODE_NONE = 0;
/**
@@ -233,6 +226,13 @@
public static final int NEW_INSETS_MODE_FULL = 2;
/**
+ * @see #USE_NEW_INSETS_PROPERTY
+ * @hide
+ */
+ public static int sNewInsetsMode =
+ SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, NEW_INSETS_MODE_IME);
+
+ /**
* Set this system property to true to force the view hierarchy to render
* at 60 Hz. This can be used to measure the potential framerate.
*/
diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java
index 8ae8520..5e71f27 100644
--- a/core/java/android/view/WindowInsetsAnimationCallback.java
+++ b/core/java/android/view/WindowInsetsAnimationCallback.java
@@ -89,6 +89,7 @@
private float mFraction;
@Nullable private final Interpolator mInterpolator;
private long mDurationMs;
+ private float mAlpha;
public InsetsAnimation(
@InsetsType int typeMask, @Nullable Interpolator interpolator, long durationMs) {
@@ -177,6 +178,18 @@
public void setDuration(long durationMs) {
mDurationMs = durationMs;
}
+
+ /**
+ * @return alpha of {@link WindowInsets.Type.InsetsType}.
+ */
+ @FloatRange(from = 0f, to = 1f)
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ void setAlpha(@FloatRange(from = 0f, to = 1f) float alpha) {
+ mAlpha = alpha;
+ }
}
/**
diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java
index 5149103..2bf0d27 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -93,6 +93,12 @@
float getCurrentFraction();
/**
+ * Current alpha value of the window.
+ * @return float value between 0 and 1.
+ */
+ float getCurrentAlpha();
+
+ /**
* @return The {@link InsetsType}s this object is currently controlling.
*/
@InsetsType int getTypes();
diff --git a/core/java/android/view/inline/InlineContentView.java b/core/java/android/view/inline/InlineContentView.java
index 4bc2176..b143fad 100644
--- a/core/java/android/view/inline/InlineContentView.java
+++ b/core/java/android/view/inline/InlineContentView.java
@@ -50,7 +50,10 @@
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
- // TODO(b/137800469): implement this.
+ new SurfaceControl.Transaction()
+ .setVisibility(surfaceControl, false)
+ .reparent(surfaceControl, null)
+ .apply();
}
});
}
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index 25e34d3..c10144e 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -271,7 +271,7 @@
};
@DataClass.Generated(
- time = 1574446220398L,
+ time = 1575933636929L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 112653a..1e5a3b0 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -21,11 +21,16 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ComponentName;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.util.Log;
+import android.view.autofill.AutofillId;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
/**
* The InputMethod interface represents an input method which can generate key
@@ -104,6 +109,20 @@
}
/**
+ * Called to notify the IME that Autofill Frameworks requested an inline suggestions request.
+ *
+ * @hide
+ */
+ default void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ try {
+ cb.onInlineSuggestionsUnsupported();
+ } catch (RemoteException e) {
+ Log.w("InputMethod", "RemoteException calling onInlineSuggestionsUnsupported: " + e);
+ }
+ }
+
+ /**
* Called first thing after an input method is created, this supplies a
* unique token for the session it has with the system service. It is
* needed to identify itself with the service to validate its operations.
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index f5708a5c..dec9ae7 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -259,7 +259,7 @@
throw new IllegalStateException("Failed to touch " + file + ": " + e);
}
}
- MediaStore.scanFile(getContext(), file);
+ MediaStore.scanFile(getContext().getContentResolver(), file);
return childId;
}
@@ -316,10 +316,10 @@
private void moveInMediaStore(@Nullable File oldVisibleFile, @Nullable File newVisibleFile) {
if (oldVisibleFile != null) {
- MediaStore.scanFile(getContext(), oldVisibleFile);
+ MediaStore.scanFile(getContext().getContentResolver(), oldVisibleFile);
}
if (newVisibleFile != null) {
- MediaStore.scanFile(getContext(), newVisibleFile);
+ MediaStore.scanFile(getContext().getContentResolver(), newVisibleFile);
}
}
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 2ee902a..58aaa80 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -16,13 +16,16 @@
package com.android.internal.view;
+import android.content.ComponentName;
import android.os.IBinder;
import android.os.ResultReceiver;
+import android.view.autofill.AutofillId;
import android.view.InputChannel;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.IInputSessionCallback;
@@ -35,6 +38,9 @@
oneway interface IInputMethod {
void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps);
+ void onCreateInlineSuggestionsRequest(in ComponentName componentName, in AutofillId autofillId,
+ in IInlineSuggestionsRequestCallback cb);
+
void bindInput(in InputBinding binding);
void unbindInput();
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f7a994f..3cde887 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -129,6 +129,7 @@
extern int register_android_database_SQLiteConnection(JNIEnv* env);
extern int register_android_database_SQLiteGlobal(JNIEnv* env);
extern int register_android_database_SQLiteDebug(JNIEnv* env);
+extern int register_android_media_MediaMetrics(JNIEnv *env);
extern int register_android_os_Debug(JNIEnv* env);
extern int register_android_os_GraphicsEnvironment(JNIEnv* env);
extern int register_android_os_HidlSupport(JNIEnv* env);
@@ -1520,6 +1521,7 @@
REG_JNI(register_android_media_AudioProductStrategies),
REG_JNI(register_android_media_AudioVolumeGroups),
REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
+ REG_JNI(register_android_media_MediaMetrics),
REG_JNI(register_android_media_MicrophoneInfo),
REG_JNI(register_android_media_RemoteDisplay),
REG_JNI(register_android_media_ToneGenerator),
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 0c2129f..e3e07de 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2472,4 +2472,9 @@
// CATEGORY: SETTINGS
// OS: R
SETTINGS_BUGREPORT_HANDLER = 1808;
+
+ // Panel for adding Wi-Fi networks
+ // CATEGORY: SETTINGS
+ // OS: R
+ PANEL_ADD_WIFI_NETWORKS = 1809;
}
diff --git a/core/res/res/layout/chooser_grid_preview_text.xml b/core/res/res/layout/chooser_grid_preview_text.xml
index 9c725b9..4d7846d 100644
--- a/core/res/res/layout/chooser_grid_preview_text.xml
+++ b/core/res/res/layout/chooser_grid_preview_text.xml
@@ -45,7 +45,8 @@
android:ellipsize="end"
android:fontFamily="@android:string/config_headlineFontFamily"
android:textColor="?android:attr/textColorPrimary"
- android:maxLines="2"/>
+ android:maxLines="2"
+ android:focusable="true"/>
<LinearLayout
android:id="@+id/copy_button"
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index cce38f6..68d95cd 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -116,7 +116,8 @@
consumers.put(ITYPE_NAVIGATION_BAR, navConsumer);
mController = new InsetsAnimationControlImpl(consumers,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
- () -> mMockTransactionApplier, mMockController, 10 /* durationMs */);
+ () -> mMockTransactionApplier, mMockController, 10 /* durationMs */,
+ false /* fade */);
}
@Test
@@ -133,6 +134,7 @@
0f /* fraction */);
mController.applyChangeInsets(new InsetsState());
assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
+ assertEquals(1f, mController.getCurrentAlpha(), 1f - mController.getCurrentAlpha());
ArgumentCaptor<SurfaceParams> captor = ArgumentCaptor.forClass(SurfaceParams.class);
verify(mMockTransactionApplier).scheduleApply(captor.capture());
@@ -147,6 +149,23 @@
}
@Test
+ public void testChangeAlphaNoInsets() {
+ Insets initialInsets = mController.getCurrentInsets();
+ mController.setInsetsAndAlpha(null, 0.5f, 0f /* fraction*/);
+ mController.applyChangeInsets(new InsetsState());
+ assertEquals(0.5f, mController.getCurrentAlpha(), 0.5f - mController.getCurrentAlpha());
+ assertEquals(initialInsets, mController.getCurrentInsets());
+ }
+
+ @Test
+ public void testChangeInsetsAndAlpha() {
+ mController.setInsetsAndAlpha(Insets.of(0, 30, 40, 0), 0.5f, 1f);
+ mController.applyChangeInsets(new InsetsState());
+ assertEquals(0.5f, mController.getCurrentAlpha(), 0.5f - mController.getCurrentAlpha());
+ assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
+ }
+
+ @Test
public void testFinishing() {
when(mMockController.getState()).thenReturn(mInsetsState);
mController.finish(true /* shown */);
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index e07edd4..624fc50 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -240,6 +240,13 @@
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.tethering">
+ <permission name="android.permission.MANAGE_USB"/>
+ <permission name="android.permission.MODIFY_PHONE_STATE"/>
+ <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+ <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.server.telecom">
<permission name="android.permission.BIND_CONNECTION_SERVICE"/>
<permission name="android.permission.BIND_INCALL_SERVICE"/>
@@ -343,6 +350,8 @@
<permission name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
<!-- Permission required for Telecom car mode CTS tests. -->
<permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+ <!-- Permission required for Tethering CTS tests. -->
+ <permission name="android.permission.TETHER_PRIVILEGED"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java
new file mode 100644
index 0000000..88a8295
--- /dev/null
+++ b/media/java/android/media/MediaMetrics.java
@@ -0,0 +1,634 @@
+/*
+ * Copyright 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.media;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Bundle;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * MediaMetrics is the Java interface to the MediaMetrics service.
+ *
+ * This is used to collect media statistics by the framework.
+ * It is not intended for direct application use.
+ *
+ * @hide
+ */
+public class MediaMetrics {
+ public static final String TAG = "MediaMetrics";
+
+ /**
+ * The TYPE constants below should match those in native MediaMetricsItem.h
+ */
+ private static final int TYPE_NONE = 0;
+ private static final int TYPE_INT32 = 1; // Java integer
+ private static final int TYPE_INT64 = 2; // Java long
+ private static final int TYPE_DOUBLE = 3; // Java double
+ private static final int TYPE_CSTRING = 4; // Java string
+ private static final int TYPE_RATE = 5; // Two longs, ignored in Java
+
+ // The charset used for encoding Strings to bytes.
+ private static final Charset MEDIAMETRICS_CHARSET = StandardCharsets.UTF_8;
+
+ /**
+ * Item records properties and delivers to the MediaMetrics service
+ *
+ */
+ public static class Item {
+
+ /*
+ * MediaMetrics Item
+ *
+ * Creates a Byte String and sends to the MediaMetrics service.
+ * The Byte String serves as a compact form for logging data
+ * with low overhead for storage.
+ *
+ * The Byte String format is as follows:
+ *
+ * For Java
+ * int64 corresponds to long
+ * int32, uint32 corresponds to int
+ * uint16 corresponds to char
+ * uint8, int8 corresponds to byte
+ *
+ * For items transmitted from Java, uint8 and uint32 values are limited
+ * to INT8_MAX and INT32_MAX. This constrains the size of large items
+ * to 2GB, which is consistent with ByteBuffer max size. A native item
+ * can conceivably have size of 4GB.
+ *
+ * Physical layout of integers and doubles within the MediaMetrics byte string
+ * is in Native / host order, which is usually little endian.
+ *
+ * Note that primitive data (ints, doubles) within a Byte String has
+ * no extra padding or alignment requirements, like ByteBuffer.
+ *
+ * -- begin of item
+ * -- begin of header
+ * (uint32) item size: including the item size field
+ * (uint32) header size, including the item size and header size fields.
+ * (uint16) version: exactly 0
+ * (uint16) key size, that is key strlen + 1 for zero termination.
+ * (int8)+ key, a string which is 0 terminated (UTF-8).
+ * (int32) pid
+ * (int32) uid
+ * (int64) timestamp
+ * -- end of header
+ * -- begin body
+ * (uint32) number of properties
+ * -- repeat for number of properties
+ * (uint16) property size, including property size field itself
+ * (uint8) type of property
+ * (int8)+ key string, including 0 termination
+ * based on type of property (given above), one of:
+ * (int32)
+ * (int64)
+ * (double)
+ * (int8)+ for TYPE_CSTRING, including 0 termination
+ * (int64, int64) for rate
+ * -- end body
+ * -- end of item
+ *
+ * To record a MediaMetrics event, one creates a new item with an id,
+ * then use a series of puts to add properties
+ * and then a record() to send to the MediaMetrics service.
+ *
+ * The properties may not be unique, and putting a later property with
+ * the same name as an earlier property will overwrite the value and type
+ * of the prior property.
+ *
+ * The timestamp can only be recorded by a system service (and is ignored otherwise;
+ * the MediaMetrics service will fill in the timestamp as needed).
+ *
+ * The units of time are in SystemClock.elapsedRealtimeNanos().
+ *
+ * A clear() may be called to reset the properties to empty, the time to 0, but keep
+ * the other entries the same. This may be called after record().
+ * Additional properties may be added after calling record(). Changing the same property
+ * repeatedly is discouraged as - for this particular implementation - extra data
+ * is stored per change.
+ *
+ * new MediaMetrics.Item(mSomeId)
+ * .putString("event", "javaCreate")
+ * .putInt("value", intValue)
+ * .record();
+ */
+
+ /**
+ * Creates an Item with server added uid, time.
+ *
+ * This is the typical way to record a MediaMetrics item.
+ *
+ * @param key the Metrics ID associated with the item.
+ */
+ public Item(String key) {
+ this(key, -1 /* pid */, -1 /* uid */, 0 /* SystemClock.elapsedRealtimeNanos() */,
+ 2048 /* capacity */);
+ }
+
+ /**
+ * Creates an Item specifying pid, uid, time, and initial Item capacity.
+ *
+ * This might be used by a service to specify a different PID or UID for a client.
+ *
+ * @param key the Metrics ID associated with the item.
+ * An app may only set properties on an item which has already been
+ * logged previously by a service.
+ * @param pid the process ID corresponding to the item.
+ * A value of -1 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill this in.
+ * @param uid the user ID corresponding to the item.
+ * A value of -1 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill this in.
+ * @param timeNs the time when the item occurred (may be in the past).
+ * A value of 0 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill it in.
+ * Should be obtained from SystemClock.elapsedRealtimeNanos().
+ * @param capacity the anticipated size to use for the buffer.
+ * If the capacity is too small, the buffer will be resized to accommodate.
+ * This is amortized to copy data no more than twice.
+ */
+ public Item(String key, int pid, int uid, long timeNs, int capacity) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final int keyLength = keyBytes.length;
+ if (keyLength > Character.MAX_VALUE - 1) {
+ throw new IllegalArgumentException("Key length too large");
+ }
+
+ // Version 0 - compute the header offsets here.
+ mHeaderSize = 4 + 4 + 2 + 2 + keyLength + 1 + 4 + 4 + 8; // see format above.
+ mPidOffset = mHeaderSize - 16;
+ mUidOffset = mHeaderSize - 12;
+ mTimeNsOffset = mHeaderSize - 8;
+ mPropertyCountOffset = mHeaderSize;
+ mPropertyStartOffset = mHeaderSize + 4;
+
+ mKey = key;
+ mBuffer = ByteBuffer.allocateDirect(
+ Math.max(capacity, mHeaderSize + MINIMUM_PAYLOAD_SIZE));
+
+ // Version 0 - fill the ByteBuffer with the header (some details updated later).
+ mBuffer.order(ByteOrder.nativeOrder())
+ .putInt((int) 0) // total size in bytes (filled in later)
+ .putInt((int) mHeaderSize) // size of header
+ .putChar((char) FORMAT_VERSION) // version
+ .putChar((char) (keyLength + 1)) // length, with zero termination
+ .put(keyBytes).put((byte) 0)
+ .putInt(pid)
+ .putInt(uid)
+ .putLong(timeNs);
+ if (mHeaderSize != mBuffer.position()) {
+ throw new IllegalStateException("Mismatched sizing");
+ }
+ mBuffer.putInt(0); // number of properties (to be later filled in by record()).
+ }
+
+ /**
+ * Sets the property with key to an integer (32 bit) value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putInt(String key, int value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 4 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_INT32)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putInt(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a long (64 bit) value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putLong(String key, long value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_INT64)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putLong(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a double value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putDouble(String key, double value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_DOUBLE)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putDouble(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a String value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putString(String key, String value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final byte[] valueBytes = value.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, valueBytes.length + 1);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_CSTRING)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .put(valueBytes).put((byte) 0); // value, zero term.
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the pid to the provided value.
+ *
+ * @param pid which can be -1 if the service is to fill it in from the calling info.
+ * @return itself
+ */
+ public Item setPid(int pid) {
+ mBuffer.putInt(mPidOffset, pid); // pid location in byte string.
+ return this;
+ }
+
+ /**
+ * Sets the uid to the provided value.
+ *
+ * The UID represents the client associated with the property. This must be the UID
+ * of the application if it comes from the application client.
+ *
+ * Trusted services are allowed to set the uid for a client-related item.
+ *
+ * @param uid which can be -1 if the service is to fill it in from calling info.
+ * @return itself
+ */
+ public Item setUid(int uid) {
+ mBuffer.putInt(mUidOffset, uid); // uid location in byte string.
+ return this;
+ }
+
+ /**
+ * Sets the timestamp to the provided value.
+ *
+ * The time is referenced by the Boottime obtained by SystemClock.elapsedRealtimeNanos().
+ * This should be associated with the occurrence of the event. It is recommended that
+ * the event be registered immediately when it occurs, and no later than 500ms
+ * (and certainly not in the future).
+ *
+ * @param timeNs which can be 0 if the service is to fill it in at the time of call.
+ * @return itself
+ */
+ public Item setTimestamp(long timeNs) {
+ mBuffer.putLong(mTimeNsOffset, timeNs); // time location in byte string.
+ return this;
+ }
+
+ /**
+ * Clears the properties and resets the time to 0.
+ *
+ * No other values are changed.
+ *
+ * @return itself
+ */
+ public Item clear() {
+ mBuffer.position(mPropertyStartOffset);
+ mBuffer.limit(mBuffer.capacity());
+ mBuffer.putLong(mTimeNsOffset, 0); // reset time.
+ mPropertyCount = 0;
+ return this;
+ }
+
+ /**
+ * Sends the item to the MediaMetrics service.
+ *
+ * The item properties are unchanged, hence record() may be called more than once
+ * to send the same item twice. Also, record() may be called without any properties.
+ *
+ * @return true if successful.
+ */
+ public boolean record() {
+ updateHeader();
+ return native_submit_bytebuffer(mBuffer, mBuffer.limit()) >= 0;
+ }
+
+ /**
+ * Converts the Item to a Bundle.
+ *
+ * This is primarily used as a test API for CTS.
+ *
+ * @return a Bundle with the keys set according to data in the Item's buffer.
+ */
+ @TestApi
+ public Bundle toBundle() {
+ updateHeader();
+
+ final ByteBuffer buffer = mBuffer.duplicate();
+ buffer.order(ByteOrder.nativeOrder()) // restore order property
+ .flip(); // convert from write buffer to read buffer
+
+ return toBundle(buffer);
+ }
+
+ // The following constants are used for tests to extract
+ // the content of the Bundle for CTS testing.
+ @TestApi
+ public static final String BUNDLE_TOTAL_SIZE = "_totalSize";
+ @TestApi
+ public static final String BUNDLE_HEADER_SIZE = "_headerSize";
+ @TestApi
+ public static final String BUNDLE_VERSION = "_version";
+ @TestApi
+ public static final String BUNDLE_KEY_SIZE = "_keySize";
+ @TestApi
+ public static final String BUNDLE_KEY = "_key";
+ @TestApi
+ public static final String BUNDLE_PID = "_pid";
+ @TestApi
+ public static final String BUNDLE_UID = "_uid";
+ @TestApi
+ public static final String BUNDLE_TIMESTAMP = "_timestamp";
+ @TestApi
+ public static final String BUNDLE_PROPERTY_COUNT = "_propertyCount";
+
+ /**
+ * Converts a buffer contents to a bundle
+ *
+ * This is primarily used as a test API for CTS.
+ *
+ * @param buffer contains the byte data serialized according to the byte string version.
+ * @return a Bundle with the keys set according to data in the buffer.
+ */
+ @TestApi
+ public static Bundle toBundle(ByteBuffer buffer) {
+ final Bundle bundle = new Bundle();
+
+ final int totalSize = buffer.getInt();
+ final int headerSize = buffer.getInt();
+ final char version = buffer.getChar();
+ final char keySize = buffer.getChar(); // includes zero termination, i.e. keyLength + 1
+
+ if (totalSize < 0 || headerSize < 0) {
+ throw new IllegalArgumentException("Item size cannot be > " + Integer.MAX_VALUE);
+ }
+ final String key;
+ if (keySize > 0) {
+ key = getStringFromBuffer(buffer, keySize);
+ } else {
+ throw new IllegalArgumentException("Illegal null key");
+ }
+
+ final int pid = buffer.getInt();
+ final int uid = buffer.getInt();
+ final long timestamp = buffer.getLong();
+
+ // Verify header size (depending on version).
+ final int headerRead = buffer.position();
+ if (version == 0) {
+ if (headerRead != headerSize) {
+ throw new IllegalArgumentException(
+ "Item key:" + key
+ + " headerRead:" + headerRead + " != headerSize:" + headerSize);
+ }
+ } else {
+ // future versions should only increase header size
+ // by adding to the end.
+ if (headerRead > headerSize) {
+ throw new IllegalArgumentException(
+ "Item key:" + key
+ + " headerRead:" + headerRead + " > headerSize:" + headerSize);
+ } else if (headerRead < headerSize) {
+ buffer.position(headerSize);
+ }
+ }
+
+ // Body always starts with properties.
+ final int propertyCount = buffer.getInt();
+ if (propertyCount < 0) {
+ throw new IllegalArgumentException(
+ "Cannot have more than " + Integer.MAX_VALUE + " properties");
+ }
+ bundle.putInt(BUNDLE_TOTAL_SIZE, totalSize);
+ bundle.putInt(BUNDLE_HEADER_SIZE, headerSize);
+ bundle.putChar(BUNDLE_VERSION, version);
+ bundle.putChar(BUNDLE_KEY_SIZE, keySize);
+ bundle.putString(BUNDLE_KEY, key);
+ bundle.putInt(BUNDLE_PID, pid);
+ bundle.putInt(BUNDLE_UID, uid);
+ bundle.putLong(BUNDLE_TIMESTAMP, timestamp);
+ bundle.putInt(BUNDLE_PROPERTY_COUNT, propertyCount);
+
+ for (int i = 0; i < propertyCount; ++i) {
+ final int initialBufferPosition = buffer.position();
+ final char propSize = buffer.getChar();
+ final byte type = buffer.get();
+
+ // Log.d(TAG, "(" + i + ") propSize:" + ((int)propSize) + " type:" + type);
+ final String propKey = getStringFromBuffer(buffer);
+ switch (type) {
+ case TYPE_INT32:
+ bundle.putInt(propKey, buffer.getInt());
+ break;
+ case TYPE_INT64:
+ bundle.putLong(propKey, buffer.getLong());
+ break;
+ case TYPE_DOUBLE:
+ bundle.putDouble(propKey, buffer.getDouble());
+ break;
+ case TYPE_CSTRING:
+ bundle.putString(propKey, getStringFromBuffer(buffer));
+ break;
+ case TYPE_NONE:
+ break; // ignore on Java side
+ case TYPE_RATE:
+ buffer.getLong(); // consume the first int64_t of rate
+ buffer.getLong(); // consume the second int64_t of rate
+ break; // ignore on Java side
+ default:
+ // These are unsupported types for version 0
+ // We ignore them if the version is greater than 0.
+ if (version == 0) {
+ throw new IllegalArgumentException(
+ "Property " + propKey + " has unsupported type " + type);
+ }
+ buffer.position(initialBufferPosition + propSize); // advance and skip
+ break;
+ }
+ final int deltaPosition = buffer.position() - initialBufferPosition;
+ if (deltaPosition != propSize) {
+ throw new IllegalArgumentException("propSize:" + propSize
+ + " != deltaPosition:" + deltaPosition);
+ }
+ }
+
+ final int finalPosition = buffer.position();
+ if (finalPosition != totalSize) {
+ throw new IllegalArgumentException("totalSize:" + totalSize
+ + " != finalPosition:" + finalPosition);
+ }
+ return bundle;
+ }
+
+ // Version 0 byte offsets for the header.
+ private static final int FORMAT_VERSION = 0;
+ private static final int TOTAL_SIZE_OFFSET = 0;
+ private static final int HEADER_SIZE_OFFSET = 4;
+ private static final int MINIMUM_PAYLOAD_SIZE = 4;
+ private final int mPidOffset; // computed in constructor
+ private final int mUidOffset; // computed in constructor
+ private final int mTimeNsOffset; // computed in constructor
+ private final int mPropertyCountOffset; // computed in constructor
+ private final int mPropertyStartOffset; // computed in constructor
+ private final int mHeaderSize; // computed in constructor
+
+ private final String mKey;
+
+ private ByteBuffer mBuffer; // may be reallocated if capacity is insufficient.
+ private int mPropertyCount = 0; // overflow not checked (mBuffer would overflow first).
+
+ private int reserveProperty(byte[] keyBytes, int payloadSize) {
+ final int keyLength = keyBytes.length;
+ if (keyLength > Character.MAX_VALUE) {
+ throw new IllegalStateException("property key too long "
+ + new String(keyBytes, MEDIAMETRICS_CHARSET));
+ }
+ if (payloadSize > Character.MAX_VALUE) {
+ throw new IllegalStateException("payload too large " + payloadSize);
+ }
+
+ // See the byte string property format above.
+ final int size = 2 /* length */
+ + 1 /* type */
+ + keyLength + 1 /* key length with zero termination */
+ + payloadSize; /* payload size */
+
+ if (size > Character.MAX_VALUE) {
+ throw new IllegalStateException("Item property "
+ + new String(keyBytes, MEDIAMETRICS_CHARSET) + " is too large to send");
+ }
+
+ if (mBuffer.remaining() < size) {
+ int newCapacity = mBuffer.position() + size;
+ if (newCapacity > Integer.MAX_VALUE >> 1) {
+ throw new IllegalStateException(
+ "Item memory requirements too large: " + newCapacity);
+ }
+ newCapacity <<= 1;
+ ByteBuffer buffer = ByteBuffer.allocateDirect(newCapacity);
+ buffer.order(ByteOrder.nativeOrder());
+
+ // Copy data from old buffer to new buffer.
+ mBuffer.flip();
+ buffer.put(mBuffer);
+
+ // set buffer to new buffer
+ mBuffer = buffer;
+ }
+ return size;
+ }
+
+ // Used for test
+ private static String getStringFromBuffer(ByteBuffer buffer) {
+ return getStringFromBuffer(buffer, Integer.MAX_VALUE);
+ }
+
+ // Used for test
+ private static String getStringFromBuffer(ByteBuffer buffer, int size) {
+ int i = buffer.position();
+ int limit = buffer.limit();
+ if (size < Integer.MAX_VALUE - i && i + size < limit) {
+ limit = i + size;
+ }
+ for (; i < limit; ++i) {
+ if (buffer.get(i) == 0) {
+ final int newPosition = i + 1;
+ if (size != Integer.MAX_VALUE && newPosition - buffer.position() != size) {
+ throw new IllegalArgumentException("chars consumed at " + i + ": "
+ + (newPosition - buffer.position()) + " != size: " + size);
+ }
+ final String found;
+ if (buffer.hasArray()) {
+ found = new String(
+ buffer.array(), buffer.position() + buffer.arrayOffset(),
+ i - buffer.position(), MEDIAMETRICS_CHARSET);
+ buffer.position(newPosition);
+ } else {
+ final byte[] array = new byte[i - buffer.position()];
+ buffer.get(array);
+ found = new String(array, MEDIAMETRICS_CHARSET);
+ buffer.get(); // remove 0.
+ }
+ return found;
+ }
+ }
+ throw new IllegalArgumentException(
+ "No zero termination found in string position: "
+ + buffer.position() + " end: " + i);
+ }
+
+ /**
+ * May be called multiple times - just makes the header consistent with the current
+ * properties written.
+ */
+ private void updateHeader() {
+ // Buffer sized properly in constructor.
+ mBuffer.putInt(TOTAL_SIZE_OFFSET, mBuffer.position()) // set total length
+ .putInt(mPropertyCountOffset, (char) mPropertyCount); // set number of properties
+ }
+ }
+
+ private static native int native_submit_bytebuffer(@NonNull ByteBuffer buffer, int length);
+}
diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java
index 515f6a8..40e9073 100644
--- a/media/java/android/media/MediaScannerConnection.java
+++ b/media/java/android/media/MediaScannerConnection.java
@@ -19,6 +19,7 @@
import android.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentProviderClient;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.ServiceConnection;
import android.net.Uri;
@@ -197,7 +198,7 @@
private static Uri scanFileQuietly(ContentProviderClient client, File file) {
Uri uri = null;
try {
- uri = MediaStore.scanFile(client, file.getCanonicalFile());
+ uri = MediaStore.scanFile(ContentResolver.wrap(client), file.getCanonicalFile());
Log.d(TAG, "Scanned " + file + " to " + uri);
} catch (Exception e) {
Log.w(TAG, "Failed to scan " + file + ": " + e);
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 9064e68..a1e1591 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -908,7 +908,7 @@
}
// Tell MediaScanner about the new file. Wait for it to assign a {@link Uri}.
- return MediaStore.scanFile(mContext, outFile);
+ return MediaStore.scanFile(mContext.getContentResolver(), outFile);
}
private static final String getExternalDirectoryForType(final int type) {
diff --git a/media/java/android/media/RouteSessionController.java b/media/java/android/media/RouteSessionController.java
new file mode 100644
index 0000000..5ff7218
--- /dev/null
+++ b/media/java/android/media/RouteSessionController.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 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.media;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+/**
+ * A class to control media route session in media route provider.
+ * For example, adding/removing/transferring routes to session can be done through this class.
+ * Instances are created by {@link MediaRouter2}.
+ *
+ * TODO: When session is introduced, change Javadoc of all methods/classes by using [@link Session].
+ *
+ * @hide
+ */
+public class RouteSessionController {
+ private final int mSessionId;
+ private final String mCategory;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords =
+ new CopyOnWriteArrayList<>();
+
+ private volatile boolean mIsReleased;
+
+ /**
+ * @param sessionId the ID of the session.
+ * @param category The category of media routes that the session includes.
+ */
+ RouteSessionController(int sessionId, @NonNull String category) {
+ mSessionId = sessionId;
+ mCategory = category;
+ }
+
+ /**
+ * @return the ID of this controller
+ */
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ /**
+ * @return the category of routes that the session includes.
+ */
+ @NonNull
+ public String getCategory() {
+ return mCategory;
+ }
+
+ /**
+ * @return the list of currently connected routes
+ */
+ @NonNull
+ public List<MediaRoute2Info> getRoutes() {
+ // TODO: Implement this when SessionInfo is introduced.
+ return null;
+ }
+
+ /**
+ * Returns true if the session is released, false otherwise.
+ * If it is released, then all other getters from this instance may return invalid values.
+ * Also, any operations to this instance will be ignored once released.
+ *
+ * @see #release
+ * @see Callback#onReleased
+ */
+ public boolean isReleased() {
+ return mIsReleased;
+ }
+
+ /**
+ * Add routes to the remote session.
+ *
+ * @see #getRoutes()
+ * @see Callback#onSessionInfoChanged
+ */
+ public void addRoutes(List<MediaRoute2Info> routes) {
+ // TODO: Implement this when the actual connection logic is implemented.
+ }
+
+ /**
+ * Remove routes from this session. Media may be stopped on those devices.
+ * Route removal requests that are not currently in {@link #getRoutes()} will be ignored.
+ *
+ * @see #getRoutes()
+ * @see Callback#onSessionInfoChanged
+ */
+ public void removeRoutes(List<MediaRoute2Info> routes) {
+ // TODO: Implement this when the actual connection logic is implemented.
+ }
+
+ /**
+ * Registers a {@link Callback} for monitoring route changes.
+ * If the same callback is registered previously, previous executor will be overwritten with the
+ * new one.
+ */
+ public void registerCallback(Executor executor, Callback callback) {
+ if (mIsReleased) {
+ return;
+ }
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(callback, "callback must not be null");
+
+ synchronized (mLock) {
+ CallbackRecord recordWithSameCallback = null;
+ for (CallbackRecord record : mCallbackRecords) {
+ if (callback == record.mCallback) {
+ recordWithSameCallback = record;
+ break;
+ }
+ }
+
+ if (recordWithSameCallback != null) {
+ recordWithSameCallback.mExecutor = executor;
+ } else {
+ mCallbackRecords.add(new CallbackRecord(executor, callback));
+ }
+ }
+ }
+
+ /**
+ * Unregisters a previously registered {@link Callback}.
+ */
+ public void unregisterCallback(Callback callback) {
+ Objects.requireNonNull(callback, "callback must not be null");
+
+ synchronized (mLock) {
+ CallbackRecord recordToRemove = null;
+ for (CallbackRecord record : mCallbackRecords) {
+ if (callback == record.mCallback) {
+ recordToRemove = record;
+ break;
+ }
+ }
+
+ if (recordToRemove != null) {
+ mCallbackRecords.remove(recordToRemove);
+ }
+ }
+ }
+
+ /**
+ * Release this session.
+ * Any operation on this session after calling this method will be ignored.
+ *
+ * @param stopMedia Should the device where the media is played
+ * be stopped after this session is released.
+ */
+ public void release(boolean stopMedia) {
+ mIsReleased = true;
+ mCallbackRecords.clear();
+ // TODO: Use stopMedia variable when the actual connection logic is implemented.
+ }
+
+ /**
+ * Callback class for getting updates on routes and session release.
+ */
+ public static class Callback {
+
+ /**
+ * Called when the session info has changed.
+ * TODO: When SessionInfo is introduced, uncomment below argument.
+ */
+ void onSessionInfoChanged(/* SessionInfo info */) {}
+
+ /**
+ * Called when the session is released. Session can be released by the controller using
+ * {@link #release(boolean)}, or by the {@link MediaRoute2ProviderService} itself.
+ * One can do clean-ups here.
+ *
+ * TODO: When SessionInfo is introduced, change the javadoc of releasing session on
+ * provider side.
+ */
+ void onReleased(int reason, boolean shouldStop) {}
+ }
+
+ private class CallbackRecord {
+ public final Callback mCallback;
+ public Executor mExecutor;
+
+ CallbackRecord(@NonNull Executor executor, @NonNull Callback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+ }
+}
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index a315c1e..6705b0c 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -33,14 +33,13 @@
import android.graphics.ImageDecoder.ImageInfo;
import android.graphics.ImageDecoder.Source;
import android.graphics.Matrix;
-import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
-import android.provider.MediaStore.ThumbnailConstants;
+import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
@@ -77,15 +76,7 @@
public static final int OPTIONS_RECYCLE_INPUT = 0x2;
private static Size convertKind(int kind) {
- if (kind == ThumbnailConstants.MICRO_KIND) {
- return Point.convert(ThumbnailConstants.MICRO_SIZE);
- } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
- return Point.convert(ThumbnailConstants.FULL_SCREEN_SIZE);
- } else if (kind == ThumbnailConstants.MINI_KIND) {
- return Point.convert(ThumbnailConstants.MINI_SIZE);
- } else {
- throw new IllegalArgumentException("Unsupported kind: " + kind);
- }
+ return MediaStore.Images.Thumbnails.getKindSize(kind);
}
private static class Resizer implements ImageDecoder.OnHeaderDecodedListener {
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 0f402eb..f3c071a0 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -16,8 +16,10 @@
package android.mtp;
+import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ContentProviderClient;
+import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@@ -422,13 +424,13 @@
}
// Add the new file to MediaProvider
if (succeeded) {
- MediaStore.scanFile(mContext, obj.getPath().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
}
}
@VisibleForNative
private void rescanFile(String path, int handle, int format) {
- MediaStore.scanFile(mContext, new File(path));
+ MediaStore.scanFile(mContext.getContentResolver(), new File(path));
}
@VisibleForNative
@@ -587,13 +589,13 @@
if (obj.isDir()) {
// for directories, check if renamed from something hidden to something non-hidden
if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
- MediaStore.scanFile(mContext, newPath.toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), newPath.toFile());
}
} else {
// for files, check if renamed from .nomedia to something else
if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
&& !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
- MediaStore.scanFile(mContext, newPath.getParent().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), newPath.getParent().toFile());
}
}
return MtpConstants.RESPONSE_OK;
@@ -662,7 +664,7 @@
mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
} else {
// Old parent doesn't exist - add the object
- MediaStore.scanFile(mContext, path.toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), path.toFile());
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in mMediaProvider.update", e);
@@ -689,7 +691,7 @@
if (!success) {
return;
}
- MediaStore.scanFile(mContext, obj.getPath().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
}
@VisibleForNative
@@ -909,7 +911,7 @@
String[] whereArgs = new String[]{path.toString()};
if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) > 0) {
if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
- MediaStore.scanFile(mContext, path.getParent().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), path.getParent().toFile());
}
} else {
Log.i(TAG, "Mediaprovider didn't delete " + path);
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index 65d0fef..c7dbca6 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -41,11 +41,7 @@
mDescription = volume.getDescription(null);
mRemovable = volume.isRemovable();
mMaxFileSize = volume.getMaxFileSize();
- if (volume.isPrimary()) {
- mVolumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
- } else {
- mVolumeName = volume.getNormalizedUuid();
- }
+ mVolumeName = volume.getMediaStoreVolumeName();
}
/**
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index 494c617..e17a617 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -23,6 +23,7 @@
#include "android_media_MediaMetricsJNI.h"
#include "android_os_Parcel.h"
+#include "android_runtime/AndroidRuntime.h"
// This source file is compiled and linked into:
// core/jni/ (libandroid_runtime.so)
@@ -124,6 +125,28 @@
return bh.bundle;
}
+// Implementation of MediaMetrics.native_submit_bytebuffer(),
+// Delivers the byte buffer to the mediametrics service.
+static jint android_media_MediaMetrics_submit_bytebuffer(
+ JNIEnv* env, jobject thiz, jobject byteBuffer, jint length)
+{
+ const jbyte* buffer =
+ reinterpret_cast<const jbyte*>(env->GetDirectBufferAddress(byteBuffer));
+ if (buffer == nullptr) {
+ ALOGE("Error retrieving source of audio data to play, can't play");
+ return (jint)BAD_VALUE;
+ }
+
+ // TODO: directly record item to MediaMetrics service.
+ mediametrics::Item item;
+ if (item.readFromByteString((char *)buffer, length) != NO_ERROR) {
+ ALOGW("%s: cannot read from byte string", __func__);
+ return (jint)BAD_VALUE;
+ }
+ item.selfrecord();
+ return (jint)NO_ERROR;
+}
+
// Helper function to convert a native PersistableBundle to a Java
// PersistableBundle.
jobject MediaMetricsJNI::nativeToJavaPersistableBundle(JNIEnv *env,
@@ -191,5 +214,18 @@
return newBundle;
}
-}; // namespace android
+// ----------------------------------------------------------------------------
+static constexpr JNINativeMethod gMethods[] = {
+ {"native_submit_bytebuffer", "(Ljava/nio/ByteBuffer;I)I",
+ (void *)android_media_MediaMetrics_submit_bytebuffer},
+};
+
+// Registers the native methods, called from core/jni/AndroidRuntime.cpp
+int register_android_media_MediaMetrics(JNIEnv *env)
+{
+ return AndroidRuntime::registerNativeMethods(
+ env, "android/media/MediaMetrics", gMethods, std::size(gMethods));
+}
+
+}; // namespace android
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 5d0db01..19ff244 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -215,7 +215,6 @@
Settings.Global.DEFAULT_DNS_SERVER,
Settings.Global.DEFAULT_INSTALL_LOCATION,
Settings.Global.DEFAULT_RESTRICT_BACKGROUND_DATA,
- Settings.Global.DEFAULT_USER_ID_TO_BOOT_INTO,
Settings.Global.DESK_DOCK_SOUND,
Settings.Global.DESK_UNDOCK_SOUND,
Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b89f141..51bf441 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -224,6 +224,9 @@
<!-- Permission requried for CTS test - CellBroadcastIntentsTest -->
<uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS"/>
+ <!-- Permission required for CTS test - TetheringManagerTest -->
+ <uses-permission android:name="android.permission.TETHER_PRIVILEGED"/>
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
index 2d37b4c..15fd1f7 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
@@ -101,7 +101,7 @@
}
private Uri scanFile(@NonNull final File file) {
- return MediaStore.scanFile(this, file);
+ return MediaStore.scanFile(getContentResolver(), file);
}
private void set(@NonNull final String name, @NonNull final Uri uri) {
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0407ffe..45318fd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -915,7 +915,7 @@
<!-- QuickSettings: Label for the toggle to activate dark theme (A.K.A Dark Mode). [CHAR LIMIT=20] -->
<string name="quick_settings_ui_mode_night_label">Dark theme</string>
<!-- QuickSettings: Secondary text for the dark theme tile when enabled by battery saver. [CHAR LIMIT=20] -->
- <string name="quick_settings_dark_mode_secondary_label_battery_saver">Battery saver</string>
+ <string name="quick_settings_dark_mode_secondary_label_battery_saver">Battery Saver</string>
<!-- QuickSettings: Secondary text for when the Dark Mode will be enabled at sunset. [CHAR LIMIT=20] -->
<string name="quick_settings_dark_mode_secondary_label_on_at_sunset">On at sunset</string>
<!-- QuickSettings: Secondary text for when the Dark Mode will be on until sunrise. [CHAR LIMIT=20] -->
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 0383dee..d3ccbeb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -371,7 +371,8 @@
Bitmap thumbnailBitmap = null;
try {
ContentResolver resolver = getContentResolver();
- Size size = Point.convert(MediaStore.ThumbnailConstants.MINI_SIZE);
+ DisplayMetrics metrics = getResources().getDisplayMetrics();
+ Size size = new Size(metrics.widthPixels, metrics.heightPixels / 2);
thumbnailBitmap = resolver.loadThumbnail(uri, size, null);
} catch (IOException e) {
Log.e(TAG, "Error creating thumbnail: " + e.getMessage());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 76925b4..2f401e5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -23,6 +23,8 @@
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
@@ -48,7 +50,9 @@
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -275,6 +279,7 @@
Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
Context context = mParams.context;
+ ContentResolver resolver = context.getContentResolver();
Bitmap image = mParams.image;
Resources r = context.getResources();
@@ -284,23 +289,27 @@
mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context));
// Save the screenshot to the MediaStore
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
- params.setRelativePath(Environment.DIRECTORY_PICTURES + File.separator
- + Environment.DIRECTORY_SCREENSHOTS);
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES
+ + File.separator + Environment.DIRECTORY_SCREENSHOTS);
+ values.put(MediaColumns.DISPLAY_NAME, mImageFileName);
+ values.put(MediaColumns.MIME_TYPE, "image/png");
+ values.put(MediaColumns.DATE_ADDED, mImageTime / 1000);
+ values.put(MediaColumns.DATE_MODIFIED, mImageTime / 1000);
+ values.put(MediaColumns.DATE_EXPIRES, (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000);
+ values.put(MediaColumns.IS_PENDING, 1);
- final Uri uri = MediaStore.createPending(context, params);
- final MediaStore.PendingSession session = MediaStore.openPending(context, uri);
+ final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
try {
// First, write the actual data for our screenshot
- try (OutputStream out = session.openOutputStream()) {
+ try (OutputStream out = resolver.openOutputStream(uri)) {
if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
throw new IOException("Failed to compress");
}
}
// Next, write metadata to help index the screenshot
- try (ParcelFileDescriptor pfd = session.open()) {
+ try (ParcelFileDescriptor pfd = resolver.openFile(uri, "rw", null)) {
final ExifInterface exif = new ExifInterface(pfd.getFileDescriptor());
exif.setAttribute(ExifInterface.TAG_SOFTWARE,
@@ -327,12 +336,15 @@
exif.saveAttributes();
}
- session.publish();
+
+ // Everything went well above, publish it!
+ values.clear();
+ values.put(MediaColumns.IS_PENDING, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ resolver.update(uri, values, null, null);
} catch (Exception e) {
- session.abandon();
+ resolver.delete(uri, null);
throw e;
- } finally {
- IoUtils.closeQuietly(session);
}
populateNotificationActions(context, r, uri, smartActionsFuture, mNotificationBuilder);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 4657de2..f4c7e23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -810,6 +810,8 @@
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mWallpaperSupported =
+ mContext.getSystemService(WallpaperManager.class).isWallpaperSupported();
// Connect in to the status bar manager service
mCommandQueue.addCallback(this);
@@ -823,9 +825,6 @@
createAndAddWindows(result);
- mWallpaperSupported =
- mContext.getSystemService(WallpaperManager.class).isWallpaperSupported();
-
if (mWallpaperSupported) {
// Make sure we always have the most current wallpaper info.
IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index d7ed2e9..202f900 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -82,6 +82,7 @@
import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.infra.AbstractPerUserSystemService;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -168,6 +169,8 @@
@Nullable
private ServiceInfo mRemoteAugmentedAutofillServiceInfo;
+ private final InputMethodManagerInternal mInputMethodManagerInternal;
+
AutofillManagerServiceImpl(AutofillManagerService master, Object lock,
LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui,
AutofillCompatState autofillCompatState,
@@ -179,6 +182,7 @@
mUi = ui;
mFieldClassificationStrategy = new FieldClassificationStrategy(getContext(), userId);
mAutofillCompatState = autofillCompatState;
+ mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
updateLocked(disabled);
}
@@ -493,7 +497,7 @@
sessionId, taskId, uid, activityToken, appCallbackToken, hasCallback,
mUiLatencyHistory, mWtfHistory, serviceComponentName,
componentName, compatMode, bindInstantServiceAllowed, forAugmentedAutofillOnly,
- flags);
+ flags, mInputMethodManagerInternal);
mSessions.put(newSession.id, newSession);
return newSession;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 3b2da91..67bcccd 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -42,6 +42,8 @@
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.AutofillOverlay;
import android.app.assist.AssistStructure.ViewNode;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -80,6 +82,8 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LocalLog;
+import android.util.Log;
+import android.util.Size;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -90,22 +94,38 @@
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
import android.view.autofill.IAutofillWindowPresenter;
+import android.view.inline.InlinePresentationSpec;
+import android.view.inputmethod.InlineSuggestion;
+import android.view.inputmethod.InlineSuggestionInfo;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
+import com.android.internal.view.inline.IInlineContentCallback;
+import com.android.internal.view.inline.IInlineContentProvider;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.autofill.ui.PendingUi;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -290,6 +310,23 @@
@GuardedBy("mLock")
private boolean mForAugmentedAutofillOnly;
+ @NonNull
+ private final InputMethodManagerInternal mInputMethodManagerInternal;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private CompletableFuture<InlineSuggestionsRequest> mSuggestionsRequestFuture;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private CompletableFuture<IInlineSuggestionsResponseCallback>
+ mInlineSuggestionsResponseCallbackFuture;
+
+ @Nullable
+ private InlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback;
+
+ private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
+
/**
* Receiver of assist data from the app's {@link Activity}.
*/
@@ -386,7 +423,23 @@
final ArrayList<FillContext> contexts =
mergePreviousSessionLocked(/* forSave= */ false);
- request = new FillRequest(requestId, contexts, mClientState, flags);
+
+ InlineSuggestionsRequest suggestionsRequest = null;
+ if (mSuggestionsRequestFuture != null) {
+ try {
+ suggestionsRequest = mSuggestionsRequestFuture.get(
+ INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ Log.w(TAG, "Exception getting inline suggestions request in time: " + e);
+ } catch (CancellationException e) {
+ Log.w(TAG, "Inline suggestions request cancelled");
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ request = new FillRequest(requestId, contexts, mClientState, flags,
+ suggestionsRequest);
}
mRemoteFillService.onFillRequest(request);
@@ -569,6 +622,70 @@
}
/**
+ * Returns whether inline suggestions are enabled for Autofill.
+ */
+ // TODO(b/137800469): Implement this
+ private boolean isInlineSuggestionsEnabled() {
+ return true;
+ }
+
+ /**
+ * Ask the IME to make an inline suggestions request if enabled.
+ */
+ private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState,
+ int newState, int flags) {
+ if (isInlineSuggestionsEnabled()) {
+ mSuggestionsRequestFuture = new CompletableFuture<>();
+ mInlineSuggestionsResponseCallbackFuture = new CompletableFuture<>();
+
+ if (mInlineSuggestionsRequestCallback == null) {
+ mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallback(this);
+ }
+
+ mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
+ mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback);
+ }
+
+ requestNewFillResponseLocked(viewState, newState, flags);
+ }
+
+ private static final class InlineSuggestionsRequestCallback
+ extends IInlineSuggestionsRequestCallback.Stub {
+ private final WeakReference<Session> mSession;
+
+ private InlineSuggestionsRequestCallback(Session session) {
+ mSession = new WeakReference<>(session);
+ }
+
+ @Override
+ public void onInlineSuggestionsUnsupported() throws RemoteException {
+ Log.i(TAG, "inline suggestions request unsupported, "
+ + "falling back to regular autofill");
+ final Session session = mSession.get();
+ if (session != null) {
+ synchronized (session.mLock) {
+ session.mSuggestionsRequestFuture.cancel(true);
+ session.mInlineSuggestionsResponseCallbackFuture.cancel(true);
+ }
+ }
+ }
+
+ @Override
+ public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+ IInlineSuggestionsResponseCallback callback) throws RemoteException {
+ Log.i(TAG, "onInlineSuggestionsRequest() received: "
+ + request);
+ final Session session = mSession.get();
+ if (session != null) {
+ synchronized (session.mLock) {
+ session.mSuggestionsRequestFuture.complete(request);
+ session.mInlineSuggestionsResponseCallbackFuture.complete(callback);
+ }
+ }
+ }
+ }
+
+ /**
* Reads a new structure and then request a new fill response from the fill service.
*/
@GuardedBy("mLock")
@@ -584,6 +701,7 @@
triggerAugmentedAutofillLocked();
return;
}
+
viewState.setState(newState);
int requestId;
@@ -636,7 +754,8 @@
@NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
@NonNull LocalLog wtfHistory, @Nullable ComponentName serviceComponentName,
@NonNull ComponentName componentName, boolean compatMode,
- boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags) {
+ boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags,
+ @NonNull InputMethodManagerInternal inputMethodManagerInternal) {
if (sessionId < 0) {
wtf(null, "Non-positive sessionId: %s", sessionId);
}
@@ -661,6 +780,8 @@
mForAugmentedAutofillOnly = forAugmentedAutofillOnly;
setClientLocked(client);
+ mInputMethodManagerInternal = inputMethodManagerInternal;
+
mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
}
@@ -2208,7 +2329,8 @@
if ((flags & FLAG_MANUAL_REQUEST) != 0) {
mForAugmentedAutofillOnly = false;
if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
- requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags);
+ maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
+ ViewState.STATE_RESTARTED_SESSION, flags);
return;
}
@@ -2218,7 +2340,8 @@
Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
+ viewState.getStateAsString());
}
- requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags);
+ maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
+ ViewState.STATE_STARTED_PARTITION, flags);
} else {
if (sVerbose) {
Slog.v(TAG, "Not starting new partition for view " + id + ": "
@@ -2325,7 +2448,8 @@
// View is triggering autofill.
mCurrentViewId = viewState.id;
viewState.update(value, virtualBounds, flags);
- requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
+ maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
+ ViewState.STATE_STARTED_SESSION, flags);
break;
case ACTION_VALUE_CHANGED:
if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
@@ -2527,6 +2651,16 @@
wtf(null, "onFillReady(): no service label or icon");
return;
}
+
+ final List<Slice> inlineSuggestionSlices = response.getInlineSuggestionSlices();
+ if (inlineSuggestionSlices != null) {
+ if (requestShowInlineSuggestions(inlineSuggestionSlices, response)) {
+ //TODO(b/137800469): Add logging instead of bypassing below logic.
+ return;
+ }
+ }
+
+
getUiForShowing().showFillUi(filledId, response, filterText,
mService.getServicePackageName(), mComponentName,
serviceLabel, serviceIcon, this, id, mCompatMode);
@@ -2558,6 +2692,109 @@
}
}
+ /**
+ * Returns whether we made a request to show inline suggestions.
+ */
+ private boolean requestShowInlineSuggestions(List<Slice> inlineSuggestionSlices,
+ FillResponse response) {
+ IInlineSuggestionsResponseCallback inlineContentCallback = null;
+ synchronized (mLock) {
+ if (mInlineSuggestionsResponseCallbackFuture != null) {
+ try {
+ inlineContentCallback = mInlineSuggestionsResponseCallbackFuture.get(
+ INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
+ } catch (CancellationException e) {
+ Log.w(TAG, "Inline suggestions callback cancelled");
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ if (inlineContentCallback == null) {
+ Log.w(TAG, "Session input method callback is not set yet");
+ return false;
+ }
+
+ final List<Dataset> datasets = response.getDatasets();
+ if (datasets == null) {
+ Log.w(TAG, "response returned null datasets");
+ return false;
+ }
+
+ final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
+ final int slicesSize = inlineSuggestionSlices.size();
+ if (datasets.size() < slicesSize) {
+ Log.w(TAG, "Too many slices provided, not enough corresponding datasets");
+ return false;
+ }
+
+ for (int sliceIndex = 0; sliceIndex < slicesSize; sliceIndex++) {
+ Log.i(TAG, "Reading slice-" + sliceIndex + " at requestshowinlinesuggestions");
+ final Slice inlineSuggestionSlice = inlineSuggestionSlices.get(sliceIndex);
+ final List<SliceItem> sliceItems = inlineSuggestionSlice.getItems();
+
+ final int itemsSize = sliceItems.size();
+ int minWidth = -1;
+ int maxWidth = -1;
+ int minHeight = -1;
+ int maxHeight = -1;
+ for (int itemIndex = 0; itemIndex < itemsSize; itemIndex++) {
+ final SliceItem item = sliceItems.get(itemIndex);
+ final String subtype = item.getSubType();
+ switch (item.getSubType()) {
+ case "SUBTYPE_MIN_WIDTH":
+ minWidth = item.getInt();
+ break;
+ case "SUBTYPE_MAX_WIDTH":
+ maxWidth = item.getInt();
+ break;
+ case "SUBTYPE_MIN_HEIGHT":
+ minHeight = item.getInt();
+ break;
+ case "SUBTYPE_MAX_HEIGHT":
+ maxHeight = item.getInt();
+ break;
+ default:
+ Log.i(TAG, "unrecognized inline suggestions subtype: " + subtype);
+ }
+ }
+
+ if (minWidth < 0 || maxWidth < 0 || minHeight < 0 || maxHeight < 0) {
+ Log.w(TAG, "missing inline suggestion requirements");
+ return false;
+ }
+
+ final InlinePresentationSpec spec = new InlinePresentationSpec.Builder(
+ new Size(minWidth, minHeight), new Size(maxWidth, maxHeight)).build();
+ final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
+ spec, InlineSuggestionInfo.SOURCE_AUTOFILL, new String[] { "" });
+ final Dataset dataset = datasets.get(sliceIndex);
+
+ inlineSuggestions.add(new InlineSuggestion(inlineSuggestionInfo,
+ new IInlineContentProvider.Stub() {
+ @Override
+ public void provideContent(int width, int height,
+ IInlineContentCallback callback) throws RemoteException {
+ getUiForShowing().getSuggestionSurfaceForShowing(dataset, response,
+ mCurrentViewId, width, height, callback);
+ }
+ }));
+ }
+
+ try {
+ inlineContentCallback.onInlineSuggestionsResponse(
+ new InlineSuggestionsResponse(inlineSuggestions));
+ } catch (RemoteException e) {
+ Log.w(TAG, "onFillReady() remote error calling onInlineSuggestionsResponse()");
+ return false;
+ }
+
+ return true;
+ }
+
boolean isDestroyed() {
synchronized (mLock) {
return mDestroyed;
@@ -2831,6 +3068,7 @@
final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId);
+ // TODO(b/137800469): implement inlined suggestions for augmented autofill
remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId,
currentValue);
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 26bb7c3..eadfd31 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -24,6 +24,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.metrics.LogMaker;
import android.os.Bundle;
@@ -37,13 +39,19 @@
import android.text.TextUtils;
import android.util.Slog;
import android.view.KeyEvent;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowlessViewRoot;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
import android.view.autofill.IAutofillWindowPresenter;
+import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.view.inline.IInlineContentCallback;
import com.android.server.LocalServices;
import com.android.server.UiModeManagerInternal;
import com.android.server.UiThread;
@@ -171,6 +179,75 @@
}
/**
+ * TODO(b/137800469): Fill in javadoc.
+ * TODO(b/137800469): peoperly manage lifecycle of suggestions surfaces.
+ */
+ public void getSuggestionSurfaceForShowing(@NonNull Dataset dataset,
+ @NonNull FillResponse response, AutofillId autofillId, int width, int height,
+ IInlineContentCallback cb) {
+ if (dataset == null) {
+ Slog.w(TAG, "getSuggestionSurfaceForShowing() called with null dataset");
+ }
+ mHandler.post(() -> {
+ final SurfaceControl suggestionSurface = inflateInlineSuggestion(dataset, response,
+ autofillId, width, height);
+
+ try {
+ cb.onContent(suggestionSurface);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException replying onContent(" + suggestionSurface + "): " + e);
+ }
+ });
+ }
+
+ /**
+ * TODO(b/137800469): Fill in javadoc, generate custom templated view for inline suggestions.
+ * TODO: Move to ExtServices.
+ *
+ * @return a {@link SurfaceControl} with the inflated content embedded in it.
+ */
+ private SurfaceControl inflateInlineSuggestion(@NonNull Dataset dataset,
+ @NonNull FillResponse response, AutofillId autofillId, int width, int height) {
+ Slog.i(TAG, "inflate() called");
+ final Context context = mContext;
+ final int index = dataset.getFieldIds().indexOf(autofillId);
+ if (index < 0) {
+ Slog.w(TAG, "inflateInlineSuggestion(): AutofillId=" + autofillId
+ + " not found in dataset");
+ }
+
+ final AutofillValue datasetValue = dataset.getFieldValues().get(index);
+ final SurfaceControl sc = new SurfaceControl.Builder()
+ // TODO(b/137800469): sanitize name
+ .setName("af suggestion")
+ .build();
+
+ //TODO(b/137800469): Pass in inputToken from IME.
+ final WindowlessViewRoot wvr = new WindowlessViewRoot(context, context.getDisplay(), sc,
+ null);
+
+ TextView textView = new TextView(context);
+ textView.setText(datasetValue.getTextValue());
+ textView.setBackgroundColor(Color.WHITE);
+ textView.setTextColor(Color.BLACK);
+ textView.setOnClickListener(v -> {
+ Slog.d(TAG, "Inline suggestion clicked");
+ hideFillUiUiThread(mCallback, true);
+ if (mCallback != null) {
+ final int datasetIndex = response.getDatasets().indexOf(dataset);
+ mCallback.fill(response.getRequestId(), datasetIndex, dataset);
+ }
+ });
+
+ WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(width, height,
+ WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
+ wvr.addView(textView, lp);
+
+ return sc;
+ }
+
+ /**
* Shows the fill UI, removing the previous fill UI if the has changed.
*
* @param focusedId the currently focused field
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 9b1d9e9..5a78036 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -118,6 +118,7 @@
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.DataUnit;
import android.util.FeatureFlagUtils;
@@ -3161,16 +3162,20 @@
final boolean forWrite = (flags & StorageManager.FLAG_FOR_WRITE) != 0;
final boolean realState = (flags & StorageManager.FLAG_REAL_STATE) != 0;
final boolean includeInvisible = (flags & StorageManager.FLAG_INCLUDE_INVISIBLE) != 0;
+ final boolean includeRecent = (flags & StorageManager.FLAG_INCLUDE_RECENT) != 0;
// Report all volumes as unmounted until we've recorded that user 0 has unlocked. There
// are no guarantees that callers will see a consistent view of the volume before that
// point
final boolean systemUserUnlocked = isSystemUnlocked(UserHandle.USER_SYSTEM);
+ final boolean userIsDemo;
final boolean userKeyUnlocked;
final boolean storagePermission;
final long token = Binder.clearCallingIdentity();
try {
+ userIsDemo = LocalServices.getService(UserManagerInternal.class)
+ .getUserInfo(userId).isDemo();
userKeyUnlocked = isUserKeyUnlocked(userId);
storagePermission = mStorageManagerInternal.hasExternalStorage(uid, packageName);
} finally {
@@ -3180,6 +3185,7 @@
boolean foundPrimary = false;
final ArrayList<StorageVolume> res = new ArrayList<>();
+ final ArraySet<String> resUuids = new ArraySet<>();
synchronized (mLock) {
for (int i = 0; i < mVolumes.size(); i++) {
final VolumeInfo vol = mVolumes.valueAt(i);
@@ -3222,7 +3228,43 @@
} else {
res.add(userVol);
}
+ resUuids.add(userVol.getUuid());
}
+
+ if (includeRecent) {
+ final long lastWeek = System.currentTimeMillis() - DateUtils.WEEK_IN_MILLIS;
+ for (int i = 0; i < mRecords.size(); i++) {
+ final VolumeRecord rec = mRecords.valueAt(i);
+
+ // Skip if we've already included it above
+ if (resUuids.contains(rec.fsUuid)) continue;
+
+ // Treat as recent if mounted within the last week
+ if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) {
+ final StorageVolume userVol = rec.buildStorageVolume(mContext);
+ res.add(userVol);
+ resUuids.add(userVol.getUuid());
+ }
+ }
+ }
+ }
+
+ // Synthesize a volume for preloaded media under demo users, so that
+ // it's scanned into MediaStore
+ if (userIsDemo) {
+ final String id = "demo";
+ final File path = Environment.getDataPreloadsMediaDirectory();
+ final boolean primary = false;
+ final boolean removable = false;
+ final boolean emulated = true;
+ final boolean allowMassStorage = false;
+ final long maxFileSize = 0;
+ final UserHandle user = new UserHandle(userId);
+ final String envState = Environment.MEDIA_MOUNTED_READ_ONLY;
+ final String description = mContext.getString(android.R.string.unknownName);
+
+ res.add(new StorageVolume(id, path, path, description, primary, removable,
+ emulated, allowMassStorage, maxFileSize, user, id, envState));
}
if (!foundPrimary) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 2de18c3..60f0e8e 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import android.app.ActivityManager;
@@ -1013,8 +1014,13 @@
private boolean isForegroundActivity(int uid, int pid) {
try {
- List<ActivityManager.RunningAppProcessInfo> procs =
+ final List<ActivityManager.RunningAppProcessInfo> procs =
ActivityManager.getService().getRunningAppProcesses();
+ if (procs == null) {
+ Slog.e(getTag(), "Processes null, defaulting to true");
+ return true;
+ }
+
int N = procs.size();
for (int i = 0; i < N; i++) {
ActivityManager.RunningAppProcessInfo proc = procs.get(i);
@@ -1206,6 +1212,11 @@
* @return authenticator id for the calling user
*/
protected long getAuthenticatorId(String opPackageName) {
+ if (isKeyguard(opPackageName)) {
+ // If an app tells us it's keyguard, check that it actually is.
+ checkPermission(USE_BIOMETRIC_INTERNAL);
+ }
+
final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
return mAuthenticatorIds.getOrDefault(userId, 0L);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 944a95d..44c8971 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -18,8 +18,14 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InputMethodInfo;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.server.LocalServices;
import java.util.Collections;
@@ -57,6 +63,17 @@
public abstract List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId);
/**
+ * Called by the Autofill Frameworks to request an {@link InlineSuggestionsRequest} from
+ * the input method.
+ *
+ * @param componentName {@link ComponentName} of current app/activity.
+ * @param autofillId {@link AutofillId} of currently focused field.
+ * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request object.
+ */
+ public abstract void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb);
+
+ /**
* Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing.
*/
private static final InputMethodManagerInternal NOP =
@@ -78,6 +95,17 @@
public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
return Collections.emptyList();
}
+
+ @Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ try {
+ cb.onInlineSuggestionsUnsupported();
+ } catch (RemoteException e) {
+ Log.w("IMManagerInternal", "RemoteException calling"
+ + " onInlineSuggestionsUnsupported: " + e);
+ }
+ }
};
/**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 471fa72..5865dc4 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -109,6 +109,7 @@
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
@@ -140,6 +141,7 @@
import com.android.internal.os.TransferPipe;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodClient;
@@ -211,6 +213,8 @@
static final int MSG_SYSTEM_UNLOCK_USER = 5000;
+ static final int MSG_INLINE_SUGGESTIONS_REQUEST = 6000;
+
static final long TIME_TO_RECONNECT = 3 * 1000;
static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
@@ -1785,6 +1789,16 @@
return settings.getEnabledInputMethodListLocked();
}
+ @GuardedBy("mMethodMap")
+ private void onCreateInlineSuggestionsRequestLocked(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
+ if (mCurMethod != null) {
+ executeOrSendMessage(mCurMethod,
+ mCaller.obtainMessageOOOO(MSG_INLINE_SUGGESTIONS_REQUEST, mCurMethod,
+ componentName, autofillId, callback));
+ }
+ }
+
/**
* @param imiId if null, returns enabled subtypes for the current imi
* @return enabled subtypes of the specified imi
@@ -3874,6 +3888,21 @@
final int userId = msg.arg1;
onUnlockUser(userId);
return true;
+
+ // ---------------------------------------------------------------
+ case MSG_INLINE_SUGGESTIONS_REQUEST:
+ args = (SomeArgs) msg.obj;
+ final ComponentName componentName = (ComponentName) args.arg2;
+ final AutofillId autofillId = (AutofillId) args.arg3;
+ final IInlineSuggestionsRequestCallback callback =
+ (IInlineSuggestionsRequestCallback) args.arg4;
+ try {
+ ((IInputMethod) args.arg1).onCreateInlineSuggestionsRequest(componentName,
+ autofillId, callback);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e);
+ }
+ return true;
}
return false;
}
@@ -4434,6 +4463,13 @@
}
}
+ private void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
+ synchronized (mMethodMap) {
+ onCreateInlineSuggestionsRequestLocked(componentName, autofillId, callback);
+ }
+ }
+
private static final class LocalServiceImpl extends InputMethodManagerInternal {
@NonNull
private final InputMethodManagerService mService;
@@ -4464,6 +4500,12 @@
public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
return mService.getEnabledInputMethodListAsUser(userId);
}
+
+ @Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+ }
}
@BinderThread
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 02e29e0..c13d55a 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -59,10 +59,12 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.InputChannel;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
import android.view.inputmethod.InputMethodInfo;
@@ -82,6 +84,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
@@ -187,6 +190,18 @@
@UserIdInt int userId) {
return userIdToInputMethodInfoMapper.getAsList(userId);
}
+
+ @Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ try {
+ //TODO(b/137800469): support multi client IMEs.
+ cb.onInlineSuggestionsUnsupported();
+ } catch (RemoteException e) {
+ Log.w("MultiClientIMManager", "RemoteException calling"
+ + " onInlineSuggestionsUnsupported: " + e);
+ }
+ }
});
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1153fb5..99d5e4a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -17962,14 +17962,6 @@
}
}
mPermissionManager.resetRuntimePermissions(pkg, nextUserId);
- // Also delete contributed media, when requested
- if ((flags & PackageManager.DELETE_CONTRIBUTED_MEDIA) != 0) {
- try {
- MediaStore.deleteContributedMedia(mContext, ps.name, UserHandle.of(nextUserId));
- } catch (IOException e) {
- Slog.w(TAG, "Failed to delete contributed media for " + ps.name, e);
- }
- }
}
if (outInfo != null) {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 9324870..6cdfcff 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -58,6 +58,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.PackageWatchdog;
+import com.android.server.SystemConfig;
import com.android.server.Watchdog;
import com.android.server.pm.Installer;
@@ -1008,11 +1009,19 @@
installerPackageName) == PackageManager.PERMISSION_GRANTED;
// For now only allow rollbacks for modules or for testing.
- return (isModule(packageName) && manageRollbacksGranted)
+ return (isRollbackWhitelisted(packageName) && manageRollbacksGranted)
|| testManageRollbacksGranted;
}
/**
+ * Returns true is this package is eligible for enabling rollback.
+ */
+ private boolean isRollbackWhitelisted(String packageName) {
+ // TODO: Remove #isModule when the white list is ready.
+ return SystemConfig.getInstance().getRollbackWhitelistedPackages().contains(packageName)
+ || isModule(packageName);
+ }
+ /**
* Returns true if the package name is the name of a module.
*/
private boolean isModule(String packageName) {
diff --git a/services/core/java/com/android/server/utils/TEST_MAPPING b/services/core/java/com/android/server/utils/TEST_MAPPING
new file mode 100644
index 0000000..bb7cea9
--- /dev/null
+++ b/services/core/java/com/android/server/utils/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.utils"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/utils/quota/UptcMap.java b/services/core/java/com/android/server/utils/quota/UptcMap.java
new file mode 100644
index 0000000..7b49913
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/UptcMap.java
@@ -0,0 +1,163 @@
+/*
+ * 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;
+import android.util.ArrayMap;
+import android.util.SparseArrayMap;
+
+import java.util.function.Consumer;
+
+/**
+ * A SparseArrayMap of ArrayMaps, which is suitable for holding userId-packageName-tag combination
+ * (UPTC)->object associations. Tags are any desired String.
+ *
+ * @see Uptc
+ */
+class UptcMap<T> {
+ private final SparseArrayMap<ArrayMap<String, T>> mData = new SparseArrayMap<>();
+
+ public void add(int userId, @NonNull String packageName, @Nullable String tag,
+ @Nullable T obj) {
+ ArrayMap<String, T> data = mData.get(userId, packageName);
+ if (data == null) {
+ data = new ArrayMap<>();
+ mData.add(userId, packageName, data);
+ }
+ data.put(tag, obj);
+ }
+
+ public void clear() {
+ mData.clear();
+ }
+
+ public boolean contains(int userId, @NonNull String packageName) {
+ return mData.contains(userId, packageName);
+ }
+
+ public boolean contains(int userId, @NonNull String packageName, @Nullable String tag) {
+ // This structure never inserts a null ArrayMap, so if get(userId, packageName) returns
+ // null, the UPTC was never inserted.
+ ArrayMap<String, T> data = mData.get(userId, packageName);
+ return data != null && data.containsKey(tag);
+ }
+
+ /** Removes all the data for the user, if there was any. */
+ public void delete(int userId) {
+ mData.delete(userId);
+ }
+
+ /** Removes the data for the user, package, and tag, if there was any. */
+ public void delete(int userId, @NonNull String packageName, @Nullable String tag) {
+ final ArrayMap<String, T> data = mData.get(userId, packageName);
+ if (data != null) {
+ data.remove(tag);
+ if (data.size() == 0) {
+ mData.delete(userId, packageName);
+ }
+ }
+ }
+
+ /** Removes the data for the user and package, if there was any. */
+ public ArrayMap<String, T> delete(int userId, @NonNull String packageName) {
+ return mData.delete(userId, packageName);
+ }
+
+ /**
+ * Returns the set of tag -> object mappings for the given userId and packageName
+ * combination.
+ */
+ @Nullable
+ public ArrayMap<String, T> get(int userId, @NonNull String packageName) {
+ return mData.get(userId, packageName);
+ }
+
+ /** Returns the saved object for the given UPTC. */
+ @Nullable
+ public T get(int userId, @NonNull String packageName, @Nullable String tag) {
+ final ArrayMap<String, T> data = mData.get(userId, packageName);
+ return data != null ? data.get(tag) : null;
+ }
+
+ /**
+ * Returns the index for which {@link #getUserIdAtIndex(int)} would return the specified userId,
+ * or a negative number if the specified userId is not mapped.
+ */
+ public int indexOfUserId(int userId) {
+ return mData.indexOfKey(userId);
+ }
+
+ /**
+ * Returns the index for which {@link #getPackageNameAtIndex(int, int)} would return the
+ * specified userId, or a negative number if the specified userId and packageName are not mapped
+ * together.
+ */
+ public int indexOfUserIdAndPackage(int userId, @NonNull String packageName) {
+ return mData.indexOfKey(userId, packageName);
+ }
+
+ /** Returns the userId at the given index. */
+ public int getUserIdAtIndex(int index) {
+ return mData.keyAt(index);
+ }
+
+ /** Returns the package name at the given index. */
+ @NonNull
+ public String getPackageNameAtIndex(int userIndex, int packageIndex) {
+ return mData.keyAt(userIndex, packageIndex);
+ }
+
+ /** Returns the tag at the given index. */
+ @NonNull
+ public String getTagAtIndex(int userIndex, int packageIndex, int tagIndex) {
+ // This structure never inserts a null ArrayMap, so if the indices are valid, valueAt()
+ // won't return null.
+ return mData.valueAt(userIndex, packageIndex).keyAt(tagIndex);
+ }
+
+ /** Returns the size of the outer (userId) array. */
+ public int userCount() {
+ return mData.numMaps();
+ }
+
+ /** Returns the number of packages saved for a given userId. */
+ public int packageCountForUser(int userId) {
+ return mData.numElementsForKey(userId);
+ }
+
+ /** Returns the number of tags saved for a given userId-packageName combination. */
+ public int tagCountForUserAndPackage(int userId, @NonNull String packageName) {
+ final ArrayMap data = mData.get(userId, packageName);
+ return data != null ? data.size() : 0;
+ }
+
+ /** Returns the value T at the given user, package, and tag indices. */
+ @Nullable
+ public T valueAt(int userIndex, int packageIndex, int tagIndex) {
+ final ArrayMap<String, T> data = mData.valueAt(userIndex, packageIndex);
+ return data != null ? data.valueAt(tagIndex) : null;
+ }
+
+ public void forEach(Consumer<T> consumer) {
+ mData.forEach((tagMap) -> {
+ for (int i = tagMap.size() - 1; i >= 0; --i) {
+ consumer.accept(tagMap.valueAt(i));
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e3ea2c5..4667eab 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4955,7 +4955,11 @@
// Re-parent IME's SurfaceControl when MagnificationSpec changed.
updateImeParent();
- applyMagnificationSpec(getPendingTransaction(), spec);
+ if (spec.scale != 1.0) {
+ applyMagnificationSpec(getPendingTransaction(), spec);
+ } else {
+ clearMagnificationSpec(getPendingTransaction());
+ }
getPendingTransaction().apply();
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d73cb50f..06cea37 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -244,6 +244,8 @@
protected final Rect mTmpRect = new Rect();
final Rect mTmpPrevBounds = new Rect();
+ private MagnificationSpec mLastMagnificationSpec;
+
WindowContainer(WindowManagerService wms) {
mWmService = wms;
mPendingTransaction = wms.mTransactionFactory.get();
@@ -1726,6 +1728,7 @@
if (shouldMagnify()) {
t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale)
.setPosition(mSurfaceControl, spec.offsetX, spec.offsetY);
+ mLastMagnificationSpec = spec;
} else {
for (int i = 0; i < mChildren.size(); i++) {
mChildren.get(i).applyMagnificationSpec(t, spec);
@@ -1733,6 +1736,17 @@
}
}
+ void clearMagnificationSpec(Transaction t) {
+ if (mLastMagnificationSpec != null) {
+ t.setMatrix(mSurfaceControl, 1, 0, 0, 1)
+ .setPosition(mSurfaceControl, 0, 0);
+ }
+ mLastMagnificationSpec = null;
+ for (int i = 0; i < mChildren.size(); i++) {
+ mChildren.get(i).clearMagnificationSpec(t);
+ }
+ }
+
void prepareSurfaces() {
// If a leash has been set when the transaction was committed, then the leash reparent has
// been committed.
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 7d6b0c9..a1e0237 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -105,6 +105,7 @@
import com.android.server.gpu.GpuService;
import com.android.server.hdmi.HdmiControlService;
import com.android.server.incident.IncidentCompanionService;
+import com.android.server.incremental.IncrementalManagerService;
import com.android.server.input.InputManagerService;
import com.android.server.inputmethod.InputMethodManagerService;
import com.android.server.inputmethod.InputMethodSystemProperty;
@@ -324,6 +325,7 @@
private ContentResolver mContentResolver;
private EntropyMixer mEntropyMixer;
private DataLoaderManagerService mDataLoaderManagerService;
+ private IncrementalManagerService mIncrementalManagerService;
private boolean mOnlyCore;
private boolean mFirstBoot;
@@ -706,6 +708,11 @@
DataLoaderManagerService.class);
t.traceEnd();
+ // Incremental service needs to be started before package manager
+ t.traceBegin("StartIncrementalManagerService");
+ mIncrementalManagerService = IncrementalManagerService.start(mSystemContext);
+ t.traceEnd();
+
// Power manager needs to be started early because other services need it.
// Native daemons may be watching for it to be registered so it must be ready
// to handle incoming binder calls immediately (including being able to verify
@@ -2066,6 +2073,12 @@
mPackageManagerService.systemReady();
t.traceEnd();
+ if (mIncrementalManagerService != null) {
+ t.traceBegin("MakeIncrementalManagerServiceReady");
+ mIncrementalManagerService.systemReady();
+ t.traceEnd();
+ }
+
t.traceBegin("MakeDisplayManagerServiceReady");
try {
// TODO: use boot phase and communicate these flags some other way
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 37f3388..cab5286 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -1998,6 +1998,27 @@
}
}
+ /**
+ * Gets the total capacity of SMS storage on RUIM and SIM cards
+ *
+ * @return the total capacity count of SMS on RUIM and SIM cards
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public int getSmsCapacityOnIcc() {
+ int ret = 0;
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ ret = iccISms.getSmsCapacityOnIccForSubscriber(getSubscriptionId());
+ }
+ } catch (RemoteException ex) {
+ //ignore it
+ }
+ return ret;
+ }
+
// see SmsMessage.getStatusOnIcc
/** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index ac4c8ec..9f4d066 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -594,4 +594,12 @@
* @return true for success, false otherwise.
*/
boolean setSmscAddressOnIccEfForSubscriber(String smsc, int subId, String callingPackage);
+
+ /**
+ * Get the capacity count of sms on Icc card.
+ *
+ * @param subId for subId which getSmsCapacityOnIcc is queried.
+ * @return capacity of ICC
+ */
+ int getSmsCapacityOnIccForSubscriber(int subId);
}
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index 9865f76..2430d82 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -211,4 +211,9 @@
String smsc, int subId, String callingPackage) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public int getSmsCapacityOnIccForSubscriber(int subId) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index 0208c3a..9d913b9 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -16,6 +16,7 @@
package android.test.mock;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
@@ -211,6 +212,15 @@
throw new UnsupportedOperationException();
}
+ /**
+ * {@inheritDoc Context#getCrateDir()}
+ * @hide
+ */
+ @Override
+ public File getCrateDir(@NonNull String crateId) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public File getNoBackupFilesDir() {
throw new UnsupportedOperationException();
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index abe6c61..daa85bd 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -1098,4 +1098,28 @@
InstallUtils.dropShellPermissionIdentity();
}
}
+
+ /**
+ * Test we can't enable rollback for non-whitelisted app without
+ * TEST_MANAGE_ROLLBACKS permission
+ */
+ @Test
+ public void testNonRollbackWhitelistedApp() throws Exception {
+ try {
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.MANAGE_ROLLBACKS);
+
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ Thread.sleep(TimeUnit.SECONDS.toMillis(2));
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+ } finally {
+ InstallUtils.dropShellPermissionIdentity();
+ }
+ }
}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 7b289d8..879ac64 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -40,6 +40,7 @@
import com.android.cts.install.lib.Uninstall;
import com.android.cts.rollback.lib.Rollback;
import com.android.cts.rollback.lib.RollbackUtils;
+import com.android.internal.R;
import libcore.io.IoUtils;
@@ -341,6 +342,37 @@
getNetworkStackPackageName())).isNull();
}
+ private static String getModuleMetadataPackageName() {
+ return InstrumentationRegistry.getInstrumentation().getContext()
+ .getResources().getString(R.string.config_defaultModuleMetadataProvider);
+ }
+
+ @Test
+ public void testRollbackWhitelistedApp_Phase1() throws Exception {
+ // Remove available rollbacks
+ String pkgName = getModuleMetadataPackageName();
+ RollbackUtils.getRollbackManager().expireRollbackForPackage(pkgName);
+ assertThat(RollbackUtils.getAvailableRollback(pkgName)).isNull();
+
+ // Overwrite existing permissions. We don't want TEST_MANAGE_ROLLBACKS which allows us
+ // to enable rollback for any app
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.MANAGE_ROLLBACKS);
+
+ // Re-install a whitelisted app with rollbacks enabled
+ String filePath = InstrumentationRegistry.getInstrumentation().getContext()
+ .getPackageManager().getPackageInfo(pkgName, 0).applicationInfo.sourceDir;
+ TestApp app = new TestApp("ModuleMetadata", pkgName, -1, false, new File(filePath));
+ Install.single(app).setStaged().setEnableRollback()
+ .addInstallFlags(PackageManager.INSTALL_REPLACE_EXISTING).commit();
+ }
+
+ @Test
+ public void testRollbackWhitelistedApp_Phase2() throws Exception {
+ assertThat(RollbackUtils.getAvailableRollback(getModuleMetadataPackageName())).isNotNull();
+ }
+
private static void runShellCommand(String cmd) {
ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
.executeShellCommand(cmd);
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index e4a8feb..07d829d 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -175,6 +175,16 @@
runPhase("testPreviouslyAbandonedRollbacks_Phase3");
}
+ /**
+ * Tests we can enable rollback for a whitelisted app.
+ */
+ @Test
+ public void testRollbackWhitelistedApp() throws Exception {
+ runPhase("testRollbackWhitelistedApp_Phase1");
+ getDevice().reboot();
+ runPhase("testRollbackWhitelistedApp_Phase2");
+ }
+
private void crashProcess(String processName, int numberOfCrashes) throws Exception {
String pid = "";
String lastPid = "invalid";
diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp
index 7733761..15c3278 100644
--- a/tools/stats_log_api_gen/Android.bp
+++ b/tools/stats_log_api_gen/Android.bp
@@ -21,6 +21,7 @@
name: "stats-log-api-gen",
srcs: [
"Collation.cpp",
+ "atoms_info_writer.cpp",
"java_writer.cpp",
"java_writer_q.cpp",
"main.cpp",
@@ -102,13 +103,19 @@
cc_library {
name: "libstatslog",
host_supported: true,
- generated_sources: ["statslog.cpp"],
- generated_headers: ["statslog.h"],
+ generated_sources: [
+ "statslog.cpp",
+ ],
+ generated_headers: [
+ "statslog.h"
+ ],
cflags: [
"-Wall",
"-Werror",
],
- export_generated_headers: ["statslog.h"],
+ export_generated_headers: [
+ "statslog.h"
+ ],
shared_libs: [
"liblog",
"libcutils",
@@ -127,3 +134,4 @@
},
},
}
+
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 373adca..fa55601 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -379,6 +379,7 @@
int errorCount = 0;
const bool dbg = false;
+ int maxPushedAtomId = 2;
for (int i = 0; i < descriptor->field_count(); i++) {
const FieldDescriptor *atomField = descriptor->field(i);
@@ -447,8 +448,14 @@
}
atoms->non_chained_decls.insert(nonChainedAtomDecl);
}
+
+ if (atomDecl.code < PULL_ATOM_START_ID && atomDecl.code > maxPushedAtomId) {
+ maxPushedAtomId = atomDecl.code;
+ }
}
+ atoms->maxPushedAtomId = maxPushedAtomId;
+
if (dbg) {
printf("signatures = [\n");
for (map<vector<java_type_t>, set<string>>::const_iterator it =
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 44746c9..3efdd52 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -111,6 +111,7 @@
set<AtomDecl> decls;
set<AtomDecl> non_chained_decls;
map<vector<java_type_t>, set<string>> non_chained_signatures_to_modules;
+ int maxPushedAtomId;
};
/**
@@ -123,4 +124,4 @@
} // namespace android
-#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H
\ No newline at end of file
+#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H
diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp
new file mode 100644
index 0000000..54a9982
--- /dev/null
+++ b/tools/stats_log_api_gen/atoms_info_writer.cpp
@@ -0,0 +1,227 @@
+/*
+ * 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.
+ */
+
+#include "atoms_info_writer.h"
+#include "utils.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+namespace android {
+namespace stats_log_api_gen {
+
+static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) {
+ fprintf(out, "struct StateAtomFieldOptions {\n");
+ fprintf(out, " std::vector<int> primaryFields;\n");
+ fprintf(out, " int exclusiveField;\n");
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out, "struct AtomsInfo {\n");
+ fprintf(out,
+ " const static std::set<int> "
+ "kTruncatingTimestampAtomBlackList;\n");
+ fprintf(out, " const static std::map<int, int> kAtomsWithUidField;\n");
+ fprintf(out,
+ " const static std::set<int> kAtomsWithAttributionChain;\n");
+ fprintf(out,
+ " const static std::map<int, StateAtomFieldOptions> "
+ "kStateAtomsFieldOptions;\n");
+ fprintf(out,
+ " const static std::map<int, std::vector<int>> "
+ "kBytesFieldAtoms;\n");
+ fprintf(out,
+ " const static std::set<int> kWhitelistedAtoms;\n");
+ fprintf(out, "};\n");
+ fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n", atoms.maxPushedAtomId);
+
+}
+
+static void write_atoms_info_cpp_body(FILE* out, const Atoms& atoms) {
+ std::set<string> kTruncatingAtomNames = {"mobile_radio_power_state_changed",
+ "audio_state_changed",
+ "call_state_changed",
+ "phone_signal_strength_changed",
+ "mobile_bytes_transfer_by_fg_bg",
+ "mobile_bytes_transfer"};
+ fprintf(out,
+ "const std::set<int> "
+ "AtomsInfo::kTruncatingTimestampAtomBlackList = {\n");
+ for (set<string>::const_iterator blacklistedAtom = kTruncatingAtomNames.begin();
+ blacklistedAtom != kTruncatingAtomNames.end(); blacklistedAtom++) {
+ fprintf(out, " %s,\n", make_constant_name(*blacklistedAtom).c_str());
+ }
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out,
+ "const std::set<int> AtomsInfo::kAtomsWithAttributionChain = {\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ for (vector<AtomField>::const_iterator field = atom->fields.begin();
+ field != atom->fields.end(); field++) {
+ if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ string constant = make_constant_name(atom->name);
+ fprintf(out, " %s,\n", constant.c_str());
+ break;
+ }
+ }
+ }
+
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out,
+ "const std::set<int> AtomsInfo::kWhitelistedAtoms = {\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->whitelisted) {
+ string constant = make_constant_name(atom->name);
+ fprintf(out, " %s,\n", constant.c_str());
+ }
+ }
+
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out, "static std::map<int, int> getAtomUidField() {\n");
+ fprintf(out, " std::map<int, int> uidField;\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->uidField == 0) {
+ continue;
+ }
+ fprintf(out,
+ "\n // Adding uid field for atom "
+ "(%d)%s\n",
+ atom->code, atom->name.c_str());
+ fprintf(out, " uidField[static_cast<int>(%s)] = %d;\n",
+ make_constant_name(atom->name).c_str(), atom->uidField);
+ }
+
+ fprintf(out, " return uidField;\n");
+ fprintf(out, "};\n");
+
+ fprintf(out,
+ "const std::map<int, int> AtomsInfo::kAtomsWithUidField = "
+ "getAtomUidField();\n");
+
+ fprintf(out,
+ "static std::map<int, StateAtomFieldOptions> "
+ "getStateAtomFieldOptions() {\n");
+ fprintf(out, " std::map<int, StateAtomFieldOptions> options;\n");
+ fprintf(out, " StateAtomFieldOptions opt;\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->primaryFields.size() == 0 && atom->exclusiveField == 0) {
+ continue;
+ }
+ fprintf(out,
+ "\n // Adding primary and exclusive fields for atom "
+ "(%d)%s\n",
+ atom->code, atom->name.c_str());
+ fprintf(out, " opt.primaryFields.clear();\n");
+ for (const auto& field : atom->primaryFields) {
+ fprintf(out, " opt.primaryFields.push_back(%d);\n", field);
+ }
+
+ fprintf(out, " opt.exclusiveField = %d;\n", atom->exclusiveField);
+ fprintf(out, " options[static_cast<int>(%s)] = opt;\n",
+ make_constant_name(atom->name).c_str());
+ }
+
+ fprintf(out, " return options;\n");
+ fprintf(out, "}\n");
+
+ fprintf(out,
+ "const std::map<int, StateAtomFieldOptions> "
+ "AtomsInfo::kStateAtomsFieldOptions = "
+ "getStateAtomFieldOptions();\n");
+
+ fprintf(out,
+ "static std::map<int, std::vector<int>> "
+ "getBinaryFieldAtoms() {\n");
+ fprintf(out, " std::map<int, std::vector<int>> options;\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->binaryFields.size() == 0) {
+ continue;
+ }
+ fprintf(out,
+ "\n // Adding binary fields for atom "
+ "(%d)%s\n",
+ atom->code, atom->name.c_str());
+
+ for (const auto& field : atom->binaryFields) {
+ fprintf(out, " options[static_cast<int>(%s)].push_back(%d);\n",
+ make_constant_name(atom->name).c_str(), field);
+ }
+ }
+
+ fprintf(out, " return options;\n");
+ fprintf(out, "}\n");
+
+ fprintf(out,
+ "const std::map<int, std::vector<int>> "
+ "AtomsInfo::kBytesFieldAtoms = "
+ "getBinaryFieldAtoms();\n");
+
+}
+
+int write_atoms_info_header(FILE* out, const Atoms &atoms, const string& namespaceStr) {
+ // Print prelude
+ fprintf(out, "// This file is autogenerated\n");
+ fprintf(out, "\n");
+ fprintf(out, "#pragma once\n");
+ fprintf(out, "\n");
+ fprintf(out, "#include <vector>\n");
+ fprintf(out, "#include <map>\n");
+ fprintf(out, "#include <set>\n");
+ fprintf(out, "\n");
+
+ write_namespace(out, namespaceStr);
+
+ write_atoms_info_header_body(out, atoms);
+
+ fprintf(out, "\n");
+ write_closing_namespace(out, namespaceStr);
+
+ return 0;
+}
+
+int write_atoms_info_cpp(FILE *out, const Atoms &atoms, const string& namespaceStr,
+ const string& importHeader, const string& statslogHeader) {
+ // Print prelude
+ fprintf(out, "// This file is autogenerated\n");
+ fprintf(out, "\n");
+ fprintf(out, "#include <%s>\n", importHeader.c_str());
+ fprintf(out, "#include <%s>\n", statslogHeader.c_str());
+ fprintf(out, "\n");
+
+ write_namespace(out, namespaceStr);
+
+ write_atoms_info_cpp_body(out, atoms);
+
+ // Print footer
+ fprintf(out, "\n");
+ write_closing_namespace(out, namespaceStr);
+
+ return 0;
+}
+
+} // namespace stats_log_api_gen
+} // namespace android
diff --git a/tools/stats_log_api_gen/atoms_info_writer.h b/tools/stats_log_api_gen/atoms_info_writer.h
new file mode 100644
index 0000000..bc67782
--- /dev/null
+++ b/tools/stats_log_api_gen/atoms_info_writer.h
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Collation.h"
+
+#include <stdio.h>
+#include <string.h>
+
+namespace android {
+namespace stats_log_api_gen {
+
+using namespace std;
+
+int write_atoms_info_cpp(FILE* out, const Atoms& atoms, const string& namespaceStr,
+ const string& importHeader, const string& statslogHeader
+);
+
+int write_atoms_info_header(FILE* out, const Atoms& atoms, const string& namespaceStr);
+
+} // namespace stats_log_api_gen
+} // namespace android
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index bc6d82a..ad171da 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -1,6 +1,7 @@
#include "Collation.h"
+#include "atoms_info_writer.h"
#if !defined(STATS_SCHEMA_LEGACY)
#include "java_writer.h"
#endif
@@ -18,8 +19,6 @@
#include <stdlib.h>
#include <string.h>
-#include "android-base/strings.h"
-
using namespace google::protobuf;
using namespace std;
@@ -28,152 +27,6 @@
using android::os::statsd::Atom;
-static void write_atoms_info_cpp(FILE *out, const Atoms &atoms) {
- std::set<string> kTruncatingAtomNames = {"mobile_radio_power_state_changed",
- "audio_state_changed",
- "call_state_changed",
- "phone_signal_strength_changed",
- "mobile_bytes_transfer_by_fg_bg",
- "mobile_bytes_transfer"};
- fprintf(out,
- "const std::set<int> "
- "AtomsInfo::kTruncatingTimestampAtomBlackList = {\n");
- for (set<string>::const_iterator blacklistedAtom = kTruncatingAtomNames.begin();
- blacklistedAtom != kTruncatingAtomNames.end(); blacklistedAtom++) {
- fprintf(out, " %s,\n", make_constant_name(*blacklistedAtom).c_str());
- }
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- fprintf(out,
- "const std::set<int> AtomsInfo::kAtomsWithAttributionChain = {\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- for (vector<AtomField>::const_iterator field = atom->fields.begin();
- field != atom->fields.end(); field++) {
- if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- string constant = make_constant_name(atom->name);
- fprintf(out, " %s,\n", constant.c_str());
- break;
- }
- }
- }
-
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- fprintf(out,
- "const std::set<int> AtomsInfo::kWhitelistedAtoms = {\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->whitelisted) {
- string constant = make_constant_name(atom->name);
- fprintf(out, " %s,\n", constant.c_str());
- }
- }
-
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- fprintf(out, "static std::map<int, int> getAtomUidField() {\n");
- fprintf(out, " std::map<int, int> uidField;\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->uidField == 0) {
- continue;
- }
- fprintf(out,
- "\n // Adding uid field for atom "
- "(%d)%s\n",
- atom->code, atom->name.c_str());
- fprintf(out, " uidField[static_cast<int>(%s)] = %d;\n",
- make_constant_name(atom->name).c_str(), atom->uidField);
- }
-
- fprintf(out, " return uidField;\n");
- fprintf(out, "};\n");
-
- fprintf(out,
- "const std::map<int, int> AtomsInfo::kAtomsWithUidField = "
- "getAtomUidField();\n");
-
- fprintf(out,
- "static std::map<int, StateAtomFieldOptions> "
- "getStateAtomFieldOptions() {\n");
- fprintf(out, " std::map<int, StateAtomFieldOptions> options;\n");
- fprintf(out, " StateAtomFieldOptions opt;\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->primaryFields.size() == 0 && atom->exclusiveField == 0) {
- continue;
- }
- fprintf(out,
- "\n // Adding primary and exclusive fields for atom "
- "(%d)%s\n",
- atom->code, atom->name.c_str());
- fprintf(out, " opt.primaryFields.clear();\n");
- for (const auto& field : atom->primaryFields) {
- fprintf(out, " opt.primaryFields.push_back(%d);\n", field);
- }
-
- fprintf(out, " opt.exclusiveField = %d;\n", atom->exclusiveField);
- fprintf(out, " options[static_cast<int>(%s)] = opt;\n",
- make_constant_name(atom->name).c_str());
- }
-
- fprintf(out, " return options;\n");
- fprintf(out, "}\n");
-
- fprintf(out,
- "const std::map<int, StateAtomFieldOptions> "
- "AtomsInfo::kStateAtomsFieldOptions = "
- "getStateAtomFieldOptions();\n");
-
- fprintf(out,
- "static std::map<int, std::vector<int>> "
- "getBinaryFieldAtoms() {\n");
- fprintf(out, " std::map<int, std::vector<int>> options;\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->binaryFields.size() == 0) {
- continue;
- }
- fprintf(out,
- "\n // Adding binary fields for atom "
- "(%d)%s\n",
- atom->code, atom->name.c_str());
-
- for (const auto& field : atom->binaryFields) {
- fprintf(out, " options[static_cast<int>(%s)].push_back(%d);\n",
- make_constant_name(atom->name).c_str(), field);
- }
- }
-
- fprintf(out, " return options;\n");
- fprintf(out, "}\n");
-
- fprintf(out,
- "const std::map<int, std::vector<int>> "
- "AtomsInfo::kBytesFieldAtoms = "
- "getBinaryFieldAtoms();\n");
-}
-
-// Writes namespaces for the cpp and header files, returning the number of namespaces written.
-void write_namespace(FILE* out, const string& cppNamespaces) {
- vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
- for (string cppNamespace : cppNamespaceVec) {
- fprintf(out, "namespace %s {\n", cppNamespace.c_str());
- }
-}
-
-// Writes namespace closing brackets for cpp and header files.
-void write_closing_namespace(FILE* out, const string& cppNamespaces) {
- vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
- for (auto it = cppNamespaceVec.rbegin(); it != cppNamespaceVec.rend(); ++it) {
- fprintf(out, "} // namespace %s\n", it->c_str());
- }
-}
-
static int write_stats_log_cpp(FILE *out, const Atoms &atoms, const AtomDecl &attributionDecl,
const string& moduleName, const string& cppNamespace,
const string& importHeader) {
@@ -202,11 +55,6 @@
fprintf(out, "const static bool kStatsdEnabled = false;\n");
fprintf(out, "#endif\n");
- // AtomsInfo is only used by statsd internally and is not needed for other modules.
- if (moduleName == DEFAULT_MODULE_NAME) {
- write_atoms_info_cpp(out, atoms);
- }
-
fprintf(out, "int64_t lastRetryTimestampNs = -1;\n");
fprintf(out, "const int64_t kMinRetryIntervalNs = NS_PER_SEC * 60 * 20; // 20 minutes\n");
fprintf(out, "static std::mutex mLogdRetryMutex;\n");
@@ -543,42 +391,6 @@
return 0;
}
-static void write_cpp_usage(
- FILE* out, const string& method_name, const string& atom_code_name,
- const AtomDecl& atom, const AtomDecl &attributionDecl) {
- fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(),
- atom_code_name.c_str());
-
- for (vector<AtomField>::const_iterator field = atom.fields.begin();
- field != atom.fields.end(); field++) {
- if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- for (auto chainField : attributionDecl.fields) {
- if (chainField.javaType == JAVA_TYPE_STRING) {
- fprintf(out, ", const std::vector<%s>& %s",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str());
- } else {
- fprintf(out, ", const %s* %s, size_t %s_length",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str(), chainField.name.c_str());
- }
- }
- } else if (field->javaType == JAVA_TYPE_KEY_VALUE_PAIR) {
- fprintf(out, ", const std::map<int, int32_t>& %s_int"
- ", const std::map<int, int64_t>& %s_long"
- ", const std::map<int, char const*>& %s_str"
- ", const std::map<int, float>& %s_float",
- field->name.c_str(),
- field->name.c_str(),
- field->name.c_str(),
- field->name.c_str());
- } else {
- fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
- }
- }
- fprintf(out, ");\n");
-}
-
static void write_cpp_method_header(
FILE* out,
const string& method_name,
@@ -645,45 +457,8 @@
fprintf(out, " * API For logging statistics events.\n");
fprintf(out, " */\n");
fprintf(out, "\n");
- fprintf(out, "/**\n");
- fprintf(out, " * Constants for atom codes.\n");
- fprintf(out, " */\n");
- fprintf(out, "enum {\n");
- std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
- build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
-
- size_t i = 0;
- int maxPushedAtomId = 2;
- // Print atom constants
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- // Skip if the atom is not needed for the module.
- if (!atom_needed_for_module(*atom, moduleName)) {
- continue;
- }
- string constant = make_constant_name(atom->name);
- fprintf(out, "\n");
- fprintf(out, " /**\n");
- fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str());
- write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl);
-
- auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
- if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
- write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second,
- attributionDecl);
- }
- fprintf(out, " */\n");
- char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
- fprintf(out, " %s = %d%s\n", constant.c_str(), atom->code, comma);
- if (atom->code < PULL_ATOM_START_ID && atom->code > maxPushedAtomId) {
- maxPushedAtomId = atom->code;
- }
- i++;
- }
- fprintf(out, "\n");
- fprintf(out, "};\n");
- fprintf(out, "\n");
+ write_native_atom_constants(out, atoms, attributionDecl, moduleName);
// Print constants for the enum values.
fprintf(out, "//\n");
@@ -723,36 +498,6 @@
fprintf(out, "};\n");
fprintf(out, "\n");
- // This metadata is only used by statsd, which uses the default libstatslog.
- if (moduleName == DEFAULT_MODULE_NAME) {
-
- fprintf(out, "struct StateAtomFieldOptions {\n");
- fprintf(out, " std::vector<int> primaryFields;\n");
- fprintf(out, " int exclusiveField;\n");
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- fprintf(out, "struct AtomsInfo {\n");
- fprintf(out,
- " const static std::set<int> "
- "kTruncatingTimestampAtomBlackList;\n");
- fprintf(out, " const static std::map<int, int> kAtomsWithUidField;\n");
- fprintf(out,
- " const static std::set<int> kAtomsWithAttributionChain;\n");
- fprintf(out,
- " const static std::map<int, StateAtomFieldOptions> "
- "kStateAtomsFieldOptions;\n");
- fprintf(out,
- " const static std::map<int, std::vector<int>> "
- "kBytesFieldAtoms;");
- fprintf(out,
- " const static std::set<int> kWhitelistedAtoms;\n");
- fprintf(out, "};\n");
-
- fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n",
- maxPushedAtomId);
- }
-
// Print write methods
fprintf(out, "//\n");
fprintf(out, "// Write methods\n");
@@ -1235,15 +980,21 @@
fprintf(stderr, "usage: stats-log-api-gen OPTIONS\n");
fprintf(stderr, "\n");
fprintf(stderr, "OPTIONS\n");
- fprintf(stderr, " --cpp FILENAME the header file to output\n");
- fprintf(stderr, " --header FILENAME the cpp file to output\n");
+ fprintf(stderr, " --cpp FILENAME the header file to output for write helpers\n");
+ fprintf(stderr, " --header FILENAME the cpp file to output for write helpers\n");
+ fprintf(stderr,
+ " --atomsInfoCpp FILENAME the header file to output for statsd metadata\n");
+ fprintf(stderr, " --atomsInfoHeader FILENAME the cpp file to output for statsd metadata\n");
fprintf(stderr, " --help this message\n");
fprintf(stderr, " --java FILENAME the java file to output\n");
fprintf(stderr, " --jni FILENAME the jni file to output\n");
fprintf(stderr, " --module NAME optional, module name to generate outputs for\n");
fprintf(stderr, " --namespace COMMA,SEP,NAMESPACE required for cpp/header with module\n");
fprintf(stderr, " comma separated namespace of the files\n");
- fprintf(stderr, " --importHeader NAME required for cpp/jni to say which header to import\n");
+ fprintf(stderr," --importHeader NAME required for cpp/jni to say which header to import "
+ "for write helpers\n");
+ fprintf(stderr," --atomsInfoImportHeader NAME required for cpp to say which header to import "
+ "for statsd metadata\n");
fprintf(stderr, " --javaPackage PACKAGE the package for the java file.\n");
fprintf(stderr, " required for java with module\n");
fprintf(stderr, " --javaClass CLASS the class name of the java class.\n");
@@ -1260,10 +1011,13 @@
string headerFilename;
string javaFilename;
string jniFilename;
+ string atomsInfoCppFilename;
+ string atomsInfoHeaderFilename;
string moduleName = DEFAULT_MODULE_NAME;
string cppNamespace = DEFAULT_CPP_NAMESPACE;
string cppHeaderImport = DEFAULT_CPP_HEADER_IMPORT;
+ string atomsInfoCppHeaderImport = DEFAULT_ATOMS_INFO_CPP_HEADER_IMPORT;
string javaPackage = DEFAULT_JAVA_PACKAGE;
string javaClass = DEFAULT_JAVA_CLASS;
@@ -1335,14 +1089,38 @@
return 1;
}
javaClass = argv[index];
+ } else if (0 == strcmp("--atomsInfoHeader", argv[index])) {
+ index++;
+ if (index >= argc) {
+ print_usage();
+ return 1;
+ }
+ atomsInfoHeaderFilename = argv[index];
+ } else if (0 == strcmp("--atomsInfoCpp", argv[index])) {
+ index++;
+ if (index >= argc) {
+ print_usage();
+ return 1;
+ }
+ atomsInfoCppFilename = argv[index];
+ } else if (0 == strcmp("--atomsInfoImportHeader", argv[index])) {
+ index++;
+ if (index >= argc) {
+ print_usage();
+ return 1;
+ }
+ atomsInfoCppHeaderImport = argv[index];
}
+
index++;
}
if (cppFilename.size() == 0
&& headerFilename.size() == 0
&& javaFilename.size() == 0
- && jniFilename.size() == 0) {
+ && jniFilename.size() == 0
+ && atomsInfoHeaderFilename.size() == 0
+ && atomsInfoCppFilename.size() == 0) {
print_usage();
return 1;
}
@@ -1359,6 +1137,30 @@
collate_atom(android::os::statsd::AttributionNode::descriptor(),
&attributionDecl, &attributionSignature);
+ // Write the atoms info .cpp file
+ if (atomsInfoCppFilename.size() != 0) {
+ FILE* out = fopen(atomsInfoCppFilename.c_str(), "w");
+ if (out == NULL) {
+ fprintf(stderr, "Unable to open file for write: %s\n", atomsInfoCppFilename.c_str());
+ return 1;
+ }
+ errorCount = android::stats_log_api_gen::write_atoms_info_cpp(
+ out, atoms, cppNamespace, atomsInfoCppHeaderImport, cppHeaderImport);
+ fclose(out);
+ }
+
+ // Write the atoms info .h file
+ if (atomsInfoHeaderFilename.size() != 0) {
+ FILE* out = fopen(atomsInfoHeaderFilename.c_str(), "w");
+ if (out == NULL) {
+ fprintf(stderr, "Unable to open file for write: %s\n", atomsInfoHeaderFilename.c_str());
+ return 1;
+ }
+ errorCount = android::stats_log_api_gen::write_atoms_info_header(out, atoms, cppNamespace);
+ fclose(out);
+ }
+
+
// Write the .cpp file
if (cppFilename.size() != 0) {
FILE* out = fopen(cppFilename.c_str(), "w");
diff --git a/tools/stats_log_api_gen/utils.cpp b/tools/stats_log_api_gen/utils.cpp
index 141861d..d6cfe95 100644
--- a/tools/stats_log_api_gen/utils.cpp
+++ b/tools/stats_log_api_gen/utils.cpp
@@ -16,9 +16,19 @@
#include "utils.h"
+#include "android-base/strings.h"
+
namespace android {
namespace stats_log_api_gen {
+static void build_non_chained_decl_map(const Atoms& atoms,
+ std::map<int, set<AtomDecl>::const_iterator>* decl_map) {
+ for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin();
+ atom != atoms.non_chained_decls.end(); atom++) {
+ decl_map->insert(std::make_pair(atom->code, atom));
+ }
+}
+
/**
* Turn lower and camel case into upper case with underscores.
*/
@@ -102,14 +112,98 @@
return modules.find(moduleName) != modules.end();
}
-void build_non_chained_decl_map(const Atoms& atoms,
- std::map<int, set<AtomDecl>::const_iterator>* decl_map) {
- for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin();
- atom != atoms.non_chained_decls.end(); atom++) {
- decl_map->insert(std::make_pair(atom->code, atom));
+// Native
+// Writes namespaces for the cpp and header files, returning the number of namespaces written.
+void write_namespace(FILE* out, const string& cppNamespaces) {
+ vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
+ for (string cppNamespace : cppNamespaceVec) {
+ fprintf(out, "namespace %s {\n", cppNamespace.c_str());
}
}
+// Writes namespace closing brackets for cpp and header files.
+void write_closing_namespace(FILE* out, const string& cppNamespaces) {
+ vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
+ for (auto it = cppNamespaceVec.rbegin(); it != cppNamespaceVec.rend(); ++it) {
+ fprintf(out, "} // namespace %s\n", it->c_str());
+ }
+}
+
+static void write_cpp_usage(
+ FILE* out, const string& method_name, const string& atom_code_name,
+ const AtomDecl& atom, const AtomDecl &attributionDecl) {
+ fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(),
+ atom_code_name.c_str());
+
+ for (vector<AtomField>::const_iterator field = atom.fields.begin();
+ field != atom.fields.end(); field++) {
+ if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ for (auto chainField : attributionDecl.fields) {
+ if (chainField.javaType == JAVA_TYPE_STRING) {
+ fprintf(out, ", const std::vector<%s>& %s",
+ cpp_type_name(chainField.javaType),
+ chainField.name.c_str());
+ } else {
+ fprintf(out, ", const %s* %s, size_t %s_length",
+ cpp_type_name(chainField.javaType),
+ chainField.name.c_str(), chainField.name.c_str());
+ }
+ }
+ } else if (field->javaType == JAVA_TYPE_KEY_VALUE_PAIR) {
+ fprintf(out, ", const std::map<int, int32_t>& %s_int"
+ ", const std::map<int, int64_t>& %s_long"
+ ", const std::map<int, char const*>& %s_str"
+ ", const std::map<int, float>& %s_float",
+ field->name.c_str(),
+ field->name.c_str(),
+ field->name.c_str(),
+ field->name.c_str());
+ } else {
+ fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
+ }
+ }
+ fprintf(out, ");\n");
+}
+
+void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+ const string& moduleName) {
+ fprintf(out, "/**\n");
+ fprintf(out, " * Constants for atom codes.\n");
+ fprintf(out, " */\n");
+ fprintf(out, "enum {\n");
+
+ std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
+ build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
+
+ size_t i = 0;
+ // Print atom constants
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ // Skip if the atom is not needed for the module.
+ if (!atom_needed_for_module(*atom, moduleName)) {
+ continue;
+ }
+ string constant = make_constant_name(atom->name);
+ fprintf(out, "\n");
+ fprintf(out, " /**\n");
+ fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str());
+ write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl);
+
+ auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
+ if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
+ write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second,
+ attributionDecl);
+ }
+ fprintf(out, " */\n");
+ char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
+ fprintf(out, " %s = %d%s\n", constant.c_str(), atom->code, comma);
+ i++;
+ }
+ fprintf(out, "\n");
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+}
+
// Java
void write_java_atom_codes(FILE* out, const Atoms& atoms, const string& moduleName) {
fprintf(out, " // Constants for atom codes.\n");
diff --git a/tools/stats_log_api_gen/utils.h b/tools/stats_log_api_gen/utils.h
index e860fa9..a89387f 100644
--- a/tools/stats_log_api_gen/utils.h
+++ b/tools/stats_log_api_gen/utils.h
@@ -33,6 +33,7 @@
const string DEFAULT_MODULE_NAME = "DEFAULT";
const string DEFAULT_CPP_NAMESPACE = "android,util";
const string DEFAULT_CPP_HEADER_IMPORT = "statslog.h";
+const string DEFAULT_ATOMS_INFO_CPP_HEADER_IMPORT = "atoms_info.h";
const string DEFAULT_JAVA_PACKAGE = "android.util";
const string DEFAULT_JAVA_CLASS = "StatsLogInternal";
@@ -49,8 +50,14 @@
bool signature_needed_for_module(const set<string>& modules, const string& moduleName);
-void build_non_chained_decl_map(const Atoms& atoms,
- std::map<int, set<AtomDecl>::const_iterator>* decl_map);
+// Common Native helpers
+void write_namespace(FILE* out, const string& cppNamespaces);
+
+void write_closing_namespace(FILE* out, const string& cppNamespaces);
+
+void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+ const string& moduleName
+);
// Common Java helpers.
void write_java_atom_codes(FILE* out, const Atoms& atoms, const string& moduleName);