diff options
96 files changed, 2888 insertions, 1010 deletions
diff --git a/Android.bp b/Android.bp index 21552695d7bd..15188ece0a6d 100644 --- a/Android.bp +++ b/Android.bp @@ -1660,6 +1660,8 @@ filegroup { "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 6ddd847435f4..52dd1a16cc93 100644 --- a/api/current.txt +++ b/api/current.txt @@ -22653,6 +22653,7 @@ package android.inputmethodservice { 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 @@ package android.inputmethodservice { 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 @@ package android.os.storage { 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 @@ package android.os.storage { 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 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.service.autofill { 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 @@ package android.service.autofill { 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 @@ package android.view { 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 @@ package android.view { 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 4e8e325bce15..8b30d0a5cf39 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -436,10 +436,8 @@ package android.provider { } 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 @@ package android.provider { 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 8d55a33faedd..11089277a067 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6365,6 +6365,7 @@ package android.os { } 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 @@ package android.provider { } 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 @@ package android.telephony { 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 1c6bce05b8f8..219258ef50b0 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -697,6 +697,7 @@ package android.content { 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 @@ package android.provider { } 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 484f82330055..afff61497157 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -146,6 +146,7 @@ cc_defaults { "libprotoutil", "libservices", "libstatslog", + "libstatsmetadata", "libstatssocket", "libsysutils", "libtimestats_proto", @@ -153,6 +154,63 @@ cc_defaults { ], } +// ================ +// 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 84a06070e431..a545fc5718d0 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 98d41c22d540..34818145a922 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 b142caca3acc..dc69b78f0329 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 53fa6301a836..031c43740d9d 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 23d2aceb4fc3..564b9ee8051c 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 464cec36d5e3..088f607ecfce 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 9131802c83e7..2ad8217c45d4 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 e65325a52b98..7453370f25fd 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 0a86363a7090..f3e94331a23e 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 e91fb0d4b27c..6abdfa3dbb24 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 5c9636fa99cc..5b7a30d4a5aa 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 266ea351b5d4..673082834e18 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 46f88d5c81e4..155e93f9be19 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -99,6 +99,7 @@ import java.io.InputStream; 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 @@ class ContextImpl extends Context { @GuardedBy("mSync") private File mFilesDir; @GuardedBy("mSync") + private File mCratesDir; + @GuardedBy("mSync") private File mNoBackupFilesDir; @GuardedBy("mSync") private File mCacheDir; @@ -702,6 +705,24 @@ class ContextImpl extends Context { } @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 80c9ba2a9c48..eb50581f3319 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -1314,8 +1314,8 @@ public class DownloadManager { // 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 a1305da0a502..ce21db335615 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -152,6 +152,8 @@ import android.os.Vibrator; 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 @@ public final class SystemServiceRegistry { 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 d0844491da53..24b5061bf27b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1060,6 +1060,31 @@ public abstract class Context { 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 d6442e28439f..d1b5135e959d 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -252,6 +252,16 @@ public class ContextWrapper extends Context { 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 94af5416aa8d..0d1f4046e70e 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1513,16 +1513,6 @@ public abstract class PackageManager { 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 48d88678f721..39c1daca1bfd 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -823,6 +823,7 @@ public class SQLiteQueryBuilder { 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 a47f601033cf..62f0196693ea 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -19,6 +19,7 @@ package android.inputmethodservice; 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.RemoteException; 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 android.view.inputmethod.InputMethodSubtype; 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 @@ class IInputMethodWrapper extends IInputMethod.Stub 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 @@ class IInputMethodWrapper extends IInputMethod.Stub 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 @@ class IInputMethodWrapper extends IInputMethod.Stub @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 156bcfe147f7..7da7dc120dcb 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -23,6 +23,8 @@ import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; 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.Nullable; 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.Build; 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.Window; 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.IInputContentUriToken; 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 @@ public class InputMethodService extends AbstractInputMethodService { 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 @@ public class InputMethodService extends AbstractInputMethodService { /** * {@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 @@ public class InputMethodService extends AbstractInputMethodService { } } + // 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 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * 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 a92237b9f17f..61da5e67e57b 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -34,6 +34,8 @@ import android.text.TextUtils; import android.util.Log; import java.io.File; +import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedList; /** @@ -528,6 +530,22 @@ public class Environment { } /** + * 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 62603fee1137..2e9f27e74544 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -264,6 +264,8 @@ public class StorageManager { 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 @@ public class StorageManager { * 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 @@ public class StorageManager { 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 @@ public class StorageManager { } /** - * 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 @@ public class StorageManager { } /** + * 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 aefe8430f9de..560d6171d5ee 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -29,6 +29,7 @@ import android.os.Parcel; 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 @@ public final class StorageVolume implements Parcelable { * @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 @@ public final class StorageVolume implements Parcelable { } /** {@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 @@ public final class StorageVolume implements Parcelable { 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 1a794ebf2a59..99b45d60c319 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 @@ public class VolumeRecord implements Parcelable { 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 00c9e72df880..b216e2b7ed8d 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 701ba91a8daf..63204d36f396 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -21,17 +21,15 @@ import android.annotation.CurrentTimeMillisLong; 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.database.Cursor; 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 @@ public final class MediaStore { /** {@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 @@ public final class MediaStore { /** {@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 @@ public final class MediaStore { * 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 @@ public final class MediaStore { } /** - * 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 @@ public final class MediaStore { 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 final class MediaStore { 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 @@ public final class MediaStore { } } - /** {@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 @@ public final class MediaStore { /** * Currently outstanding thumbnail requests that can be cancelled. */ - @GuardedBy("sPending") + // @GuardedBy("sPending") private static ArrayMap<Uri, CancellationSignal> sPending = new ArrayMap<>(); /** @@ -1997,16 +1767,7 @@ public final class MediaStore { @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 @@ public final class MediaStore { } 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 @@ public final class MediaStore { @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 @@ public final class MediaStore { * 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 final class MediaStore { 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 final class MediaStore { 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 final class MediaStore { */ 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 @@ public final class MediaStore { } /** - * 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 @@ public final class MediaStore { 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 @@ public final class MediaStore { 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 e73a74f69dee..f4e2329797b2 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -13653,13 +13653,6 @@ public final class Settings { 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 e53ebada55fb..72e9ad047ed7 100644 --- a/core/java/android/service/autofill/FillRequest.java +++ b/core/java/android/service/autofill/FillRequest.java @@ -23,6 +23,7 @@ import android.os.Bundle; 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 @@ public final class FillRequest implements Parcelable { */ 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 @@ public final class FillRequest implements Parcelable { * * @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 @@ public final class FillRequest implements Parcelable { 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 @@ public final class FillRequest implements Parcelable { mFlags, FLAG_MANUAL_REQUEST | FLAG_COMPATIBILITY_MODE_REQUEST); + this.mInlineSuggestionsRequest = inlineSuggestionsRequest; onConstructed(); } @@ -256,6 +278,19 @@ public final class FillRequest implements Parcelable { 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 @@ public final class FillRequest implements Parcelable { "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 @@ public final class FillRequest implements Parcelable { } @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 c99fe6149aab..02a6390a08bb 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -25,6 +25,7 @@ import android.annotation.NonNull; 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 @@ public final class FillResponse implements Parcelable { 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 @@ public final class FillResponse implements Parcelable { mRequestId = INVALID_REQUEST_ID; mUserData = builder.mUserData; mCancelIds = builder.mCancelIds; + mInlineSuggestionSlices = (builder.mInlineSuggestionSlices != null) + ? new ParceledListSlice<>(builder.mInlineSuggestionSlices) : null; } /** @hide */ @@ -195,6 +199,11 @@ public final class FillResponse implements Parcelable { 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 @@ public final class FillResponse implements Parcelable { 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 @@ public final class FillResponse implements Parcelable { } /** + * 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 @@ public final class FillResponse implements Parcelable { 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 @@ public final class FillResponse implements Parcelable { parcel.writeParcelableArray(mFieldClassificationIds, flags); parcel.writeInt(mFlags); parcel.writeIntArray(mCancelIds); - + parcel.writeParcelable(mInlineSuggestionSlices, flags); parcel.writeInt(mRequestId); } @@ -755,6 +781,16 @@ public final class FillResponse implements Parcelable { 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 7ea4f3062e21..cdfd397a732a 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -68,22 +68,26 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll 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 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes, InsetsController.INTERPOLATOR, durationMs); + mAnimation.setAlpha(getCurrentAlpha()); mController.dispatchAnimationStarted(mAnimation, new AnimationBounds(mHiddenInsets, mShownInsets)); } @@ -120,6 +125,11 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll } @Override + public float getCurrentAlpha() { + return mCurrentAlpha; + } + + @Override @InsetsType public int getTypes() { return mTypes; } @@ -136,6 +146,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll } mPendingFraction = sanitize(fraction); mPendingInsets = sanitize(insets); + mPendingAlpha = 1 - sanitize(alpha); mController.scheduleApplyChangeInsets(); } @@ -148,17 +159,24 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll 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 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll } 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 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll // 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 88703112ae80..5563d629f25e 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -361,12 +361,12 @@ public class InsetsController implements WindowInsetsController { 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 @@ public class InsetsController implements WindowInsetsController { 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 @@ public class InsetsController implements WindowInsetsController { // 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 2d0b954deced..44f211ac8ede 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -16185,7 +16185,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 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 e3f0da1bdd5c..df6f3b80d11c 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -211,26 +211,26 @@ public final class ViewRootImpl implements ViewParent, * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static int sNewInsetsMode = - SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, 0); + public static final int NEW_INSETS_MODE_NONE = 0; /** * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static final int NEW_INSETS_MODE_NONE = 0; + public static final int NEW_INSETS_MODE_IME = 1; /** * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static final int NEW_INSETS_MODE_IME = 1; + public static final int NEW_INSETS_MODE_FULL = 2; /** * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static final int NEW_INSETS_MODE_FULL = 2; + 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 diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java index 8ae85201be92..5e71f271f1d4 100644 --- a/core/java/android/view/WindowInsetsAnimationCallback.java +++ b/core/java/android/view/WindowInsetsAnimationCallback.java @@ -89,6 +89,7 @@ public interface WindowInsetsAnimationCallback { 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 interface WindowInsetsAnimationCallback { 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 51491037c934..2bf0d277268d 100644 --- a/core/java/android/view/WindowInsetsAnimationController.java +++ b/core/java/android/view/WindowInsetsAnimationController.java @@ -93,6 +93,12 @@ public interface WindowInsetsAnimationController { 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 4bc2176e8279..b143fad778ec 100644 --- a/core/java/android/view/inline/InlineContentView.java +++ b/core/java/android/view/inline/InlineContentView.java @@ -50,7 +50,10 @@ public class InlineContentView extends SurfaceView { @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 25e34d36aa9a..c10144e6ee25 100644 --- a/core/java/android/view/inputmethod/InlineSuggestion.java +++ b/core/java/android/view/inputmethod/InlineSuggestion.java @@ -271,7 +271,7 @@ public final class InlineSuggestion implements Parcelable { }; @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 112653aa34e3..1e5a3b027609 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -21,11 +21,16 @@ import android.annotation.NonNull; 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 @@ public interface InputMethod { } /** + * 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 f5708a5c89af..dec9ae701fb2 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -259,7 +259,7 @@ public abstract class FileSystemProvider extends DocumentsProvider { throw new IllegalStateException("Failed to touch " + file + ": " + e); } } - MediaStore.scanFile(getContext(), file); + MediaStore.scanFile(getContext().getContentResolver(), file); return childId; } @@ -316,10 +316,10 @@ public abstract class FileSystemProvider extends DocumentsProvider { 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 2ee902ab6468..58aaa80b51be 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 @@ import com.android.internal.view.IInputSessionCallback; 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 f7a994f7071f..3cde887ba465 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -129,6 +129,7 @@ extern int register_android_database_CursorWindow(JNIEnv* env); 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 @@ static const RegJNIRec gRegJNI[] = { 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 0c2129ffa625..e3e07de3556e 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2472,4 +2472,9 @@ enum PageId { // 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 9c725b93eec6..4d7846dfb9cc 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 cce38f6756fc..68d95cd38497 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -116,7 +116,8 @@ public class InsetsAnimationControlImplTest { 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 @@ public class InsetsAnimationControlImplTest { 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 @@ public class InsetsAnimationControlImplTest { } @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 e07edd414c18..624fc50030a0 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -240,6 +240,13 @@ applications that come with the platform <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 @@ applications that come with the platform <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 000000000000..88a829546989 --- /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 515f6a8ce8a2..40e90731f2a2 100644 --- a/media/java/android/media/MediaScannerConnection.java +++ b/media/java/android/media/MediaScannerConnection.java @@ -19,6 +19,7 @@ package android.media; 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 @@ public class MediaScannerConnection implements ServiceConnection { 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 9064e6891be6..a1e159174f95 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -908,7 +908,7 @@ public class RingtoneManager { } // 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 000000000000..5ff721837573 --- /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 a315c1eefb52..6705b0cb79f0 100644 --- a/media/java/android/media/ThumbnailUtils.java +++ b/media/java/android/media/ThumbnailUtils.java @@ -33,14 +33,13 @@ import android.graphics.ImageDecoder; 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 class ThumbnailUtils { 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 0f402eb0bf42..f3c071a06eba 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 @@ public class MtpDatabase implements AutoCloseable { } // 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 @@ public class MtpDatabase implements AutoCloseable { 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 @@ public class MtpDatabase implements AutoCloseable { 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 @@ public class MtpDatabase implements AutoCloseable { if (!success) { return; } - MediaStore.scanFile(mContext, obj.getPath().toFile()); + MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile()); } @VisibleForNative @@ -909,7 +911,7 @@ public class MtpDatabase implements AutoCloseable { 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 65d0fef74b25..c7dbca61f90a 100644 --- a/media/java/android/mtp/MtpStorage.java +++ b/media/java/android/mtp/MtpStorage.java @@ -41,11 +41,7 @@ public class MtpStorage { 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 494c61721e02..e17a6173ba4d 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 @@ jobject MediaMetricsJNI::writeMetricsToBundle( 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 @@ jobject MediaMetricsJNI::nativeToJavaPersistableBundle(JNIEnv *env, 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 5d0db01cd5ae..19ff2444e6ca 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -215,7 +215,6 @@ public class SettingsBackupTest { 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 b89f1412c6a6..51bf441fe119 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 2d37b4c9c342..15fd1f7f4957 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java +++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java @@ -101,7 +101,7 @@ public class RingtoneOverlayService extends Service { } 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 0407ffe3ea7c..45318fde4f0e 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 0383dee4f9c3..d3ccbeb0afb1 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 @@ public class RecordingService extends Service { 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 76925b43cfb8..2f401e5271d8 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.app.PendingIntent; 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.UserHandle; 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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 Uri uri = MediaStore.createPending(context, params); - final MediaStore.PendingSession session = MediaStore.openPending(context, uri); + 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 = 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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 4657de275e38..f4c7e23f9cda 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 @@ public class StatusBar extends SystemUI implements DemoMode, 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 @@ public class StatusBar extends SystemUI implements DemoMode, 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 d7ed2e9abde4..202f90068fc1 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.AutofillManagerService.AutofillCompatState; 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 @@ final class AutofillManagerServiceImpl @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 @@ final class AutofillManagerServiceImpl mUi = ui; mFieldClassificationStrategy = new FieldClassificationStrategy(getContext(), userId); mAutofillCompatState = autofillCompatState; + mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); updateLocked(disabled); } @@ -493,7 +497,7 @@ final class AutofillManagerServiceImpl 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 3b2da911a849..67bcccd1d7de 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.IAssistDataReceiver; 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.text.TextUtils; 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.AutofillManager.SmartSuggestionMode; 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @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 class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** + * 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState triggerAugmentedAutofillLocked(); return; } + viewState.setState(newState); int requestId; @@ -636,7 +754,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mForAugmentedAutofillOnly = forAugmentedAutofillOnly; setClientLocked(client); + mInputMethodManagerInternal = inputMethodManagerInternal; + mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); } @@ -2208,7 +2329,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + /** + * 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 class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 26bb7c35b9dc..eadfd31c27bf 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.ComponentName; 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.service.autofill.ValueFinder; 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 @@ public final class AutoFillUI { } /** + * 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 9b1d9e9a246c..5a78036911a8 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.sysprop.VoldProperties; 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 @@ class StorageManagerService extends IStorageManager.Stub 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 @@ class StorageManagerService extends IStorageManager.Stub 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 @@ class StorageManagerService extends IStorageManager.Stub } 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 2de18c391d4a..60f0e8e6c63d 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 @@ public abstract class BiometricServiceBase extends SystemService 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 @@ public abstract class BiometricServiceBase extends SystemService * @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 944a95dda99b..44c8971ba4c2 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 @@ package com.android.server.inputmethod; 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 class InputMethodManagerInternal { 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 abstract class InputMethodManagerInternal { 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 471fa72b1957..5865dc471b30 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.ViewGroup; 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.SomeArgs; 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + 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 class InputMethodManagerService extends IInputMethodManager.Stub 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 02e29e0b2ab5..c13d55adda08 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.provider.Settings; 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.os.TransferPipe; 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 @@ public final class MultiClientInputMethodManagerService { @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 1153fb521c53..99d5e4a21df4 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 @@ public class PackageManagerService extends IPackageManager.Stub } } 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 9324870904d9..6cdfcff61415 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.DumpUtils; 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 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { 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 000000000000..bb7cea98eda0 --- /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/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e3ea2c5f1ac4..4667eab1f55b 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // 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 d73cb50fe107..06cea3741aa1 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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< 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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< 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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } + 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 7d6b0c99c5b5..a1e02370aa51 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.emergency.EmergencyAffordanceService; 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 @@ public final class SystemServer { private ContentResolver mContentResolver; private EntropyMixer mEntropyMixer; private DataLoaderManagerService mDataLoaderManagerService; + private IncrementalManagerService mIncrementalManagerService; private boolean mOnlyCore; private boolean mFirstBoot; @@ -706,6 +708,11 @@ public final class SystemServer { 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 @@ public final class SystemServer { 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 37f33885de20..cab5286f53b7 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -1998,6 +1998,27 @@ public final class SmsManager { } } + /** + * 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 ac4c8ecea842..9f4d066ff8b4 100644 --- a/telephony/java/com/android/internal/telephony/ISms.aidl +++ b/telephony/java/com/android/internal/telephony/ISms.aidl @@ -594,4 +594,12 @@ interface ISms { * @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 9865f76d2580..2430d8237d9c 100644 --- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java +++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java @@ -211,4 +211,9 @@ public class ISmsImplBase extends ISms.Stub { 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 0208c3a0a0de..9d913b9861e5 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 @@ public class MockContext extends Context { 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 abe6c6152da5..daa85bd06669 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 @@ public class RollbackTest { 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 7b289d8a7858..879ac64c6a55 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.TestApp; 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 @@ public class StagedRollbackTest { 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 e4a8febb8bce..07d829d2d0bb 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 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { 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 7733761eebcc..15c327852001 100644 --- a/tools/stats_log_api_gen/Android.bp +++ b/tools/stats_log_api_gen/Android.bp @@ -21,6 +21,7 @@ cc_binary_host { 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 @@ genrule { 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 @@ cc_library { }, }, } + diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp index 373adca994a1..fa556010646c 100644 --- a/tools/stats_log_api_gen/Collation.cpp +++ b/tools/stats_log_api_gen/Collation.cpp @@ -379,6 +379,7 @@ int collate_atoms(const Descriptor *descriptor, Atoms *atoms) { 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 @@ int collate_atoms(const Descriptor *descriptor, Atoms *atoms) { } 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 44746c96df1b..3efdd520d7f5 100644 --- a/tools/stats_log_api_gen/Collation.h +++ b/tools/stats_log_api_gen/Collation.h @@ -111,6 +111,7 @@ struct Atoms { 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 @@ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, vector<java_type_t> } // 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 000000000000..54a9982bb5c2 --- /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 000000000000..bc677825181f --- /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 bc6d82ad267c..ad171da0511c 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 @@ namespace stats_log_api_gen { 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 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, const AtomDecl &at 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 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, const AtomDecl &at 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 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio 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 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio 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 @@ print_usage() 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 @@ run(int argc, char const*const* argv) 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 @@ run(int argc, char const*const* argv) 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 @@ run(int argc, char const*const* argv) 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 141861d443b0..d6cfe95a34f5 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 @@ bool signature_needed_for_module(const set<string>& modules, const string& modul 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 e860fa9045cb..a89387f00bce 100644 --- a/tools/stats_log_api_gen/utils.h +++ b/tools/stats_log_api_gen/utils.h @@ -33,6 +33,7 @@ using namespace std; 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 atom_needed_for_module(const AtomDecl& atomDecl, const string& moduleName); 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); |