diff options
134 files changed, 3182 insertions, 1468 deletions
diff --git a/api/removed.txt b/api/removed.txt index 5a24f625d146..58dbeb8a7d3f 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -1,10 +1,6 @@ // Signature format: 2.0 package android.app { - public class ActivityManager { - method @Deprecated public static int getMaxNumPictureInPictureActions(); - } - public class Notification implements android.os.Parcelable { method @Deprecated public String getChannel(); method public static Class<? extends android.app.Notification.Style> getNotificationStyleClass(String); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 7a6f5584401d..a88c6a890844 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1019,12 +1019,6 @@ public class ActivityManager { return ActivityTaskManager.getMaxRecentTasksStatic(); } - /** @removed */ - @Deprecated - public static int getMaxNumPictureInPictureActions() { - return 3; - } - /** * Information you can set and retrieve about the current activity within the recent task list. */ @@ -3739,7 +3733,8 @@ public class ActivityManager { * manner, excessive calls to this API could result a {@link java.lang.RuntimeException}. * </p> * - * @param state The state data + * @param state The state data. To be advised, <b>DO NOT</b> include sensitive information/data + * (PII, SPII, or other sensitive user data) here. Maximum length is 128 bytes. */ public void setProcessStateSummary(@Nullable byte[] state) { try { diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java index bac432e42318..15237beee805 100644 --- a/core/java/android/app/ApplicationLoaders.java +++ b/core/java/android/app/ApplicationLoaders.java @@ -48,17 +48,18 @@ public class ApplicationLoaders { ClassLoader parent, String classLoaderName) { return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled, librarySearchPath, libraryPermittedPath, parent, classLoaderName, - null); + null, null); } ClassLoader getClassLoaderWithSharedLibraries( String zip, int targetSdkVersion, boolean isBundled, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, String classLoaderName, - List<ClassLoader> sharedLibraries) { + List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries) { // For normal usage the cache key used is the same as the zip path. return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath, - libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries); + libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries, + nativeSharedLibraries); } /** @@ -77,14 +78,22 @@ public class ApplicationLoaders { return loader; } + // TODO(b/142191088): allow (Java) shared libraries to have <uses-native-library> + // Until that is supported, assume that all native shared libraries are used. + // "ALL" is a magic string that libnativeloader uses to unconditionally add all available + // native shared libraries to the classloader. + List<String> nativeSharedLibraries = new ArrayList<>(); + nativeSharedLibraries.add("ALL"); return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled, - librarySearchPath, libraryPermittedPath, parent, classLoaderName, sharedLibraries); + librarySearchPath, libraryPermittedPath, parent, classLoaderName, sharedLibraries, + nativeSharedLibraries); } private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, String cacheKey, - String classLoaderName, List<ClassLoader> sharedLibraries) { + String classLoaderName, List<ClassLoader> sharedLibraries, + List<String> nativeSharedLibraries) { /* * This is the parent we use if they pass "null" in. In theory * this should be the "system" class loader; in practice we @@ -113,7 +122,8 @@ public class ApplicationLoaders { ClassLoader classloader = ClassLoaderFactory.createClassLoader( zip, librarySearchPath, libraryPermittedPath, parent, - targetSdkVersion, isBundled, classLoaderName, sharedLibraries); + targetSdkVersion, isBundled, classLoaderName, sharedLibraries, + nativeSharedLibraries); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); @@ -185,7 +195,8 @@ public class ApplicationLoaders { // assume cached libraries work with current sdk since they are built-in ClassLoader classLoader = getClassLoader(path, Build.VERSION.SDK_INT, true /*isBundled*/, null /*librarySearchPath*/, null /*libraryPermittedPath*/, null /*parent*/, - null /*cacheKey*/, null /*classLoaderName*/, sharedLibraries /*sharedLibraries*/); + null /*cacheKey*/, null /*classLoaderName*/, sharedLibraries /*sharedLibraries*/, + null /* nativeSharedLibraries */); if (classLoader == null) { // bad configuration or break in classloading code @@ -255,7 +266,8 @@ public class ApplicationLoaders { // The cache key is passed separately to enable the stub WebView to be cached under the // stub's APK path, when the actual package path is the donor APK. return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null, - cacheKey, null /* classLoaderName */, null /* sharedLibraries */); + cacheKey, null /* classLoaderName */, null /* sharedLibraries */, + null /* nativeSharedLibraries */); } /** diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index f9b48e710148..1dc54ddbac4b 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -412,6 +412,12 @@ public final class LoadedApk { return; } for (SharedLibraryInfo lib : sharedLibraries) { + if (lib.isNative()) { + // Native shared lib doesn't contribute to the native lib search path. Its name is + // sent to libnativeloader and then the native shared lib is exported from the + // default linker namespace. + continue; + } List<String> paths = lib.getAllCodePaths(); outSeenPaths.addAll(paths); for (String path : paths) { @@ -696,6 +702,12 @@ public final class LoadedApk { } List<ClassLoader> loaders = new ArrayList<>(); for (SharedLibraryInfo info : sharedLibraries) { + if (info.isNative()) { + // Native shared lib doesn't contribute to the native lib search path. Its name is + // sent to libnativeloader and then the native shared lib is exported from the + // default linker namespace. + continue; + } loaders.add(createSharedLibraryLoader( info, isBundledApp, librarySearchPath, libraryPermittedPath)); } @@ -898,10 +910,19 @@ public final class LoadedApk { mApplicationInfo.sharedLibraryInfos, isBundledApp, librarySearchPath, libraryPermittedPath); + List<String> nativeSharedLibraries = new ArrayList<>(); + if (mApplicationInfo.sharedLibraryInfos != null) { + for (SharedLibraryInfo info : mApplicationInfo.sharedLibraryInfos) { + if (info.isNative()) { + nativeSharedLibraries.add(info.getName()); + } + } + } + mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries( zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath, libraryPermittedPath, mBaseClassLoader, - mApplicationInfo.classLoaderName, sharedLibraries); + mApplicationInfo.classLoaderName, sharedLibraries, nativeSharedLibraries); mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader); setThreadPolicy(oldPolicy); diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index 5f6befdcbaef..e990fd783498 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -525,6 +525,9 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { if ((level & PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR) != 0) { protLevel += "|appPredictor"; } + if ((level & PermissionInfo.PROTECTION_FLAG_COMPANION) != 0) { + protLevel += "|companion"; + } if ((level & PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO) != 0) { protLevel += "|retailDemo"; } diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java index da2a3d885fc6..862563706da7 100644 --- a/core/java/android/content/pm/SharedLibraryInfo.java +++ b/core/java/android/content/pm/SharedLibraryInfo.java @@ -79,6 +79,7 @@ public final class SharedLibraryInfo implements Parcelable { private final long mVersion; private final @Type int mType; + private final boolean mIsNative; private final VersionedPackage mDeclaringPackage; private final List<VersionedPackage> mDependentPackages; private List<SharedLibraryInfo> mDependencies; @@ -93,13 +94,14 @@ public final class SharedLibraryInfo implements Parcelable { * @param type The lib type. * @param declaringPackage The package that declares the library. * @param dependentPackages The packages that depend on the library. + * @param isNative indicate if this shared lib is a native lib or not (i.e. java) * * @hide */ public SharedLibraryInfo(String path, String packageName, List<String> codePaths, String name, long version, int type, VersionedPackage declaringPackage, List<VersionedPackage> dependentPackages, - List<SharedLibraryInfo> dependencies) { + List<SharedLibraryInfo> dependencies, boolean isNative) { mPath = path; mPackageName = packageName; mCodePaths = codePaths; @@ -109,6 +111,16 @@ public final class SharedLibraryInfo implements Parcelable { mDeclaringPackage = declaringPackage; mDependentPackages = dependentPackages; mDependencies = dependencies; + mIsNative = isNative; + } + + /** @hide */ + public SharedLibraryInfo(String path, String packageName, List<String> codePaths, + String name, long version, int type, + VersionedPackage declaringPackage, List<VersionedPackage> dependentPackages, + List<SharedLibraryInfo> dependencies) { + this(path, packageName, codePaths, name, version, type, declaringPackage, dependentPackages, + dependencies, false /* isNative */); } private SharedLibraryInfo(Parcel parcel) { @@ -125,6 +137,7 @@ public final class SharedLibraryInfo implements Parcelable { mDeclaringPackage = parcel.readParcelable(null); mDependentPackages = parcel.readArrayList(null); mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR); + mIsNative = parcel.readBoolean(); } /** @@ -137,6 +150,15 @@ public final class SharedLibraryInfo implements Parcelable { } /** + * Tells whether this library is a native shared library or not. + * + * @hide + */ + public boolean isNative() { + return mIsNative; + } + + /** * Gets the library name an app defines in its manifest * to depend on the library. * @@ -320,6 +342,7 @@ public final class SharedLibraryInfo implements Parcelable { parcel.writeParcelable(mDeclaringPackage, flags); parcel.writeList(mDependentPackages); parcel.writeTypedList(mDependencies); + parcel.writeBoolean(mIsNative); } private static String typeToString(int type) { diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java index 2ee0ad67b108..872098c8689e 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -92,6 +92,10 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage addUsesOptionalLibrary(String libraryName); + ParsingPackage addUsesNativeLibrary(String libraryName); + + ParsingPackage addUsesOptionalNativeLibrary(String libraryName); + ParsingPackage addUsesStaticLibrary(String libraryName); ParsingPackage addUsesStaticLibraryCertDigests(String[] certSha256Digests); @@ -219,6 +223,8 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage removeUsesOptionalLibrary(String libraryName); + ParsingPackage removeUsesOptionalNativeLibrary(String libraryName); + ParsingPackage setAnyDensity(int anyDensity); ParsingPackage setAppComponentFactory(String appComponentFactory); diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index f932bc250e28..0c0dc313087e 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -186,6 +186,13 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { @NonNull @DataClass.ParcelWith(ForInternedStringList.class) + protected List<String> usesNativeLibraries = emptyList(); + @NonNull + @DataClass.ParcelWith(ForInternedStringList.class) + protected List<String> usesOptionalNativeLibraries = emptyList(); + + @NonNull + @DataClass.ParcelWith(ForInternedStringList.class) private List<String> usesStaticLibraries = emptyList(); @Nullable private long[] usesStaticLibrariesVersions; @@ -669,6 +676,27 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { } @Override + public final ParsingPackageImpl addUsesOptionalNativeLibrary(String libraryName) { + this.usesOptionalNativeLibraries = CollectionUtils.add(this.usesOptionalNativeLibraries, + TextUtils.safeIntern(libraryName)); + return this; + } + + @Override + public final ParsingPackageImpl addUsesNativeLibrary(String libraryName) { + this.usesNativeLibraries = CollectionUtils.add(this.usesNativeLibraries, + TextUtils.safeIntern(libraryName)); + return this; + } + + + @Override public ParsingPackageImpl removeUsesOptionalNativeLibrary(String libraryName) { + this.usesOptionalNativeLibraries = CollectionUtils.remove(this.usesOptionalNativeLibraries, + libraryName); + return this; + } + + @Override public ParsingPackageImpl addUsesStaticLibrary(String libraryName) { this.usesStaticLibraries = CollectionUtils.add(this.usesStaticLibraries, TextUtils.safeIntern(libraryName)); @@ -982,6 +1010,8 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { sForInternedStringList.parcel(this.libraryNames, dest, flags); sForInternedStringList.parcel(this.usesLibraries, dest, flags); sForInternedStringList.parcel(this.usesOptionalLibraries, dest, flags); + sForInternedStringList.parcel(this.usesNativeLibraries, dest, flags); + sForInternedStringList.parcel(this.usesOptionalNativeLibraries, dest, flags); sForInternedStringList.parcel(this.usesStaticLibraries, dest, flags); dest.writeLongArray(this.usesStaticLibrariesVersions); @@ -1144,6 +1174,8 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { this.libraryNames = sForInternedStringList.unparcel(in); this.usesLibraries = sForInternedStringList.unparcel(in); this.usesOptionalLibraries = sForInternedStringList.unparcel(in); + this.usesNativeLibraries = sForInternedStringList.unparcel(in); + this.usesOptionalNativeLibraries = sForInternedStringList.unparcel(in); this.usesStaticLibraries = sForInternedStringList.unparcel(in); this.usesStaticLibrariesVersions = in.createLongArray(); @@ -1417,6 +1449,18 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { @NonNull @Override + public List<String> getUsesNativeLibraries() { + return usesNativeLibraries; + } + + @NonNull + @Override + public List<String> getUsesOptionalNativeLibraries() { + return usesOptionalNativeLibraries; + } + + @NonNull + @Override public List<String> getUsesStaticLibraries() { return usesStaticLibraries; } diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java index 5b53c18b820c..7e0fe7dc41bf 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageRead.java +++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java @@ -230,6 +230,19 @@ public interface ParsingPackageRead extends Parcelable { @NonNull List<String> getUsesOptionalLibraries(); + /** @see R.styleabele#AndroidManifestUsesNativeLibrary */ + @NonNull + List<String> getUsesNativeLibraries(); + + /** + * Like {@link #getUsesNativeLibraries()}, but marked optional by setting + * {@link R.styleable#AndroidManifestUsesNativeLibrary_required} to false . Application is + * expected to handle absence manually. + * @see R.styleable#AndroidManifestUsesNativeLibrary + */ + @NonNull + List<String> getUsesOptionalNativeLibraries(); + /** * TODO(b/135203078): Move static library stuff to an inner data class * @see R.styleable#AndroidManifestUsesStaticLibrary diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index 3688f1bda979..e1f08f3e55a1 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -701,6 +701,8 @@ public class ParsingPackageUtils { return parseUsesStaticLibrary(input, pkg, res, parser); case "uses-library": return parseUsesLibrary(input, pkg, res, parser); + case "uses-native-library": + return parseUsesNativeLibrary(input, pkg, res, parser); case "uses-package": // Dependencies for app installers; we don't currently try to // enforce this. @@ -2017,6 +2019,8 @@ public class ParsingPackageUtils { return parseUsesStaticLibrary(input, pkg, res, parser); case "uses-library": return parseUsesLibrary(input, pkg, res, parser); + case "uses-native-library": + return parseUsesNativeLibrary(input, pkg, res, parser); case "processes": return parseProcesses(input, pkg, res, parser, mSeparateProcesses, flags); case "uses-package": @@ -2178,6 +2182,37 @@ public class ParsingPackageUtils { } @NonNull + private static ParseResult<ParsingPackage> parseUsesNativeLibrary(ParseInput input, + ParsingPackage pkg, Resources res, XmlResourceParser parser) { + TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesNativeLibrary); + try { + // Note: don't allow this value to be a reference to a resource + // that may change. + String lname = sa.getNonResourceString( + R.styleable.AndroidManifestUsesNativeLibrary_name); + boolean req = sa.getBoolean(R.styleable.AndroidManifestUsesNativeLibrary_required, + true); + + if (lname != null) { + if (req) { + // Upgrade to treat as stronger constraint + pkg.addUsesNativeLibrary(lname) + .removeUsesOptionalNativeLibrary(lname); + } else { + // Ignore if someone already defined as required + if (!ArrayUtils.contains(pkg.getUsesNativeLibraries(), lname)) { + pkg.addUsesOptionalNativeLibrary(lname); + } + } + } + + return input.success(pkg); + } finally { + sa.recycle(); + } + } + + @NonNull private static ParseResult<ParsingPackage> parseProcesses(ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser, String[] separateProcesses, int flags) throws IOException, XmlPullParserException { diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java index 5496e17206d9..d8a2082f4eae 100644 --- a/core/java/android/metrics/LogMaker.java +++ b/core/java/android/metrics/LogMaker.java @@ -39,7 +39,7 @@ public class LogMaker { /** * Min required eventlog line length. * See: android/util/cts/EventLogTest.java - * Size checks enforced here are intended only as sanity checks; + * Size limits enforced here are intended only as a precaution; * your logs may be truncated earlier. Please log responsibly. * * @hide diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index e3cb382256ae..ab19fa9a1256 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -551,7 +551,7 @@ public class Html { out.append(((ImageSpan) style[j]).getSource()); out.append("\">"); - // Don't output the dummy character underlying the image. + // Don't output the placeholder character underlying the image. i = next; } if (style[j] instanceof AbsoluteSizeSpan) { diff --git a/core/java/android/text/format/OWNERS b/core/java/android/text/format/OWNERS new file mode 100644 index 000000000000..32adc12bb901 --- /dev/null +++ b/core/java/android/text/format/OWNERS @@ -0,0 +1,3 @@ +# Inherits OWNERS from parent directory, plus the following + +vichang@google.com diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java index 80c8ec852146..5b5c8548f281 100644 --- a/core/java/android/text/format/Time.java +++ b/core/java/android/text/format/Time.java @@ -361,7 +361,7 @@ public class Time { */ @Override public String toString() { - // toString() uses its own TimeCalculator rather than the shared one. Otherwise crazy stuff + // toString() uses its own TimeCalculator rather than the shared one. Otherwise weird stuff // happens during debugging when the debugger calls toString(). TimeCalculator calculator = new TimeCalculator(this.timezone); calculator.copyFieldsFromTime(this); diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java index 064bc6947fc4..713cfb48c95f 100644 --- a/core/java/android/view/FocusFinder.java +++ b/core/java/android/view/FocusFinder.java @@ -311,9 +311,6 @@ public class FocusFinder { } final int count = focusables.size(); - if (count < 2) { - return null; - } switch (direction) { case View.FOCUS_FORWARD: return getNextFocusable(focused, focusables, count); @@ -376,29 +373,29 @@ public class FocusFinder { } private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) { - if (count < 2) { - return null; - } if (focused != null) { int position = focusables.lastIndexOf(focused); if (position >= 0 && position + 1 < count) { return focusables.get(position + 1); } } - return focusables.get(0); + if (!focusables.isEmpty()) { + return focusables.get(0); + } + return null; } private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) { - if (count < 2) { - return null; - } if (focused != null) { int position = focusables.indexOf(focused); if (position > 0) { return focusables.get(position - 1); } } - return focusables.get(count - 1); + if (!focusables.isEmpty()) { + return focusables.get(count - 1); + } + return null; } private static View getNextKeyboardNavigationCluster( diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index fbfeda6f0bcc..b398cf6c9cb3 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -727,16 +727,22 @@ public final class AutofillManager { mState = savedInstanceState.getInt(STATE_TAG, STATE_UNKNOWN); if (mSessionId != NO_SESSION) { - ensureServiceClientAddedIfNeededLocked(); + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); final AutofillClient client = getClient(); if (client != null) { final SyncResultReceiver receiver = new SyncResultReceiver( SYNC_CALLS_TIMEOUT_MS); try { - mService.restoreSession(mSessionId, client.autofillClientGetActivityToken(), - mServiceClient.asBinder(), receiver); - final boolean sessionWasRestored = receiver.getIntResult() == 1; + boolean sessionWasRestored = false; + if (clientAdded) { + mService.restoreSession(mSessionId, + client.autofillClientGetActivityToken(), + mServiceClient.asBinder(), receiver); + sessionWasRestored = receiver.getIntResult() == 1; + } else { + Log.w(TAG, "No service client for session " + mSessionId); + } if (!sessionWasRestored) { Log.w(TAG, "Session " + mSessionId + " could not be restored"); @@ -850,8 +856,8 @@ public final class AutofillManager { if (isDisabledByServiceLocked()) { return false; } - ensureServiceClientAddedIfNeededLocked(); - return mEnabled; + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); + return clientAdded ? mEnabled : false; } } @@ -1007,7 +1013,12 @@ public final class AutofillManager { AutofillCallback callback = null; - ensureServiceClientAddedIfNeededLocked(); + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); + + if (!clientAdded) { + if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): no service client"); + return callback; + } if (!mEnabled && !mEnabledForAugmentedAutofillOnly) { if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled"); @@ -1060,9 +1071,10 @@ public final class AutofillManager { @GuardedBy("mLock") void notifyViewExitedLocked(@NonNull View view) { - ensureServiceClientAddedIfNeededLocked(); + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); - if ((mEnabled || mEnabledForAugmentedAutofillOnly) && isActiveLocked()) { + if (clientAdded && (mEnabled || mEnabledForAugmentedAutofillOnly) + && isActiveLocked()) { // dont notify exited when Activity is already in background if (!isClientDisablingEnterExitEvent()) { final AutofillId id = view.getAutofillId(); @@ -1178,7 +1190,12 @@ public final class AutofillManager { AutofillCallback callback = null; if (shouldIgnoreViewEnteredLocked(id, flags)) return callback; - ensureServiceClientAddedIfNeededLocked(); + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); + + if (!clientAdded) { + if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): no service client"); + return callback; + } if (!mEnabled && !mEnabledForAugmentedAutofillOnly) { if (sVerbose) { @@ -1241,9 +1258,10 @@ public final class AutofillManager { @GuardedBy("mLock") private void notifyViewExitedLocked(@NonNull View view, int virtualId) { - ensureServiceClientAddedIfNeededLocked(); + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); - if ((mEnabled || mEnabledForAugmentedAutofillOnly) && isActiveLocked()) { + if (clientAdded && (mEnabled || mEnabledForAugmentedAutofillOnly) + && isActiveLocked()) { // don't notify exited when Activity is already in background if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view, virtualId); @@ -1905,11 +1923,16 @@ public final class AutofillManager { } } + /** + * Tries to add AutofillManagerClient to service if it does not been added. Returns {@code true} + * if the AutofillManagerClient is added successfully or is already added. Otherwise, + * returns {@code false}. + */ @GuardedBy("mLock") - private void ensureServiceClientAddedIfNeededLocked() { + private boolean tryAddServiceClientIfNeededLocked() { final AutofillClient client = getClient(); if (client == null) { - return; + return false; } if (mServiceClient == null) { @@ -1924,7 +1947,10 @@ public final class AutofillManager { flags = receiver.getIntResult(); } catch (SyncResultReceiver.TimeoutException e) { Log.w(TAG, "Failed to initialize autofill: " + e); - return; + // Reset the states initialized above. + mService.removeClient(mServiceClient, userId); + mServiceClient = null; + return false; } mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0; sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0; @@ -1949,6 +1975,7 @@ public final class AutofillManager { throw e.rethrowFromSystemServer(); } } + return true; } @GuardedBy("mLock") @@ -1962,12 +1989,13 @@ public final class AutofillManager { && view.isLaidOut() && view.isVisibleToUser()) { - ensureServiceClientAddedIfNeededLocked(); + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); if (sVerbose) { - Log.v(TAG, "startAutofillIfNeededLocked(): enabled=" + mEnabled); + Log.v(TAG, "startAutofillIfNeededLocked(): enabled=" + mEnabled + " mServiceClient=" + + mServiceClient); } - if (mEnabled && !isClientDisablingEnterExitEvent()) { + if (clientAdded && mEnabled && !isClientDisablingEnterExitEvent()) { final AutofillId id = view.getAutofillId(); final AutofillValue value = view.getAutofillValue(); // Starts new session. @@ -2692,6 +2720,7 @@ public final class AutofillManager { pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId); pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked()); pw.print(pfx); pw.print("context: "); pw.println(mContext); + pw.print(pfx); pw.print("service client: "); pw.println(mServiceClient); final AutofillClient client = getClient(); if (client != null) { pw.print(pfx); pw.print("client: "); pw.print(client); diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 1c75232dc15c..052bca57d77c 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -1511,7 +1511,7 @@ public class WebView extends AbsoluteLayout * * @param hosts the list of hosts * @param callback will be called with {@code true} if hosts are successfully added to the - * whitelist. It will be called with {@code false} if any hosts are malformed. The callback + * allowlist. It will be called with {@code false} if any hosts are malformed. The callback * will be run on the UI thread */ public static void setSafeBrowsingWhitelist(@NonNull List<String> hosts, diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index 150fa88a36e3..7b6e1a370479 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -173,8 +173,9 @@ public class WebViewClient { * when accessing private data or the view system. * * <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe - * Browsing checks. If this is undesired, whitelist the URL with {@link - * WebView#setSafeBrowsingWhitelist} or ignore the warning with {@link #onSafeBrowsingHit}. + * Browsing checks. If this is undesired, you can use {@link WebView#setSafeBrowsingWhitelist} + * to skip Safe Browsing checks for that host or dismiss the warning in {@link + * #onSafeBrowsingHit} by calling {@link SafeBrowsingResponse#proceed}. * * @param view The {@link android.webkit.WebView} that is requesting the * resource. @@ -211,8 +212,9 @@ public class WebViewClient { * when accessing private data or the view system. * * <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe - * Browsing checks. If this is undesired, whitelist the URL with {@link - * WebView#setSafeBrowsingWhitelist} or ignore the warning with {@link #onSafeBrowsingHit}. + * Browsing checks. If this is undesired, you can use {@link WebView#setSafeBrowsingWhitelist} + * to skip Safe Browsing checks for that host or dismiss the warning in {@link + * #onSafeBrowsingHit} by calling {@link SafeBrowsingResponse#proceed}. * * @param view The {@link android.webkit.WebView} that is requesting the * resource. diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 23bbe69afafb..89206fda39f3 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -1142,7 +1142,7 @@ public final class Magnifier { bitmapRenderNode.setOutline(outline); bitmapRenderNode.setClipToOutline(true); - // Create a dummy draw, which will be replaced later with real drawing. + // Create a placeholder draw, which will be replaced later with real drawing. final RecordingCanvas canvas = bitmapRenderNode.beginRecording( mContentWidth, mContentHeight); try { diff --git a/core/java/com/android/internal/listeners/ListenerExecutor.java b/core/java/com/android/internal/listeners/ListenerExecutor.java index e78e32b829b3..9979e6056f50 100644 --- a/core/java/com/android/internal/listeners/ListenerExecutor.java +++ b/core/java/com/android/internal/listeners/ListenerExecutor.java @@ -40,7 +40,7 @@ public interface ListenerExecutor { /** * Called before this operation is to be run. Some operations may be canceled before they * are run, in which case this method may not be called. {@link #onPostExecute(boolean)} - * will only be run if this method was run. + * will only be run if this method was run. This callback is invoked on the calling thread. */ default void onPreExecute() {} @@ -49,7 +49,7 @@ public interface ListenerExecutor { * RuntimeException, which will propagate normally. Implementations of * {@link ListenerExecutor} have the option to override * {@link ListenerExecutor#onOperationFailure(ListenerOperation, Exception)} instead to - * intercept failures at the class level. + * intercept failures at the class level. This callback is invoked on the executor thread. */ default void onFailure(Exception e) { // implementations should handle any exceptions that may be thrown @@ -59,21 +59,24 @@ public interface ListenerExecutor { /** * Called after the operation is run. This method will always be called if * {@link #onPreExecute()} is called. Success implies that the operation was run to - * completion with no failures. + * completion with no failures. This callback may be invoked on the calling thread or + * executor thread. */ default void onPostExecute(boolean success) {} /** * Called after this operation is complete (which does not imply that it was necessarily * run). Will always be called once per operation, no matter if the operation was run or - * not. Success implies that the operation was run to completion with no failures. + * not. Success implies that the operation was run to completion with no failures. This + * callback may be invoked on the calling thread or executor thread. */ default void onComplete(boolean success) {} } /** * May be override to handle operation failures at a class level. Will not be invoked in the - * event of a RuntimeException, which will propagate normally. + * event of a RuntimeException, which will propagate normally. This callback is invoked on the + * executor thread. */ default <TListener> void onOperationFailure(ListenerOperation<TListener> operation, Exception exception) { @@ -83,7 +86,8 @@ public interface ListenerExecutor { /** * Executes the given listener operation on the given executor, using the provided listener * supplier. If the supplier returns a null value, or a value during the operation that does not - * match the value prior to the operation, then the operation is considered canceled. + * match the value prior to the operation, then the operation is considered canceled. If a null + * operation is supplied, nothing happens. */ default <TListener> void executeSafely(Executor executor, Supplier<TListener> listenerSupplier, @Nullable ListenerOperation<TListener> operation) { diff --git a/core/java/com/android/internal/os/ClassLoaderFactory.java b/core/java/com/android/internal/os/ClassLoaderFactory.java index a18943c264f5..f83c5bdc4e28 100644 --- a/core/java/com/android/internal/os/ClassLoaderFactory.java +++ b/core/java/com/android/internal/os/ClassLoaderFactory.java @@ -101,7 +101,7 @@ public class ClassLoaderFactory { String librarySearchPath, String libraryPermittedPath, ClassLoader parent, int targetSdkVersion, boolean isNamespaceShared, String classLoaderName) { return createClassLoader(dexPath, librarySearchPath, libraryPermittedPath, - parent, targetSdkVersion, isNamespaceShared, classLoaderName, null); + parent, targetSdkVersion, isNamespaceShared, classLoaderName, null, null); } @@ -111,18 +111,24 @@ public class ClassLoaderFactory { public static ClassLoader createClassLoader(String dexPath, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, int targetSdkVersion, boolean isNamespaceShared, String classLoaderName, - List<ClassLoader> sharedLibraries) { + List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries) { final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent, classLoaderName, sharedLibraries); + String sonameList = ""; + if (nativeSharedLibraries != null) { + sonameList = String.join(":", nativeSharedLibraries); + } + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "createClassloaderNamespace"); String errorMessage = createClassloaderNamespace(classLoader, targetSdkVersion, librarySearchPath, libraryPermittedPath, isNamespaceShared, - dexPath); + dexPath, + sonameList); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); if (errorMessage != null) { @@ -139,5 +145,6 @@ public class ClassLoaderFactory { String librarySearchPath, String libraryPermittedPath, boolean isNamespaceShared, - String dexPath); + String dexPath, + String sonameList); } diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 0eb3981ed598..c6a1153c747f 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -49,10 +49,12 @@ import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -104,11 +106,17 @@ public class SystemConfig { public final String name; public final String filename; public final String[] dependencies; + public final boolean isNative; SharedLibraryEntry(String name, String filename, String[] dependencies) { + this(name, filename, dependencies, false /* isNative */); + } + + SharedLibraryEntry(String name, String filename, String[] dependencies, boolean isNative) { this.name = name; this.filename = filename; this.dependencies = dependencies; + this.isNative = isNative; } } @@ -170,12 +178,6 @@ public class SystemConfig { // URL-handling state upon factory reset. final ArraySet<String> mLinkedApps = new ArraySet<>(); - // These are the packages that are whitelisted to be able to run as system user - final ArraySet<String> mSystemUserWhitelistedApps = new ArraySet<>(); - - // These are the packages that should not run under system user - final ArraySet<String> mSystemUserBlacklistedApps = new ArraySet<>(); - // These are the components that are enabled by default as VR mode listener services. final ArraySet<ComponentName> mDefaultVrComponents = new ArraySet<>(); @@ -309,14 +311,6 @@ public class SystemConfig { return mLinkedApps; } - public ArraySet<String> getSystemUserWhitelistedApps() { - return mSystemUserWhitelistedApps; - } - - public ArraySet<String> getSystemUserBlacklistedApps() { - return mSystemUserBlacklistedApps; - } - public ArraySet<String> getHiddenApiWhitelistedApps() { return mHiddenApiPackageWhitelist; } @@ -457,6 +451,7 @@ public class SystemConfig { log.traceBegin("readAllPermissions"); try { readAllPermissions(); + readPublicNativeLibrariesList(); } finally { log.traceEnd(); } @@ -895,34 +890,6 @@ public class SystemConfig { } XmlUtils.skipCurrentTag(parser); } break; - case "system-user-whitelisted-app": { - if (allowAppConfigs) { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, "<" + name + "> without package in " - + permFile + " at " + parser.getPositionDescription()); - } else { - mSystemUserWhitelistedApps.add(pkgname); - } - } else { - logNotAllowedInPartition(name, permFile, parser); - } - XmlUtils.skipCurrentTag(parser); - } break; - case "system-user-blacklisted-app": { - if (allowAppConfigs) { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, "<" + name + "> without package in " - + permFile + " at " + parser.getPositionDescription()); - } else { - mSystemUserBlacklistedApps.add(pkgname); - } - } else { - logNotAllowedInPartition(name, permFile, parser); - } - XmlUtils.skipCurrentTag(parser); - } break; case "default-enabled-vr-app": { if (allowAppConfigs) { String pkgname = parser.getAttributeValue(null, "package"); @@ -1513,6 +1480,37 @@ public class SystemConfig { } } + private void readPublicNativeLibrariesList() { + readPublicLibrariesListFile(new File("/vendor/etc/public.libraries.txt")); + String[] dirs = {"/system/etc", "/system_ext/etc", "/product/etc"}; + for (String dir : dirs) { + for (File f : (new File(dir)).listFiles()) { + String name = f.getName(); + if (name.startsWith("public.libraries-") && name.endsWith(".txt")) { + readPublicLibrariesListFile(f); + } + } + } + } + + private void readPublicLibrariesListFile(File listFile) { + try (BufferedReader br = new BufferedReader(new FileReader(listFile))) { + String line; + while ((line = br.readLine()) != null) { + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + // Line format is <soname> [abi]. We take the soname part. + String soname = line.trim().split(" ")[0]; + SharedLibraryEntry entry = new SharedLibraryEntry( + soname, soname, new String[0], true); + mSharedLibraries.put(entry.name, entry); + } + } catch (IOException e) { + Slog.w(TAG, "Failed to read public libraries file " + listFile, e); + } + } + private static boolean isSystemProcess() { return Process.myUid() == Process.SYSTEM_UID; } diff --git a/core/jni/com_android_internal_os_ClassLoaderFactory.cpp b/core/jni/com_android_internal_os_ClassLoaderFactory.cpp index f8d41e4bef54..59c413ff58a6 100644 --- a/core/jni/com_android_internal_os_ClassLoaderFactory.cpp +++ b/core/jni/com_android_internal_os_ClassLoaderFactory.cpp @@ -28,16 +28,19 @@ static jstring createClassloaderNamespace_native(JNIEnv* env, jstring librarySearchPath, jstring libraryPermittedPath, jboolean isShared, - jstring dexPath) { + jstring dexPath, + jstring sonameList) { return android::CreateClassLoaderNamespace(env, targetSdkVersion, classLoader, isShared == JNI_TRUE, dexPath, - librarySearchPath, libraryPermittedPath); + librarySearchPath, + libraryPermittedPath, + sonameList); } static const JNINativeMethod g_methods[] = { { "createClassloaderNamespace", - "(Ljava/lang/ClassLoader;ILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;)Ljava/lang/String;", + "(Ljava/lang/ClassLoader;ILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)Ljava/lang/String;", reinterpret_cast<void*>(createClassloaderNamespace_native) }, }; diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index eb30c9be4eba..ac08d96ab303 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2173,6 +2173,29 @@ <attr name="required" /> </declare-styleable> + <!-- The <code>uses-native-library</code> specifies a native shared library that this + package requires to be linked against. Specifying this flag tells the + system to make the native library to be available to your app. + + <p>On devices running R or lower, this is ignored and the app has access to all + the public native shared libraries that are exported from the platform. This is + also ignored if the app is targeting R or lower. + + <p>This appears as a child tag of the + {@link #AndroidManifestApplication application} tag. --> + <declare-styleable name="AndroidManifestUsesNativeLibrary" parent="AndroidManifestApplication"> + <!-- Required name of the library you use. --> + <attr name="name" /> + <!-- Specify whether this native library is required for the application. + The default is true, meaning the application requires the + library, and does not want to be installed on devices that + don't support it. If you set this to false, then this will + allow the application to be installed even if the library + doesn't exist, and you will need to check for its presence + dynamically at runtime. --> + <attr name="required" /> + </declare-styleable> + <!-- The <code>uses-static-library</code> specifies a shared <strong>static</strong> library that this package requires to be statically linked against. Specifying this tag tells the system to include this library's code in your class loader. diff --git a/core/tests/coretests/src/android/graphics/PathTest.java b/core/tests/coretests/src/android/graphics/PathTest.java index c6d6d1ff90d5..b50792ca6b38 100644 --- a/core/tests/coretests/src/android/graphics/PathTest.java +++ b/core/tests/coretests/src/android/graphics/PathTest.java @@ -28,7 +28,9 @@ public class PathTest extends TestCase { final Path.FillType defaultFillType = path.getFillType(); final Path.FillType fillType = Path.FillType.INVERSE_EVEN_ODD; - assertFalse(fillType.equals(defaultFillType)); // Sanity check for the test itself. + + // This test is only meaningful if it changes from the default. + assertFalse(fillType.equals(defaultFillType)); path.setFillType(fillType); path.reset(); diff --git a/data/etc/platform.xml b/data/etc/platform.xml index e1f6b2aa76ab..dd8f40d586bc 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -263,10 +263,4 @@ be able to connect to the internet when such a proxy is in use, since all outgoing connections originate from this app. --> <allow-in-power-save-except-idle package="com.android.proxyhandler" /> - - <!-- These are the packages that are white-listed to be able to run as system user --> - <system-user-whitelisted-app package="com.android.settings" /> - - <!-- These are the packages that shouldn't run as system user --> - <system-user-blacklisted-app package="com.android.wallpaper.livepicker" /> </permissions> diff --git a/data/keyboards/Vendor_2e95_Product_7725.kl b/data/keyboards/Vendor_2e95_Product_7725.kl new file mode 100644 index 000000000000..7672e22f8adc --- /dev/null +++ b/data/keyboards/Vendor_2e95_Product_7725.kl @@ -0,0 +1,64 @@ +# Copyright (C) 2020 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. + +# +# Scuf Vantage Controller +# + +# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html + +# Square +key 0x130 BUTTON_X +# Cross +key 0x131 BUTTON_A +# Circle +key 0x132 BUTTON_B +# Triangle +key 0x133 BUTTON_Y + +key 0x134 BUTTON_L1 +key 0x135 BUTTON_R1 +key 0x136 BUTTON_L2 +key 0x137 BUTTON_R2 + +# L2 Trigger axis +axis 0x03 LTRIGGER +# R2 Trigger axis +axis 0x04 RTRIGGER + +# Left Analog Stick +axis 0x00 X +axis 0x01 Y +# Right Analog Stick +axis 0x02 Z +axis 0x05 RZ + +# Left stick click +key 0x13a BUTTON_THUMBL +# Right stick click +key 0x13b BUTTON_THUMBR + +# Hat +axis 0x10 HAT_X +axis 0x11 HAT_Y + +# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt +# Share +key 0x138 BUTTON_SELECT +# Options +key 0x139 BUTTON_START +# PS key +key 0x13c BUTTON_MODE +# Touchpad press +key 0x13d BUTTON_1 diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java index d8d96413a93d..43373ffbd3f4 100644 --- a/graphics/java/android/graphics/Region.java +++ b/graphics/java/android/graphics/Region.java @@ -409,10 +409,10 @@ public class Region implements Parcelable { mNativeRegion = ni; } - /* add dummy parameter so constructor can be called from jni without + /* Add an unused parameter so constructor can be called from jni without triggering 'not cloneable' exception */ @UnsupportedAppUsage - private Region(long ni, int dummy) { + private Region(long ni, int unused) { this(ni); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index cb64796196ed..338ece5afbc2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -30,6 +30,7 @@ import android.os.ServiceManager; import android.util.Slog; import android.util.SparseArray; import android.view.IDisplayWindowInsetsController; +import android.view.IWindowManager; import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -60,20 +61,20 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged private static final int DIRECTION_HIDE = 2; private static final int FLOATING_IME_BOTTOM_INSET = -80; - protected final SystemWindows mSystemWindows; + protected final IWindowManager mWmService; protected final Handler mHandler; - final TransactionPool mTransactionPool; - - final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>(); - - final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>(); - - public DisplayImeController(SystemWindows syswin, DisplayController displayController, - Handler handler, TransactionPool transactionPool) { - mHandler = handler; - mSystemWindows = syswin; + private final TransactionPool mTransactionPool; + private final DisplayController mDisplayController; + private final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>(); + private final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>(); + + public DisplayImeController(IWindowManager wmService, DisplayController displayController, + Handler mainHandler, TransactionPool transactionPool) { + mHandler = mainHandler; + mWmService = wmService; mTransactionPool = transactionPool; - displayController.addDisplayWindowListener(this); + mDisplayController = displayController; + mDisplayController.addDisplayWindowListener(this); } @Override @@ -81,9 +82,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged // Add's a system-ui window-manager specifically for ime. This type is special because // WM will defer IME inset handling to it in multi-window scenarious. PerDisplay pd = new PerDisplay(displayId, - mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation()); + mDisplayController.getDisplayLayout(displayId).rotation()); try { - mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd); + mWmService.setDisplayWindowInsetsController(displayId, pd); } catch (RemoteException e) { Slog.w(TAG, "Unable to set insets controller on display " + displayId); } @@ -96,7 +97,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged if (pd == null) { return; } - if (mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation() + if (mDisplayController.getDisplayLayout(displayId).rotation() != pd.mRotation && isImeShowing(displayId)) { pd.startAnimation(true, false /* forceRestart */); } @@ -105,7 +106,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged @Override public void onDisplayRemoved(int displayId) { try { - mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null); + mWmService.setDisplayWindowInsetsController(displayId, null); } catch (RemoteException e) { Slog.w(TAG, "Unable to remove insets controller on display " + displayId); } @@ -263,7 +264,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged private void setVisibleDirectly(boolean visible) { mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible); try { - mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); + mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); } catch (RemoteException e) { } } @@ -282,7 +283,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged // an IME inset). For now, we assume that no non-floating IME will be <= this nav bar // frame height so any reported frame that is <= nav-bar frame height is assumed to // be floating. - return frame.height() <= mSystemWindows.mDisplayController.getDisplayLayout(mDisplayId) + return frame.height() <= mDisplayController.getDisplayLayout(mDisplayId) .navBarFrameHeight(); } @@ -297,8 +298,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged // This is a "floating" or "expanded" IME, so to get animations, just // pretend the ime has some size just below the screen. mImeFrame.set(newFrame); - final int floatingInset = (int) ( - mSystemWindows.mDisplayController.getDisplayLayout(mDisplayId) + final int floatingInset = (int) (mDisplayController.getDisplayLayout(mDisplayId) .density() * FLOATING_IME_BOTTOM_INSET); mImeFrame.bottom -= floatingInset; } else if (newFrame.height() != 0) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java index 34018e8cb305..8abe9eeb6a9a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -60,11 +60,10 @@ public class SystemWindows { private static final String TAG = "SystemWindows"; private final SparseArray<PerDisplay> mPerDisplay = new SparseArray<>(); - final HashMap<View, SurfaceControlViewHost> mViewRoots = new HashMap<>(); - public Context mContext; - public IWindowManager mWmService; - IWindowSession mSession; - DisplayController mDisplayController; + private final HashMap<View, SurfaceControlViewHost> mViewRoots = new HashMap<>(); + private final DisplayController mDisplayController; + private final IWindowManager mWmService; + private IWindowSession mSession; private final DisplayController.OnDisplaysChangedListener mDisplayListener = new DisplayController.OnDisplaysChangedListener() { @@ -84,9 +83,7 @@ public class SystemWindows { public void onDisplayRemoved(int displayId) { } }; - public SystemWindows(Context context, DisplayController displayController, - IWindowManager wmService) { - mContext = context; + public SystemWindows(DisplayController displayController, IWindowManager wmService) { mWmService = wmService; mDisplayController = displayController; mDisplayController.addDisplayWindowListener(mDisplayListener); @@ -210,8 +207,8 @@ public class SystemWindows { } final Display display = mDisplayController.getDisplay(mDisplayId); SurfaceControlViewHost viewRoot = - new SurfaceControlViewHost(mContext, display, wwm, - true /* useSfChoreographer */); + new SurfaceControlViewHost( + view.getContext(), display, wwm, true /* useSfChoreographer */); attrs.flags |= FLAG_HARDWARE_ACCELERATED; viewRoot.setView(view, attrs); mViewRoots.put(view, viewRoot); @@ -313,7 +310,7 @@ public class SystemWindows { } } - class ContainerWindow extends IWindow.Stub { + static class ContainerWindow extends IWindow.Stub { ContainerWindow() {} @Override diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index c0a24438987a..1a89cfd5d0ad 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -209,11 +209,8 @@ static SkImageInfo validateAlpha(const SkImageInfo& info) { void Bitmap::reconfigure(const SkImageInfo& newInfo, size_t rowBytes) { mInfo = validateAlpha(newInfo); - // Dirty hack is dirty - // TODO: Figure something out here, Skia's current design makes this - // really hard to work with. Skia really, really wants immutable objects, - // but with the nested-ref-count hackery going on that's just not - // feasible without going insane trying to figure it out + // TODO: Skia intends for SkPixelRef to be immutable, but this method + // modifies it. Find another way to support reusing the same pixel memory. this->android_only_reset(mInfo.width(), mInfo.height(), rowBytes); } diff --git a/location/java/android/location/GpsStatus.java b/location/java/android/location/GpsStatus.java index 496885cd1f37..997339eb2a80 100644 --- a/location/java/android/location/GpsStatus.java +++ b/location/java/android/location/GpsStatus.java @@ -151,6 +151,16 @@ public final class GpsStatus { return status; } + /** + * Builds an empty GpsStatus. Should only be used for legacy reasons. + * + * @hide + */ + @NonNull + static GpsStatus createEmpty() { + return new GpsStatus(); + } + private GpsStatus() { } diff --git a/location/java/android/location/ILocationListener.aidl b/location/java/android/location/ILocationListener.aidl index 6e7f6a52d669..29b483af8721 100644 --- a/location/java/android/location/ILocationListener.aidl +++ b/location/java/android/location/ILocationListener.aidl @@ -24,6 +24,6 @@ import android.os.IRemoteCallback; */ oneway interface ILocationListener { - void onLocationChanged(in Location location, in IRemoteCallback onCompleteCallback); + void onLocationChanged(in Location location, in @nullable IRemoteCallback onCompleteCallback); void onProviderEnabledChanged(String provider, boolean enabled); } diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index bb8f81dfaa32..0e7eaa21888e 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -113,7 +113,7 @@ interface ILocationManager List<LocationRequest> getTestProviderCurrentRequests(String provider); LocationTime getGnssTimeMillis(); - boolean sendExtraCommand(String provider, String command, inout Bundle extras); + void sendExtraCommand(String provider, String command, inout Bundle extras); // used by gts tests to verify whitelists String[] getBackgroundThrottlingWhitelist(); diff --git a/location/java/android/location/LocationListener.java b/location/java/android/location/LocationListener.java index 8df08345c79b..2738ff4ff38c 100644 --- a/location/java/android/location/LocationListener.java +++ b/location/java/android/location/LocationListener.java @@ -36,7 +36,9 @@ import android.os.Bundle; public interface LocationListener { /** - * Called when the location has changed. + * Called when the location has changed. A wakelock is held on behalf on the listener for some + * brief amount of time as this callback executes. If this callback performs long running + * operations, it is the client's responsibility to obtain their own wakelock. * * @param location the updated location */ @@ -52,18 +54,17 @@ public interface LocationListener { default void onStatusChanged(String provider, int status, Bundle extras) {} /** - * Called when the provider is enabled by the user. + * Called when a provider this listener is registered with becomes enabled. * - * @param provider the name of the location provider that has become enabled + * @param provider the name of the location provider */ default void onProviderEnabled(@NonNull String provider) {} /** - * Called when the provider is disabled by the user. If requestLocationUpdates - * is called on an already disabled provider, this method is called - * immediately. + * Called when the provider this listener is registered with becomes disabled. If a provider is + * disabled when this listener is registered, this callback will be invoked immediately. * - * @param provider the name of the location provider that has become disabled + * @param provider the name of the location provider */ default void onProviderDisabled(@NonNull String provider) {} } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index c0b8e1bf3bbe..1803027743f6 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -303,7 +303,6 @@ public class LocationManager { public static final String METADATA_SETTINGS_FOOTER_STRING = "com.android.settings.location.FOOTER_STRING"; - private static final long MAX_SINGLE_LOCATION_TIMEOUT_MS = 30 * 1000; @GuardedBy("sLocationListeners") @@ -311,7 +310,9 @@ public class LocationManager { sLocationListeners = new WeakHashMap<>(); final Context mContext; - @UnsupportedAppUsage + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link " + + "LocationManager}") final ILocationManager mService; private final Object mLock = new Object(); @@ -421,8 +422,7 @@ public class LocationManager { try { return mService.getExtraLocationControllerPackage(); } catch (RemoteException e) { - e.rethrowFromSystemServer(); - return null; + throw e.rethrowFromSystemServer(); } } @@ -437,7 +437,7 @@ public class LocationManager { try { mService.setExtraLocationControllerPackage(packageName); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -452,7 +452,7 @@ public class LocationManager { try { mService.setExtraLocationControllerPackageEnabled(enabled); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -466,8 +466,7 @@ public class LocationManager { try { return mService.isExtraLocationControllerPackageEnabled(); } catch (RemoteException e) { - e.rethrowFromSystemServer(); - return false; + throw e.rethrowFromSystemServer(); } } @@ -485,7 +484,7 @@ public class LocationManager { try { mService.setExtraLocationControllerPackage(packageName); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -503,7 +502,7 @@ public class LocationManager { try { mService.setExtraLocationControllerPackageEnabled(enabled); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -1199,7 +1198,7 @@ public class LocationManager { mContext.getPackageName(), mContext.getAttributionTag(), AppOpsManager.toReceiverId(listener)); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } } @@ -1301,7 +1300,7 @@ public class LocationManager { // unregistration is complete. mService.unregisterLocationListener(transport); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } } @@ -1517,7 +1516,8 @@ public class LocationManager { Preconditions.checkArgument(command != null, "invalid null command"); try { - return mService.sendExtraCommand(provider, command, extras); + mService.sendExtraCommand(provider, command, extras); + return true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1835,6 +1835,10 @@ public class LocationManager { } else { status.setStatus(gnssStatus, ttff); } + } else if (status == null) { + // even though this method is marked as nullable, legacy behavior was to never return + // a null result, and there are applications that rely on this behavior. + status = GpsStatus.createEmpty(); } return status; } @@ -2424,7 +2428,7 @@ public class LocationManager { try { cancellationSignal.cancel(); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } } @@ -2464,7 +2468,8 @@ public class LocationManager { } @Override - public void onLocationChanged(Location location, IRemoteCallback onCompleteCallback) { + public void onLocationChanged(Location location, + @Nullable IRemoteCallback onCompleteCallback) { executeSafely(mExecutor, () -> mListener, new ListenerOperation<LocationListener>() { @Override public void operate(LocationListener listener) { @@ -2473,7 +2478,13 @@ public class LocationManager { @Override public void onComplete(boolean success) { - markComplete(onCompleteCallback); + if (onCompleteCallback != null) { + try { + onCompleteCallback.sendResult(null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } }); } @@ -2488,14 +2499,6 @@ public class LocationManager { } }); } - - private void markComplete(IRemoteCallback onCompleteCallback) { - try { - onCompleteCallback.sendResult(null); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - } } private static class NmeaAdapter extends GnssStatus.Callback implements OnNmeaMessageListener { diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 6d690f0aa397..4a6724a09c1e 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -1411,9 +1411,9 @@ public class ExifInterface { private static final int IMAGE_TYPE_WEBP = 14; static { - sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); + sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", Locale.US); sFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); - sFormatterTz = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss XXX"); + sFormatterTz = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss XXX", Locale.US); sFormatterTz.setTimeZone(TimeZone.getTimeZone("UTC")); // Build up the hash tables to look up Exif tags for reading Exif tags. diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java index 3c2be5f93e30..029e61492b6d 100644 --- a/media/java/android/media/browse/MediaBrowser.java +++ b/media/java/android/media/browse/MediaBrowser.java @@ -25,7 +25,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ParceledListSlice; import android.media.MediaDescription; -import android.media.session.MediaController; import android.media.session.MediaSession; import android.os.Binder; import android.os.Bundle; @@ -757,8 +756,8 @@ public final class MediaBrowser { * Flag: Indicates that the item is playable. * <p> * The id of this item may be passed to - * {@link MediaController.TransportControls#playFromMediaId(String, Bundle)} - * to start playing it. + * {@link android.media.session.MediaController.TransportControls + * #playFromMediaId(String, Bundle)} to start playing it. * </p> */ public static final int FLAG_PLAYABLE = 1 << 1; @@ -1107,13 +1106,7 @@ public final class MediaBrowser { } @Override - public void onLoadChildren(String parentId, ParceledListSlice list) { - onLoadChildrenWithOptions(parentId, list, null); - } - - @Override - public void onLoadChildrenWithOptions(String parentId, ParceledListSlice list, - final Bundle options) { + public void onLoadChildren(String parentId, ParceledListSlice list, Bundle options) { MediaBrowser mediaBrowser = mMediaBrowser.get(); if (mediaBrowser != null) { mediaBrowser.onLoadChildren(this, parentId, list, options); diff --git a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl index 7e3f2f8868fb..a8772076af97 100644 --- a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl +++ b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl @@ -23,7 +23,5 @@ oneway interface IMediaBrowserServiceCallbacks { void onConnect(String root, in MediaSession.Token session, in Bundle extras); @UnsupportedAppUsage void onConnectFailed(); - void onLoadChildren(String mediaId, in ParceledListSlice list); - void onLoadChildrenWithOptions(String mediaId, in ParceledListSlice list, - in Bundle options); + void onLoadChildren(String mediaId, in ParceledListSlice list, in Bundle options); } diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java index 06adf30a8303..39c7682a2a74 100644 --- a/media/java/android/service/media/MediaBrowserService.java +++ b/media/java/android/service/media/MediaBrowserService.java @@ -687,7 +687,7 @@ public abstract class MediaBrowserService extends Service { final ParceledListSlice<MediaBrowser.MediaItem> pls = filteredList == null ? null : new ParceledListSlice<>(filteredList); try { - connection.callbacks.onLoadChildrenWithOptions(parentId, pls, options); + connection.callbacks.onLoadChildren(parentId, pls, options); } catch (RemoteException ex) { // The other side is in the process of crashing. Log.w(TAG, "Calling onLoadChildren() failed for id=" + parentId diff --git a/non-updatable-api/removed.txt b/non-updatable-api/removed.txt index ba05a1b89988..f2dfb84eb8fe 100644 --- a/non-updatable-api/removed.txt +++ b/non-updatable-api/removed.txt @@ -1,10 +1,6 @@ // Signature format: 2.0 package android.app { - public class ActivityManager { - method @Deprecated public static int getMaxNumPictureInPictureActions(); - } - public class Notification implements android.os.Parcelable { method @Deprecated public String getChannel(); method public static Class<? extends android.app.Notification.Style> getNotificationStyleClass(String); diff --git a/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml b/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml index 0a294246dfaa..09fbf7a59a8c 100644 --- a/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml +++ b/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml @@ -15,7 +15,6 @@ ~ limitations under the License. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:fitsSystemWindows="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml index 2dc499c160c6..2c9788955bfa 100644 --- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml +++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml @@ -22,12 +22,10 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <!-- TODO(b/151617493): replace marginBottom with insets. --> <ViewStub android:id="@+id/notification_panel_stub" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout="@layout/notification_panel_container" - android:layout_marginBottom="@dimen/navigation_bar_height"/> + android:layout="@layout/notification_panel_container"/> <ViewStub android:id="@+id/keyguard_stub" android:layout_width="match_parent" diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java index 8accdd660f65..7b6dceb5fcd7 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java @@ -128,9 +128,9 @@ public abstract class CarSystemUIModule { @Singleton @Provides - static SystemWindows provideSystemWindows(Context context, DisplayController displayController, + static SystemWindows provideSystemWindows(DisplayController displayController, IWindowManager wmService) { - return new SystemWindows(context, displayController, wmService); + return new SystemWindows(displayController, wmService); } @Singleton diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java index 69766cc6c0d0..51a7245ea5c6 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java @@ -141,6 +141,11 @@ public class CarKeyguardViewController extends OverlayViewController implements } @Override + protected boolean shouldShowNavigationBar() { + return true; + } + + @Override public void onFinishInflate() { mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext, mViewMediatorCallback, mLockPatternUtils, diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java index 1eead62c042a..8d5843635e5f 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java @@ -16,6 +16,8 @@ package com.android.systemui.car.notification; +import static android.view.WindowInsets.Type.navigationBars; + import android.app.ActivityManager; import android.car.Car; import android.car.drivingstate.CarUxRestrictionsManager; @@ -197,6 +199,16 @@ public class NotificationPanelViewController extends OverlayPanelViewController } @Override + protected boolean shouldShowStatusBar() { + return true; + } + + @Override + protected int getInsetTypesToFit() { + return navigationBars(); + } + + @Override protected boolean shouldShowHUN() { return mEnableHeadsUpNotificationWhenNotificationShadeOpen; } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java index 45f3d342fb6e..0d77c1341ffb 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java @@ -91,6 +91,11 @@ public class UserSwitchTransitionViewController extends OverlayViewController { R.integer.config_userSwitchTransitionViewShownTimeoutMs); } + @Override + protected int getInsetTypesToFit() { + return 0; + } + /** * Makes the user switch transition view appear and draws the content inside of it if a user * that is different from the previous user is provided and if the dialog is not already diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java index 3969f92c690a..53deb9d9dc5d 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java @@ -16,9 +16,12 @@ package com.android.systemui.car.window; +import static android.view.WindowInsets.Type.statusBars; + import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; +import android.view.WindowInsets; /** * Owns a {@link View} that is present in SystemUIOverlayWindow. @@ -140,9 +143,25 @@ public class OverlayViewController { } /** + * Returns {@code true} if status bar should be displayed over this view. + */ + protected boolean shouldShowStatusBar() { + return false; + } + + /** * Returns {@code true} if this view should be hidden during the occluded state. */ protected boolean shouldShowWhenOccluded() { return false; } + + /** + * Returns the insets types to fit to the sysui overlay window when this + * {@link OverlayViewController} is in the foreground. + */ + @WindowInsets.Type.InsetsType + protected int getInsetTypesToFit() { + return statusBars(); + } } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java index 8e9410964313..2494242c24f0 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java @@ -16,13 +16,17 @@ package com.android.systemui.car.window; +import static android.view.WindowInsets.Type.navigationBars; +import static android.view.WindowInsets.Type.statusBars; +import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + import android.annotation.Nullable; import android.util.Log; +import android.view.WindowInsets.Type.InsetsType; +import android.view.WindowInsetsController; import androidx.annotation.VisibleForTesting; -import com.android.systemui.car.navigationbar.CarNavigationBarController; - import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -48,10 +52,7 @@ public class OverlayViewGlobalStateController { private static final String TAG = OverlayViewGlobalStateController.class.getSimpleName(); private static final int UNKNOWN_Z_ORDER = -1; private final SystemUIOverlayWindowController mSystemUIOverlayWindowController; - private final CarNavigationBarController mCarNavigationBarController; - - private boolean mIsOccluded; - + private final WindowInsetsController mWindowInsetsController; @VisibleForTesting Map<OverlayViewController, Integer> mZOrderMap; @VisibleForTesting @@ -60,14 +61,15 @@ public class OverlayViewGlobalStateController { Set<OverlayViewController> mViewsHiddenForOcclusion; @VisibleForTesting OverlayViewController mHighestZOrder; + private boolean mIsOccluded; @Inject public OverlayViewGlobalStateController( - CarNavigationBarController carNavigationBarController, SystemUIOverlayWindowController systemUIOverlayWindowController) { mSystemUIOverlayWindowController = systemUIOverlayWindowController; mSystemUIOverlayWindowController.attach(); - mCarNavigationBarController = carNavigationBarController; + mWindowInsetsController = + mSystemUIOverlayWindowController.getBaseLayout().getWindowInsetsController(); mZOrderMap = new HashMap<>(); mZOrderVisibleSortedMap = new TreeMap<>(); mViewsHiddenForOcclusion = new HashSet<>(); @@ -115,7 +117,9 @@ public class OverlayViewGlobalStateController { } updateInternalsWhenShowingView(viewController); + refreshInsetTypesToFit(); refreshNavigationBarVisibility(); + refreshStatusBarVisibility(); Log.d(TAG, "Content shown: " + viewController.getClass().getName()); debugLog(); @@ -185,7 +189,9 @@ public class OverlayViewGlobalStateController { mZOrderVisibleSortedMap.remove(mZOrderMap.get(viewController)); refreshHighestZOrderWhenHidingView(viewController); + refreshInsetTypesToFit(); refreshNavigationBarVisibility(); + refreshStatusBarVisibility(); if (mZOrderVisibleSortedMap.isEmpty()) { setWindowVisible(false); @@ -208,10 +214,28 @@ public class OverlayViewGlobalStateController { } private void refreshNavigationBarVisibility() { + mWindowInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); if (mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowNavigationBar()) { - mCarNavigationBarController.showBars(); + mWindowInsetsController.show(navigationBars()); } else { - mCarNavigationBarController.hideBars(); + mWindowInsetsController.hide(navigationBars()); + } + } + + private void refreshStatusBarVisibility() { + mWindowInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); + if (mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowStatusBar()) { + mWindowInsetsController.show(statusBars()); + } else { + mWindowInsetsController.hide(statusBars()); + } + } + + private void refreshInsetTypesToFit() { + if (mZOrderVisibleSortedMap.isEmpty()) { + setFitInsetsTypes(statusBars()); + } else { + setFitInsetsTypes(mHighestZOrder.getInsetTypesToFit()); } } @@ -224,6 +248,10 @@ public class OverlayViewGlobalStateController { mSystemUIOverlayWindowController.setWindowVisible(visible); } + private void setFitInsetsTypes(@InsetsType int types) { + mSystemUIOverlayWindowController.setFitInsetsTypes(types); + } + /** * Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the * sysui overlay window. diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java index bcd96f63a2b4..029bd3702afe 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java @@ -25,6 +25,7 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; import com.android.systemui.R; @@ -99,7 +100,6 @@ public class SystemUIOverlayWindowController implements PixelFormat.TRANSLUCENT); mLp.token = new Binder(); mLp.gravity = Gravity.TOP; - mLp.setFitInsetsTypes(/* types= */ 0); mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; mLp.setTitle("SystemUIOverlayWindow"); mLp.packageName = mContext.getPackageName(); @@ -110,6 +110,12 @@ public class SystemUIOverlayWindowController implements setWindowVisible(false); } + /** Sets the types of insets to fit. Note: This should be rarely used. */ + public void setFitInsetsTypes(@WindowInsets.Type.InsetsType int types) { + mLpChanged.setFitInsetsTypes(types); + updateWindow(); + } + /** Sets the window to the visible state. */ public void setWindowVisible(boolean visible) { mVisible = visible; diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java index 63f8c72b354a..5c80202ba592 100644 --- a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java +++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java @@ -16,12 +16,14 @@ package com.android.systemui.wm; +import android.content.Context; import android.os.Handler; import android.os.RemoteException; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.view.IDisplayWindowInsetsController; +import android.view.IWindowManager; import android.view.InsetsController; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -32,7 +34,6 @@ import androidx.annotation.VisibleForTesting; import com.android.systemui.dagger.qualifiers.Main; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; -import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TransactionPool; import javax.inject.Inject; @@ -50,29 +51,32 @@ public class DisplaySystemBarsController extends DisplayImeController { private static final String TAG = "DisplaySystemBarsController"; private SparseArray<PerDisplay> mPerDisplaySparseArray; + private final Context mContext; @Inject public DisplaySystemBarsController( - SystemWindows syswin, + Context context, + IWindowManager wmService, DisplayController displayController, @Main Handler mainHandler, TransactionPool transactionPool) { - super(syswin, displayController, mainHandler, transactionPool); + super(wmService, displayController, mainHandler, transactionPool); + mContext = context; } @Override public void onDisplayAdded(int displayId) { PerDisplay pd = new PerDisplay(displayId); try { - mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd); + mWmService.setDisplayWindowInsetsController(displayId, pd); } catch (RemoteException e) { Slog.w(TAG, "Unable to set insets controller on display " + displayId); } // Lazy loading policy control filters instead of during boot. if (mPerDisplaySparseArray == null) { mPerDisplaySparseArray = new SparseArray<>(); - BarControlPolicy.reloadFromSetting(mSystemWindows.mContext); - BarControlPolicy.registerContentObserver(mSystemWindows.mContext, mHandler, () -> { + BarControlPolicy.reloadFromSetting(mContext); + BarControlPolicy.registerContentObserver(mContext, mHandler, () -> { int size = mPerDisplaySparseArray.size(); for (int i = 0; i < size; i++) { mPerDisplaySparseArray.valueAt(i).modifyDisplayWindowInsets(); @@ -85,7 +89,7 @@ public class DisplaySystemBarsController extends DisplayImeController { @Override public void onDisplayRemoved(int displayId) { try { - mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null); + mWmService.setDisplayWindowInsetsController(displayId, null); } catch (RemoteException e) { Slog.w(TAG, "Unable to remove insets controller on display " + displayId); } @@ -155,7 +159,7 @@ public class DisplaySystemBarsController extends DisplayImeController { showInsets(barVisibilities[0], /* fromIme= */ false); hideInsets(barVisibilities[1], /* fromIme= */ false); try { - mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); + mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); } catch (RemoteException e) { Slog.w(TAG, "Unable to update window manager service."); } diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java index 20f9bc8ec1cb..ff286650ea50 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java @@ -16,9 +16,14 @@ package com.android.systemui.car.window; +import static android.view.WindowInsets.Type.navigationBars; +import static android.view.WindowInsets.Type.statusBars; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -28,19 +33,18 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; +import android.view.WindowInsetsController; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.car.CarSystemUiTest; -import com.android.systemui.car.navigationbar.CarNavigationBarController; import com.android.systemui.tests.R; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.Arrays; @@ -58,8 +62,6 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { private ViewGroup mBaseLayout; @Mock - private CarNavigationBarController mCarNavigationBarController; - @Mock private SystemUIOverlayWindowController mSystemUIOverlayWindowController; @Mock private OverlayViewMediator mOverlayViewMediator; @@ -71,18 +73,22 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { private OverlayPanelViewController mOverlayPanelViewController; @Mock private Runnable mRunnable; + @Mock + private WindowInsetsController mWindowInsetsController; @Before public void setUp() { MockitoAnnotations.initMocks(/* testClass= */ this); - mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate( - R.layout.overlay_view_global_state_controller_test, /* root= */ null); + mBaseLayout = spy((ViewGroup) LayoutInflater.from(mContext).inflate( + R.layout.overlay_view_global_state_controller_test, /* root= */ null)); + + when(mBaseLayout.getWindowInsetsController()).thenReturn(mWindowInsetsController); when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout); mOverlayViewGlobalStateController = new OverlayViewGlobalStateController( - mCarNavigationBarController, mSystemUIOverlayWindowController); + mSystemUIOverlayWindowController); verify(mSystemUIOverlayWindowController).attach(); } @@ -108,7 +114,7 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).hideBars(); + verify(mWindowInsetsController).hide(navigationBars()); } @Test @@ -118,7 +124,37 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).showBars(); + verify(mWindowInsetsController).show(navigationBars()); + } + + @Test + public void showView_nothingAlreadyShown_shouldShowStatusBarFalse_statusBarsHidden() { + setupOverlayViewController1(); + when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(false); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).hide(statusBars()); + } + + @Test + public void showView_nothingAlreadyShown_shouldShowStatusBarTrue_statusBarsShown() { + setupOverlayViewController1(); + when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(true); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).show(statusBars()); + } + + @Test + public void showView_nothingAlreadyShown_fitsNavBarInsets_insetsAdjusted() { + setupOverlayViewController1(); + when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(navigationBars()); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars()); } @Test @@ -168,10 +204,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { setOverlayViewControllerAsShowing(mOverlayViewController1); setupOverlayViewController2(); when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false); + reset(mWindowInsetsController); mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); - verify(mCarNavigationBarController).hideBars(); + verify(mWindowInsetsController).hide(navigationBars()); } @Test @@ -183,7 +220,46 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); - verify(mCarNavigationBarController).showBars(); + verify(mWindowInsetsController).show(navigationBars()); + } + + @Test + public void showView_newHighestZOrder_shouldShowStatusBarFalse_statusBarsHidden() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(false); + reset(mWindowInsetsController); + + mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); + + verify(mWindowInsetsController).hide(statusBars()); + } + + @Test + public void showView_newHighestZOrder_shouldShowStatusBarTrue_statusBarsShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(true); + + mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); + + verify(mWindowInsetsController).show(statusBars()); + } + + @Test + public void showView_newHighestZOrder_fitsNavBarInsets_insetsAdjusted() { + setupOverlayViewController1(); + when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(statusBars()); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + when(mOverlayViewController2.getInsetTypesToFit()).thenReturn(navigationBars()); + reset(mWindowInsetsController); + + mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); + + verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars()); } @Test @@ -216,10 +292,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { setOverlayViewControllerAsShowing(mOverlayViewController2); when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true); when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false); + reset(mWindowInsetsController); mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).hideBars(); + verify(mWindowInsetsController).hide(navigationBars()); } @Test @@ -231,7 +308,44 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).showBars(); + verify(mWindowInsetsController).show(navigationBars()); + } + + @Test + public void showView_oldHighestZOrder_shouldShowStatusBarFalse_statusBarsHidden() { + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(true); + when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(false); + reset(mWindowInsetsController); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).hide(statusBars()); + } + + @Test + public void showView_oldHighestZOrder_shouldShowStatusBarTrue_statusBarsShown() { + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(false); + when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(true); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).show(statusBars()); + } + + @Test + public void showView_oldHighestZOrder_fitsNavBarInsets_insetsAdjusted() { + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(statusBars()); + when(mOverlayViewController2.getInsetTypesToFit()).thenReturn(navigationBars()); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars()); } @Test @@ -402,10 +516,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { setupOverlayViewController2(); setOverlayViewControllerAsShowing(mOverlayViewController2); when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false); + reset(mWindowInsetsController); mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); - verify(mCarNavigationBarController).hideBars(); + verify(mWindowInsetsController).hide(navigationBars()); } @Test @@ -418,7 +533,48 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); - verify(mCarNavigationBarController).showBars(); + verify(mWindowInsetsController).show(navigationBars()); + } + + @Test + public void hideView_newHighestZOrder_shouldShowStatusBarFalse_statusBarHidden() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(false); + reset(mWindowInsetsController); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); + + verify(mWindowInsetsController).hide(statusBars()); + } + + @Test + public void hideView_newHighestZOrder_shouldShowStatusBarTrue_statusBarShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(true); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); + + verify(mWindowInsetsController).show(statusBars()); + } + + @Test + public void hideView_newHighestZOrder_fitsNavBarInsets_insetsAdjusted() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(navigationBars()); + when(mOverlayViewController2.getInsetTypesToFit()).thenReturn(statusBars()); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); + + verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars()); } @Test @@ -441,10 +597,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { setupOverlayViewController2(); setOverlayViewControllerAsShowing(mOverlayViewController2); when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false); + reset(mWindowInsetsController); mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).hideBars(); + verify(mWindowInsetsController).hide(navigationBars()); } @Test @@ -457,7 +614,48 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).showBars(); + verify(mWindowInsetsController).show(navigationBars()); + } + + @Test + public void hideView_oldHighestZOrder_shouldShowStatusBarFalse_statusBarHidden() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(false); + reset(mWindowInsetsController); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).hide(statusBars()); + } + + @Test + public void hideView_oldHighestZOrder_shouldShowStatusBarTrue_statusBarShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(true); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).show(statusBars()); + } + + @Test + public void hideView_oldHighestZOrder_fitsNavBarInsets_insetsAdjusted() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(statusBars()); + when(mOverlayViewController2.getInsetTypesToFit()).thenReturn(navigationBars()); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars()); } @Test @@ -479,7 +677,27 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).showBars(); + verify(mWindowInsetsController).show(navigationBars()); + } + + @Test + public void hideView_viewControllerOnlyShown_statusBarShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).show(statusBars()); + } + + @Test + public void hideView_viewControllerOnlyShown_insetsAdjustedToDefault() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + verify(mSystemUIOverlayWindowController).setFitInsetsTypes(statusBars()); } @Test @@ -615,7 +833,7 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { private void setOverlayViewControllerAsShowing(OverlayViewController overlayViewController) { mOverlayViewGlobalStateController.showView(overlayViewController, /* show= */ null); - Mockito.reset(mCarNavigationBarController, mSystemUIOverlayWindowController); + reset(mSystemUIOverlayWindowController); when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout); } } diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java index 0f28d38f7878..391f75e35382 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java @@ -34,7 +34,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TransactionPool; import org.junit.Before; @@ -53,8 +52,6 @@ public class DisplaySystemBarsControllerTest extends SysuiTestCase { private static final int DISPLAY_ID = 1; @Mock - private SystemWindows mSystemWindows; - @Mock private IWindowManager mIWindowManager; @Mock private DisplayController mDisplayController; @@ -66,11 +63,10 @@ public class DisplaySystemBarsControllerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mSystemWindows.mContext = mContext; - mSystemWindows.mWmService = mIWindowManager; mController = new DisplaySystemBarsController( - mSystemWindows, + mContext, + mIWindowManager, mDisplayController, mHandler, mTransactionPool diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java index 16ef59f201f1..02f4457bffdb 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java @@ -29,6 +29,7 @@ import android.text.Html; import android.util.Log; import android.view.Gravity; import android.view.View; +import android.widget.AdapterView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; @@ -75,6 +76,14 @@ public class DeviceChooserActivity extends Activity { mDeviceListView = findViewById(R.id.device_list); final DeviceDiscoveryService.DevicesAdapter adapter = getService().mDevicesAdapter; mDeviceListView.setAdapter(adapter); + mDeviceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> adapterView, View view, int pos, long l) { + getService().mSelectedDevice = + (DeviceFilterPair) adapterView.getItemAtPosition(pos); + adapter.notifyDataSetChanged(); + } + }); adapter.registerDataSetObserver(new DataSetObserver() { @Override public void onChanged() { @@ -157,4 +166,4 @@ public class DeviceChooserActivity extends Activity { new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice.device)); finish(); } -}
\ No newline at end of file +} diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java index 7aa997e39307..bcaee367b03c 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java @@ -349,10 +349,6 @@ public class DeviceDiscoveryService extends Service { ? WIFI_ICON : BLUETOOTH_ICON, null, null, null); - textView.setOnClickListener((view) -> { - mSelectedDevice = device; - notifyDataSetChanged(); - }); } //TODO move to a layout file diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index d480ff63d8e3..508cbfccffe9 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -140,8 +140,8 @@ <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string> <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string> - <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Aplicações removidas"</string> - <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Aplicações e utilizadores removidos"</string> + <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Apps removidas"</string> + <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Apps e utilizadores removidos"</string> <string name="data_usage_ota" msgid="7984667793701597001">"Atualizações do sistema"</string> <string name="tether_settings_title_usb" msgid="3728686573430917722">"Ligação USB"</string> <string name="tether_settings_title_wifi" msgid="4803402057533895526">"Hotspot portátil"</string> @@ -365,7 +365,7 @@ <string name="transition_animation_scale_title" msgid="1278477690695439337">"Escala de animação de transição"</string> <string name="animator_duration_scale_title" msgid="7082913931326085176">"Escala de duração de animação"</string> <string name="overlay_display_devices_title" msgid="5411894622334469607">"Simular apresentações secundárias"</string> - <string name="debug_applications_category" msgid="5394089406638954196">"Aplicações"</string> + <string name="debug_applications_category" msgid="5394089406638954196">"Apps"</string> <string name="immediately_destroy_activities" msgid="1826287490705167403">"Não manter atividades"</string> <string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"Destruir atividades assim que o utilizador sair"</string> <string name="app_process_limit_title" msgid="8361367869453043007">"Limite do processo em 2º plano"</string> @@ -396,7 +396,7 @@ <item msgid="4548987861791236754">"Cores naturais e realistas"</item> <item msgid="1282170165150762976">"Cores otimizadas para conteúdos digitais"</item> </string-array> - <string name="inactive_apps_title" msgid="5372523625297212320">"Aplicações em espera"</string> + <string name="inactive_apps_title" msgid="5372523625297212320">"Apps em espera"</string> <string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Inativo. Toque para ativar/desativar."</string> <string name="inactive_app_active_summary" msgid="8047630990208722344">"Ativo. Toque para ativar/desativar."</string> <string name="standby_bucket_summary" msgid="5128193447550429600">"Estado do Modo de espera das apps:<xliff:g id="BUCKET"> %s</xliff:g>"</string> diff --git a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml index 87dd58e4f0ed..213bb923db65 100644 --- a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml +++ b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.bubbles.BubbleManageEducationView +<com.android.systemui.bubbles.ManageEducationView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" @@ -87,4 +87,4 @@ /> </LinearLayout> </LinearLayout> -</com.android.systemui.bubbles.BubbleManageEducationView> +</com.android.systemui.bubbles.ManageEducationView> diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/DisplayIdIndexSupplier.java b/packages/SystemUI/src/com/android/systemui/accessibility/DisplayIdIndexSupplier.java new file mode 100644 index 000000000000..769a344eedac --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/DisplayIdIndexSupplier.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility; + +import android.annotation.Nullable; +import android.hardware.display.DisplayManager; +import android.util.SparseArray; +import android.view.Display; + +import androidx.annotation.NonNull; + +/** + * Supplies the instance with given display Id. It generates a new instance if the corresponding + * one is not existed. It should run in single thread to avoid race conditions. + * + * @param <T> the type of results supplied by {@link #createInstance(Display)}. + */ +abstract class DisplayIdIndexSupplier<T> { + + private final SparseArray<T> mSparseArray = new SparseArray<>(); + private final DisplayManager mDisplayManager; + + /** + * @param displayManager DisplayManager + */ + DisplayIdIndexSupplier(DisplayManager displayManager) { + mDisplayManager = displayManager; + } + + /** + * @param displayId the logical display Id + * @return {@code null} if the given display id is invalid + */ + @Nullable + public T get(int displayId) { + T instance = mSparseArray.get(displayId); + if (instance != null) { + return instance; + } + final Display display = mDisplayManager.getDisplay(displayId); + if (display == null) { + return null; + } + instance = createInstance(display); + mSparseArray.put(displayId, instance); + return instance; + } + + @NonNull + protected abstract T createInstance(Display display); + + /** + * Removes the instance with given display Id. + * + * @param displayId the logical display id + */ + public void remove(int displayId) { + mSparseArray.remove(displayId); + } + + /** + * Clears all elements. + */ + public void clear() { + mSparseArray.clear(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java index 398a2c9c9d41..68a0a65ef50f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java @@ -30,6 +30,7 @@ import com.android.systemui.R; /** * Shows/hides a {@link android.widget.ImageView} on the screen and changes the values of * {@link Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE} when the UI is toggled. + * The button UI would automatically be dismissed after displaying for a period of time. */ class MagnificationModeSwitch { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java index e73ff13ceac1..ffc70bcf63d0 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java @@ -19,31 +19,33 @@ package com.android.systemui.accessibility; import android.annotation.MainThread; import android.content.Context; import android.hardware.display.DisplayManager; -import android.util.Log; -import android.util.SparseArray; import android.view.Display; +import com.android.internal.annotations.VisibleForTesting; + import javax.inject.Singleton; /** - * Class to control magnification mode switch button. Shows the button UI when both full-screen - * and window magnification mode are capable, and when the magnification scale is changed. And - * the button UI would automatically be dismissed after displaying for a period of time. + * A class to control {@link MagnificationModeSwitch}. It should show the button UI with following + * conditions: + * <ol> + * <li> Both full-screen and window magnification mode are capable.</li> + * <li> The magnification scale is changed by a user.</li> + * <ol> */ @Singleton public class ModeSwitchesController { - private static final String TAG = "ModeSwitchesController"; - - private final Context mContext; - private final DisplayManager mDisplayManager; - - private final SparseArray<MagnificationModeSwitch> mDisplaysToSwitches = - new SparseArray<>(); + private final SwitchSupplier mSwitchSupplier; public ModeSwitchesController(Context context) { - mContext = context; - mDisplayManager = mContext.getSystemService(DisplayManager.class); + mSwitchSupplier = new SwitchSupplier(context, + context.getSystemService(DisplayManager.class)); + } + + @VisibleForTesting + ModeSwitchesController(SwitchSupplier switchSupplier) { + mSwitchSupplier = switchSupplier; } /** @@ -52,20 +54,17 @@ public class ModeSwitchesController { * * @param displayId The logical display id * @param mode The magnification mode - * * @see android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW * @see android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN */ @MainThread void showButton(int displayId, int mode) { - if (mDisplaysToSwitches.get(displayId) == null) { - final MagnificationModeSwitch magnificationModeSwitch = - createMagnificationSwitchController(displayId); - if (magnificationModeSwitch == null) { - return; - } + final MagnificationModeSwitch magnificationModeSwitch = + mSwitchSupplier.get(displayId); + if (magnificationModeSwitch == null) { + return; } - mDisplaysToSwitches.get(displayId).showButton(mode); + magnificationModeSwitch.showButton(mode); } /** @@ -74,30 +73,34 @@ public class ModeSwitchesController { * @param displayId The logical display id */ void removeButton(int displayId) { - if (mDisplaysToSwitches.get(displayId) == null) { + final MagnificationModeSwitch magnificationModeSwitch = + mSwitchSupplier.get(displayId); + if (magnificationModeSwitch == null) { return; } - mDisplaysToSwitches.get(displayId).removeButton(); + magnificationModeSwitch.removeButton(); } - private MagnificationModeSwitch createMagnificationSwitchController(int displayId) { - if (mDisplayManager.getDisplay(displayId) == null) { - Log.w(TAG, "createMagnificationSwitchController displayId is invalid."); - return null; + @VisibleForTesting + static class SwitchSupplier extends DisplayIdIndexSupplier<MagnificationModeSwitch> { + + private final Context mContext; + + /** + * @param context Context + * @param displayManager DisplayManager + */ + SwitchSupplier(Context context, DisplayManager displayManager) { + super(displayManager); + mContext = context; } - final MagnificationModeSwitch - magnificationModeSwitch = new MagnificationModeSwitch( - getDisplayContext(displayId)); - mDisplaysToSwitches.put(displayId, magnificationModeSwitch); - return magnificationModeSwitch; - } - private Context getDisplayContext(int displayId) { - final Display display = mDisplayManager.getDisplay(displayId); - final Context context = (displayId == Display.DEFAULT_DISPLAY) - ? mContext - : mContext.createDisplayContext(display); - return context; + @Override + protected MagnificationModeSwitch createInstance(Display display) { + final Context context = (display.getDisplayId() == Display.DEFAULT_DISPLAY) + ? mContext + : mContext.createDisplayContext(display); + return new MagnificationModeSwitch(context); + } } - } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 980e4c0fd333..361ea674cead 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -29,7 +29,6 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; import android.content.res.Configuration; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; @@ -39,12 +38,10 @@ import android.hardware.biometrics.PromptInfo; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; -import android.hardware.fingerprint.IFingerprintService; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.Log; import android.view.WindowManager; @@ -247,6 +244,10 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, IActivityTaskManager getActivityTaskManager() { return ActivityTaskManager.getService(); } + + FingerprintManager getFingerprintManager(Context context) { + return context.getSystemService(FingerprintManager.class); + } } @Inject @@ -273,7 +274,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mActivityTaskManager = mInjector.getActivityTaskManager(); - final FingerprintManager fpm = mContext.getSystemService(FingerprintManager.class); + final FingerprintManager fpm = mInjector.getFingerprintManager(mContext); if (fpm != null && fpm.isHardwareDetected()) { final List<FingerprintSensorProperties> fingerprintSensorProperties = fpm.getSensorProperties(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java deleted file mode 100644 index 9db371e487c7..000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.bubbles; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import androidx.dynamicanimation.animation.DynamicAnimation; -import androidx.dynamicanimation.animation.SpringAnimation; -import androidx.dynamicanimation.animation.SpringForce; - -import com.android.systemui.R; - -/** Dismiss view that contains a scrim gradient, as well as a dismiss icon, text, and circle. */ -public class BubbleDismissView extends FrameLayout { - /** Duration for animations involving the dismiss target text/icon. */ - private static final int DISMISS_TARGET_ANIMATION_BASE_DURATION = 150; - private static final float SCALE_FOR_POP = 1.2f; - private static final float SCALE_FOR_DISMISS = 0.9f; - - private LinearLayout mDismissTarget; - private ImageView mDismissIcon; - private View mDismissCircle; - - private SpringAnimation mDismissTargetAlphaSpring; - private SpringAnimation mDismissTargetVerticalSpring; - - public BubbleDismissView(Context context) { - super(context); - setVisibility(GONE); - - LayoutInflater.from(context).inflate(R.layout.bubble_dismiss_target, this, true); - mDismissTarget = findViewById(R.id.bubble_dismiss_icon_container); - mDismissIcon = findViewById(R.id.bubble_dismiss_close_icon); - mDismissCircle = findViewById(R.id.bubble_dismiss_circle); - - // Set up the basic target area animations. These are very simple animations that don't need - // fancy interpolators. - final AccelerateDecelerateInterpolator interpolator = - new AccelerateDecelerateInterpolator(); - mDismissIcon.animate() - .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION) - .setInterpolator(interpolator); - mDismissCircle.animate() - .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION / 2) - .setInterpolator(interpolator); - - mDismissTargetAlphaSpring = - new SpringAnimation(mDismissTarget, DynamicAnimation.ALPHA) - .setSpring(new SpringForce() - .setStiffness(SpringForce.STIFFNESS_LOW) - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); - mDismissTargetVerticalSpring = - new SpringAnimation(mDismissTarget, DynamicAnimation.TRANSLATION_Y) - .setSpring(new SpringForce() - .setStiffness(SpringForce.STIFFNESS_MEDIUM) - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); - - mDismissTargetAlphaSpring.addEndListener((anim, canceled, alpha, velocity) -> { - // Since DynamicAnimations end when they're 'nearly' done, we can't rely on alpha being - // exactly zero when this listener is triggered. However, if it's less than 50% we can - // safely assume it was animating out rather than in. - if (alpha < 0.5f) { - // If the alpha spring was animating the view out, set it to GONE when it's done. - setVisibility(INVISIBLE); - } - }); - } - - /** Springs in the dismiss target. */ - void springIn() { - setVisibility(View.VISIBLE); - - // Fade in the dismiss target icon. - mDismissIcon.animate() - .setDuration(50) - .scaleX(1f) - .scaleY(1f) - .alpha(1f); - mDismissTarget.setAlpha(0f); - mDismissTargetAlphaSpring.animateToFinalPosition(1f); - - // Spring up the dismiss target. - mDismissTarget.setTranslationY(mDismissTarget.getHeight() / 2f); - mDismissTargetVerticalSpring.animateToFinalPosition(0); - - mDismissCircle.setAlpha(0f); - mDismissCircle.setScaleX(SCALE_FOR_POP); - mDismissCircle.setScaleY(SCALE_FOR_POP); - - // Fade in circle and reduce size. - mDismissCircle.animate() - .alpha(1f) - .scaleX(1f) - .scaleY(1f); - } - - /** Springs out the dismiss target. */ - void springOut() { - // Fade out the target icon. - mDismissIcon.animate() - .setDuration(50) - .scaleX(SCALE_FOR_DISMISS) - .scaleY(SCALE_FOR_DISMISS) - .alpha(0f); - - // Fade out the target. - mDismissTargetAlphaSpring.animateToFinalPosition(0f); - - // Spring the target down a bit. - mDismissTargetVerticalSpring.animateToFinalPosition(mDismissTarget.getHeight() / 2f); - - // Pop out the circle. - mDismissCircle.animate() - .scaleX(SCALE_FOR_DISMISS) - .scaleY(SCALE_FOR_DISMISS) - .alpha(0f); - } - - /** Returns the Y value of the center of the dismiss target. */ - float getDismissTargetCenterY() { - return getTop() + mDismissTarget.getTop() + mDismissTarget.getHeight() / 2f; - } - - /** Returns the dismiss target, which contains the text/icon and any added padding. */ - View getDismissTarget() { - return mDismissTarget; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java deleted file mode 100644 index 86244ba5248a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.bubbles; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.internal.util.ContrastColorUtil; -import com.android.systemui.R; - -/** - * Educational view to highlight the manage button that allows a user to configure the settings - * for the bubble. Shown only the first time a user expands a bubble. - */ -public class BubbleManageEducationView extends LinearLayout { - - private View mManageView; - private TextView mTitleTextView; - private TextView mDescTextView; - - public BubbleManageEducationView(Context context) { - this(context, null); - } - - public BubbleManageEducationView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public BubbleManageEducationView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public BubbleManageEducationView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mManageView = findViewById(R.id.manage_education_view); - mTitleTextView = findViewById(R.id.user_education_title); - mDescTextView = findViewById(R.id.user_education_description); - - final TypedArray ta = mContext.obtainStyledAttributes( - new int[] {android.R.attr.colorAccent, - android.R.attr.textColorPrimaryInverse}); - final int bgColor = ta.getColor(0, Color.BLACK); - int textColor = ta.getColor(1, Color.WHITE); - ta.recycle(); - - textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true); - mTitleTextView.setTextColor(textColor); - mDescTextView.setTextColor(textColor); - } - - /** - * Specifies the position for the manage view. - */ - public void setManageViewPosition(int x, int y) { - mManageView.setTranslationX(x); - mManageView.setTranslationY(y); - } - - /** - * @return the height of the view that shows the educational text and pointer. - */ - public int getManageViewHeight() { - return mManageView.getHeight(); - } - - @Override - public void setLayoutDirection(int direction) { - super.setLayoutDirection(direction); - if (getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { - mManageView.setBackgroundResource(R.drawable.bubble_stack_user_education_bg_rtl); - mTitleTextView.setGravity(Gravity.RIGHT); - mDescTextView.setGravity(Gravity.RIGHT); - } else { - mManageView.setBackgroundResource(R.drawable.bubble_stack_user_education_bg); - mTitleTextView.setGravity(Gravity.LEFT); - mDescTextView.setGravity(Gravity.LEFT); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java deleted file mode 100644 index bb9d1095a37a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.bubbles; - -import static android.view.Display.INVALID_DISPLAY; -import static android.view.View.GONE; - -import static com.android.systemui.bubbles.BadgedImageView.DEFAULT_PATH_SIZE; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Matrix; -import android.graphics.Path; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.InsetDrawable; -import android.util.PathParser; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import com.android.systemui.R; - -/** - * Class for showing aged out bubbles. - */ -public class BubbleOverflow implements BubbleViewProvider { - public static final String KEY = "Overflow"; - - private BadgedImageView mOverflowBtn; - private BubbleExpandedView mExpandedView; - private LayoutInflater mInflater; - private Context mContext; - private Bitmap mIcon; - private Path mPath; - private int mBitmapSize; - private int mIconBitmapSize; - private int mDotColor; - - public BubbleOverflow(Context context) { - mContext = context; - mInflater = LayoutInflater.from(context); - } - - void setUpOverflow(ViewGroup parentViewGroup, BubbleStackView stackView) { - updateDimensions(); - mExpandedView = (BubbleExpandedView) mInflater.inflate( - R.layout.bubble_expanded_view, parentViewGroup /* root */, - false /* attachToRoot */); - mExpandedView.setOverflow(true); - mExpandedView.setStackView(stackView); - mExpandedView.applyThemeAttrs(); - updateIcon(mContext, parentViewGroup); - } - - void updateDimensions() { - mBitmapSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size); - mIconBitmapSize = mContext.getResources().getDimensionPixelSize( - R.dimen.bubble_overflow_icon_bitmap_size); - if (mExpandedView != null) { - mExpandedView.updateDimensions(); - } - } - - void updateIcon(Context context, ViewGroup parentViewGroup) { - mContext = context; - mInflater = LayoutInflater.from(context); - mOverflowBtn = (BadgedImageView) mInflater.inflate(R.layout.bubble_overflow_button, - parentViewGroup /* root */, - false /* attachToRoot */); - mOverflowBtn.setContentDescription(mContext.getResources().getString( - R.string.bubble_overflow_button_content_description)); - Resources res = mContext.getResources(); - - // Set color for button icon and dot - TypedValue typedValue = new TypedValue(); - mContext.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true); - int colorAccent = mContext.getColor(typedValue.resourceId); - mOverflowBtn.getDrawable().setTint(colorAccent); - mDotColor = colorAccent; - - // Set color for button and activity background - ColorDrawable bg = new ColorDrawable(res.getColor(R.color.bubbles_light)); - final int mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - if (mode == Configuration.UI_MODE_NIGHT_YES) { - bg = new ColorDrawable(res.getColor(R.color.bubbles_dark)); - } - - // Apply icon inset - InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(), - mBitmapSize - mIconBitmapSize /* inset */); - AdaptiveIconDrawable adaptiveIconDrawable = new AdaptiveIconDrawable(bg, fg); - - BubbleIconFactory iconFactory = new BubbleIconFactory(mContext); - mIcon = iconFactory.createBadgedIconBitmap(adaptiveIconDrawable, - null /* user */, - true /* shrinkNonAdaptiveIcons */).icon; - - // Get path with dot location - float scale = iconFactory.getNormalizer().getScale(mOverflowBtn.getDrawable(), - null /* outBounds */, null /* path */, null /* outMaskShape */); - float radius = DEFAULT_PATH_SIZE / 2f; - mPath = PathParser.createPathFromPathData( - mContext.getResources().getString(com.android.internal.R.string.config_icon_mask)); - Matrix matrix = new Matrix(); - matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, - radius /* pivot y */); - mPath.transform(matrix); - - mOverflowBtn.setRenderedBubble(this); - } - - void setVisible(int visible) { - mOverflowBtn.setVisibility(visible); - } - - @Override - public BubbleExpandedView getExpandedView() { - return mExpandedView; - } - - @Override - public int getDotColor() { - return mDotColor; - } - - @Override - public Bitmap getBadgedImage() { - return mIcon; - } - - @Override - public boolean showDot() { - return false; - } - - @Override - public Path getDotPath() { - return mPath; - } - - @Override - public void setContentVisibility(boolean visible) { - mExpandedView.setContentVisibility(visible); - } - - @Override - public View getIconView() { - return mOverflowBtn; - } - - @Override - public String getKey() { - return BubbleOverflow.KEY; - } - - @Override - public int getDisplayId() { - return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt new file mode 100644 index 000000000000..155b71b99ff9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles + +import android.content.Context +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.Matrix +import android.graphics.Path +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.InsetDrawable +import android.util.PathParser +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import com.android.systemui.R + +class BubbleOverflow( + private val context: Context, + private val stack: BubbleStackView +) : BubbleViewProvider { + + private var bitmap: Bitmap? = null + private var dotPath: Path? = null + private var bitmapSize = 0 + private var iconBitmapSize = 0 + private var dotColor = 0 + + private val inflater: LayoutInflater = LayoutInflater.from(context) + private val expandedView: BubbleExpandedView = inflater + .inflate(R.layout.bubble_expanded_view, null /* root */, false /* attachToRoot */) + as BubbleExpandedView + private val overflowBtn: BadgedImageView = inflater + .inflate(R.layout.bubble_overflow_button, null /* root */, false /* attachToRoot */) + as BadgedImageView + init { + updateResources() + with(expandedView) { + setOverflow(true) + setStackView(stack) + applyThemeAttrs() + } + with(overflowBtn) { + setContentDescription(context.resources.getString( + R.string.bubble_overflow_button_content_description)) + updateBtnTheme() + } + } + + fun update() { + updateResources() + expandedView.applyThemeAttrs() + // Apply inset and new style to fresh icon drawable. + overflowBtn.setImageResource(R.drawable.ic_bubble_overflow_button) + updateBtnTheme() + } + + fun updateResources() { + bitmapSize = context.resources.getDimensionPixelSize(R.dimen.bubble_bitmap_size) + iconBitmapSize = context.resources.getDimensionPixelSize( + R.dimen.bubble_overflow_icon_bitmap_size) + val bubbleSize = context.resources.getDimensionPixelSize(R.dimen.individual_bubble_size) + overflowBtn.setLayoutParams(FrameLayout.LayoutParams(bubbleSize, bubbleSize)) + expandedView.updateDimensions() + } + + fun updateBtnTheme() { + val res = context.resources + + // Set overflow button accent color, dot color + val typedValue = TypedValue() + context.theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true) + + val colorAccent = res.getColor(typedValue.resourceId) + overflowBtn.getDrawable()?.setTint(colorAccent) + dotColor = colorAccent + + // Set button and activity background color + val nightMode = (res.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + == Configuration.UI_MODE_NIGHT_YES) + val bg = ColorDrawable(res.getColor( + if (nightMode) R.color.bubbles_dark else R.color.bubbles_light)) + + // Set button icon + val iconFactory = BubbleIconFactory(context) + val fg = InsetDrawable(overflowBtn.getDrawable(), + bitmapSize - iconBitmapSize /* inset */) + bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(bg, fg), + null /* user */, true /* shrinkNonAdaptiveIcons */).icon + + // Set dot path + dotPath = PathParser.createPathFromPathData( + res.getString(com.android.internal.R.string.config_icon_mask)) + val scale = iconFactory.normalizer.getScale(overflowBtn.getDrawable(), + null /* outBounds */, null /* path */, null /* outMaskShape */) + val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f + val matrix = Matrix() + matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, + radius /* pivot y */) + dotPath?.transform(matrix) + overflowBtn.setRenderedBubble(this) + } + + fun setVisible(visible: Int) { + overflowBtn.visibility = visible + } + + override fun getExpandedView(): BubbleExpandedView? { + return expandedView + } + + override fun getDotColor(): Int { + return dotColor + } + + override fun getBadgedImage(): Bitmap? { + return bitmap + } + + override fun showDot(): Boolean { + return false + } + + override fun getDotPath(): Path? { + return dotPath + } + + override fun setContentVisibility(visible: Boolean) { + expandedView.setContentVisibility(visible) + } + + override fun getIconView(): View? { + return overflowBtn + } + + override fun getKey(): String { + return KEY + } + + override fun getDisplayId(): Int { + return expandedView.virtualDisplayId + } + + companion object { + @JvmField val KEY = "Overflow" + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index f02945ef843a..ea12c9598b91 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -48,7 +48,6 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; -import android.graphics.drawable.TransitionDrawable; import android.os.Bundle; import android.os.Handler; import android.provider.Settings; @@ -95,7 +94,6 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; -import com.android.systemui.util.DismissCircleView; import com.android.systemui.util.FloatingContentCoordinator; import com.android.systemui.util.RelativeTouchListener; import com.android.systemui.util.animation.PhysicsAnimator; @@ -118,7 +116,7 @@ public class BubbleStackView extends FrameLayout private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES; /** Animation durations for bubble stack user education views. **/ - private static final int ANIMATE_STACK_USER_EDUCATION_DURATION = 200; + static final int ANIMATE_STACK_USER_EDUCATION_DURATION = 200; private static final int ANIMATE_STACK_USER_EDUCATION_DURATION_SHORT = 40; /** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */ @@ -139,9 +137,6 @@ public class BubbleStackView extends FrameLayout /** Percent to darken the bubbles when they're in the dismiss target. */ private static final float DARKEN_PERCENT = 0.3f; - /** Duration of the dismiss scrim fading in/out. */ - private static final int DISMISS_TRANSITION_DURATION_MS = 200; - /** How long to wait, in milliseconds, before hiding the flyout. */ @VisibleForTesting static final int FLYOUT_HIDE_AFTER = 5000; @@ -300,7 +295,7 @@ public class BubbleStackView extends FrameLayout public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Stack view state:"); pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress); - pw.print(" showingDismiss: "); pw.println(mShowingDismiss); + pw.print(" showingDismiss: "); pw.println(mDismissView.isShowing()); pw.print(" isExpansionAnimating: "); pw.println(mIsExpansionAnimating); pw.print(" expandedContainerVis: "); pw.println(mExpandedViewContainer.getVisibility()); pw.print(" expandedContainerAlpha: "); pw.println(mExpandedViewContainer.getAlpha()); @@ -347,7 +342,6 @@ public class BubbleStackView extends FrameLayout private boolean mViewUpdatedRequested = false; private boolean mIsExpansionAnimating = false; private boolean mIsBubbleSwitchAnimating = false; - private boolean mShowingDismiss = false; /** The view to desaturate/darken when magneted to the dismiss target. */ @Nullable private View mDesaturateAndDarkenTargetView; @@ -465,7 +459,7 @@ public class BubbleStackView extends FrameLayout if (wasFlungOut) { mExpandedAnimationController.snapBubbleBack( mExpandedAnimationController.getDraggedOutBubble(), velX, velY); - hideDismissTarget(); + mDismissView.hide(); } else { mExpandedAnimationController.onUnstuckFromTarget(); } @@ -479,9 +473,9 @@ public class BubbleStackView extends FrameLayout mExpandedAnimationController.dismissDraggedOutBubble( mExpandedAnimationController.getDraggedOutBubble() /* bubble */, - mDismissTargetContainer.getHeight() /* translationYBy */, + mDismissView.getHeight() /* translationYBy */, BubbleStackView.this::dismissMagnetizedObject /* after */); - hideDismissTarget(); + mDismissView.hide(); } }; @@ -502,7 +496,7 @@ public class BubbleStackView extends FrameLayout if (wasFlungOut) { mStackAnimationController.flingStackThenSpringToEdge( mStackAnimationController.getStackPosition().x, velX, velY); - hideDismissTarget(); + mDismissView.hide(); } else { mStackAnimationController.onUnstuckFromTarget(); } @@ -511,14 +505,14 @@ public class BubbleStackView extends FrameLayout @Override public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { mStackAnimationController.animateStackDismissal( - mDismissTargetContainer.getHeight() /* translationYBy */, + mDismissView.getHeight() /* translationYBy */, () -> { resetDesaturationAndDarken(); dismissMagnetizedObject(); } ); - hideDismissTarget(); + mDismissView.hide(); } }; @@ -639,7 +633,7 @@ public class BubbleStackView extends FrameLayout } // Show the dismiss target, if we haven't already. - springInDismissTargetMaybe(); + mDismissView.show(); // First, see if the magnetized object consumes the event - if so, we shouldn't move the // bubble since it's stuck to the target. @@ -681,7 +675,7 @@ public class BubbleStackView extends FrameLayout SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED); } - hideDismissTarget(); + mDismissView.hide(); } mIsDraggingStack = false; @@ -743,12 +737,7 @@ public class BubbleStackView extends FrameLayout } }; - private View mDismissTargetCircle; - private ViewGroup mDismissTargetContainer; - private PhysicsAnimator<View> mDismissTargetAnimator; - private PhysicsAnimator.SpringConfig mDismissTargetSpring = new PhysicsAnimator.SpringConfig( - SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); - + private DismissView mDismissView; private int mOrientation = Configuration.ORIENTATION_UNDEFINED; @Nullable @@ -759,7 +748,7 @@ public class BubbleStackView extends FrameLayout private View mUserEducationView; private boolean mShouldShowManageEducation; - private BubbleManageEducationView mManageEducationView; + private ManageEducationView mManageEducationView; private boolean mAnimatingManageEducationAway; private ViewGroup mManageMenu; @@ -866,34 +855,8 @@ public class BubbleStackView extends FrameLayout .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring); - final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size); - mDismissTargetCircle = new DismissCircleView(context); - final FrameLayout.LayoutParams newParams = - new FrameLayout.LayoutParams(targetSize, targetSize); - newParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; - mDismissTargetCircle.setLayoutParams(newParams); - mDismissTargetAnimator = PhysicsAnimator.getInstance(mDismissTargetCircle); - - mDismissTargetContainer = new FrameLayout(context); - mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams( - MATCH_PARENT, - getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height), - Gravity.BOTTOM)); - - final int bottomMargin = - getResources().getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin); - mDismissTargetContainer.setPadding(0, 0, 0, bottomMargin); - mDismissTargetContainer.setClipToPadding(false); - mDismissTargetContainer.setClipChildren(false); - mDismissTargetContainer.addView(mDismissTargetCircle); - mDismissTargetContainer.setVisibility(View.INVISIBLE); - mDismissTargetContainer.setBackgroundResource( - R.drawable.floating_dismiss_gradient_transition); - addView(mDismissTargetContainer); - - // Start translated down so the target springs up. - mDismissTargetCircle.setTranslationY( - getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height)); + mDismissView = new DismissView(context); + addView(mDismissView); final ContentResolver contentResolver = getContext().getContentResolver(); final int dismissRadius = Settings.Secure.getInt( @@ -901,13 +864,23 @@ public class BubbleStackView extends FrameLayout // Save the MagneticTarget instance for the newly set up view - we'll add this to the // MagnetizedObjects. - mMagneticTarget = new MagnetizedObject.MagneticTarget(mDismissTargetCircle, dismissRadius); + mMagneticTarget = new MagnetizedObject.MagneticTarget( + mDismissView.getCircle(), dismissRadius); setClipChildren(false); setFocusable(true); mBubbleContainer.bringToFront(); - setUpOverflow(); + mBubbleOverflow = new BubbleOverflow(getContext(), this); + mBubbleContainer.addView(mBubbleOverflow.getIconView(), + mBubbleContainer.getChildCount() /* index */, + new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + updateOverflow(); + mBubbleOverflow.getIconView().setOnClickListener((View v) -> { + setSelectedBubble(mBubbleOverflow); + showManageMenu(false); + }); mOnImeVisibilityChanged = onImeVisibilityChanged; mHideCurrentInputMethodCallback = hideCurrentInputMethodCallback; @@ -933,7 +906,7 @@ public class BubbleStackView extends FrameLayout (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { mExpandedAnimationController.updateResources(mOrientation, mDisplaySize); mStackAnimationController.updateResources(mOrientation); - mBubbleOverflow.updateDimensions(); + mBubbleOverflow.updateResources(); // Need to update the padding around the view WindowInsets insets = getRootWindowInsets(); @@ -1162,12 +1135,9 @@ public class BubbleStackView extends FrameLayout Log.d(TAG, "shouldShowManageEducation: " + mShouldShowManageEducation); } if (mShouldShowManageEducation) { - mManageEducationView = (BubbleManageEducationView) - mInflater.inflate(R.layout.bubbles_manage_button_education, this, + mManageEducationView = (ManageEducationView) + mInflater.inflate(R.layout.bubbles_manage_button_education, this /* root */, false /* attachToRoot */); - mManageEducationView.setVisibility(GONE); - mManageEducationView.setElevation(mBubbleElevation); - mManageEducationView.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE); addView(mManageEducationView); } } @@ -1187,32 +1157,21 @@ public class BubbleStackView extends FrameLayout addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); } - private void setUpOverflow() { - int overflowBtnIndex = 0; - if (mBubbleOverflow == null) { - mBubbleOverflow = new BubbleOverflow(getContext()); - mBubbleOverflow.setUpOverflow(mBubbleContainer, this); - } else { - mBubbleContainer.removeView(mBubbleOverflow.getIconView()); - mBubbleOverflow.setUpOverflow(mBubbleContainer, this); - overflowBtnIndex = mBubbleContainer.getChildCount(); - } - mBubbleContainer.addView(mBubbleOverflow.getIconView(), overflowBtnIndex, - new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - mBubbleOverflow.getIconView().setOnClickListener(v -> { - setSelectedBubble(mBubbleOverflow); - showManageMenu(false); - }); + private void updateOverflow() { + mBubbleOverflow.update(); + mBubbleContainer.reorderView(mBubbleOverflow.getIconView(), + mBubbleContainer.getChildCount() - 1 /* index */); updateOverflowVisibility(); } + /** * Handle theme changes. */ public void onThemeChanged() { setUpFlyout(); - setUpOverflow(); setUpUserEducation(); setUpManageMenu(); + updateOverflow(); updateExpandedViewTheme(); } @@ -1261,7 +1220,7 @@ public class BubbleStackView extends FrameLayout /** Respond to the display size change by recalculating view size and location. */ public void onDisplaySizeChanged() { - setUpOverflow(); + updateOverflow(); WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); wm.getDefaultDisplay().getRealSize(mDisplaySize); @@ -1279,12 +1238,7 @@ public class BubbleStackView extends FrameLayout } mExpandedAnimationController.updateResources(mOrientation, mDisplaySize); mStackAnimationController.updateResources(mOrientation); - - final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size); - mDismissTargetCircle.getLayoutParams().width = targetSize; - mDismissTargetCircle.getLayoutParams().height = targetSize; - mDismissTargetCircle.requestLayout(); - + mDismissView.updateResources(); mMagneticTarget.setMagneticFieldRadiusPx(mBubbleSize * 2); } @@ -1796,28 +1750,8 @@ public class BubbleStackView extends FrameLayout && mManageEducationView.getVisibility() != VISIBLE && mIsExpanded && mExpandedBubble.getExpandedView() != null) { - mManageEducationView.setAlpha(0); - mManageEducationView.setVisibility(VISIBLE); - mManageEducationView.post(() -> { - mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect); - final int viewHeight = mManageEducationView.getManageViewHeight(); - final int inset = getResources().getDimensionPixelSize( - R.dimen.bubbles_manage_education_top_inset); - mManageEducationView.bringToFront(); - mManageEducationView.setManageViewPosition(0, mTempRect.top - viewHeight + inset); - mManageEducationView.animate() - .setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION) - .setInterpolator(FAST_OUT_SLOW_IN).alpha(1); - mManageEducationView.findViewById(R.id.manage).setOnClickListener(view -> { - mExpandedBubble.getExpandedView().findViewById(R.id.settings_button) - .performClick(); - maybeShowManageEducation(false); - }); - mManageEducationView.findViewById(R.id.got_it).setOnClickListener(view -> - maybeShowManageEducation(false)); - mManageEducationView.setOnClickListener(view -> - maybeShowManageEducation(false)); - }); + mManageEducationView.show(mExpandedBubble.getExpandedView(), mTempRect, + () -> maybeShowManageEducation(false) /* run on click */); Prefs.putBoolean(getContext(), HAS_SEEN_BUBBLES_MANAGE_EDUCATION, true); } else if (!show && mManageEducationView.getVisibility() == VISIBLE @@ -2362,48 +2296,6 @@ public class BubbleStackView extends FrameLayout } } - /** Animates in the dismiss target. */ - private void springInDismissTargetMaybe() { - if (mShowingDismiss) { - return; - } - - mShowingDismiss = true; - - mDismissTargetContainer.bringToFront(); - mDismissTargetContainer.setZ(Short.MAX_VALUE - 1); - mDismissTargetContainer.setVisibility(VISIBLE); - - ((TransitionDrawable) mDismissTargetContainer.getBackground()).startTransition( - DISMISS_TRANSITION_DURATION_MS); - - mDismissTargetAnimator.cancel(); - mDismissTargetAnimator - .spring(DynamicAnimation.TRANSLATION_Y, 0f, mDismissTargetSpring) - .start(); - } - - /** - * Animates the dismiss target out, as well as the circle that encircles the bubbles, if they - * were dragged into the target and encircled. - */ - private void hideDismissTarget() { - if (!mShowingDismiss) { - return; - } - - mShowingDismiss = false; - - ((TransitionDrawable) mDismissTargetContainer.getBackground()).reverseTransition( - DISMISS_TRANSITION_DURATION_MS); - - mDismissTargetAnimator - .spring(DynamicAnimation.TRANSLATION_Y, mDismissTargetContainer.getHeight(), - mDismissTargetSpring) - .withEndActions(() -> mDismissTargetContainer.setVisibility(View.INVISIBLE)) - .start(); - } - /** Animates the flyout collapsed (to dot), or the reverse, starting with the given velocity. */ private void animateFlyoutCollapsed(boolean collapsed, float velX) { final boolean onLeft = mStackAnimationController.isStackOnLeftSide(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt new file mode 100644 index 000000000000..71faf4a2eeb7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt @@ -0,0 +1,85 @@ +package com.android.systemui.bubbles + +import android.content.Context +import android.graphics.drawable.TransitionDrawable +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY +import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW +import com.android.systemui.R +import com.android.systemui.util.DismissCircleView +import com.android.systemui.util.animation.PhysicsAnimator + +/* + * View that handles interactions between DismissCircleView and BubbleStackView. + */ +class DismissView(context: Context) : FrameLayout(context) { + + var circle = DismissCircleView(context).apply { + val targetSize: Int = context.resources.getDimensionPixelSize(R.dimen.dismiss_circle_size) + val newParams = LayoutParams(targetSize, targetSize) + newParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + setLayoutParams(newParams) + setTranslationY( + resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height).toFloat()) + } + + var isShowing = false + private val animator = PhysicsAnimator.getInstance(circle) + private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY); + private val DISMISS_SCRIM_FADE_MS = 200 + init { + setLayoutParams(LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height), + Gravity.BOTTOM)) + setPadding(0, 0, 0, resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin)) + setClipToPadding(false) + setClipChildren(false) + setVisibility(View.INVISIBLE) + setBackgroundResource( + R.drawable.floating_dismiss_gradient_transition) + addView(circle) + } + + /** + * Animates this view in. + */ + fun show() { + if (isShowing) return + isShowing = true + bringToFront() + setZ(Short.MAX_VALUE - 1f) + setVisibility(View.VISIBLE) + (getBackground() as TransitionDrawable).startTransition(DISMISS_SCRIM_FADE_MS) + animator.cancel() + animator + .spring(DynamicAnimation.TRANSLATION_Y, 0f, spring) + .start() + } + + /** + * Animates this view out, as well as the circle that encircles the bubbles, if they + * were dragged into the target and encircled. + */ + fun hide() { + if (!isShowing) return + isShowing = false + (getBackground() as TransitionDrawable).reverseTransition(DISMISS_SCRIM_FADE_MS) + animator + .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(), + spring) + .withEndActions({ setVisibility(View.INVISIBLE) }) + .start() + } + + fun updateResources() { + val targetSize: Int = context.resources.getDimensionPixelSize(R.dimen.dismiss_circle_size) + circle.layoutParams.width = targetSize + circle.layoutParams.height = targetSize + circle.requestLayout() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt new file mode 100644 index 000000000000..c58ab31c4561 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.bubbles + +import android.content.Context +import android.graphics.Color +import android.graphics.Rect +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.widget.Button +import android.widget.LinearLayout +import android.widget.TextView +import com.android.internal.util.ContrastColorUtil +import com.android.systemui.Interpolators +import com.android.systemui.R + +/** + * Educational view to highlight the manage button that allows a user to configure the settings + * for the bubble. Shown only the first time a user expands a bubble. + */ +class ManageEducationView @JvmOverloads constructor( + context: Context?, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) { + + private val manageView by lazy { findViewById<View>(R.id.manage_education_view) } + private val manageButton by lazy { findViewById<Button>(R.id.manage) } + private val gotItButton by lazy { findViewById<Button>(R.id.got_it) } + private val titleTextView by lazy { findViewById<TextView>(R.id.user_education_title) } + private val descTextView by lazy { findViewById<TextView>(R.id.user_education_description) } + private var isInflated = false + + init { + this.visibility = View.GONE + this.elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat() + this.layoutDirection = View.LAYOUT_DIRECTION_LOCALE + } + + override fun setLayoutDirection(direction: Int) { + super.setLayoutDirection(direction) + // setLayoutDirection runs before onFinishInflate + // so skip if views haven't inflated; otherwise we'll get NPEs + if (!isInflated) return + setDirection() + } + + override fun onFinishInflate() { + super.onFinishInflate() + isInflated = true + setDirection() + setTextColor() + } + + private fun setTextColor() { + val typedArray = mContext.obtainStyledAttributes(intArrayOf(android.R.attr.colorAccent, + android.R.attr.textColorPrimaryInverse)) + val bgColor = typedArray.getColor(0 /* index */, Color.BLACK) + var textColor = typedArray.getColor(1 /* index */, Color.WHITE) + typedArray.recycle() + textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true) + titleTextView.setTextColor(textColor) + descTextView.setTextColor(textColor) + } + + fun setDirection() { + manageView.setBackgroundResource( + if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) + R.drawable.bubble_stack_user_education_bg_rtl + else R.drawable.bubble_stack_user_education_bg) + titleTextView.gravity = Gravity.START + descTextView.gravity = Gravity.START + } + + fun show(expandedView: BubbleExpandedView, rect : Rect, hideMenu: Runnable) { + alpha = 0f + visibility = View.VISIBLE + post { + expandedView.getManageButtonBoundsOnScreen(rect) + with(hideMenu) { + manageButton + .setOnClickListener { + expandedView.findViewById<View>(R.id.settings_button).performClick() + this.run() + } + gotItButton.setOnClickListener { this.run() } + setOnClickListener { this.run() } + } + with(manageView) { + translationX = 0f + val inset = resources.getDimensionPixelSize( + R.dimen.bubbles_manage_education_top_inset) + translationY = (rect.top - manageView.height + inset).toFloat() + } + bringToFront() + animate() + .setDuration(BubbleStackView.ANIMATE_STACK_USER_EDUCATION_DURATION.toLong()) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .alpha(1f) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt index 662831e4a445..24ca9708a4e3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt @@ -27,6 +27,7 @@ import javax.inject.Inject import javax.inject.Singleton private const val TAG = "MediaDataFilter" +private const val DEBUG = true /** * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user @@ -98,7 +99,7 @@ class MediaDataFilter @Inject constructor( // are up to date mediaEntries.clear() keyCopy.forEach { - Log.d(TAG, "Removing $it after user change") + if (DEBUG) Log.d(TAG, "Removing $it after user change") listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) } @@ -106,7 +107,7 @@ class MediaDataFilter @Inject constructor( dataSource.getData().forEach { (key, data) -> if (lockscreenUserManager.isCurrentProfile(data.userId)) { - Log.d(TAG, "Re-adding $key after user change") + if (DEBUG) Log.d(TAG, "Re-adding $key after user change") mediaEntries.put(key, data) listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) @@ -119,6 +120,7 @@ class MediaDataFilter @Inject constructor( * Invoked when the user has dismissed the media carousel */ fun onSwipeToDismiss() { + if (DEBUG) Log.d(TAG, "Media carousel swiped away") val mediaKeys = mediaEntries.keys.toSet() mediaKeys.forEach { mediaDataManager.setTimedOut(it, timedOut = true) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index b3277737f397..d82150f2346b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -63,6 +63,7 @@ private val ART_URIS = arrayOf( ) private const val TAG = "MediaDataManager" +private const val DEBUG = true private const val DEFAULT_LUMINOSITY = 0.25f private const val LUMINOSITY_THRESHOLD = 0.05f private const val SATURATION_MULTIPLIER = 0.8f @@ -253,7 +254,7 @@ class MediaDataManager( fun removeListener(listener: Listener) = listeners.remove(listener) /** - * Called whenever the player has been paused or stopped for a while. + * Called whenever the player has been paused or stopped for a while, or swiped from QQS. * This will make the player not active anymore, hiding it from QQS and Keyguard. * @see MediaData.active */ @@ -263,6 +264,7 @@ class MediaDataManager( return } it.active = !timedOut + if (DEBUG) Log.d(TAG, "Updating $token timedOut: $timedOut") onMediaDataLoaded(token, token, it) } } @@ -283,7 +285,9 @@ class MediaDataManager( return } - Log.d(TAG, "adding track for $userId from browser: $desc") + if (DEBUG) { + Log.d(TAG, "adding track for $userId from browser: $desc") + } // Album art var artworkBitmap = desc.iconBitmap @@ -383,7 +387,7 @@ class MediaDataManager( if (actions != null) { for ((index, action) in actions.withIndex()) { if (action.getIcon() == null) { - Log.i(TAG, "No icon for action $index ${action.title}") + if (DEBUG) Log.i(TAG, "No icon for action $index ${action.title}") actionsToShowCollapsed.remove(index) continue } @@ -427,7 +431,7 @@ class MediaDataManager( if (!TextUtils.isEmpty(uriString)) { val albumArt = loadBitmapFromUri(Uri.parse(uriString)) if (albumArt != null) { - Log.d(TAG, "loaded art from $uri") + if (DEBUG) Log.d(TAG, "loaded art from $uri") return albumArt } } @@ -514,7 +518,7 @@ class MediaDataManager( Assert.isMainThread() val removed = mediaEntries.remove(key) if (useMediaResumption && removed?.resumeAction != null) { - Log.d(TAG, "Not removing $key because resumable") + if (DEBUG) Log.d(TAG, "Not removing $key because resumable") // Move to resume key (aka package name) if that key doesn't already exist. val resumeAction = getResumeMediaAction(removed.resumeAction!!) val updated = removed.copy(token = null, actions = listOf(resumeAction), diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 9115b4849355..d03082e6b442 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -809,7 +809,9 @@ public class OverviewProxyService extends CurrentUserTracker implements @Override public void addCallback(OverviewProxyListener listener) { - mConnectionCallbacks.add(listener); + if (!mConnectionCallbacks.contains(listener)) { + mConnectionCallbacks.add(listener); + } listener.onConnectionChanged(mOverviewProxy != null); listener.onNavBarButtonAlphaChanged(mNavBarButtonAlpha, false); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java index 8c24c540e743..2638d28733e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java @@ -154,7 +154,7 @@ public class NavigationBarController implements Callbacks { Dependency.get(IWindowManager.class)); navBar.setAutoHideController(autoHideController); navBar.restoreAppearanceAndTransientState(); - mNavigationBars.append(displayId, navBar); + mNavigationBars.put(displayId, navBar); if (result != null) { navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index a35aca553c4f..9606318e1992 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -121,6 +121,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa private final Context mContext; private final OverviewProxyService mOverviewProxyService; + private final SysUiState mSysUiState; private final Runnable mStateChangeCallback; private final PluginManager mPluginManager; @@ -197,14 +198,22 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa } }; + private final SysUiState.SysUiStateCallback mSysUiStateCallback = + new SysUiState.SysUiStateCallback() { + @Override + public void onSystemUiStateChanged(int sysUiFlags) { + mSysUiFlags = sysUiFlags; + } + }; + public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, - SysUiState sysUiFlagContainer, PluginManager pluginManager, - Runnable stateChangeCallback) { + SysUiState sysUiState, PluginManager pluginManager, Runnable stateChangeCallback) { super(Dependency.get(BroadcastDispatcher.class)); mContext = context; mDisplayId = context.getDisplayId(); mMainExecutor = context.getMainExecutor(); mOverviewProxyService = overviewProxyService; + mSysUiState = sysUiState; mPluginManager = pluginManager; mStateChangeCallback = stateChangeCallback; ComponentName recentsComponentName = ComponentName.unflattenFromString( @@ -238,7 +247,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged); updateCurrentUserResources(); - sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags); } public void updateCurrentUserResources() { @@ -287,6 +295,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mIsAttached = true; Dependency.get(ProtoTracer.class).add(this); mOverviewProxyService.addCallback(mQuickSwitchListener); + mSysUiState.addCallback(mSysUiStateCallback); updateIsEnabled(); startTracking(); } @@ -298,6 +307,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mIsAttached = false; Dependency.get(ProtoTracer.class).remove(this); mOverviewProxyService.removeCallback(mQuickSwitchListener); + mSysUiState.removeCallback(mSysUiStateCallback); updateIsEnabled(); stopTracking(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index b7733cc5acd7..f43fa648a1a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -120,6 +120,7 @@ import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; +import com.android.systemui.statusbar.NavigationBarController; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; @@ -131,6 +132,7 @@ import com.android.systemui.util.LifecycleFragment; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -354,6 +356,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback // If the button will actually become visible and the navbar is about to hide, // tell the statusbar to keep it around for longer mAutoHideController.touchAutoHide(); + mNavigationBarView.notifyActiveTouchRegions(); } }; @@ -550,6 +553,9 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener( mOrientationHandleGlobalLayoutListener); } + mHandler.removeCallbacks(mAutoDim); + mNavigationBarView = null; + mOrientationHandle = null; } @Override @@ -1458,11 +1464,11 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView); if (navigationBarView == null) return null; - final NavigationBarFragment fragment = FragmentHostManager.get(navigationBarView) - .create(NavigationBarFragment.class); navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { + final NavigationBarFragment fragment = + FragmentHostManager.get(v).create(NavigationBarFragment.class); final FragmentHostManager fragmentHost = FragmentHostManager.get(v); fragmentHost.getFragmentManager().beginTransaction() .replace(R.id.navigation_bar_frame, fragment, TAG) @@ -1472,6 +1478,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback @Override public void onViewDetachedFromWindow(View v) { + final FragmentHostManager fragmentHost = FragmentHostManager.get(v); + fragmentHost.removeTagListener(TAG, listener); FragmentHostManager.removeAndDestroy(v); navigationBarView.removeOnAttachStateChangeListener(this); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 7936e533f76d..84512ac85fa9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -115,12 +115,8 @@ public class NavigationBarView extends FrameLayout implements int mNavigationIconHints = 0; private int mNavBarMode; - private Rect mHomeButtonBounds = new Rect(); - private Rect mBackButtonBounds = new Rect(); - private Rect mRecentsButtonBounds = new Rect(); - private Rect mRotationButtonBounds = new Rect(); private final Region mActiveRegion = new Region(); - private int[] mTmpPosition = new int[2]; + private Rect mTmpBounds = new Rect(); private KeyButtonDrawable mBackIcon; private KeyButtonDrawable mHomeDefaultIcon; @@ -712,6 +708,7 @@ public class NavigationBarView extends FrameLayout implements getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE); + notifyActiveTouchRegions(); } @VisibleForTesting @@ -929,42 +926,30 @@ public class NavigationBarView extends FrameLayout implements protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); + notifyActiveTouchRegions(); + mRecentsOnboarding.setNavBarHeight(getMeasuredHeight()); + } + + /** + * Notifies the overview service of the active touch regions. + */ + public void notifyActiveTouchRegions() { mActiveRegion.setEmpty(); - updateButtonLocation(getBackButton(), mBackButtonBounds, true); - updateButtonLocation(getHomeButton(), mHomeButtonBounds, false); - updateButtonLocation(getRecentsButton(), mRecentsButtonBounds, false); - updateButtonLocation(getRotateSuggestionButton(), mRotationButtonBounds, true); - // TODO: Handle button visibility changes + updateButtonLocation(getBackButton()); + updateButtonLocation(getHomeButton()); + updateButtonLocation(getRecentsButton()); + updateButtonLocation(getRotateSuggestionButton()); mOverviewProxyService.onActiveNavBarRegionChanges(mActiveRegion); - mRecentsOnboarding.setNavBarHeight(getMeasuredHeight()); } - private void updateButtonLocation(ButtonDispatcher button, Rect buttonBounds, - boolean isActive) { + private void updateButtonLocation(ButtonDispatcher button) { View view = button.getCurrentView(); - if (view == null) { - buttonBounds.setEmpty(); + if (view == null || !button.isVisible()) { return; } - // Temporarily reset the translation back to origin to get the position in window - final float posX = view.getTranslationX(); - final float posY = view.getTranslationY(); - view.setTranslationX(0); - view.setTranslationY(0); - - if (isActive) { - view.getLocationOnScreen(mTmpPosition); - buttonBounds.set(mTmpPosition[0], mTmpPosition[1], - mTmpPosition[0] + view.getMeasuredWidth(), - mTmpPosition[1] + view.getMeasuredHeight()); - mActiveRegion.op(buttonBounds, Op.UNION); - } - view.getLocationInWindow(mTmpPosition); - buttonBounds.set(mTmpPosition[0], mTmpPosition[1], - mTmpPosition[0] + view.getMeasuredWidth(), - mTmpPosition[1] + view.getMeasuredHeight()); - view.setTranslationX(posX); - view.setTranslationY(posY); + + view.getBoundsOnScreen(mTmpBounds); + mActiveRegion.op(mTmpBounds, Op.UNION); } private void updateOrientationViews() { 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 1f2a2c652331..c5571e8ceb20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -18,9 +18,6 @@ package com.android.systemui.statusbar.phone; import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; -import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS; -import static android.app.StatusBarManager.DISABLE_CLOCK; -import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowType; @@ -4174,7 +4171,6 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void setTopAppHidesStatusBar(boolean topAppHidesStatusBar) { mTopHidesStatusBar = topAppHidesStatusBar; - updateStatusBarIcons(topAppHidesStatusBar); if (!topAppHidesStatusBar && mWereIconsJustHidden) { // Immediately update the icon hidden state, since that should only apply if we're // staying fullscreen. @@ -4184,17 +4180,6 @@ public class StatusBar extends SystemUI implements DemoMode, updateHideIconsForBouncer(true /* animate */); } - private void updateStatusBarIcons(boolean topAppHidesStatusBar) { - int flags1 = StatusBarManager.DISABLE_NONE; - int flags2 = StatusBarManager.DISABLE2_NONE; - if (topAppHidesStatusBar) { - flags1 = DISABLE_NOTIFICATION_ICONS | DISABLE_CLOCK; - flags2 = DISABLE2_SYSTEM_ICONS; - } - - mCommandQueue.disable(mDisplayId, flags1, flags2, false); - } - protected void toggleKeyboardShortcuts(int deviceId) { KeyboardShortcuts.toggle(mContext, deviceId); } diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java index 7561af770298..b1241b160d70 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java @@ -70,6 +70,8 @@ public class UsbDebuggingActivity extends AlertActivity if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) { mDisconnectedReceiver = new UsbDisconnectedReceiver(this); + IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE); + mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter); } Intent intent = getIntent(); @@ -119,6 +121,7 @@ public class UsbDebuggingActivity extends AlertActivity } boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false); if (!connected) { + Log.d(TAG, "USB disconnected, notifying service"); notifyService(false); mActivity.finish(); } @@ -126,29 +129,20 @@ public class UsbDebuggingActivity extends AlertActivity } @Override - public void onStart() { - super.onStart(); - if (mDisconnectedReceiver != null) { - IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE); - mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter); - } - } - - @Override - protected void onStop() { + protected void onDestroy() { if (mDisconnectedReceiver != null) { mBroadcastDispatcher.unregisterReceiver(mDisconnectedReceiver); } - super.onStop(); - } - - @Override - protected void onDestroy() { - // If the ADB service has not yet been notified due to this dialog being closed in some - // other way then notify the service to deny the connection to ensure system_server sends - // a response to adbd. - if (!mServiceNotified) { - notifyService(false); + // Only notify the service if the activity is finishing; if onDestroy has been called due to + // a configuration change then allow the user to still authorize the connection the next + // time the activity is in the foreground. + if (isFinishing()) { + // If the ADB service has not yet been notified due to this dialog being closed in some + // other way then notify the service to deny the connection to ensure system_server + // sends a response to adbd. + if (!mServiceNotified) { + notifyService(false); + } } super.onDestroy(); } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java index bba5ff5b54ca..fbc167683a2a 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java @@ -52,16 +52,16 @@ public class WindowManagerShellModule { @Singleton @Provides - static SystemWindows provideSystemWindows(Context context, DisplayController displayController, + static SystemWindows provideSystemWindows(DisplayController displayController, IWindowManager wmService) { - return new SystemWindows(context, displayController, wmService); + return new SystemWindows(displayController, wmService); } @Singleton @Provides static DisplayImeController provideDisplayImeController( - SystemWindows syswin, DisplayController displayController, + IWindowManager wmService, DisplayController displayController, @Main Handler mainHandler, TransactionPool transactionPool) { - return new DisplayImeController(syswin, displayController, mainHandler, transactionPool); + return new DisplayImeController(wmService, displayController, mainHandler, transactionPool); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java new file mode 100644 index 000000000000..9cb4fb319fa2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility; + +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; + +import android.hardware.display.DisplayManager; +import android.testing.AndroidTestingRunner; +import android.view.Display; + +import androidx.annotation.NonNull; +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class DisplayIdIndexSupplierTest extends SysuiTestCase { + + private DisplayIdIndexSupplier<Object> mDisplayIdIndexSupplier; + + @Before + public void setUp() throws Exception { + mDisplayIdIndexSupplier = new DisplayIdIndexSupplier( + mContext.getSystemService(DisplayManager.class)) { + + @NonNull + @Override + protected Object createInstance(Display display) { + return new Object(); + } + }; + } + + @Test + public void get_instanceIsNotNull() { + Object object = mDisplayIdIndexSupplier.get(Display.DEFAULT_DISPLAY); + assertNotNull(object); + } + + @Test + public void get_removeExistedObject_newObject() { + Object object = mDisplayIdIndexSupplier.get(Display.DEFAULT_DISPLAY); + mDisplayIdIndexSupplier.remove(Display.DEFAULT_DISPLAY); + + Object newObject = mDisplayIdIndexSupplier.get(Display.DEFAULT_DISPLAY); + + assertNotEquals(object, newObject); + } + + @Test + public void get_clearAllObjects_newObject() { + Object object = mDisplayIdIndexSupplier.get(Display.DEFAULT_DISPLAY); + mDisplayIdIndexSupplier.clear(); + + Object newObject = mDisplayIdIndexSupplier.get(Display.DEFAULT_DISPLAY); + + assertNotEquals(object, newObject); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java index d6d2fcd9610a..69482791ef23 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java @@ -16,20 +16,13 @@ package com.android.systemui.accessibility; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.Context; import android.provider.Settings; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; import android.view.Display; -import android.view.View; -import android.view.WindowManager; -import android.view.WindowMetrics; import androidx.test.filters.SmallTest; @@ -38,45 +31,43 @@ import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper /** Tests the ModeSwitchesController. */ public class ModeSwitchesControllerTest extends SysuiTestCase { - private WindowManager mWindowManager; + @Mock + private ModeSwitchesController.SwitchSupplier mSupplier; + @Mock + private MagnificationModeSwitch mModeSwitch; private ModeSwitchesController mModeSwitchesController; + @Before public void setUp() { - mWindowManager = mock(WindowManager.class); - Display display = mContext.getSystemService(WindowManager.class).getDefaultDisplay(); - when(mWindowManager.getDefaultDisplay()).thenReturn(display); - WindowMetrics metrics = mContext.getSystemService(WindowManager.class) - .getMaximumWindowMetrics(); - when(mWindowManager.getMaximumWindowMetrics()).thenReturn(metrics); - mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); - mModeSwitchesController = new ModeSwitchesController(mContext); + MockitoAnnotations.initMocks(this); + when(mSupplier.get(anyInt())).thenReturn(mModeSwitch); + mModeSwitchesController = new ModeSwitchesController(mSupplier); } @Test public void testShowButton() { mModeSwitchesController.showButton(Display.DEFAULT_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); - verify(mWindowManager).addView(any(), any()); + + verify(mModeSwitch).showButton(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); } @Test public void testRemoveButton() { mModeSwitchesController.showButton(Display.DEFAULT_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); - ArgumentCaptor<View> captor = ArgumentCaptor.forClass(View.class); - verify(mWindowManager).addView(captor.capture(), any(WindowManager.LayoutParams.class)); mModeSwitchesController.removeButton(Display.DEFAULT_DISPLAY); - verify(mWindowManager).removeView(eq(captor.getValue())); + verify(mModeSwitch).removeButton(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index e8a0c738f966..d4a94c5b9e66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -45,6 +45,7 @@ import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -530,6 +531,11 @@ public class AuthControllerTest extends SysuiTestCase { IActivityTaskManager getActivityTaskManager() { return mock(IActivityTaskManager.class); } + + @Override + FingerprintManager getFingerprintManager(Context context) { + return mock(FingerprintManager.class); + } } } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index b6ad1a526165..1038069f5d11 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2050,7 +2050,9 @@ public final class ProcessList { final int pid = precedence.pid; long now = System.currentTimeMillis(); final long end = now + PROC_KILL_TIMEOUT; + final int oldPolicy = StrictMode.getThreadPolicyMask(); try { + StrictMode.setThreadPolicyMask(0); Process.waitForProcessDeath(pid, PROC_KILL_TIMEOUT); // It's killed successfully, but we'd make sure the cleanup work is done. synchronized (precedence) { @@ -2069,9 +2071,11 @@ public final class ProcessList { } } } catch (Exception e) { - // It's still alive... + // It's still alive... maybe blocked at uninterruptible sleep ? Slog.wtf(TAG, precedence.toString() + " refused to die, but we need to launch " - + app); + + app, e); + } finally { + StrictMode.setThreadPolicyMask(oldPolicy); } } try { @@ -2416,7 +2420,15 @@ public final class ProcessList { ProcessList.killProcessGroup(app.uid, app.pid); checkSlow(startTime, "startProcess: done killing old proc"); - Slog.wtf(TAG_PROCESSES, app.toString() + " is attached to a previous process"); + if (!app.killed || mService.mLastMemoryLevel <= ProcessStats.ADJ_MEM_FACTOR_NORMAL + || app.setProcState < ActivityManager.PROCESS_STATE_CACHED_EMPTY + || app.lastCachedPss < getCachedRestoreThresholdKb()) { + // Throw a wtf if it's not killed, or killed but not because the system was in + // memory pressure + the app was in "cch-empty" and used large amount of memory + Slog.wtf(TAG_PROCESSES, app.toString() + " is attached to a previous process"); + } else { + Slog.w(TAG_PROCESSES, app.toString() + " is attached to a previous process"); + } // We are not going to re-use the ProcessRecord, as we haven't dealt with the cleanup // routine of it yet, but we'd set it as the precedence of the new process. precedence = app; @@ -2819,7 +2831,15 @@ public final class ProcessList { // We are re-adding a persistent process. Whatevs! Just leave it there. Slog.w(TAG, "Re-adding persistent process " + proc); } else if (old != null) { - Slog.wtf(TAG, "Already have existing proc " + old + " when adding " + proc); + if (old.killed) { + // The old process has been killed, we probably haven't had + // a chance to clean up the old record, just log a warning + Slog.w(TAG, "Existing proc " + old + " was killed " + + (SystemClock.uptimeMillis() - old.mKillTime) + + "ms ago when adding " + proc); + } else { + Slog.wtf(TAG, "Already have existing proc " + old + " when adding " + proc); + } } UidRecord uidRec = mActiveUids.get(proc.uid); if (uidRec == null) { diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 42e3061a840e..6e1bd8faeaf9 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -352,6 +352,8 @@ class ProcessRecord implements WindowProcessListener { boolean mReachable; // Whether or not this process is reachable from given process + long mKillTime; // The timestamp in uptime when this process was killed. + void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo, long startTime) { this.startUid = startUid; @@ -925,6 +927,7 @@ class ProcessRecord implements WindowProcessListener { if (!mPersistent) { killed = true; killedByAm = true; + mKillTime = SystemClock.uptimeMillis(); } Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 3ff6ec1afa41..86e6a3220507 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -244,7 +244,7 @@ abstract class HdmiCecLocalDevice { if (dest != mAddress && dest != Constants.ADDR_BROADCAST) { return false; } - // Cache incoming message. Note that it caches only white-listed one. + // Cache incoming message if it is included in the list of cacheable opcodes. mCecMessageCache.cacheMessage(message); return onMessage(message); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index aed94fc85431..64d70d6601f6 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -225,7 +225,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { if (SystemProperties.getBoolean(Constants.PROPERTY_KEEP_AWAKE, true)) { mWakeLock = new SystemWakeLock(); } else { - // Create a dummy lock object that doesn't do anything about wake lock, + // Create a stub lock object that doesn't do anything about wake lock, // hence allows the device to go to sleep even if it's the active source. mWakeLock = new ActiveWakeLock() { @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 2c0ddaf35182..804cc92cca08 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -1667,6 +1667,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (avr == null) { return; } + setArcStatus(false); // Seq #44. removeAction(RequestArcInitiationAction.class); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 0154fe07a418..254285dfbd41 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2942,7 +2942,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub vis = 0; } if (!mCurPerceptible) { - vis = 0; + vis &= ~InputMethodService.IME_VISIBLE; } // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked(). final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index f69c8239762d..d933c109b27d 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -17,6 +17,9 @@ package com.android.server.location; import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.app.AppOpsManager.OP_MOCK_LOCATION; +import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; +import static android.app.AppOpsManager.OP_MONITOR_LOCATION; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -120,10 +123,16 @@ import com.android.server.location.util.AppForegroundHelper; import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; import com.android.server.location.util.LocationAttributionHelper; +import com.android.server.location.util.LocationPermissionsHelper; +import com.android.server.location.util.LocationPowerSaveModeHelper; import com.android.server.location.util.LocationUsageLogger; +import com.android.server.location.util.ScreenInteractiveHelper; import com.android.server.location.util.SettingsHelper; import com.android.server.location.util.SystemAppForegroundHelper; import com.android.server.location.util.SystemAppOpsHelper; +import com.android.server.location.util.SystemLocationPermissionsHelper; +import com.android.server.location.util.SystemLocationPowerSaveModeHelper; +import com.android.server.location.util.SystemScreenInteractiveHelper; import com.android.server.location.util.SystemSettingsHelper; import com.android.server.location.util.SystemUserInfoHelper; import com.android.server.location.util.UserInfoHelper; @@ -173,7 +182,7 @@ public class LocationManagerService extends ILocationManager.Stub { publishBinderService(Context.LOCATION_SERVICE, mService); // client caching behavior is only enabled after seeing the first invalidate - invalidateLocalLocationEnabledCaches(); + LocationManager.invalidateLocalLocationEnabledCaches(); // disable caching for our own process Objects.requireNonNull(mService.mContext.getSystemService(LocationManager.class)) .disableLocalLocationEnabledCaches(); @@ -486,7 +495,7 @@ public class LocationManagerService extends ILocationManager.Stub { private void onLocationModeChanged(int userId) { boolean enabled = mSettingsHelper.isLocationEnabled(userId); - invalidateLocalLocationEnabledCaches(); + LocationManager.invalidateLocalLocationEnabledCaches(); if (D) { Log.d(TAG, "[u" + userId + "] location enabled = " + enabled); @@ -1232,19 +1241,20 @@ public class LocationManagerService extends ILocationManager.Stub { if (!currentlyMonitoring) { if (allowMonitoring) { if (!highPower) { - return mAppOpsHelper.startLocationMonitoring(mCallerIdentity); + return mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, mCallerIdentity); } else { - return mAppOpsHelper.startHighPowerLocationMonitoring(mCallerIdentity); + return mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + mCallerIdentity); } } } else { - if (!allowMonitoring || !mAppOpsHelper.checkLocationAccess(mCallerIdentity, + if (!allowMonitoring || !mAppOpsHelper.checkOpNoThrow(LocationPermissions.asAppOp( LocationPermissions.getPermissionLevel(mContext, mCallerIdentity.getUid(), - mCallerIdentity.getPid()))) { + mCallerIdentity.getPid())), mCallerIdentity)) { if (!highPower) { - mAppOpsHelper.stopLocationMonitoring(mCallerIdentity); + mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, mCallerIdentity); } else { - mAppOpsHelper.stopHighPowerLocationMonitoring(mCallerIdentity); + mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, mCallerIdentity); } return false; } @@ -1589,8 +1599,9 @@ public class LocationManagerService extends ILocationManager.Stub { continue; } - if (!mAppOpsHelper.checkLocationAccess(identity, - record.mRequest.isCoarse() ? PERMISSION_COARSE : PERMISSION_FINE)) { + if (!mAppOpsHelper.checkOpNoThrow(LocationPermissions.asAppOp( + record.mRequest.isCoarse() ? PERMISSION_COARSE : PERMISSION_FINE), + identity)) { continue; } final boolean isBatterySaverDisablingLocation = shouldThrottleRequests @@ -2118,7 +2129,8 @@ public class LocationManagerService extends ILocationManager.Stub { } // appops check should always be right before delivery - if (!mAppOpsHelper.noteLocationAccess(identity, permissionLevel)) { + if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel), + identity)) { return null; } @@ -2179,7 +2191,8 @@ public class LocationManagerService extends ILocationManager.Stub { if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) { // appops check should always be right before delivery - if (mAppOpsHelper.noteLocationAccess(identity, permissionLevel)) { + if (mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel), + identity)) { transport.deliverResult(lastLocation); } else { transport.deliverResult(null); @@ -2329,7 +2342,7 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public boolean sendExtraCommand(String provider, String command, Bundle extras) { + public void sendExtraCommand(String provider, String command, Bundle extras) { LocationPermissions.enforceCallingOrSelfLocationPermission(mContext, PERMISSION_COARSE); mContext.enforceCallingOrSelfPermission( permission.ACCESS_LOCATION_EXTRA_COMMANDS, null); @@ -2350,8 +2363,6 @@ public class LocationManagerService extends ILocationManager.Stub { LocationStatsEnums.USAGE_ENDED, LocationStatsEnums.API_SEND_EXTRA_COMMAND, provider); - - return true; } @Override @@ -2553,7 +2564,8 @@ public class LocationManagerService extends ILocationManager.Stub { r.mLastFixBroadcast = location; // appops check should always be right before delivery - if (!mAppOpsHelper.noteLocationAccess(receiver.mCallerIdentity, permissionLevel)) { + if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel), + receiver.mCallerIdentity)) { continue; } @@ -2644,7 +2656,7 @@ public class LocationManagerService extends ILocationManager.Stub { // unsafe is ok because app ops will verify the package name CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); - if (!mAppOpsHelper.noteMockLocationAccess(identity)) { + if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) { return; } @@ -2664,7 +2676,7 @@ public class LocationManagerService extends ILocationManager.Stub { // unsafe is ok because app ops will verify the package name CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); - if (!mAppOpsHelper.noteMockLocationAccess(identity)) { + if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) { return; } @@ -2687,7 +2699,7 @@ public class LocationManagerService extends ILocationManager.Stub { // unsafe is ok because app ops will verify the package name CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); - if (!mAppOpsHelper.noteMockLocationAccess(identity)) { + if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) { return; } @@ -2708,7 +2720,7 @@ public class LocationManagerService extends ILocationManager.Stub { // unsafe is ok because app ops will verify the package name CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); - if (!mAppOpsHelper.noteMockLocationAccess(identity)) { + if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) { return; } @@ -2926,24 +2938,36 @@ public class LocationManagerService extends ILocationManager.Stub { private final UserInfoHelper mUserInfoHelper; private final SystemAppOpsHelper mAppOpsHelper; + private final SystemLocationPermissionsHelper mLocationPermissionsHelper; private final SystemSettingsHelper mSettingsHelper; private final SystemAppForegroundHelper mAppForegroundHelper; - private final LocationUsageLogger mLocationUsageLogger; + private final SystemLocationPowerSaveModeHelper mLocationPowerSaveModeHelper; + private final SystemScreenInteractiveHelper mScreenInteractiveHelper; private final LocationAttributionHelper mLocationAttributionHelper; + private final LocationUsageLogger mLocationUsageLogger; + private final LocationRequestStatistics mLocationRequestStatistics; SystemInjector(Context context, UserInfoHelper userInfoHelper) { mUserInfoHelper = userInfoHelper; mAppOpsHelper = new SystemAppOpsHelper(context); + mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context, + mAppOpsHelper); mSettingsHelper = new SystemSettingsHelper(context); mAppForegroundHelper = new SystemAppForegroundHelper(context); - mLocationUsageLogger = new LocationUsageLogger(); + mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context); + mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context); mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper); + mLocationUsageLogger = new LocationUsageLogger(); + mLocationRequestStatistics = new LocationRequestStatistics(); } void onSystemReady() { mAppOpsHelper.onSystemReady(); + mLocationPermissionsHelper.onSystemReady(); mSettingsHelper.onSystemReady(); mAppForegroundHelper.onSystemReady(); + mLocationPowerSaveModeHelper.onSystemReady(); + mScreenInteractiveHelper.onSystemReady(); } @Override @@ -2957,6 +2981,11 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override + public LocationPermissionsHelper getLocationPermissionsHelper() { + return mLocationPermissionsHelper; + } + + @Override public SettingsHelper getSettingsHelper() { return mSettingsHelper; } @@ -2972,8 +3001,23 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override + public LocationPowerSaveModeHelper getLocationPowerSaveModeHelper() { + return mLocationPowerSaveModeHelper; + } + + @Override + public ScreenInteractiveHelper getScreenInteractiveHelper() { + return mScreenInteractiveHelper; + } + + @Override public LocationAttributionHelper getLocationAttributionHelper() { return mLocationAttributionHelper; } + + @Override + public LocationRequestStatistics getLocationRequestStatistics() { + return mLocationRequestStatistics; + } } } diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java index f37992a456ac..f6896b86f9b9 100644 --- a/services/core/java/com/android/server/location/PassiveProvider.java +++ b/services/core/java/com/android/server/location/PassiveProvider.java @@ -50,14 +50,10 @@ public class PassiveProvider extends AbstractLocationProvider { Criteria.POWER_LOW, Criteria.ACCURACY_COARSE); - private volatile boolean mReportLocation; - public PassiveProvider(Context context) { // using a direct executor is ok because this class has no locks that could deadlock super(DIRECT_EXECUTOR, CallerIdentity.fromContext(context)); - mReportLocation = false; - setProperties(PROPERTIES); setAllowed(true); } @@ -66,15 +62,11 @@ public class PassiveProvider extends AbstractLocationProvider { * Pass a location into the passive provider. */ public void updateLocation(Location location) { - if (mReportLocation) { - reportLocation(location); - } + reportLocation(location); } @Override - public void onSetRequest(ProviderRequest request) { - mReportLocation = request.reportLocation; - } + public void onSetRequest(ProviderRequest request) {} @Override protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {} diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java index c855a12606b2..2d9734ef0553 100644 --- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java +++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java @@ -16,7 +16,6 @@ package com.android.server.location.geofence; -import static android.Manifest.permission; import static android.location.LocationManager.KEY_PROXIMITY_ENTERING; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; @@ -44,8 +43,8 @@ import com.android.server.PendingIntentUtils; import com.android.server.location.LocationPermissions; import com.android.server.location.listeners.ListenerMultiplexer; import com.android.server.location.listeners.PendingIntentListenerRegistration; -import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; +import com.android.server.location.util.LocationPermissionsHelper; import com.android.server.location.util.LocationUsageLogger; import com.android.server.location.util.SettingsHelper; import com.android.server.location.util.UserInfoHelper; @@ -86,7 +85,7 @@ public class GeofenceManager extends // we store these values because we don't trust the listeners not to give us dupes, not to // spam us, and because checking the values may be more expensive - private boolean mAppOpsAllowed; + private boolean mPermitted; private @Nullable Location mCachedLocation; private float mCachedLocationDistanceM; @@ -101,7 +100,7 @@ public class GeofenceManager extends mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class)) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - TAG + ":" + identity.getPackageName()); + TAG + ":" + identity.getPackageName()); mWakeLock.setReferenceCounted(true); mWakeLock.setWorkSource(identity.addToWorkSource(null)); } @@ -114,7 +113,8 @@ public class GeofenceManager extends @Override protected void onPendingIntentListenerRegister() { mGeofenceState = STATE_UNKNOWN; - mAppOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity(), PERMISSION_FINE); + mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE, + getIdentity()); } @Override @@ -127,18 +127,32 @@ public class GeofenceManager extends } } - boolean isAppOpsAllowed() { - return mAppOpsAllowed; + boolean isPermitted() { + return mPermitted; } - boolean onAppOpsChanged(String packageName) { + boolean onLocationPermissionsChanged(String packageName) { if (getIdentity().getPackageName().equals(packageName)) { - boolean appOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity(), - PERMISSION_FINE); - if (appOpsAllowed != mAppOpsAllowed) { - mAppOpsAllowed = appOpsAllowed; - return true; - } + return onLocationPermissionsChanged(); + } + + return false; + } + + boolean onLocationPermissionsChanged(int uid) { + if (getIdentity().getUid() == uid) { + return onLocationPermissionsChanged(); + } + + return false; + } + + private boolean onLocationPermissionsChanged() { + boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE, + getIdentity()); + if (permitted != mPermitted) { + mPermitted = permitted; + return true; } return false; @@ -186,10 +200,10 @@ public class GeofenceManager extends mWakeLock.acquire(WAKELOCK_TIMEOUT_MS); try { - pendingIntent.send(mContext, 0, intent, - (pI, i, rC, rD, rE) -> mWakeLock.release(), - null, permission.ACCESS_FINE_LOCATION, - PendingIntentUtils.createDontSendToRestrictedAppsBundle(null)); + // send() only enforces permissions for broadcast intents, but since clients can + // select any kind of pending intent we do not rely on send() to enforce permissions + pendingIntent.send(mContext, 0, intent, (pI, i, rC, rD, rE) -> mWakeLock.release(), + null, null, PendingIntentUtils.createDontSendToRestrictedAppsBundle(null)); } catch (PendingIntent.CanceledException e) { mWakeLock.release(); removeRegistration(new GeofenceKey(pendingIntent, getRequest()), this); @@ -202,7 +216,7 @@ public class GeofenceManager extends builder.append(getIdentity()); ArraySet<String> flags = new ArraySet<>(1); - if (!mAppOpsAllowed) { + if (!mPermitted) { flags.add("na"); } if (!flags.isEmpty()) { @@ -224,10 +238,22 @@ public class GeofenceManager extends private final SettingsHelper.UserSettingChangedListener mLocationPackageBlacklistChangedListener = this::onLocationPackageBlacklistChanged; - private final AppOpsHelper.LocationAppOpListener mAppOpsChangedListener = this::onAppOpsChanged; + private final LocationPermissionsHelper.LocationPermissionsListener + mLocationPermissionsListener = + new LocationPermissionsHelper.LocationPermissionsListener() { + @Override + public void onLocationPermissionsChanged(String packageName) { + GeofenceManager.this.onLocationPermissionsChanged(packageName); + } + + @Override + public void onLocationPermissionsChanged(int uid) { + GeofenceManager.this.onLocationPermissionsChanged(uid); + } + }; protected final UserInfoHelper mUserInfoHelper; - protected final AppOpsHelper mAppOpsHelper; + protected final LocationPermissionsHelper mLocationPermissionsHelper; protected final SettingsHelper mSettingsHelper; protected final LocationUsageLogger mLocationUsageLogger; @@ -241,7 +267,7 @@ public class GeofenceManager extends mContext = context.createAttributionContext(ATTRIBUTION_TAG); mUserInfoHelper = injector.getUserInfoHelper(); mSettingsHelper = injector.getSettingsHelper(); - mAppOpsHelper = injector.getAppOpsHelper(); + mLocationPermissionsHelper = injector.getLocationPermissionsHelper(); mLocationUsageLogger = injector.getLocationUsageLogger(); } @@ -281,7 +307,7 @@ public class GeofenceManager extends @Override protected boolean isActive(GeofenceRegistration registration) { CallerIdentity identity = registration.getIdentity(); - return registration.isAppOpsAllowed() + return registration.isPermitted() && mUserInfoHelper.isCurrentUserId(identity.getUserId()) && mSettingsHelper.isLocationEnabled(identity.getUserId()) && !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(), @@ -294,7 +320,7 @@ public class GeofenceManager extends mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener); mSettingsHelper.addOnLocationPackageBlacklistChangedListener( mLocationPackageBlacklistChangedListener); - mAppOpsHelper.addListener(mAppOpsChangedListener); + mLocationPermissionsHelper.addListener(mLocationPermissionsListener); } @Override @@ -303,7 +329,7 @@ public class GeofenceManager extends mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener); mSettingsHelper.removeOnLocationPackageBlacklistChangedListener( mLocationPackageBlacklistChangedListener); - mAppOpsHelper.removeListener(mAppOpsChangedListener); + mLocationPermissionsHelper.removeListener(mLocationPermissionsListener); } @Override @@ -434,7 +460,11 @@ public class GeofenceManager extends updateRegistrations(registration -> registration.getIdentity().getUserId() == userId); } - private void onAppOpsChanged(String packageName) { - updateRegistrations(registration -> registration.onAppOpsChanged(packageName)); + private void onLocationPermissionsChanged(String packageName) { + updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName)); + } + + private void onLocationPermissionsChanged(int uid) { + updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid)); } } diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java index 53e660ad6475..0b7968be484b 100644 --- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java @@ -31,8 +31,8 @@ import com.android.server.LocalServices; import com.android.server.location.listeners.BinderListenerRegistration; import com.android.server.location.listeners.ListenerMultiplexer; import com.android.server.location.util.AppForegroundHelper; -import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; +import com.android.server.location.util.LocationPermissionsHelper; import com.android.server.location.util.SettingsHelper; import com.android.server.location.util.UserInfoHelper; import com.android.server.location.util.UserInfoHelper.UserListener; @@ -65,7 +65,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter // we store these values because we don't trust the listeners not to give us dupes, not to // spam us, and because checking the values may be more expensive private boolean mForeground; - private boolean mAppOpsAllowed; + private boolean mPermitted; protected GnssListenerRegistration(@Nullable TRequest request, CallerIdentity callerIdentity, TListener listener) { @@ -84,24 +84,39 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter return mForeground; } - boolean isAppOpsAllowed() { - return mAppOpsAllowed; + boolean isPermitted() { + return mPermitted; } @Override protected void onBinderListenerRegister() { - mAppOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity(), PERMISSION_FINE); + mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE, + getIdentity()); mForeground = mAppForegroundHelper.isAppForeground(getIdentity().getUid()); } - boolean onAppOpsChanged(String packageName) { + boolean onLocationPermissionsChanged(String packageName) { if (getIdentity().getPackageName().equals(packageName)) { - boolean appOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity(), - PERMISSION_FINE); - if (appOpsAllowed != mAppOpsAllowed) { - mAppOpsAllowed = appOpsAllowed; - return true; - } + return onLocationPermissionsChanged(); + } + + return false; + } + + boolean onLocationPermissionsChanged(int uid) { + if (getIdentity().getUid() == uid) { + return onLocationPermissionsChanged(); + } + + return false; + } + + private boolean onLocationPermissionsChanged() { + boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE, + getIdentity()); + if (permitted != mPermitted) { + mPermitted = permitted; + return true; } return false; @@ -125,7 +140,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter if (!mForeground) { flags.add("bg"); } - if (!mAppOpsAllowed) { + if (!mPermitted) { flags.add("na"); } if (!flags.isEmpty()) { @@ -141,7 +156,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter protected final UserInfoHelper mUserInfoHelper; protected final SettingsHelper mSettingsHelper; - protected final AppOpsHelper mAppOpsHelper; + protected final LocationPermissionsHelper mLocationPermissionsHelper; protected final AppForegroundHelper mAppForegroundHelper; protected final LocationManagerInternal mLocationManagerInternal; @@ -154,14 +169,26 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter private final SettingsHelper.UserSettingChangedListener mLocationPackageBlacklistChangedListener = this::onLocationPackageBlacklistChanged; - private final AppOpsHelper.LocationAppOpListener mAppOpsChangedListener = this::onAppOpsChanged; + private final LocationPermissionsHelper.LocationPermissionsListener + mLocationPermissionsListener = + new LocationPermissionsHelper.LocationPermissionsListener() { + @Override + public void onLocationPermissionsChanged(String packageName) { + GnssListenerMultiplexer.this.onLocationPermissionsChanged(packageName); + } + + @Override + public void onLocationPermissionsChanged(int uid) { + GnssListenerMultiplexer.this.onLocationPermissionsChanged(uid); + } + }; private final AppForegroundHelper.AppForegroundListener mAppForegroundChangedListener = this::onAppForegroundChanged; protected GnssListenerMultiplexer(Injector injector) { mUserInfoHelper = injector.getUserInfoHelper(); mSettingsHelper = injector.getSettingsHelper(); - mAppOpsHelper = injector.getAppOpsHelper(); + mLocationPermissionsHelper = injector.getLocationPermissionsHelper(); mAppForegroundHelper = injector.getAppForegroundHelper(); mLocationManagerInternal = Objects.requireNonNull( LocalServices.getService(LocationManagerInternal.class)); @@ -208,7 +235,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter CallerIdentity identity = registration.getIdentity(); // TODO: this should be checking if the gps provider is enabled, not if location is enabled, // but this is the same for now. - return registration.isAppOpsAllowed() + return registration.isPermitted() && (registration.isForeground() || isBackgroundRestrictionExempt(identity)) && mUserInfoHelper.isCurrentUserId(identity.getUserId()) && mSettingsHelper.isLocationEnabled(identity.getUserId()) @@ -241,7 +268,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter mBackgroundThrottlePackageWhitelistChangedListener); mSettingsHelper.addOnLocationPackageBlacklistChangedListener( mLocationPackageBlacklistChangedListener); - mAppOpsHelper.addListener(mAppOpsChangedListener); + mLocationPermissionsHelper.addListener(mLocationPermissionsListener); mAppForegroundHelper.addListener(mAppForegroundChangedListener); } @@ -257,7 +284,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter mBackgroundThrottlePackageWhitelistChangedListener); mSettingsHelper.removeOnLocationPackageBlacklistChangedListener( mLocationPackageBlacklistChangedListener); - mAppOpsHelper.removeListener(mAppOpsChangedListener); + mLocationPermissionsHelper.removeListener(mLocationPermissionsListener); mAppForegroundHelper.removeListener(mAppForegroundChangedListener); } @@ -279,8 +306,12 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter updateRegistrations(registration -> registration.getIdentity().getUserId() == userId); } - private void onAppOpsChanged(String packageName) { - updateRegistrations(registration -> registration.onAppOpsChanged(packageName)); + private void onLocationPermissionsChanged(String packageName) { + updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName)); + } + + private void onLocationPermissionsChanged(int uid) { + updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid)); } private void onAppForegroundChanged(int uid, boolean foreground) { diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 8aaf4bf6d8b0..8004ec70aaf3 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -2008,9 +2008,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private final class FusedLocationListener extends LocationChangeListener { @Override public void onLocationChanged(Location location) { - if (LocationManager.FUSED_PROVIDER.equals(location.getProvider())) { - injectBestLocation(location); - } + injectBestLocation(location); } } diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java index 58e725ca152d..8e81f29550d6 100644 --- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java +++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java @@ -18,10 +18,9 @@ package com.android.server.location.gnss; import static android.location.LocationManager.GPS_PROVIDER; -import static com.android.server.location.LocationPermissions.PERMISSION_FINE; - import android.Manifest; import android.annotation.Nullable; +import android.app.AppOpsManager; import android.content.Context; import android.location.GnssAntennaInfo; import android.location.GnssMeasurementCorrections; @@ -47,10 +46,8 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; -import com.android.server.location.util.AppForegroundHelper; import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; -import com.android.server.location.util.SettingsHelper; import java.io.FileDescriptor; import java.util.List; @@ -68,9 +65,7 @@ public class GnssManagerService implements GnssNative.Callbacks { } private final Context mContext; - private final SettingsHelper mSettingsHelper; private final AppOpsHelper mAppOpsHelper; - private final AppForegroundHelper mAppForegroundHelper; private final LocationManagerInternal mLocationManagerInternal; private final GnssLocationProvider mGnssLocationProvider; @@ -109,9 +104,7 @@ public class GnssManagerService implements GnssNative.Callbacks { GnssNative.initialize(); mContext = context.createAttributionContext(ATTRIBUTION_ID); - mSettingsHelper = injector.getSettingsHelper(); mAppOpsHelper = injector.getAppOpsHelper(); - mAppForegroundHelper = injector.getAppForegroundHelper(); mLocationManagerInternal = LocalServices.getService(LocationManagerInternal.class); if (gnssLocationProvider == null) { @@ -192,9 +185,10 @@ public class GnssManagerService implements GnssNative.Callbacks { public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName, String attributionTag) { mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); + mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null); CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); - if (!mAppOpsHelper.checkLocationAccess(identity, PERMISSION_FINE)) { + if (!mAppOpsHelper.checkOpNoThrow(AppOpsManager.OP_FINE_LOCATION, identity)) { return false; } diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java index 9227a177f861..0815d46a735d 100644 --- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java @@ -16,10 +16,10 @@ package com.android.server.location.gnss; -import static com.android.server.location.LocationPermissions.PERMISSION_FINE; import static com.android.server.location.gnss.GnssManagerService.D; import static com.android.server.location.gnss.GnssManagerService.TAG; +import android.app.AppOpsManager; import android.location.GnssMeasurementsEvent; import android.location.GnssRequest; import android.location.IGnssMeasurementsListener; @@ -30,6 +30,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; import com.android.server.location.util.LocationUsageLogger; import com.android.server.location.util.SettingsHelper; @@ -47,6 +48,7 @@ public class GnssMeasurementsProvider extends GnssListenerMultiplexer<GnssRequest, IGnssMeasurementsListener, Boolean> implements SettingsHelper.GlobalSettingChangedListener { + private final AppOpsHelper mAppOpsHelper; private final LocationUsageLogger mLogger; private final GnssMeasurementProviderNative mNative; @@ -57,6 +59,7 @@ public class GnssMeasurementsProvider extends @VisibleForTesting public GnssMeasurementsProvider(Injector injector, GnssMeasurementProviderNative aNative) { super(injector); + mAppOpsHelper = injector.getAppOpsHelper(); mLogger = injector.getLocationUsageLogger(); mNative = aNative; } @@ -163,7 +166,8 @@ public class GnssMeasurementsProvider extends */ public void onMeasurementsAvailable(GnssMeasurementsEvent event) { deliverToListeners(registration -> { - if (mAppOpsHelper.noteLocationAccess(registration.getIdentity(), PERMISSION_FINE)) { + if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, + registration.getIdentity())) { return listener -> listener.onGnssMeasurementsReceived(event); } else { return null; diff --git a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java index a07fbe41c975..7dcffc664f52 100644 --- a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java @@ -16,10 +16,10 @@ package com.android.server.location.gnss; -import static com.android.server.location.LocationPermissions.PERMISSION_FINE; import static com.android.server.location.gnss.GnssManagerService.D; import static com.android.server.location.gnss.GnssManagerService.TAG; +import android.app.AppOpsManager; import android.location.GnssNavigationMessage; import android.location.IGnssNavigationMessageListener; import android.location.util.identity.CallerIdentity; @@ -27,6 +27,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; /** @@ -39,6 +40,7 @@ import com.android.server.location.util.Injector; public class GnssNavigationMessageProvider extends GnssListenerMultiplexer<Void, IGnssNavigationMessageListener, Void> { + private final AppOpsHelper mAppOpsHelper; private final GnssNavigationMessageProviderNative mNative; public GnssNavigationMessageProvider(Injector injector) { @@ -49,6 +51,7 @@ public class GnssNavigationMessageProvider extends public GnssNavigationMessageProvider(Injector injector, GnssNavigationMessageProviderNative aNative) { super(injector); + mAppOpsHelper = injector.getAppOpsHelper(); mNative = aNative; } @@ -90,7 +93,8 @@ public class GnssNavigationMessageProvider extends */ public void onNavigationMessageAvailable(GnssNavigationMessage event) { deliverToListeners(registration -> { - if (mAppOpsHelper.noteLocationAccess(registration.getIdentity(), PERMISSION_FINE)) { + if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, + registration.getIdentity())) { return listener -> listener.onGnssNavigationMessageReceived(event); } else { return null; diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java index d33b05866877..19f79273c992 100644 --- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java @@ -16,10 +16,10 @@ package com.android.server.location.gnss; -import static com.android.server.location.LocationPermissions.PERMISSION_FINE; import static com.android.server.location.gnss.GnssManagerService.D; import static com.android.server.location.gnss.GnssManagerService.TAG; +import android.app.AppOpsManager; import android.location.GnssStatus; import android.location.IGnssStatusListener; import android.location.util.identity.CallerIdentity; @@ -27,6 +27,7 @@ import android.os.IBinder; import android.stats.location.LocationStatsEnums; import android.util.Log; +import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; import com.android.server.location.util.LocationUsageLogger; @@ -35,10 +36,12 @@ import com.android.server.location.util.LocationUsageLogger; */ public class GnssStatusProvider extends GnssListenerMultiplexer<Void, IGnssStatusListener, Void> { + private final AppOpsHelper mAppOpsHelper; private final LocationUsageLogger mLogger; public GnssStatusProvider(Injector injector) { super(injector); + mAppOpsHelper = injector.getAppOpsHelper(); mLogger = injector.getLocationUsageLogger(); } @@ -113,7 +116,8 @@ public class GnssStatusProvider extends GnssListenerMultiplexer<Void, IGnssStatu */ public void onSvStatusChanged(GnssStatus gnssStatus) { deliverToListeners(registration -> { - if (mAppOpsHelper.noteLocationAccess(registration.getIdentity(), PERMISSION_FINE)) { + if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, + registration.getIdentity())) { return listener -> listener.onSvStatusChanged(gnssStatus); } else { return null; @@ -126,7 +130,8 @@ public class GnssStatusProvider extends GnssListenerMultiplexer<Void, IGnssStatu */ public void onNmeaReceived(long timestamp, String nmea) { deliverToListeners(registration -> { - if (mAppOpsHelper.noteLocationAccess(registration.getIdentity(), PERMISSION_FINE)) { + if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, + registration.getIdentity())) { return listener -> listener.onNmeaReceived(timestamp, nmea); } else { return null; diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java index f5889ceeed6a..528cf8acd5b3 100644 --- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.os.Binder; import android.os.Build; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Pair; @@ -40,8 +41,8 @@ import java.util.function.Predicate; * A base class to multiplex client listener registrations within system server. Registrations are * divided into two categories, active registrations and inactive registrations, as defined by * {@link #isActive(ListenerRegistration)}. If a registration's active state changes, - * {@link #updateRegistrations(Predicate)} or {@link #updateRegistration(Object, Predicate)} must be - * invoked and return true for any registration whose active state may have changed. + * {@link #updateRegistrations(Predicate)} must be invoked and return true for any registration + * whose active state may have changed. * * Callbacks invoked for various changes will always be ordered according to this lifecycle list: * @@ -217,7 +218,6 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, mRegistrations.put(key, registration); } - if (wasEmpty) { onRegister(); } @@ -268,7 +268,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire(); ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) { - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TKey key = mRegistrations.keyAt(i); if (predicate.test(key)) { removeRegistration(key, mRegistrations.valueAt(i)); @@ -287,7 +288,7 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, * completely at some later time. */ protected final void removeRegistration(@NonNull Object key, - @NonNull ListenerRegistration registration) { + @NonNull ListenerRegistration<?, ?> registration) { synchronized (mRegistrations) { int index = mRegistrations.indexOfKey(key); if (index < 0) { @@ -353,7 +354,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, } ArrayList<TRegistration> actives = new ArrayList<>(mRegistrations.size()); - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); if (registration.isActive()) { actives.add(registration); @@ -395,7 +397,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, protected final void updateService(Predicate<TRegistration> predicate) { synchronized (mRegistrations) { boolean updateService = false; - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); if (predicate.test(registration) && registration.isActive()) { updateService = true; @@ -434,7 +437,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire(); ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) { - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); if (predicate.test(registration)) { onRegistrationActiveChanged(registration); @@ -446,33 +450,6 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, } } - /** - * Evaluates the predicate on the registration with the given key. The predicate should return - * true if the active state of the registration may have changed as a result. Any - * {@link #updateService()} invocations made while this method is executing will be deferred - * until after the method is complete so as to avoid redundant work. - */ - protected final void updateRegistration(TKey key, @NonNull Predicate<TRegistration> predicate) { - synchronized (mRegistrations) { - // since updating a registration can invoke a variety of callbacks, we need to ensure - // those callbacks themselves do not re-enter, as this could lead to out-of-order - // callbacks. note that try-with-resources ordering is meaningful here as well. we want - // to close the reentrancy guard first, as this may generate additional service updates, - // then close the update service buffer. - long identity = Binder.clearCallingIdentity(); - try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire(); - ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) { - - TRegistration registration = mRegistrations.get(key); - if (registration != null && predicate.test(registration)) { - onRegistrationActiveChanged(registration); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - @GuardedBy("mRegistrations") private void onRegistrationActiveChanged(TRegistration registration) { if (Build.IS_DEBUGGABLE) { @@ -511,7 +488,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, synchronized (mRegistrations) { long identity = Binder.clearCallingIdentity(); try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) { - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); if (registration.isActive()) { ListenerOperation<TListener> operation = function.apply(registration); @@ -537,7 +515,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, synchronized (mRegistrations) { long identity = Binder.clearCallingIdentity(); try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) { - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); if (registration.isActive()) { execute(registration, operation); @@ -571,7 +550,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, ipw.println("listeners:"); ipw.increaseIndent(); - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); ipw.print(registration); if (!registration.isActive()) { @@ -612,23 +592,33 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, */ private final class ReentrancyGuard implements AutoCloseable { + @GuardedBy("mRegistrations") private int mGuardCount; - private @Nullable ArrayList<Pair<Object, ListenerRegistration>> mScheduledRemovals; + @GuardedBy("mRegistrations") + private @Nullable ArraySet<Pair<Object, ListenerRegistration<?, ?>>> mScheduledRemovals; ReentrancyGuard() { mGuardCount = 0; mScheduledRemovals = null; } + @GuardedBy("mRegistrations") boolean isReentrant() { + if (Build.IS_DEBUGGABLE) { + Preconditions.checkState(Thread.holdsLock(mRegistrations)); + } return mGuardCount != 0; } - void markForRemoval(Object key, ListenerRegistration registration) { + @GuardedBy("mRegistrations") + void markForRemoval(Object key, ListenerRegistration<?, ?> registration) { + if (Build.IS_DEBUGGABLE) { + Preconditions.checkState(Thread.holdsLock(mRegistrations)); + } Preconditions.checkState(isReentrant()); if (mScheduledRemovals == null) { - mScheduledRemovals = new ArrayList<>(mRegistrations.size()); + mScheduledRemovals = new ArraySet<>(mRegistrations.size()); } mScheduledRemovals.add(new Pair<>(key, registration)); } @@ -640,7 +630,7 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, @Override public void close() { - ArrayList<Pair<Object, ListenerRegistration>> scheduledRemovals = null; + ArraySet<Pair<Object, ListenerRegistration<?, ?>>> scheduledRemovals = null; Preconditions.checkState(mGuardCount > 0); if (--mGuardCount == 0) { @@ -650,8 +640,10 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, if (scheduledRemovals != null) { try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) { - for (int i = 0; i < scheduledRemovals.size(); i++) { - Pair<Object, ListenerRegistration> pair = scheduledRemovals.get(i); + final int size = scheduledRemovals.size(); + for (int i = 0; i < size; i++) { + Pair<Object, ListenerRegistration<?, ?>> pair = scheduledRemovals.valueAt( + i); removeRegistration(pair.first, pair.second); } } diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java index e529a7d81b70..6a815ead9f9f 100644 --- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java +++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java @@ -61,7 +61,7 @@ public abstract class RemovableListenerRegistration<TRequest, TListener> extends * Removes this registration. Does nothing if invoked before {@link #onRegister(Object)} or * after {@link #onUnregister()}. It is safe to invoke this from within either function. */ - public void remove() { + public final void remove() { Object key = mKey; if (key != null) { getOwner().removeRegistration(key, this); diff --git a/services/core/java/com/android/server/location/util/AppOpsHelper.java b/services/core/java/com/android/server/location/util/AppOpsHelper.java index 3e42f27da78c..1578289d53b4 100644 --- a/services/core/java/com/android/server/location/util/AppOpsHelper.java +++ b/services/core/java/com/android/server/location/util/AppOpsHelper.java @@ -16,15 +16,8 @@ package com.android.server.location.util; -import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; -import static android.app.AppOpsManager.OP_MONITOR_LOCATION; - -import android.app.AppOpsManager; import android.location.util.identity.CallerIdentity; -import com.android.server.location.LocationPermissions; -import com.android.server.location.LocationPermissions.PermissionLevel; - import java.util.concurrent.CopyOnWriteArrayList; /** @@ -70,84 +63,27 @@ public abstract class AppOpsHelper { } /** - * Checks if the given identity may have locations delivered without noting that a location is - * being delivered. This is a looser guarantee than - * {@link #noteLocationAccess(CallerIdentity, int)}, and this function does not validate package - * arguments and so should not be used with unvalidated arguments or before actually delivering - * locations. - * - * @see AppOpsManager#checkOpNoThrow(int, int, String) - */ - public final boolean checkLocationAccess(CallerIdentity callerIdentity, - @PermissionLevel int permissionLevel) { - if (permissionLevel == LocationPermissions.PERMISSION_NONE) { - return false; - } - - return checkOpNoThrow(LocationPermissions.asAppOp(permissionLevel), callerIdentity); - } - - /** - * Notes location access to the given identity, ie, location delivery. This method should be - * called right before a location is delivered, and if it returns false, the location should not - * be delivered. - */ - public final boolean noteLocationAccess(CallerIdentity identity, - @PermissionLevel int permissionLevel) { - if (permissionLevel == LocationPermissions.PERMISSION_NONE) { - return false; - } - - return noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel), identity); - } - - /** - * Notifies app ops that the given identity is using location at normal/low power levels. If - * this function returns false, do not later call - * {@link #stopLocationMonitoring(CallerIdentity)}. + * Starts the given appop. */ - public final boolean startLocationMonitoring(CallerIdentity identity) { - return startOpNoThrow(OP_MONITOR_LOCATION, identity); - } + public abstract boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity); /** - * Notifies app ops that the given identity is no longer using location at normal/low power - * levels. + * Finishes the given appop. */ - public final void stopLocationMonitoring(CallerIdentity identity) { - finishOp(OP_MONITOR_LOCATION, identity); - } + public abstract void finishOp(int appOp, CallerIdentity callerIdentity); /** - * Notifies app ops that the given identity is using location at high levels. If this function - * returns false, do not later call {@link #stopLocationMonitoring(CallerIdentity)}. + * Checks the given appop. */ - public final boolean startHighPowerLocationMonitoring(CallerIdentity identity) { - return startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity); - } + public abstract boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity); /** - * Notifies app ops that the given identity is no longer using location at high power levels. + * Notes the given appop (and may throw a security exception). */ - public final void stopHighPowerLocationMonitoring(CallerIdentity identity) { - finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity); - } + public abstract boolean noteOp(int appOp, CallerIdentity callerIdentity); /** - * Notes access to any mock location APIs. If this call returns false, access to the APIs should - * silently fail. + * Notes the given appop. */ - public final boolean noteMockLocationAccess(CallerIdentity callerIdentity) { - return noteOp(AppOpsManager.OP_MOCK_LOCATION, callerIdentity); - } - - protected abstract boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity); - - protected abstract void finishOp(int appOp, CallerIdentity callerIdentity); - - protected abstract boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity); - - protected abstract boolean noteOp(int appOp, CallerIdentity callerIdentity); - - protected abstract boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity); + public abstract boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity); } diff --git a/services/core/java/com/android/server/location/util/Injector.java b/services/core/java/com/android/server/location/util/Injector.java index e16df5dc26cd..379b303bbfc3 100644 --- a/services/core/java/com/android/server/location/util/Injector.java +++ b/services/core/java/com/android/server/location/util/Injector.java @@ -17,6 +17,7 @@ package com.android.server.location.util; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.location.LocationRequestStatistics; /** * Injects various location dependencies so that they may be controlled by tests. @@ -30,15 +31,27 @@ public interface Injector { /** Returns an AppOpsHelper. */ AppOpsHelper getAppOpsHelper(); + /** Returns a LocationPermissionsHelper. */ + LocationPermissionsHelper getLocationPermissionsHelper(); + /** Returns a SettingsHelper. */ SettingsHelper getSettingsHelper(); /** Returns an AppForegroundHelper. */ AppForegroundHelper getAppForegroundHelper(); - /** Returns a LocationUsageLogger. */ - LocationUsageLogger getLocationUsageLogger(); + /** Returns a LocationPowerSaveModeHelper. */ + LocationPowerSaveModeHelper getLocationPowerSaveModeHelper(); + + /** Returns a ScreenInteractiveHelper. */ + ScreenInteractiveHelper getScreenInteractiveHelper(); /** Returns a LocationAttributionHelper. */ LocationAttributionHelper getLocationAttributionHelper(); + + /** Returns a LocationUsageLogger. */ + LocationUsageLogger getLocationUsageLogger(); + + /** Returns a LocationRequestStatistics. */ + LocationRequestStatistics getLocationRequestStatistics(); } diff --git a/services/core/java/com/android/server/location/util/LocationAttributionHelper.java b/services/core/java/com/android/server/location/util/LocationAttributionHelper.java index 8fe09412c166..bc3ac0ff2e48 100644 --- a/services/core/java/com/android/server/location/util/LocationAttributionHelper.java +++ b/services/core/java/com/android/server/location/util/LocationAttributionHelper.java @@ -16,9 +16,16 @@ package com.android.server.location.util; +import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; +import static android.app.AppOpsManager.OP_MONITOR_LOCATION; + +import static com.android.server.location.LocationManagerService.D; +import static com.android.server.location.LocationManagerService.TAG; + import android.location.util.identity.CallerIdentity; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -83,7 +90,7 @@ public class LocationAttributionHelper { i -> new ArraySet<>()); boolean empty = keySet.isEmpty(); if (keySet.add(new ProviderListener(provider, key)) && empty) { - if (!mAppOpsHelper.startLocationMonitoring(identity)) { + if (!mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) { mAttributions.remove(identity); } } @@ -99,7 +106,7 @@ public class LocationAttributionHelper { if (keySet != null && keySet.remove(new ProviderListener(provider, key)) && keySet.isEmpty()) { mAttributions.remove(identity); - mAppOpsHelper.stopLocationMonitoring(identity); + mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, identity); } } @@ -113,14 +120,18 @@ public class LocationAttributionHelper { i -> new ArraySet<>()); boolean empty = keySet.isEmpty(); if (keySet.add(new ProviderListener(provider, key)) && empty) { - if (!mAppOpsHelper.startHighPowerLocationMonitoring(identity)) { + if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity)) { + if (D) { + Log.v(TAG, "starting high power location attribution for " + identity); + } + } else { mHighPowerAttributions.remove(identity); } } } /** - * Report high power location usage has stopped for the given caller on the given provider, + * Report high power location usage has stopped for the given caller on the given provider, * with a unique key. */ public synchronized void reportHighPowerLocationStop(CallerIdentity identity, String provider, @@ -128,8 +139,11 @@ public class LocationAttributionHelper { Set<ProviderListener> keySet = mHighPowerAttributions.get(identity); if (keySet != null && keySet.remove(new ProviderListener(provider, key)) && keySet.isEmpty()) { + if (D) { + Log.v(TAG, "stopping high power location attribution for " + identity); + } mHighPowerAttributions.remove(identity); - mAppOpsHelper.stopHighPowerLocationMonitoring(identity); + mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity); } } } diff --git a/services/core/java/com/android/server/location/util/LocationPermissionsHelper.java b/services/core/java/com/android/server/location/util/LocationPermissionsHelper.java new file mode 100644 index 000000000000..daf56797c0c9 --- /dev/null +++ b/services/core/java/com/android/server/location/util/LocationPermissionsHelper.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.util; + +import static com.android.server.location.LocationPermissions.PERMISSION_NONE; + +import android.location.util.identity.CallerIdentity; + +import com.android.server.location.LocationPermissions; +import com.android.server.location.LocationPermissions.PermissionLevel; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Provides helpers and listeners for appops. + */ +public abstract class LocationPermissionsHelper { + + /** + * Listener for current user changes. + */ + public interface LocationPermissionsListener { + + /** + * Called when something has changed about location permissions for the given package. + */ + void onLocationPermissionsChanged(String packageName); + + /** + * Called when something has changed about location permissions for the given uid. + */ + void onLocationPermissionsChanged(int uid); + } + + private final CopyOnWriteArrayList<LocationPermissionsListener> mListeners; + private final AppOpsHelper mAppOps; + + public LocationPermissionsHelper(AppOpsHelper appOps) { + mListeners = new CopyOnWriteArrayList<>(); + mAppOps = appOps; + + mAppOps.addListener(this::onAppOpsChanged); + } + + protected final void notifyLocationPermissionsChanged(String packageName) { + for (LocationPermissionsListener listener : mListeners) { + listener.onLocationPermissionsChanged(packageName); + } + } + + protected final void notifyLocationPermissionsChanged(int uid) { + for (LocationPermissionsListener listener : mListeners) { + listener.onLocationPermissionsChanged(uid); + } + } + + private void onAppOpsChanged(String packageName) { + notifyLocationPermissionsChanged(packageName); + } + + /** + * Adds a listener for location permissions events. Callbacks occur on an unspecified thread. + */ + public final void addListener(LocationPermissionsListener listener) { + mListeners.add(listener); + } + + /** + * Removes a listener for location permissions events. + */ + public final void removeListener(LocationPermissionsListener listener) { + mListeners.remove(listener); + } + + /** + * Returns true if the given identity may access location at the given permissions level, taking + * into account both permissions and appops. + */ + public final boolean hasLocationPermissions(@PermissionLevel int permissionLevel, + CallerIdentity identity) { + if (permissionLevel == PERMISSION_NONE) { + return false; + } + + if (!hasPermission(LocationPermissions.asPermission(permissionLevel), identity)) { + return false; + } + + return mAppOps.checkOpNoThrow(permissionLevel, identity); + } + + protected abstract boolean hasPermission(String permission, CallerIdentity callerIdentity); +} diff --git a/services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java new file mode 100644 index 000000000000..a9a8c50f11dc --- /dev/null +++ b/services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.util; + +import android.os.PowerManager.LocationPowerSaveMode; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Provides accessors and listeners for location power save mode. + */ +public abstract class LocationPowerSaveModeHelper { + + /** + * Listener for location power save mode changes. + */ + public interface LocationPowerSaveModeChangedListener { + /** + * Called when the location power save mode changes. + */ + void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode); + } + + private final CopyOnWriteArrayList<LocationPowerSaveModeChangedListener> mListeners; + + public LocationPowerSaveModeHelper() { + mListeners = new CopyOnWriteArrayList<>(); + } + + /** + * Add a listener for changes to location power save mode. Callbacks occur on an unspecified + * thread. + */ + public final void addListener(LocationPowerSaveModeChangedListener listener) { + mListeners.add(listener); + } + + /** + * Removes a listener for changes to location power save mode. + */ + public final void removeListener(LocationPowerSaveModeChangedListener listener) { + mListeners.remove(listener); + } + + protected final void notifyLocationPowerSaveModeChanged( + @LocationPowerSaveMode int locationPowerSaveMode) { + for (LocationPowerSaveModeChangedListener listener : mListeners) { + listener.onLocationPowerSaveModeChanged(locationPowerSaveMode); + } + } + + /** + * Returns the current location power save mode. + */ + @LocationPowerSaveMode + public abstract int getLocationPowerSaveMode(); +} diff --git a/services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java b/services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java new file mode 100644 index 000000000000..d47bce31ed23 --- /dev/null +++ b/services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.util; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Provides accessors and listeners for screen interactive state (screen on/off). + */ +public abstract class ScreenInteractiveHelper { + + /** + * Listener for screen interactive changes. + */ + public interface ScreenInteractiveChangedListener { + /** + * Called when the screen interative state changes. + */ + void onScreenInteractiveChanged(boolean isInteractive); + } + + private final CopyOnWriteArrayList<ScreenInteractiveChangedListener> mListeners; + + public ScreenInteractiveHelper() { + mListeners = new CopyOnWriteArrayList<>(); + } + + /** + * Add a listener for changes to screen interactive state. Callbacks occur on an unspecified + * thread. + */ + public final void addListener(ScreenInteractiveChangedListener listener) { + mListeners.add(listener); + } + + /** + * Removes a listener for changes to screen interactive state. + */ + public final void removeListener(ScreenInteractiveChangedListener listener) { + mListeners.remove(listener); + } + + protected final void notifyScreenInteractiveChanged(boolean interactive) { + for (ScreenInteractiveChangedListener listener : mListeners) { + listener.onScreenInteractiveChanged(interactive); + } + } + + /** + * Returns true if the screen is currently interactive, and false otherwise. + */ + public abstract boolean isInteractive(); +} diff --git a/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java b/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java index a95383695ae8..cfb7697a8dfc 100644 --- a/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java +++ b/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java @@ -57,7 +57,7 @@ public class SystemAppOpsHelper extends AppOpsHelper { } @Override - protected boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity) { + public boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity) { Preconditions.checkState(mAppOps != null); long identity = Binder.clearCallingIdentity(); @@ -75,7 +75,7 @@ public class SystemAppOpsHelper extends AppOpsHelper { } @Override - protected void finishOp(int appOp, CallerIdentity callerIdentity) { + public void finishOp(int appOp, CallerIdentity callerIdentity) { Preconditions.checkState(mAppOps != null); long identity = Binder.clearCallingIdentity(); @@ -91,7 +91,7 @@ public class SystemAppOpsHelper extends AppOpsHelper { } @Override - protected boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity) { + public boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity) { Preconditions.checkState(mAppOps != null); long identity = Binder.clearCallingIdentity(); @@ -106,7 +106,7 @@ public class SystemAppOpsHelper extends AppOpsHelper { } @Override - protected boolean noteOp(int appOp, CallerIdentity callerIdentity) { + public boolean noteOp(int appOp, CallerIdentity callerIdentity) { Preconditions.checkState(mAppOps != null); long identity = Binder.clearCallingIdentity(); @@ -123,7 +123,7 @@ public class SystemAppOpsHelper extends AppOpsHelper { } @Override - protected boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) { + public boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) { Preconditions.checkState(mAppOps != null); long identity = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java b/services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java new file mode 100644 index 000000000000..b9c0ddef04ab --- /dev/null +++ b/services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.util; + +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.content.Context; +import android.location.util.identity.CallerIdentity; +import android.os.Binder; + +import com.android.server.FgThread; + +/** + * Provides accessors and listeners for location permissions, including appops. + */ +public class SystemLocationPermissionsHelper extends LocationPermissionsHelper { + + private final Context mContext; + + private boolean mInited; + + public SystemLocationPermissionsHelper(Context context, AppOpsHelper appOps) { + super(appOps); + mContext = context; + } + + /** Called when system is ready. */ + public void onSystemReady() { + if (mInited) { + return; + } + + mContext.getPackageManager().addOnPermissionsChangeListener( + uid -> { + // invoked on ui thread, move to fg thread so ui thread isn't blocked + FgThread.getHandler().post(() -> notifyLocationPermissionsChanged(uid)); + }); + mInited = true; + } + + @Override + protected boolean hasPermission(String permission, CallerIdentity callerIdentity) { + long identity = Binder.clearCallingIdentity(); + try { + return mContext.checkPermission(permission, callerIdentity.getPid(), + callerIdentity.getUid()) == PERMISSION_GRANTED; + } finally { + Binder.restoreCallingIdentity(identity); + } + } +} diff --git a/services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java new file mode 100644 index 000000000000..c8d8202157f0 --- /dev/null +++ b/services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.util; + +import android.content.Context; +import android.os.PowerManager; +import android.os.PowerManager.LocationPowerSaveMode; +import android.os.PowerManagerInternal; +import android.os.PowerSaveState; + +import com.android.internal.util.Preconditions; +import com.android.server.FgThread; +import com.android.server.LocalServices; + +import java.util.Objects; +import java.util.function.Consumer; + +/** + * Provides accessors and listeners for location power save mode. + */ +public class SystemLocationPowerSaveModeHelper extends LocationPowerSaveModeHelper implements + Consumer<PowerSaveState> { + + private final Context mContext; + private boolean mReady; + + @LocationPowerSaveMode + private volatile int mLocationPowerSaveMode; + + public SystemLocationPowerSaveModeHelper(Context context) { + mContext = context; + } + + /** Called when system is ready. */ + public void onSystemReady() { + if (mReady) { + return; + } + + LocalServices.getService(PowerManagerInternal.class).registerLowPowerModeObserver( + PowerManager.ServiceType.LOCATION, this); + mLocationPowerSaveMode = Objects.requireNonNull( + mContext.getSystemService(PowerManager.class)).getLocationPowerSaveMode(); + + mReady = true; + } + + @Override + public void accept(PowerSaveState powerSaveState) { + int locationPowerSaveMode; + if (!powerSaveState.batterySaverEnabled) { + locationPowerSaveMode = PowerManager.LOCATION_MODE_NO_CHANGE; + } else { + locationPowerSaveMode = powerSaveState.locationMode; + } + + if (locationPowerSaveMode == mLocationPowerSaveMode) { + return; + } + + mLocationPowerSaveMode = locationPowerSaveMode; + + // invoked on ui thread, move to fg thread so we don't block the ui thread + FgThread.getHandler().post(() -> notifyLocationPowerSaveModeChanged(locationPowerSaveMode)); + } + + @LocationPowerSaveMode + @Override + public int getLocationPowerSaveMode() { + Preconditions.checkState(mReady); + return mLocationPowerSaveMode; + } +} diff --git a/services/core/java/com/android/server/location/util/SystemScreenInteractiveHelper.java b/services/core/java/com/android/server/location/util/SystemScreenInteractiveHelper.java new file mode 100644 index 000000000000..70cedac20868 --- /dev/null +++ b/services/core/java/com/android/server/location/util/SystemScreenInteractiveHelper.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.util; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.UserHandle; + +import com.android.internal.util.Preconditions; +import com.android.server.FgThread; + +/** + * Provides accessors and listeners for screen interactive state (screen on/off). + */ +public class SystemScreenInteractiveHelper extends ScreenInteractiveHelper { + + private final Context mContext; + + private boolean mReady; + + private volatile boolean mIsInteractive; + + public SystemScreenInteractiveHelper(Context context) { + mContext = context; + } + + /** Called when system is ready. */ + public void onSystemReady() { + if (mReady) { + return; + } + + IntentFilter screenIntentFilter = new IntentFilter(); + screenIntentFilter.addAction(Intent.ACTION_SCREEN_OFF); + screenIntentFilter.addAction(Intent.ACTION_SCREEN_ON); + mContext.registerReceiverAsUser(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + boolean interactive; + if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { + interactive = true; + } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + interactive = false; + } else { + return; + } + + onScreenInteractiveChanged(interactive); + } + }, UserHandle.ALL, screenIntentFilter, null, FgThread.getHandler()); + + mReady = true; + } + + private void onScreenInteractiveChanged(boolean interactive) { + if (interactive == mIsInteractive) { + return; + } + + mIsInteractive = interactive; + notifyScreenInteractiveChanged(interactive); + } + + @Override + public boolean isInteractive() { + Preconditions.checkState(mReady); + return mIsInteractive; + } +} diff --git a/services/core/java/com/android/server/media/OWNERS b/services/core/java/com/android/server/media/OWNERS index b460cb5b23ea..2e2d812c058e 100644 --- a/services/core/java/com/android/server/media/OWNERS +++ b/services/core/java/com/android/server/media/OWNERS @@ -2,6 +2,7 @@ elaurent@google.com hdmoon@google.com insun@google.com jaewan@google.com +jinpark@google.com klhyun@google.com lajos@google.com sungsoo@google.com diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 00cb22e9d4b5..391a08db6716 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -459,12 +459,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final int returnCode = args.argi1; args.recycle(); - final boolean showNotification; - synchronized (mLock) { - showNotification = isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(); - } sendOnPackageInstalled(mContext, statusReceiver, sessionId, - showNotification, userId, + isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId, packageName, returnCode, message, extras); break; @@ -494,8 +490,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** * @return {@code true} iff the installing is app an device owner or affiliated profile owner. */ - @GuardedBy("mLock") - private boolean isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked() { + private boolean isInstallerDeviceOwnerOrAffiliatedProfileOwner() { + assertNotLocked("isInstallerDeviceOwnerOrAffiliatedProfileOwner"); + // It is safe to access mInstallerUid and mInstallSource without lock + // because they are immutable after sealing. + assertSealed("isInstallerDeviceOwnerOrAffiliatedProfileOwner"); if (userId != UserHandle.getUserId(mInstallerUid)) { return false; } @@ -513,12 +512,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * * @return {@code true} iff we need to ask to confirm the permissions? */ - @GuardedBy("mLock") - private boolean needToAskForPermissionsLocked() { - if (mPermissionsManuallyAccepted) { - return false; + private boolean needToAskForPermissions() { + final String packageName; + synchronized (mLock) { + if (mPermissionsManuallyAccepted) { + return false; + } + packageName = mPackageName; } + // It is safe to access mInstallerUid and mInstallSource without lock + // because they are immutable after sealing. final boolean isInstallPermissionGranted = (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, mInstallerUid) == PackageManager.PERMISSION_GRANTED); @@ -528,7 +532,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isUpdatePermissionGranted = (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGE_UPDATES, mInstallerUid) == PackageManager.PERMISSION_GRANTED); - final int targetPackageUid = mPm.getPackageUid(mPackageName, 0, userId); + final int targetPackageUid = mPm.getPackageUid(packageName, 0, userId); final boolean isPermissionGranted = isInstallPermissionGranted || (isUpdatePermissionGranted && targetPackageUid != -1) || (isSelfUpdatePermissionGranted && targetPackageUid == mInstallerUid); @@ -540,7 +544,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Device owners and affiliated profile owners are allowed to silently install packages, so // the permission check is waived if the installer is the device owner. return forcePermissionPrompt || !(isPermissionGranted || isInstallerRoot - || isInstallerSystem || isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked()); + || isInstallerSystem || isInstallerDeviceOwnerOrAffiliatedProfileOwner()); } public PackageInstallerSession(PackageInstallerService.InternalCallback callback, @@ -740,6 +744,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + private void assertNotLocked(String cookie) { + if (Thread.holdsLock(mLock)) { + throw new IllegalStateException(cookie + " is holding mLock"); + } + } + + private void assertSealed(String cookie) { + if (!isSealed()) { + throw new IllegalStateException(cookie + " before sealing"); + } + } + @GuardedBy("mLock") private void assertPreparedAndNotSealedLocked(String cookie) { assertPreparedAndNotCommittedOrDestroyedLocked(cookie); @@ -1693,11 +1709,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } private void handleInstall() { - final boolean needsLogging; - synchronized (mLock) { - needsLogging = isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(); - } - if (needsLogging) { + if (isInstallerDeviceOwnerOrAffiliatedProfileOwner()) { DevicePolicyEventLogger .createEvent(DevicePolicyEnums.INSTALL_PACKAGE) .setAdmin(mInstallSource.installerPackageName) @@ -1724,9 +1736,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { List<PackageInstallerSession> childSessions = getChildSessionsNotLocked(); try { - synchronized (mLock) { - installNonStagedLocked(childSessions); - } + installNonStaged(childSessions); } catch (PackageManagerException e) { final String completeMsg = ExceptionUtils.getCompleteMessage(e); Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg); @@ -1735,11 +1745,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - @GuardedBy("mLock") - private void installNonStagedLocked(List<PackageInstallerSession> childSessions) + private void installNonStaged(List<PackageInstallerSession> childSessions) throws PackageManagerException { final PackageManagerService.ActiveInstallSession installingSession = - makeSessionActiveLocked(); + makeSessionActive(); if (installingSession == null) { return; } @@ -1752,7 +1761,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final PackageInstallerSession session = childSessions.get(i); try { final PackageManagerService.ActiveInstallSession installingChildSession = - session.makeSessionActiveLocked(); + session.makeSessionActive(); if (installingChildSession != null) { installingChildSessions.add(installingChildSession); } @@ -1762,8 +1771,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } if (!success) { - sendOnPackageInstalled(mContext, mRemoteStatusReceiver, sessionId, - isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(), userId, null, + final IntentSender statusReceiver; + synchronized (mLock) { + statusReceiver = mRemoteStatusReceiver; + } + sendOnPackageInstalled(mContext, statusReceiver, sessionId, + isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId, null, failure.error, failure.getLocalizedMessage(), null); return; } @@ -1778,41 +1791,58 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * {@link PackageManagerService.ActiveInstallSession} representing this new staged state or null * in case permissions need to be requested before install can proceed. */ - @GuardedBy("mLock") - private PackageManagerService.ActiveInstallSession makeSessionActiveLocked() + private PackageManagerService.ActiveInstallSession makeSessionActive() throws PackageManagerException { - if (mRelinquished) { - throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, - "Session relinquished"); + assertNotLocked("makeSessionActive"); + + synchronized (mLock) { + if (mRelinquished) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Session relinquished"); + } + if (mDestroyed) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Session destroyed"); + } + if (!mSealed) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Session not sealed"); + } } - if (mDestroyed) { - throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed"); + + if (!params.isMultiPackage && needToAskForPermissions()) { + // User needs to confirm installation; + // give installer an intent they can use to involve + // user. + final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL); + intent.setPackage(mPm.getPackageInstallerPackageName()); + intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); + + final IntentSender statusReceiver; + synchronized (mLock) { + statusReceiver = mRemoteStatusReceiver; + } + sendOnUserActionRequired(mContext, statusReceiver, sessionId, intent); + + // Commit was keeping session marked as active until now; release + // that extra refcount so session appears idle. + closeInternal(false); + return null; } - if (!mSealed) { - throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed"); + + synchronized (mLock) { + return makeSessionActiveLocked(); } + } + @GuardedBy("mLock") + private PackageManagerService.ActiveInstallSession makeSessionActiveLocked() + throws PackageManagerException { if (!params.isMultiPackage) { Objects.requireNonNull(mPackageName); Objects.requireNonNull(mSigningDetails); Objects.requireNonNull(mResolvedBaseFile); - if (needToAskForPermissionsLocked()) { - // User needs to confirm installation; - // give installer an intent they can use to involve - // user. - final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL); - intent.setPackage(mPm.getPackageInstallerPackageName()); - intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); - - sendOnUserActionRequired(mContext, mRemoteStatusReceiver, sessionId, intent); - - // Commit was keeping session marked as active until now; release - // that extra refcount so session appears idle. - closeInternal(false); - return null; - } - // Inherit any packages and native libraries from existing install that // haven't been overridden. if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { @@ -2438,7 +2468,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * Determine if creating hard links between source and destination is * possible. That is, do they all live on the same underlying device. */ - private boolean isLinkPossible(List<File> fromFiles, File toDir) { + private static boolean isLinkPossible(List<File> fromFiles, File toDir) { try { final StructStat toStat = Os.stat(toDir.getAbsolutePath()); for (File fromFile : fromFiles) { @@ -2915,7 +2945,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } case IDataLoaderStatusListener.DATA_LOADER_UNAVAILABLE: { // Don't fail or commit the session. Allow caller to commit again. - sendPendingStreaming("DataLoader unavailable"); + final IntentSender statusReceiver; + synchronized (mLock) { + statusReceiver = mRemoteStatusReceiver; + } + sendPendingStreaming(mContext, statusReceiver, sessionId, + "DataLoader unavailable"); break; } case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE: @@ -2929,7 +2964,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } catch (RemoteException e) { // In case of streaming failure we don't want to fail or commit the session. // Just return from this method and allow caller to commit again. - sendPendingStreaming(e.getMessage()); + final IntentSender statusReceiver; + synchronized (mLock) { + statusReceiver = mRemoteStatusReceiver; + } + sendPendingStreaming(mContext, statusReceiver, sessionId, e.getMessage()); } } }; @@ -3004,16 +3043,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { detailMessage).sendToTarget(); } + @GuardedBy("mLock") + private int[] getChildSessionIdsLocked() { + final int[] childSessionIds = mChildSessionIds.copyKeys(); + return childSessionIds != null ? childSessionIds : EMPTY_CHILD_SESSION_ARRAY; + } + @Override public int[] getChildSessionIds() { - final int[] childSessionIds; synchronized (mLock) { - childSessionIds = mChildSessionIds.copyKeys(); + return getChildSessionIdsLocked(); } - if (childSessionIds != null) { - return childSessionIds; - } - return EMPTY_CHILD_SESSION_ARRAY; } private boolean canBeAddedAsChild(int parentCandidate) { @@ -3323,6 +3363,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.decreaseIndent(); } + /** + * This method doesn't change internal states and is safe to call outside the lock. + */ private static void sendOnUserActionRequired(Context context, IntentSender target, int sessionId, Intent intent) { final Intent fillIn = new Intent(); @@ -3335,6 +3378,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * This method doesn't change internal states and is safe to call outside the lock. + */ private static void sendOnPackageInstalled(Context context, IntentSender target, int sessionId, boolean showNotification, int userId, String basePackageName, int returnCode, String msg, Bundle extras) { @@ -3375,13 +3421,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - private void sendPendingStreaming(@Nullable String cause) { - final IntentSender statusReceiver; - synchronized (mLock) { - statusReceiver = mRemoteStatusReceiver; - } - - if (statusReceiver == null) { + /** + * This method doesn't change internal states and is safe to call outside the lock. + */ + private static void sendPendingStreaming(Context context, IntentSender target, int sessionId, + @Nullable String cause) { + if (target == null) { Slog.e(TAG, "Missing receiver for pending streaming status."); return; } @@ -3396,7 +3441,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready"); } try { - statusReceiver.sendIntent(mContext, 0, intent, null, null); + target.sendIntent(context, 0, intent, null, null); } catch (IntentSender.SendIntentException ignored) { } } @@ -3470,10 +3515,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (stageCid != null) { writeStringAttribute(out, ATTR_SESSION_STAGE_CID, stageCid); } - writeBooleanAttribute(out, ATTR_PREPARED, isPrepared()); - writeBooleanAttribute(out, ATTR_COMMITTED, isCommitted()); - writeBooleanAttribute(out, ATTR_DESTROYED, isDestroyed()); - writeBooleanAttribute(out, ATTR_SEALED, isSealed()); + writeBooleanAttribute(out, ATTR_PREPARED, mPrepared); + writeBooleanAttribute(out, ATTR_COMMITTED, mCommitted); + writeBooleanAttribute(out, ATTR_DESTROYED, mDestroyed); + writeBooleanAttribute(out, ATTR_SEALED, mSealed); writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage); writeBooleanAttribute(out, ATTR_STAGED_SESSION, params.isStaged); @@ -3535,7 +3580,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { params.appIconLastModified = appIconFile.lastModified(); } - final int[] childSessionIds = getChildSessionIds(); + final int[] childSessionIds = getChildSessionIdsLocked(); for (int childSessionId : childSessionIds) { out.startTag(null, TAG_CHILD_SESSION); writeIntAttribute(out, ATTR_SESSION_ID, childSessionId); @@ -3543,7 +3588,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } final InstallationFile[] files = getInstallationFilesLocked(); - for (InstallationFile file : getInstallationFilesLocked()) { + for (InstallationFile file : files) { out.startTag(null, TAG_SESSION_FILE); writeIntAttribute(out, ATTR_LOCATION, file.getLocation()); writeStringAttribute(out, ATTR_NAME, file.getName()); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index bd12fd5f5d9a..2854b337fd29 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -651,6 +651,23 @@ public class PackageManagerService extends IPackageManager.Stub private static final long THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE = 150857253; + /** + * Apps targeting Android S and above need to declare dependencies to the public native + * shared libraries that are defined by the device maker using {@code uses-native-library} tag + * in its {@code AndroidManifest.xml}. + * + * If any of the dependencies cannot be satisfied, i.e. one of the dependency doesn't exist, + * the package manager rejects to install the app. The dependency can be specified as optional + * using {@code android:required} attribute in the tag, in which case failing to satisfy the + * dependency doesn't stop the installation. + * <p>Once installed, an app is provided with only the native shared libraries that are + * specified in the app manifest. {@code dlopen}ing a native shared library that doesn't appear + * in the app manifest will fail even if it actually exists on the device. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088; + public static final String PLATFORM_PACKAGE_NAME = "android"; private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; @@ -2952,9 +2969,8 @@ public class PackageManagerService extends IPackageManager.Stub = systemConfig.getSharedLibraries(); final int builtInLibCount = libConfig.size(); for (int i = 0; i < builtInLibCount; i++) { - String name = libConfig.keyAt(i); SystemConfig.SharedLibraryEntry entry = libConfig.valueAt(i); - addBuiltInSharedLibraryLocked(entry.filename, name); + addBuiltInSharedLibraryLocked(libConfig.valueAt(i)); } // Now that we have added all the libraries, iterate again to add dependency @@ -10486,6 +10502,19 @@ public class PackageManagerService extends IPackageManager.Stub null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, existingLibraries, newLibraries); } + // TODO(b/160928779) gate this behavior using ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES + if (pkg.getTargetSdkVersion() > 30) { + if (!pkg.getUsesNativeLibraries().isEmpty() && pkg.getTargetSdkVersion() > 30) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null, + null, pkg.getPackageName(), true, pkg.getTargetSdkVersion(), + usesLibraryInfos, availablePackages, existingLibraries, newLibraries); + } + if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(), + null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(), + usesLibraryInfos, availablePackages, existingLibraries, newLibraries); + } + } return usesLibraryInfos; } @@ -12177,15 +12206,16 @@ public class PackageManagerService extends IPackageManager.Stub } @GuardedBy("mLock") - private boolean addBuiltInSharedLibraryLocked(String path, String name) { - if (nonStaticSharedLibExistsLocked(name)) { + private boolean addBuiltInSharedLibraryLocked(SystemConfig.SharedLibraryEntry entry) { + if (nonStaticSharedLibExistsLocked(entry.name)) { return false; } - SharedLibraryInfo libraryInfo = new SharedLibraryInfo(path, null, null, name, - (long) SharedLibraryInfo.VERSION_UNDEFINED, SharedLibraryInfo.TYPE_BUILTIN, - new VersionedPackage(PLATFORM_PACKAGE_NAME, (long) 0), - null, null); + SharedLibraryInfo libraryInfo = new SharedLibraryInfo(entry.filename, null, null, + entry.name, (long) SharedLibraryInfo.VERSION_UNDEFINED, + SharedLibraryInfo.TYPE_BUILTIN, + new VersionedPackage(PLATFORM_PACKAGE_NAME, (long)0), null, null, + entry.isNative); commitSharedLibraryInfoLocked(libraryInfo); return true; @@ -21900,7 +21930,11 @@ public class PackageManagerService extends IPackageManager.Stub pw.print(" -> "); } if (libraryInfo.getPath() != null) { - pw.print(" (jar) "); + if (libraryInfo.isNative()) { + pw.print(" (so) "); + } else { + pw.print(" (jar) "); + } pw.print(libraryInfo.getPath()); } else { pw.print(" (apk) "); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 13b927e7d9f4..7106499f9b56 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4784,6 +4784,23 @@ public final class Settings { } } + List<String> usesNativeLibraries = pkg.getUsesNativeLibraries(); + if (usesNativeLibraries.size() > 0) { + pw.print(prefix); pw.println(" usesNativeLibraries:"); + for (int i=0; i< usesNativeLibraries.size(); i++) { + pw.print(prefix); pw.print(" "); pw.println(usesNativeLibraries.get(i)); + } + } + + List<String> usesOptionalNativeLibraries = pkg.getUsesOptionalNativeLibraries(); + if (usesOptionalNativeLibraries.size() > 0) { + pw.print(prefix); pw.println(" usesOptionalNativeLibraries:"); + for (int i=0; i< usesOptionalNativeLibraries.size(); i++) { + pw.print(prefix); pw.print(" "); + pw.println(usesOptionalNativeLibraries.get(i)); + } + } + List<String> usesLibraryFiles = ps.getPkgState().getUsesLibraryFiles(); if (usesLibraryFiles.size() > 0) { pw.print(prefix); pw.println(" usesLibraryFiles:"); diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java index c9e0bb467ce4..39784cf32cea 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java @@ -252,6 +252,19 @@ public interface AndroidPackage extends PkgAppInfo, PkgPackageInfo, ParsingPacka @NonNull List<String> getUsesOptionalLibraries(); + /** @see R.styleabele#AndroidManifestUsesNativeLibrary */ + @NonNull + List<String> getUsesNativeLibraries(); + + /** + * Like {@link #getUsesNativeLibraries()}, but marked optional by setting + * {@link R.styleable#AndroidManifestUsesNativeLibrary_required} to false . Application is + * expected to handle absence manually. + * @see R.styleable#AndroidManifestUsesNativeLibrary + */ + @NonNull + List<String> getUsesOptionalNativeLibraries(); + /** * TODO(b/135203078): Move static library stuff to an inner data class * @see R.styleable#AndroidManifestUsesStaticLibrary diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index f5eed30a19bf..f5e1602ee6be 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -51,6 +51,7 @@ import android.app.AppGlobals; import android.app.GrantedUriPermission; import android.app.IUriGrantsManager; import android.content.ClipData; +import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; @@ -698,6 +699,11 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { final UriPermission perm = findOrCreateUriPermissionLocked( sourcePkg, targetPkg, targetUid, grantUri); perm.initPersistedModes(modeFlags, createdTime); + mPmInternal.grantImplicitAccess( + targetUserId, null, + UserHandle.getAppId(targetUid), + pi.applicationInfo.uid, + false /* direct */); } } else { Slog.w(TAG, "Persisted grant for " + uri + " had source " + sourcePkg @@ -1171,6 +1177,9 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { // grant, we can skip generating any bookkeeping; when any advanced // features have been requested, we proceed below to make sure the // provider supports granting permissions + mPmInternal.grantImplicitAccess( + UserHandle.getUserId(targetUid), null, + UserHandle.getAppId(targetUid), pi.applicationInfo.uid, false); return -1; } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 55962fc883d9..90f87b16e70d 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1160,9 +1160,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } }; - private Runnable mTryToRebindRunnable = () -> { - tryToRebind(); - }; + private Runnable mTryToRebindRunnable = this::tryToRebind; WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper, int clientUid) { mInfo = info; @@ -1295,14 +1293,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // a short time in the future, specifically to allow any pending package // update message on this same looper thread to be processed. if (!mWallpaper.wallpaperUpdating) { - mContext.getMainThreadHandler().postDelayed(() -> processDisconnect(this), + mContext.getMainThreadHandler().postDelayed(mDisconnectRunnable, 1000); } } } } - public void scheduleTimeoutLocked() { + private void scheduleTimeoutLocked() { // If we didn't reset it right away, do so after we couldn't connect to // it for an extended amount of time to avoid having a black wallpaper. final Handler fgHandler = FgThread.getHandler(); @@ -1342,11 +1340,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private void processDisconnect(final ServiceConnection connection) { + private Runnable mDisconnectRunnable = () -> { synchronized (mLock) { // The wallpaper disappeared. If this isn't a system-default one, track // crashes and fall back to default if it continues to misbehave. - if (connection == mWallpaper.connection) { + if (this == mWallpaper.connection) { final ComponentName wpService = mWallpaper.wallpaperComponent; if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId @@ -1374,7 +1372,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } } - } + }; /** * Called by a live wallpaper if its colors have changed. @@ -2786,6 +2784,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperConnection.DisplayConnector::disconnectLocked); wallpaper.connection.mService = null; wallpaper.connection.mDisplayConnector.clear(); + + FgThread.getHandler().removeCallbacks(wallpaper.connection.mResetRunnable); + mContext.getMainThreadHandler().removeCallbacks( + wallpaper.connection.mDisconnectRunnable); + mContext.getMainThreadHandler().removeCallbacks( + wallpaper.connection.mTryToRebindRunnable); + wallpaper.connection = null; if (wallpaper == mLastWallpaper) mLastWallpaper = null; } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 0cd7ffce2ed4..04b1edc3eede 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -1446,7 +1446,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { mService.deferWindowLayout(); try { stack.setWindowingMode(WINDOWING_MODE_UNDEFINED); - stack.setBounds(null); + if (stack.getWindowingMode() != WINDOWING_MODE_FREEFORM) { + stack.setBounds(null); + } toDisplay.getDefaultTaskDisplayArea().positionTaskBehindHome(stack); // Follow on the workaround: activities are kept force hidden till the new windowing diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c6b93d6ca4f4..7ec819f13e96 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3570,6 +3570,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return new JournaledFile(new File(base), new File(base + ".tmp")); } + /** + * Persist modified values to disk by calling {@link #saveSettingsLocked} for each + * affected user ID. + */ + @GuardedBy("getLockObject()") + private void saveSettingsForUsersLocked(Set<Integer> affectedUserIds) { + for (int userId : affectedUserIds) { + saveSettingsLocked(userId); + } + } + private void saveSettingsLocked(int userHandle) { DevicePolicyData policy = getUserData(userHandle); JournaledFile journal = makeJournaledFile(userHandle); @@ -4785,13 +4796,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * Updates a flag that tells us whether the user's password currently satisfies the - * requirements set by all of the user's active admins. The flag is updated both in memory - * and persisted to disk by calling {@link #saveSettingsLocked}, for the value of the flag - * be the correct one upon boot. - * This should be called whenever the password or the admin policies have changed. + * requirements set by all of the user's active admins. + * This should be called whenever the password or the admin policies have changed. The caller + * is responsible for calling {@link #saveSettingsLocked} to persist the change. + * + * @return the set of user IDs that have been affected */ @GuardedBy("getLockObject()") - private void updatePasswordValidityCheckpointLocked(int userHandle, boolean parent) { + private Set<Integer> updatePasswordValidityCheckpointLocked(int userHandle, boolean parent) { + final ArraySet<Integer> affectedUserIds = new ArraySet<>(); final int credentialOwner = getCredentialOwner(userHandle, parent); DevicePolicyData policy = getUserData(credentialOwner); PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner); @@ -4801,9 +4814,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { metrics, userHandle, parent); if (newCheckpoint != policy.mPasswordValidAtLastCheckpoint) { policy.mPasswordValidAtLastCheckpoint = newCheckpoint; - saveSettingsLocked(credentialOwner); + affectedUserIds.add(credentialOwner); } } + return affectedUserIds; } /** @@ -6175,7 +6189,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void removeCaApprovalsIfNeeded(int userId) { + private Set<Integer> removeCaApprovalsIfNeeded(int userId) { + final ArraySet<Integer> affectedUserIds = new ArraySet<>(); for (UserInfo userInfo : mUserManager.getProfiles(userId)) { boolean isSecure = mLockPatternUtils.isSecure(userInfo.id); if (userInfo.isManagedProfile()){ @@ -6184,11 +6199,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!isSecure) { synchronized (getLockObject()) { getUserData(userInfo.id).mAcceptedCaCertificates.clear(); - saveSettingsLocked(userInfo.id); + affectedUserIds.add(userInfo.id); } mCertificateMonitor.onCertificateApprovalsChanged(userId); } } + return affectedUserIds; } @Override @@ -7458,42 +7474,45 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } DevicePolicyData policy = getUserData(userId); + final ArraySet<Integer> affectedUserIds = new ArraySet<>(); synchronized (getLockObject()) { policy.mFailedPasswordAttempts = 0; - updatePasswordValidityCheckpointLocked(userId, /* parent */ false); - saveSettingsLocked(userId); - updatePasswordExpirationsLocked(userId); + affectedUserIds.add(userId); + affectedUserIds.addAll(updatePasswordValidityCheckpointLocked( + userId, /* parent */ false)); + affectedUserIds.addAll(updatePasswordExpirationsLocked(userId)); setExpirationAlarmCheckLocked(mContext, userId, /* parent */ false); // Send a broadcast to each profile using this password as its primary unlock. sendAdminCommandForLockscreenPoliciesLocked( DeviceAdminReceiver.ACTION_PASSWORD_CHANGED, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userId); + + affectedUserIds.addAll(removeCaApprovalsIfNeeded(userId)); + saveSettingsForUsersLocked(affectedUserIds); } - removeCaApprovalsIfNeeded(userId); } /** * Called any time the device password is updated. Resets all password expiration clocks. + * + * @return the set of user IDs that have been affected */ - private void updatePasswordExpirationsLocked(int userHandle) { - ArraySet<Integer> affectedUserIds = new ArraySet<Integer>(); + private Set<Integer> updatePasswordExpirationsLocked(int userHandle) { + final ArraySet<Integer> affectedUserIds = new ArraySet<>(); List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked( userHandle, /* parent */ false); - final int N = admins.size(); - for (int i = 0; i < N; i++) { + for (int i = 0; i < admins.size(); i++) { ActiveAdmin admin = admins.get(i); if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) { affectedUserIds.add(admin.getUserHandle().getIdentifier()); long timeout = admin.passwordExpirationTimeout; - long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L; - admin.passwordExpirationDate = expiration; + admin.passwordExpirationDate = + timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L; } } - for (int affectedUserId : affectedUserIds) { - saveSettingsLocked(affectedUserId); - } + return affectedUserIds; } @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAppOpsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAppOpsHelper.java index da794da7f9c9..e947e89c4f94 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAppOpsHelper.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAppOpsHelper.java @@ -57,7 +57,7 @@ public class FakeAppOpsHelper extends AppOpsHelper { } @Override - protected boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity) { + public boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity) { AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp); if (!myAppOp.mAllowed) { return false; @@ -68,20 +68,20 @@ public class FakeAppOpsHelper extends AppOpsHelper { } @Override - protected void finishOp(int appOp, CallerIdentity callerIdentity) { + public void finishOp(int appOp, CallerIdentity callerIdentity) { AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp); Preconditions.checkState(myAppOp.mStarted); myAppOp.mStarted = false; } @Override - protected boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity) { + public boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity) { AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp); return myAppOp.mAllowed; } @Override - protected boolean noteOp(int appOp, CallerIdentity callerIdentity) { + public boolean noteOp(int appOp, CallerIdentity callerIdentity) { if (!noteOpNoThrow(appOp, callerIdentity)) { throw new SecurityException( "noteOp not allowed for op " + appOp + " and caller " + callerIdentity); @@ -91,7 +91,7 @@ public class FakeAppOpsHelper extends AppOpsHelper { } @Override - protected boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) { + public boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) { AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp); if (!myAppOp.mAllowed) { return false; diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPermissionsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPermissionsHelper.java new file mode 100644 index 000000000000..e7d7e310d1e4 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPermissionsHelper.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.util; + +import android.location.util.identity.CallerIdentity; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * Version of LocationPermissionsHelper for testing. All permissions are granted unless notified + * otherwise. + */ +public class FakeLocationPermissionsHelper extends LocationPermissionsHelper { + + private final HashMap<String, Set<String>> mRevokedPermissions; + + public FakeLocationPermissionsHelper(AppOpsHelper appOps) { + super(appOps); + mRevokedPermissions = new HashMap<>(); + } + + public void grantPermission(String packageName, String permission) { + getRevokedPermissionsList(packageName).remove(permission); + notifyLocationPermissionsChanged(packageName); + } + + public void revokePermission(String packageName, String permission) { + getRevokedPermissionsList(packageName).add(permission); + notifyLocationPermissionsChanged(packageName); + } + + @Override + protected boolean hasPermission(String permission, CallerIdentity identity) { + return !getRevokedPermissionsList(identity.getPackageName()).contains(permission); + } + + private Set<String> getRevokedPermissionsList(String packageName) { + return mRevokedPermissions.computeIfAbsent(packageName, p -> new HashSet<>()); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java new file mode 100644 index 000000000000..3ead5d4f214d --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.util; + +import android.os.IPowerManager; +import android.os.PowerManager.LocationPowerSaveMode; + +/** + * Version of LocationPowerSaveModeHelper for testing. Power save mode is initialized as "no + * change". + */ +public class FakeLocationPowerSaveModeHelper extends LocationPowerSaveModeHelper { + + @LocationPowerSaveMode + private int mLocationPowerSaveMode; + + public FakeLocationPowerSaveModeHelper() { + mLocationPowerSaveMode = IPowerManager.LOCATION_MODE_NO_CHANGE; + } + + public void setLocationPowerSaveMode(int locationPowerSaveMode) { + if (locationPowerSaveMode == mLocationPowerSaveMode) { + return; + } + + mLocationPowerSaveMode = locationPowerSaveMode; + notifyLocationPowerSaveModeChanged(locationPowerSaveMode); + } + + @LocationPowerSaveMode + @Override + public int getLocationPowerSaveMode() { + return mLocationPowerSaveMode; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeScreenInteractiveHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeScreenInteractiveHelper.java new file mode 100644 index 000000000000..df697fa1a03c --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeScreenInteractiveHelper.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.util; + +/** + * Version of ScreenInteractiveHelper for testing. Screen is initialized as interactive (on). + */ +public class FakeScreenInteractiveHelper extends ScreenInteractiveHelper { + + private boolean mIsInteractive; + + public FakeScreenInteractiveHelper() { + mIsInteractive = true; + } + + public void setScreenInteractive(boolean interactive) { + if (interactive == mIsInteractive) { + return; + } + + mIsInteractive = interactive; + notifyScreenInteractiveChanged(interactive); + } + + public boolean isInteractive() { + return mIsInteractive; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeSettingsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeSettingsHelper.java index 726b1b82b699..1d0523f18008 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeSettingsHelper.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeSettingsHelper.java @@ -90,7 +90,7 @@ public class FakeSettingsHelper extends SettingsHelper { @Override public boolean isLocationEnabled(int userId) { - return mLocationEnabledSetting.getValue(Boolean.class); + return mLocationEnabledSetting.getValue(userId, Boolean.class); } @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeUserInfoHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeUserInfoHelper.java index 336e28c879ba..f5978da416be 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeUserInfoHelper.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeUserInfoHelper.java @@ -16,7 +16,6 @@ package com.android.server.location.util; -import android.os.Process; import android.util.IndentingPrintWriter; import android.util.IntArray; import android.util.SparseArray; @@ -27,19 +26,21 @@ import com.android.internal.util.Preconditions; import java.io.FileDescriptor; /** - * Version of UserInfoHelper for testing. The user this code is running under is set as the current - * user by default, with no profiles. + * Version of UserInfoHelper for testing. By default there is one user that starts in a running + * state with a userId of 0; */ public class FakeUserInfoHelper extends UserInfoHelper { + public static final int DEFAULT_USERID = 0; + private final IntArray mRunningUserIds; private final SparseArray<IntArray> mProfiles; private int mCurrentUserId; public FakeUserInfoHelper() { - mCurrentUserId = Process.myUserHandle().getIdentifier(); - mRunningUserIds = IntArray.wrap(new int[]{mCurrentUserId}); + mCurrentUserId = DEFAULT_USERID; + mRunningUserIds = IntArray.wrap(new int[]{DEFAULT_USERID}); mProfiles = new SparseArray<>(); } @@ -67,6 +68,10 @@ public class FakeUserInfoHelper extends UserInfoHelper { dispatchOnUserStopped(userId); } + public void setCurrentUserId(int parentUser) { + setCurrentUserIds(parentUser, new int[]{parentUser}); + } + public void setCurrentUserIds(int parentUser, int[] currentProfileUserIds) { Preconditions.checkArgument(ArrayUtils.contains(currentProfileUserIds, parentUser)); int oldUserId = mCurrentUserId; diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/LocationAttributionHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/util/LocationAttributionHelperTest.java index e6f625217965..4165b6ee111f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/LocationAttributionHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/LocationAttributionHelperTest.java @@ -16,7 +16,11 @@ package com.android.server.location.util; +import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; +import static android.app.AppOpsManager.OP_MONITOR_LOCATION; + import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -46,9 +50,7 @@ public class LocationAttributionHelperTest { public void setUp() { initMocks(this); - when(mAppOpsHelper.startLocationMonitoring(any(CallerIdentity.class))).thenReturn(true); - when(mAppOpsHelper.startHighPowerLocationMonitoring(any(CallerIdentity.class))).thenReturn( - true); + when(mAppOpsHelper.startOpNoThrow(anyInt(), any(CallerIdentity.class))).thenReturn(true); mHelper = new LocationAttributionHelper(mAppOpsHelper); } @@ -63,30 +65,30 @@ public class LocationAttributionHelperTest { Object key4 = new Object(); mHelper.reportLocationStart(caller1, "gps", key1); - verify(mAppOpsHelper).startLocationMonitoring(caller1); - verify(mAppOpsHelper, never()).stopLocationMonitoring(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); mHelper.reportLocationStart(caller1, "gps", key2); - verify(mAppOpsHelper).startLocationMonitoring(caller1); - verify(mAppOpsHelper, never()).stopLocationMonitoring(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); mHelper.reportLocationStart(caller2, "gps", key3); - verify(mAppOpsHelper).startLocationMonitoring(caller2); - verify(mAppOpsHelper, never()).stopLocationMonitoring(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); mHelper.reportLocationStart(caller2, "gps", key4); - verify(mAppOpsHelper).startLocationMonitoring(caller2); - verify(mAppOpsHelper, never()).stopLocationMonitoring(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); mHelper.reportLocationStop(caller1, "gps", key2); - verify(mAppOpsHelper, never()).stopLocationMonitoring(caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); mHelper.reportLocationStop(caller1, "gps", key1); - verify(mAppOpsHelper).stopLocationMonitoring(caller1); + verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller1); mHelper.reportLocationStop(caller2, "gps", key3); - verify(mAppOpsHelper, never()).stopLocationMonitoring(caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); mHelper.reportLocationStop(caller2, "gps", key4); - verify(mAppOpsHelper).stopLocationMonitoring(caller2); + verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller2); } @Test @@ -99,29 +101,29 @@ public class LocationAttributionHelperTest { Object key4 = new Object(); mHelper.reportHighPowerLocationStart(caller1, "gps", key1); - verify(mAppOpsHelper).startHighPowerLocationMonitoring(caller1); - verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); mHelper.reportHighPowerLocationStart(caller1, "gps", key2); - verify(mAppOpsHelper).startHighPowerLocationMonitoring(caller1); - verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); mHelper.reportHighPowerLocationStart(caller2, "gps", key3); - verify(mAppOpsHelper).startHighPowerLocationMonitoring(caller2); - verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); mHelper.reportHighPowerLocationStart(caller2, "gps", key4); - verify(mAppOpsHelper).startHighPowerLocationMonitoring(caller2); - verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); mHelper.reportHighPowerLocationStop(caller1, "gps", key2); - verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); mHelper.reportHighPowerLocationStop(caller1, "gps", key1); - verify(mAppOpsHelper).stopHighPowerLocationMonitoring(caller1); + verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); mHelper.reportHighPowerLocationStop(caller2, "gps", key3); - verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); mHelper.reportHighPowerLocationStop(caller2, "gps", key4); - verify(mAppOpsHelper).stopHighPowerLocationMonitoring(caller2); + verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/SystemAppOpsHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemAppOpsHelperTest.java index f40d3168cf98..093aa2e0e771 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/SystemAppOpsHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemAppOpsHelperTest.java @@ -20,12 +20,8 @@ import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_COARSE_LOCATION; import static android.app.AppOpsManager.OP_FINE_LOCATION; import static android.app.AppOpsManager.OP_MOCK_LOCATION; -import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; import static android.app.AppOpsManager.OP_MONITOR_LOCATION; -import static com.android.server.location.LocationPermissions.PERMISSION_COARSE; -import static com.android.server.location.LocationPermissions.PERMISSION_FINE; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -34,10 +30,12 @@ import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.MockitoAnnotations.initMocks; +import static org.testng.Assert.assertThrows; import android.app.AppOpsManager; import android.content.Context; @@ -105,41 +103,41 @@ public class SystemAppOpsHelperTest { } @Test - public void testCheckLocationAccess() { + public void testCheckOp() { CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); doReturn(MODE_ALLOWED).when( mAppOps).checkOpNoThrow(eq(OP_FINE_LOCATION), eq(1000), eq("mypackage")); - assertThat(mHelper.checkLocationAccess(identity, PERMISSION_FINE)).isTrue(); + assertThat(mHelper.checkOpNoThrow(OP_FINE_LOCATION, identity)).isTrue(); doReturn(MODE_IGNORED).when( mAppOps).checkOpNoThrow(eq(OP_FINE_LOCATION), eq(1000), eq("mypackage")); - assertThat(mHelper.checkLocationAccess(identity, PERMISSION_FINE)).isFalse(); + assertThat(mHelper.checkOpNoThrow(OP_FINE_LOCATION, identity)).isFalse(); identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); doReturn(MODE_ALLOWED).when( mAppOps).checkOpNoThrow(eq(OP_COARSE_LOCATION), eq(1000), eq("mypackage")); - assertThat(mHelper.checkLocationAccess(identity, PERMISSION_COARSE)).isTrue(); + assertThat(mHelper.checkOpNoThrow(OP_COARSE_LOCATION, identity)).isTrue(); doReturn(MODE_IGNORED).when( mAppOps).checkOpNoThrow(eq(OP_COARSE_LOCATION), eq(1000), eq("mypackage")); - assertThat(mHelper.checkLocationAccess(identity, PERMISSION_COARSE)).isFalse(); + assertThat(mHelper.checkOpNoThrow(OP_COARSE_LOCATION, identity)).isFalse(); } @Test - public void testNoteLocationAccess() { + public void testNoteOpNoThrow() { CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); doReturn(MODE_ALLOWED).when( mAppOps).noteOpNoThrow(eq(OP_FINE_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.noteLocationAccess(identity, PERMISSION_FINE)).isTrue(); + assertThat(mHelper.noteOpNoThrow(OP_FINE_LOCATION, identity)).isTrue(); doReturn(MODE_IGNORED).when( mAppOps).noteOpNoThrow(eq(OP_FINE_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.noteLocationAccess(identity, PERMISSION_FINE)).isFalse(); + assertThat(mHelper.noteOpNoThrow(OP_FINE_LOCATION, identity)).isFalse(); identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); @@ -147,74 +145,55 @@ public class SystemAppOpsHelperTest { doReturn(MODE_ALLOWED).when( mAppOps).noteOpNoThrow(eq(OP_COARSE_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.noteLocationAccess(identity, PERMISSION_COARSE)).isTrue(); + assertThat(mHelper.noteOpNoThrow(OP_COARSE_LOCATION, identity)).isTrue(); doReturn(MODE_IGNORED).when( mAppOps).noteOpNoThrow(eq(OP_COARSE_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.noteLocationAccess(identity, PERMISSION_COARSE)).isFalse(); + assertThat(mHelper.noteOpNoThrow(OP_COARSE_LOCATION, identity)).isFalse(); } @Test - public void testStartLocationMonitoring() { + public void testStartOp() { CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); doReturn(MODE_ALLOWED).when( mAppOps).startOpNoThrow(eq(OP_MONITOR_LOCATION), eq(1000), eq("mypackage"), eq(false), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.startLocationMonitoring(identity)).isTrue(); + assertThat(mHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)).isTrue(); doReturn(MODE_IGNORED).when( mAppOps).startOpNoThrow(eq(OP_MONITOR_LOCATION), eq(1000), eq("mypackage"), eq(false), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.startLocationMonitoring(identity)).isFalse(); + assertThat(mHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)).isFalse(); } @Test - public void testStartHighPowerLocationMonitoring() { + public void testFinishOp() { CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); - doReturn(MODE_ALLOWED).when( - mAppOps).startOpNoThrow(eq(OP_MONITOR_HIGH_POWER_LOCATION), eq(1000), - eq("mypackage"), - eq(false), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.startHighPowerLocationMonitoring(identity)).isTrue(); - - doReturn(MODE_IGNORED).when( - mAppOps).startOpNoThrow(eq(OP_MONITOR_HIGH_POWER_LOCATION), eq(1000), - eq("mypackage"), - eq(false), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.startHighPowerLocationMonitoring(identity)).isFalse(); - } - - @Test - public void testStopLocationMonitoring() { - CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); - - mHelper.stopLocationMonitoring(identity); + mHelper.finishOp(OP_MONITOR_LOCATION, identity); verify(mAppOps).finishOp(OP_MONITOR_LOCATION, 1000, "mypackage", "myfeature"); } @Test - public void testStopHighPowerLocationMonitoring() { - CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); - - mHelper.stopHighPowerLocationMonitoring(identity); - verify(mAppOps).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, 1000, "mypackage", "myfeature"); - } - - @Test - public void testNoteMockLocationAccess() { + public void testNoteOp() { CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); doReturn(MODE_ALLOWED).when( mAppOps).noteOp(eq(OP_MOCK_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.noteMockLocationAccess(identity)).isTrue(); + assertThat(mHelper.noteOp(OP_MOCK_LOCATION, identity)).isTrue(); doReturn(MODE_IGNORED).when( mAppOps).noteOp(eq(OP_MOCK_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.noteMockLocationAccess(identity)).isFalse(); + assertThat(mHelper.noteOp(OP_MOCK_LOCATION, identity)).isFalse(); + + + doThrow(new SecurityException()).when( + mAppOps).noteOp(eq(OP_MOCK_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), + nullable(String.class)); + assertThrows(SecurityException.class, () -> mHelper.noteOp(OP_MOCK_LOCATION, identity)); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java new file mode 100644 index 000000000000..2acb70c4b83d --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.location.util; + +import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF; +import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY; +import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF; +import static android.os.PowerManager.LOCATION_MODE_NO_CHANGE; +import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.after; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.content.Context; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.PowerSaveState; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.location.util.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SystemLocationPowerSaveModeHelperTest { + + private static final long TIMEOUT_MS = 5000; + private static final long FAILURE_TIMEOUT_MS = 200; + + @Mock + private PowerManagerInternal mPowerManagerInternal; + + private List<Consumer<PowerSaveState>> mListeners = new ArrayList<>(); + + private SystemLocationPowerSaveModeHelper mHelper; + + @Before + public void setUp() { + initMocks(this); + + LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternal); + + doAnswer(invocation -> mListeners.add(invocation.getArgument(1))).when( + mPowerManagerInternal).registerLowPowerModeObserver(anyInt(), any(Consumer.class)); + + PowerManager powerManager = mock(PowerManager.class); + doReturn(LOCATION_MODE_NO_CHANGE).when(powerManager).getLocationPowerSaveMode(); + Context context = mock(Context.class); + doReturn(powerManager).when(context).getSystemService(PowerManager.class); + + mHelper = new SystemLocationPowerSaveModeHelper(context); + mHelper.onSystemReady(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(PowerManagerInternal.class); + } + + private void sendPowerSaveState(PowerSaveState powerSaveState) { + for (Consumer<PowerSaveState> listener : mListeners) { + listener.accept(powerSaveState); + } + } + + @Test + public void testListener() { + LocationPowerSaveModeChangedListener listener = mock( + LocationPowerSaveModeChangedListener.class); + mHelper.addListener(listener); + + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_NO_CHANGE).setBatterySaverEnabled(false).build()); + verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged( + anyInt()); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF).setBatterySaverEnabled(false).build()); + verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged( + anyInt()); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF).setBatterySaverEnabled(false).build()); + verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged( + anyInt()); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_FOREGROUND_ONLY).setBatterySaverEnabled(false).build()); + verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged( + anyInt()); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF).setBatterySaverEnabled( + false).build()); + verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged( + anyInt()); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_NO_CHANGE).setBatterySaverEnabled(true).build()); + verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged( + anyInt()); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF).setBatterySaverEnabled(true).build()); + verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged( + LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo( + LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF).setBatterySaverEnabled(true).build()); + verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged( + LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo( + LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_FOREGROUND_ONLY).setBatterySaverEnabled(true).build()); + verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged( + LOCATION_MODE_FOREGROUND_ONLY); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_FOREGROUND_ONLY); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF).setBatterySaverEnabled( + true).build()); + verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged( + LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo( + LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_NO_CHANGE).setBatterySaverEnabled(true).build()); + verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged( + LOCATION_MODE_NO_CHANGE); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java index c22dc104f438..1867be0b9f3b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java @@ -16,22 +16,32 @@ package com.android.server.location.util; +import com.android.server.location.LocationRequestStatistics; + public class TestInjector implements Injector { private final FakeUserInfoHelper mUserInfoHelper; private final FakeAppOpsHelper mAppOpsHelper; + private final FakeLocationPermissionsHelper mLocationPermissionsHelper; private final FakeSettingsHelper mSettingsHelper; private final FakeAppForegroundHelper mAppForegroundHelper; - private final LocationUsageLogger mLocationUsageLogger; + private final FakeLocationPowerSaveModeHelper mLocationPowerSaveModeHelper; + private final FakeScreenInteractiveHelper mScreenInteractiveHelper; private final LocationAttributionHelper mLocationAttributionHelper; + private final LocationUsageLogger mLocationUsageLogger; + private final LocationRequestStatistics mLocationRequestStatistics; public TestInjector() { mUserInfoHelper = new FakeUserInfoHelper(); mAppOpsHelper = new FakeAppOpsHelper(); + mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper); mSettingsHelper = new FakeSettingsHelper(); mAppForegroundHelper = new FakeAppForegroundHelper(); - mLocationUsageLogger = new LocationUsageLogger(); + mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(); + mScreenInteractiveHelper = new FakeScreenInteractiveHelper(); mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper); + mLocationUsageLogger = new LocationUsageLogger(); + mLocationRequestStatistics = new LocationRequestStatistics(); } @Override @@ -45,6 +55,11 @@ public class TestInjector implements Injector { } @Override + public FakeLocationPermissionsHelper getLocationPermissionsHelper() { + return mLocationPermissionsHelper; + } + + @Override public FakeSettingsHelper getSettingsHelper() { return mSettingsHelper; } @@ -55,12 +70,27 @@ public class TestInjector implements Injector { } @Override - public LocationUsageLogger getLocationUsageLogger() { - return mLocationUsageLogger; + public FakeLocationPowerSaveModeHelper getLocationPowerSaveModeHelper() { + return mLocationPowerSaveModeHelper; + } + + @Override + public FakeScreenInteractiveHelper getScreenInteractiveHelper() { + return mScreenInteractiveHelper; } @Override public LocationAttributionHelper getLocationAttributionHelper() { return mLocationAttributionHelper; } + + @Override + public LocationUsageLogger getLocationUsageLogger() { + return mLocationUsageLogger; + } + + @Override + public LocationRequestStatistics getLocationRequestStatistics() { + return mLocationRequestStatistics; + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index daaabf8141ff..9a465a91e84e 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -48,8 +48,6 @@ import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isNull; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; @@ -4931,20 +4929,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { .thenReturn(passwordMetrics); dpm.reportPasswordChanged(userHandle); - // Drain ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED broadcasts as part of - // reportPasswordChanged() - // This broadcast should be sent 2-4 times: - // * Twice from calls to DevicePolicyManagerService.updatePasswordExpirationsLocked, - // once for each affected user, in DevicePolicyManagerService.reportPasswordChanged. - // * Optionally, at most twice from calls to DevicePolicyManagerService.saveSettingsLocked - // in DevicePolicyManagerService.reportPasswordChanged, once with the userId - // the password change is relevant to and another with the credential owner of said - // userId, if the password checkpoint value changes. - verify(mContext.spiedContext, atMost(4)).sendBroadcastAsUser( - MockUtils.checkIntentAction( - DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), - MockUtils.checkUserHandle(userHandle)); - verify(mContext.spiedContext, atLeast(2)).sendBroadcastAsUser( + verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( MockUtils.checkIntentAction( DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), MockUtils.checkUserHandle(userHandle)); diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java index 62b6a65cc6cb..614949c91b9a 100644 --- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java @@ -43,11 +43,19 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.content.ClipData; import android.content.Intent; import android.content.pm.ProviderInfo; import android.net.Uri; +import android.os.UserHandle; import android.util.ArraySet; import androidx.test.InstrumentationRegistry; @@ -62,6 +70,12 @@ public class UriGrantsManagerServiceTest { private UriGrantsMockContext mContext; private UriGrantsManagerInternal mService; + // we expect the following only during grant if a grant is expected + private void verifyNoVisibilityGrant() { + verify(mContext.mPmInternal, never()) + .grantImplicitAccess(anyInt(), any(), anyInt(), anyInt(), anyBoolean()); + } + @Before public void setUp() throws Exception { mContext = new UriGrantsMockContext(InstrumentationRegistry.getContext()); @@ -83,6 +97,7 @@ public class UriGrantsManagerServiceTest { assertEquals(UID_PRIMARY_SOCIAL, needed.targetUid); assertEquals(FLAG_READ, needed.flags); assertEquals(asSet(expectedGrant), needed.uris); + verifyNoVisibilityGrant(); } /** @@ -100,6 +115,7 @@ public class UriGrantsManagerServiceTest { assertEquals(UID_SECONDARY_SOCIAL, needed.targetUid); assertEquals(FLAG_READ, needed.flags); assertEquals(asSet(expectedGrant), needed.uris); + verifyNoVisibilityGrant(); } /** @@ -111,6 +127,8 @@ public class UriGrantsManagerServiceTest { final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent( intent, UID_PRIMARY_PUBLIC, PKG_SOCIAL, USER_PRIMARY); assertNull(needed); + verify(mContext.mPmInternal).grantImplicitAccess(eq(USER_PRIMARY), isNull(), eq( + UserHandle.getAppId(UID_PRIMARY_SOCIAL)), eq(UID_PRIMARY_PUBLIC), eq(false)); } /** @@ -128,6 +146,7 @@ public class UriGrantsManagerServiceTest { assertEquals(UID_SECONDARY_SOCIAL, needed.targetUid); assertEquals(FLAG_READ, needed.flags); assertEquals(asSet(expectedGrant), needed.uris); + verifyNoVisibilityGrant(); } /** diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 32686538c10d..ff54fccda767 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -75,8 +75,10 @@ struct ResourcePathData { }; // Resource file paths are expected to look like: [--/res/]type[-config]/name -static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, const char dir_sep, - std::string* out_error) { +static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, + const char dir_sep, + std::string* out_error, + const CompileOptions& options) { std::vector<std::string> parts = util::Split(path, dir_sep); if (parts.size() < 2) { if (out_error) *out_error = "bad resource path"; @@ -121,7 +123,11 @@ static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, } } - return ResourcePathData{Source(path), dir_str.to_string(), name.to_string(), + const Source res_path = options.source_path + ? StringPiece(options.source_path.value()) + : StringPiece(path); + + return ResourcePathData{res_path, dir_str.to_string(), name.to_string(), extension.to_string(), config_str.to_string(), config}; } @@ -667,7 +673,8 @@ int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* // Extract resource type information from the full path std::string err_str; ResourcePathData path_data; - if (auto maybe_path_data = ExtractResourcePathData(path, inputs->GetDirSeparator(), &err_str)) { + if (auto maybe_path_data = ExtractResourcePathData( + path, inputs->GetDirSeparator(), &err_str, options)) { path_data = maybe_path_data.value(); } else { context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << err_str); @@ -747,6 +754,11 @@ int CompileCommand::Action(const std::vector<std::string>& args) { context.GetDiagnostics()->Error(DiagMessage() << "only one of --dir and --zip can be specified"); return 1; + } else if ((options_.res_dir || options_.res_zip) && + options_.source_path && args.size() > 1) { + context.GetDiagnostics()->Error(DiagMessage(kPath) + << "Cannot use an overriding source path with multiple files."); + return 1; } else if (options_.res_dir) { if (!args.empty()) { context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified"); diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h index 1752a1adac24..1bc1f6651f85 100644 --- a/tools/aapt2/cmd/Compile.h +++ b/tools/aapt2/cmd/Compile.h @@ -28,6 +28,7 @@ namespace aapt { struct CompileOptions { std::string output_path; + Maybe<std::string> source_path; Maybe<std::string> res_dir; Maybe<std::string> res_zip; Maybe<std::string> generate_text_symbols_path; @@ -69,6 +70,9 @@ class CompileCommand : public Command { AddOptionalSwitch("-v", "Enables verbose logging", &options_.verbose); AddOptionalFlag("--trace-folder", "Generate systrace json trace fragment to specified folder.", &trace_folder_); + AddOptionalFlag("--source-path", + "Sets the compiled resource file source file path to the given string.", + &options_.source_path); } int Action(const std::vector<std::string>& args) override; diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp index fb786a31360e..0aab94d3299f 100644 --- a/tools/aapt2/cmd/Compile_test.cpp +++ b/tools/aapt2/cmd/Compile_test.cpp @@ -24,6 +24,7 @@ #include "io/ZipArchive.h" #include "java/AnnotationProcessor.h" #include "test/Test.h" +#include "format/proto/ProtoDeserialize.h" namespace aapt { @@ -253,4 +254,90 @@ TEST_F(CompilerTest, DoNotTranslateTest) { AssertTranslations(this, "donottranslate_foo", expected_not_translatable); } +TEST_F(CompilerTest, RelativePathTest) { + StdErrDiagnostics diag; + const std::string res_path = BuildPath( + {android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "res"}); + + const std::string path_values_colors = GetTestPath("values/colors.xml"); + WriteFile(path_values_colors, "<resources>" + "<color name=\"color_one\">#008577</color>" + "</resources>"); + + const std::string path_layout_layout_one = GetTestPath("layout/layout_one.xml"); + WriteFile(path_layout_layout_one, "<LinearLayout " + "xmlns:android=\"http://schemas.android.com/apk/res/android\">" + "<TextBox android:id=\"@+id/text_one\" android:background=\"@color/color_one\"/>" + "</LinearLayout>"); + + const std::string compiled_files_dir = BuildPath( + {android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "compiled"}); + CHECK(file::mkdirs(compiled_files_dir.data())); + + const std::string path_values_colors_out = + BuildPath({compiled_files_dir,"values_colors.arsc.flat"}); + const std::string path_layout_layout_one_out = + BuildPath({compiled_files_dir, "layout_layout_one.flat"}); + ::android::base::utf8::unlink(path_values_colors_out.c_str()); + ::android::base::utf8::unlink(path_layout_layout_one_out.c_str()); + const std::string apk_path = BuildPath( + {android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "out.apk"}); + + const std::string source_set_res = BuildPath({"main", "res"}); + const std::string relative_path_values_colors = + BuildPath({source_set_res, "values", "colors.xml"}); + const std::string relative_path_layout_layout_one = + BuildPath({source_set_res, "layout", "layout_one.xml"}); + + CompileCommand(&diag).Execute({ + path_values_colors, + "-o", + compiled_files_dir, + "--source-path", + relative_path_values_colors}, + &std::cerr); + + CompileCommand(&diag).Execute({ + path_layout_layout_one, + "-o", + compiled_files_dir, + "--source-path", + relative_path_layout_layout_one}, + &std::cerr); + + std::ifstream ifs_values(path_values_colors_out); + std::string content_values((std::istreambuf_iterator<char>(ifs_values)), + (std::istreambuf_iterator<char>())); + ASSERT_NE(content_values.find(relative_path_values_colors), -1); + ASSERT_EQ(content_values.find(path_values_colors), -1); + + Link({"-o", apk_path, "--manifest", GetDefaultManifest(), "--proto-format"}, + compiled_files_dir, &diag); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, &diag); + ResourceTable* resource_table = apk.get()->GetResourceTable(); + const std::vector<std::unique_ptr<StringPool::Entry>>& pool_strings = + resource_table->string_pool.strings(); + + ASSERT_EQ(pool_strings.size(), 2); + ASSERT_EQ(pool_strings[0]->value, "res/layout/layout_one.xml"); + ASSERT_EQ(pool_strings[1]->value, "res/layout-v1/layout_one.xml"); + + // Check resources.pb contains relative sources. + io::IFile* proto_file = + apk.get()->GetFileCollection()->FindFile("resources.pb"); + std::unique_ptr<io::InputStream> proto_stream = proto_file->OpenInputStream(); + io::ProtoInputStreamReader proto_reader(proto_stream.get()); + pb::ResourceTable pb_table; + proto_reader.ReadMessage(&pb_table); + + const std::string pool_strings_proto = pb_table.source_pool().data(); + + ASSERT_NE(pool_strings_proto.find(relative_path_values_colors), -1); + ASSERT_NE(pool_strings_proto.find(relative_path_layout_layout_one), -1); +} + } // namespace aapt diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index 4a6bfd031284..53d9ffe21949 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -1405,6 +1405,29 @@ class UsesStaticLibrary : public ManifestExtractor::Element { } }; +/** Represents <uses-native-library> elements. **/ +class UsesNativeLibrary : public ManifestExtractor::Element { + public: + UsesNativeLibrary() = default; + std::string name; + int required; + + void Extract(xml::Element* element) override { + auto parent_stack = extractor()->parent_stack(); + if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) { + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1); + } + } + + void Print(text::Printer* printer) override { + if (!name.empty()) { + printer->Print(StringPrintf("uses-native-library%s:'%s'\n", + (required == 0) ? "-not-required" : "", name.data())); + } + } +}; + /** * Represents <meta-data> elements. These tags are only printed when a flag is passed in to * explicitly enable meta data printing. @@ -2245,6 +2268,7 @@ T* ElementCast(ManifestExtractor::Element* element) { {"uses-static-library", std::is_base_of<UsesStaticLibrary, T>::value}, {"additional-certificate", std::is_base_of<AdditionalCertificate, T>::value}, {"uses-sdk", std::is_base_of<UsesSdkBadging, T>::value}, + {"uses-native-library", std::is_base_of<UsesNativeLibrary, T>::value}, }; auto check = kTagCheck.find(element->tag()); @@ -2295,6 +2319,7 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate( {"uses-package", &CreateType<UsesPackage>}, {"additional-certificate", &CreateType<AdditionalCertificate>}, {"uses-sdk", &CreateType<UsesSdkBadging>}, + {"uses-native-library", &CreateType<UsesNativeLibrary>}, }; // Attempt to map the xml tag to a element inflater diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 3d69093a936a..49f8e1bcd30b 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -422,6 +422,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, application_action.Action(OptionalNameIsJavaClassName); application_action["uses-library"].Action(RequiredNameIsNotEmpty); + application_action["uses-native-library"].Action(RequiredNameIsNotEmpty); application_action["library"].Action(RequiredNameIsNotEmpty); application_action["profileable"]; |