diff options
34 files changed, 681 insertions, 203 deletions
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java index ed55f0056ea0..3bbc945c8b87 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -64,7 +64,7 @@ public class AppSearchManagerService extends SystemService { public void onStart() { publishBinderService(Context.APP_SEARCH_SERVICE, new Stub()); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); - mImplInstanceManager = new ImplInstanceManager(getContext()); + mImplInstanceManager = ImplInstanceManager.getInstance(getContext()); } private class Stub extends IAppSearchManager.Stub { @@ -102,7 +102,8 @@ public class AppSearchManagerService extends SystemService { } schemasPackageAccessible.put(entry.getKey(), packageIdentifiers); } - AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); impl.setSchema( packageName, databaseName, @@ -133,7 +134,8 @@ public class AppSearchManagerService extends SystemService { final long callingIdentity = Binder.clearCallingIdentity(); try { verifyCallingPackage(callingUid, packageName); - AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); List<AppSearchSchema> schemas = impl.getSchema(packageName, databaseName); List<Bundle> schemaBundles = new ArrayList<>(schemas.size()); for (int i = 0; i < schemas.size(); i++) { @@ -166,7 +168,8 @@ public class AppSearchManagerService extends SystemService { verifyCallingPackage(callingUid, packageName); AppSearchBatchResult.Builder<String, Void> resultBuilder = new AppSearchBatchResult.Builder<>(); - AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); for (int i = 0; i < documentBundles.size(); i++) { GenericDocument document = new GenericDocument(documentBundles.get(i)); try { @@ -207,12 +210,18 @@ public class AppSearchManagerService extends SystemService { verifyCallingPackage(callingUid, packageName); AppSearchBatchResult.Builder<String, Bundle> resultBuilder = new AppSearchBatchResult.Builder<>(); - AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); for (int i = 0; i < uris.size(); i++) { String uri = uris.get(i); try { - GenericDocument document = impl.getDocument(packageName, databaseName, - namespace, uri, typePropertyPaths); + GenericDocument document = + impl.getDocument( + packageName, + databaseName, + namespace, + uri, + typePropertyPaths); resultBuilder.setSuccess(uri, document.getBundle()); } catch (Throwable t) { resultBuilder.setResult(uri, throwableToFailedResult(t)); @@ -245,7 +254,8 @@ public class AppSearchManagerService extends SystemService { final long callingIdentity = Binder.clearCallingIdentity(); try { verifyCallingPackage(callingUid, packageName); - AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); SearchResultPage searchResultPage = impl.query( packageName, @@ -278,12 +288,14 @@ public class AppSearchManagerService extends SystemService { final long callingIdentity = Binder.clearCallingIdentity(); try { verifyCallingPackage(callingUid, packageName); - AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); - SearchResultPage searchResultPage = impl.globalQuery( - queryExpression, - new SearchSpec(searchSpecBundle), - packageName, - callingUid); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + SearchResultPage searchResultPage = + impl.globalQuery( + queryExpression, + new SearchSpec(searchSpecBundle), + packageName, + callingUid); invokeCallbackOnResult( callback, AppSearchResult.newSuccessfulResult(searchResultPage.getBundle())); @@ -306,7 +318,8 @@ public class AppSearchManagerService extends SystemService { // TODO(b/162450968) check nextPageToken is being advanced by the same uid as originally // opened it try { - AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); SearchResultPage searchResultPage = impl.getNextPage(nextPageToken); invokeCallbackOnResult( callback, @@ -324,7 +337,8 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { - AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); impl.invalidateNextPageToken(nextPageToken); } catch (Throwable t) { Log.e(TAG, "Unable to invalidate the query page token", t); @@ -350,15 +364,11 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { - AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); - impl.reportUsage( - packageName, - databaseName, - namespace, - uri, - usageTimeMillis); - invokeCallbackOnResult(callback, - AppSearchResult.newSuccessfulResult(/*result=*/ null)); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + impl.reportUsage(packageName, databaseName, namespace, uri, usageTimeMillis); + invokeCallbackOnResult( + callback, AppSearchResult.newSuccessfulResult(/*result=*/ null)); } catch (Throwable t) { invokeCallbackOnError(callback, t); } finally { @@ -385,7 +395,8 @@ public class AppSearchManagerService extends SystemService { verifyCallingPackage(callingUid, packageName); AppSearchBatchResult.Builder<String, Void> resultBuilder = new AppSearchBatchResult.Builder<>(); - AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); for (int i = 0; i < uris.size(); i++) { String uri = uris.get(i); try { @@ -421,7 +432,8 @@ public class AppSearchManagerService extends SystemService { final long callingIdentity = Binder.clearCallingIdentity(); try { verifyCallingPackage(callingUid, packageName); - AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); impl.removeByQuery( packageName, databaseName, @@ -441,7 +453,8 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { - AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); impl.persistToDisk(); } catch (Throwable t) { Log.e(TAG, "Unable to persist the data to disk", t); @@ -457,7 +470,7 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { - mImplInstanceManager.getInstance(callingUserId); + mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null)); } catch (Throwable t) { invokeCallbackOnError(callback, t); diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java index fe3c2e1d1604..97b1a8cd6d50 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java @@ -41,14 +41,33 @@ import java.io.File; public final class ImplInstanceManager { private static final String APP_SEARCH_DIR = "appSearch"; - private static final SparseArray<AppSearchImpl> sInstances = new SparseArray<>(); + private static ImplInstanceManager sImplInstanceManager; - private final Context mContext; + private final SparseArray<AppSearchImpl> mInstances = new SparseArray<>(); private final String mGlobalQuerierPackage; - public ImplInstanceManager(@NonNull Context context) { - mContext = context; - mGlobalQuerierPackage = getGlobalAppSearchDataQuerierPackageName(mContext); + private ImplInstanceManager(@NonNull String globalQuerierPackage) { + mGlobalQuerierPackage = globalQuerierPackage; + } + + /** + * Gets an instance of ImplInstanceManager to be used. + * + * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the + * existing instance will be returned. + */ + @NonNull + public static ImplInstanceManager getInstance(@NonNull Context context) { + if (sImplInstanceManager == null) { + synchronized (ImplInstanceManager.class) { + if (sImplInstanceManager == null) { + sImplInstanceManager = + new ImplInstanceManager( + getGlobalAppSearchDataQuerierPackageName(context)); + } + } + } + return sImplInstanceManager; } /** @@ -57,30 +76,30 @@ public final class ImplInstanceManager { * <p>If no AppSearchImpl instance exists for this user, Icing will be initialized and one will * be created. * + * @param context The context * @param userId The multi-user userId of the device user calling AppSearch * @return An initialized {@link AppSearchImpl} for this user */ @NonNull - public AppSearchImpl getInstance(@UserIdInt int userId) + public AppSearchImpl getAppSearchImpl(@NonNull Context context, @UserIdInt int userId) throws AppSearchException { - AppSearchImpl instance = sInstances.get(userId); + AppSearchImpl instance = mInstances.get(userId); if (instance == null) { synchronized (ImplInstanceManager.class) { - instance = sInstances.get(userId); + instance = mInstances.get(userId); if (instance == null) { - instance = createImpl(userId); - sInstances.put(userId, instance); + instance = createImpl(context, userId); + mInstances.put(userId, instance); } } } return instance; } - private AppSearchImpl createImpl(@UserIdInt int userId) + private AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId) throws AppSearchException { - File appSearchDir = getAppSearchDir(mContext, userId); - return AppSearchImpl.create( - appSearchDir, mContext, userId, mGlobalQuerierPackage); + File appSearchDir = getAppSearchDir(context, userId); + return AppSearchImpl.create(appSearchDir, context, userId, mGlobalQuerierPackage); } private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) { @@ -96,7 +115,8 @@ public final class ImplInstanceManager { * * @param context Context of the system service. */ - private static String getGlobalAppSearchDataQuerierPackageName(Context context) { + @NonNull + private static String getGlobalAppSearchDataQuerierPackageName(@NonNull Context context) { String globalAppSearchDataQuerierPackage = context.getString(R.string.config_globalAppSearchDataQuerierPackage); try { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 68d3a9203f12..49f508d83f91 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5255,6 +5255,7 @@ public class Notification implements Parcelable // We still want a time to be set but gone, such that we can show and hide it // on demand in case it's a child notification without anything in the header contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime); + setTextViewColorSecondary(contentView, R.id.time, p); } } diff --git a/core/java/android/app/people/ConversationChannel.aidl b/core/java/android/app/people/ConversationChannel.aidl new file mode 100644 index 000000000000..78df2f10c337 --- /dev/null +++ b/core/java/android/app/people/ConversationChannel.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.people; + +parcelable ConversationChannel;
\ No newline at end of file diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl index 0d12ed02f610..ebe9f60dc150 100644 --- a/core/java/android/app/people/IPeopleManager.aidl +++ b/core/java/android/app/people/IPeopleManager.aidl @@ -17,6 +17,7 @@ package android.app.people; import android.app.people.ConversationStatus; +import android.app.people.ConversationChannel; import android.content.pm.ParceledListSlice; import android.net.Uri; import android.os.IBinder; @@ -26,6 +27,13 @@ import android.os.IBinder; * {@hide} */ interface IPeopleManager { + + /** + * Returns the specified conversation from the conversations list. If the conversation can't be + * found, returns null. + */ + ConversationChannel getConversation(in String packageName, int userId, in String shortcutId); + /** * Returns the recent conversations. The conversations that have customized notification * settings are excluded from the returned list. diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index b1b29254783c..8b6082b30dee 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -28,7 +28,10 @@ import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO; import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED; import static android.app.AppOpsManager.opToPermission; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED; +import static android.media.AudioSystem.MODE_IN_COMMUNICATION; +import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; +import android.Manifest; import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.ComponentName; @@ -41,12 +44,14 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.icu.text.ListFormatter; import android.location.LocationManager; +import android.media.AudioManager; import android.os.Process; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; import android.speech.RecognitionService; import android.speech.RecognizerIntent; +import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; import android.view.inputmethod.InputMethodInfo; @@ -75,6 +80,9 @@ public class PermissionUsageHelper { private static final String PROPERTY_LOCATION_INDICATORS_ENABLED = "location_indicators_enabled"; + /** Whether to show the Permissions Hub. */ + private static final String PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled"; + /** How long after an access to show it as "recent" */ private static final String RECENT_ACCESS_TIME_MS = "recent_acccess_time_ms"; @@ -84,17 +92,25 @@ public class PermissionUsageHelper { /** The name of the expected voice IME subtype */ private static final String VOICE_IME_SUBTYPE = "voice"; + private static final String SYSTEM_PKG = "android"; + private static final long DEFAULT_RUNNING_TIME_MS = 5000L; private static final long DEFAULT_RECENT_TIME_MS = 30000L; + private static boolean shouldShowPermissionsHub() { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + PROPERTY_PERMISSIONS_HUB_2_ENABLED, false); + } + private static boolean shouldShowIndicators() { return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - PROPERTY_CAMERA_MIC_ICONS_ENABLED, true); + PROPERTY_CAMERA_MIC_ICONS_ENABLED, true) || shouldShowPermissionsHub(); } private static boolean shouldShowLocationIndicator() { return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - PROPERTY_LOCATION_INDICATORS_ENABLED, false); + PROPERTY_LOCATION_INDICATORS_ENABLED, false) + || shouldShowPermissionsHub(); } private static long getRecentThreshold(Long now) { @@ -113,7 +129,7 @@ public class PermissionUsageHelper { ); private static final List<String> MIC_OPS = List.of( - OPSTR_PHONE_CALL_CAMERA, + OPSTR_PHONE_CALL_MICROPHONE, OPSTR_RECORD_AUDIO ); @@ -163,6 +179,13 @@ public class PermissionUsageHelper { return mUserContexts.get(user); } + // TODO ntmyren: Replace this with better check if this moves beyond teamfood + private boolean isAppPredictor(String packageName, UserHandle user) { + return shouldShowPermissionsHub() && getUserContext(user).getPackageManager() + .checkPermission(Manifest.permission.MANAGE_APP_PREDICTIONS, packageName) + == PackageManager.PERMISSION_GRANTED; + } + /** * @see PermissionManager.getIndicatorAppOpUsageData */ @@ -186,7 +209,28 @@ public class PermissionUsageHelper { Map<PackageAttribution, CharSequence> packagesWithAttributionLabels = getTrustedAttributions(rawUsages.get(MICROPHONE), proxyChains); - List<String> usedPermGroups = new ArrayList<>(rawUsages.keySet()); + ArrayList<String> usedPermGroups = new ArrayList<>(rawUsages.keySet()); + + // If we have a phone call, and a carrier privileged app using microphone, hide the + // phone call. + AudioManager audioManager = mContext.getSystemService(AudioManager.class); + boolean hasPhoneCall = usedPermGroups.contains(OPSTR_PHONE_CALL_CAMERA) + || usedPermGroups.contains(OPSTR_PHONE_CALL_MICROPHONE); + if (hasPhoneCall && usedPermGroups.contains(MICROPHONE) && audioManager.getMode() + == MODE_IN_COMMUNICATION) { + TelephonyManager telephonyManager = + mContext.getSystemService(TelephonyManager.class); + List<OpUsage> permUsages = rawUsages.get(MICROPHONE); + for (int usageNum = 0; usageNum < permUsages.size(); usageNum++) { + if (telephonyManager.checkCarrierPrivilegesForPackage( + permUsages.get(usageNum).packageName) + == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + usedPermGroups.remove(OPSTR_PHONE_CALL_CAMERA); + usedPermGroups.remove(OPSTR_PHONE_CALL_MICROPHONE); + } + } + } + for (int permGroupNum = 0; permGroupNum < usedPermGroups.size(); permGroupNum++) { boolean isPhone = false; String permGroup = usedPermGroups.get(permGroupNum); @@ -269,8 +313,11 @@ public class PermissionUsageHelper { if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) { continue; } - if (!isUserSensitive(packageName, user, op) - && !isLocationProvider(packageName, user)) { + + if (packageName.equals(SYSTEM_PKG) + || (!isUserSensitive(packageName, user, op) + && !isLocationProvider(packageName, user) + && !isAppPredictor(packageName, user))) { continue; } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 106e3927656f..0a1a23116941 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -414,6 +414,15 @@ public final class SurfaceControl implements Parcelable { */ public static final int SECURE = 0x00000080; + + /** + * Queue up BufferStateLayer buffers instead of dropping the oldest buffer when this flag is + * set. This blocks the client until all the buffers have been presented. If the buffers + * have presentation timestamps, then we may drop buffers. + * @hide + */ + public static final int ENABLE_BACKPRESSURE = 0x00000100; + /** * Surface creation flag: Creates a surface where color components are interpreted * as "non pre-multiplied" by their alpha channel. Of course this flag is diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 0e878fcf0e24..4ef63ae93016 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -262,7 +262,7 @@ public: } } - binder::Status onScreenCaptureComplete( + binder::Status onScreenCaptureCompleted( const gui::ScreenCaptureResults& captureResults) override { JNIEnv* env = getenv(); if (captureResults.result != NO_ERROR || captureResults.buffer == nullptr) { @@ -270,6 +270,7 @@ public: gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr); return binder::Status::ok(); } + captureResults.fence->waitForever(""); jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer( env, captureResults.buffer->toAHardwareBuffer()); const jint namedColorSpace = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java index 4874d3ccae7e..a4cd3c5a583d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java @@ -47,6 +47,11 @@ public class HandlerExecutor implements ShellExecutor { } @Override + public void removeAllCallbacks() { + mHandler.removeCallbacksAndMessages(null); + } + + @Override public void removeCallbacks(@NonNull Runnable r) { mHandler.removeCallbacks(r); } @@ -55,9 +60,4 @@ public class HandlerExecutor implements ShellExecutor { public boolean hasCallback(Runnable r) { return mHandler.hasCallbacks(r); } - - @Override - public Looper getLooper() { - return mHandler.getLooper(); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java index 1149cceb1068..b736fb0b9895 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java @@ -73,6 +73,11 @@ public interface ShellExecutor extends Executor { void executeDelayed(Runnable runnable, long delayMillis); /** + * Removes all pending callbacks. + */ + void removeAllCallbacks(); + + /** * See {@link android.os.Handler#removeCallbacks}. */ void removeCallbacks(Runnable runnable); @@ -81,9 +86,4 @@ public interface ShellExecutor extends Executor { * See {@link android.os.Handler#hasCallbacks(Runnable)}. */ boolean hasCallback(Runnable runnable); - - /** - * Returns the looper that this executor is running on. - */ - Looper getLooper(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java index a74f4761af0c..37a91d0c121c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java @@ -56,7 +56,7 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer private final float[] mColor; private final float mAlpha; private final Rect mRect; - private final Handler mHandler; + private final Executor mMainExecutor; private final Point mDisplaySize = new Point(); private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; @@ -76,13 +76,13 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer @Override public void onOneHandedAnimationStart( OneHandedAnimationController.OneHandedTransitionAnimator animator) { - mHandler.post(() -> showBackgroundPanelLayer()); + mMainExecutor.execute(() -> showBackgroundPanelLayer()); } }; @Override public void onStopFinished(Rect bounds) { - mHandler.post(() -> removeBackgroundPanelLayer()); + mMainExecutor.execute(() -> removeBackgroundPanelLayer()); } public OneHandedBackgroundPanelOrganizer(Context context, DisplayController displayController, @@ -94,7 +94,7 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer mColor = new float[]{defaultRGB, defaultRGB, defaultRGB}; mAlpha = res.getFloat(R.dimen.config_one_handed_background_alpha); mRect = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y); - mHandler = new Handler(); + mMainExecutor = executor; mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java index 1ed121f35a59..49b7e050c48b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java @@ -221,8 +221,14 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback, displaySize.y); mInputMonitor = InputManager.getInstance().monitorGestureInput( "onehanded-gesture-offset", DEFAULT_DISPLAY); - mInputEventReceiver = new EventReceiver( - mInputMonitor.getInputChannel(), mMainExecutor.getLooper()); + try { + mMainExecutor.executeBlocking(() -> { + mInputEventReceiver = new EventReceiver( + mInputMonitor.getInputChannel(), Looper.myLooper()); + }); + } catch (InterruptedException e) { + throw new RuntimeException("Failed to create input event receiver", e); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java index 60709bef4daf..c7a49ff01d15 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java @@ -132,8 +132,14 @@ public class OneHandedTouchHandler implements OneHandedTransitionCallback { if (mIsEnabled) { mInputMonitor = InputManager.getInstance().monitorGestureInput( "onehanded-touch", DEFAULT_DISPLAY); - mInputEventReceiver = new EventReceiver( - mInputMonitor.getInputChannel(), mMainExecutor.getLooper()); + try { + mMainExecutor.executeBlocking(() -> { + mInputEventReceiver = new EventReceiver( + mInputMonitor.getInputChannel(), Looper.myLooper()); + }); + } catch (InterruptedException e) { + throw new RuntimeException("Failed to create input event receiver", e); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java index 7a634c3eef78..6e3a20d5f2b2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java @@ -147,7 +147,7 @@ public class PipInputConsumer { // Choreographer.getSfInstance() must be called on the thread that the input event // receiver should be receiving events mInputEventReceiver = new InputEventReceiver(inputChannel, - mMainExecutor.getLooper(), Choreographer.getSfInstance()); + Looper.myLooper(), Choreographer.getSfInstance()); if (mRegistrationListener != null) { mRegistrationListener.onRegistrationChanged(true /* isRegistered */); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 41cc59d138fa..8fb358ad74d1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -212,8 +212,14 @@ public class PipResizeGestureHandler { // Register input event receiver mInputMonitor = InputManager.getInstance().monitorGestureInput( "pip-resize", mDisplayId); - mInputEventReceiver = new PipResizeInputEventReceiver( - mInputMonitor.getInputChannel(), mMainExecutor.getLooper()); + try { + mMainExecutor.executeBlocking(() -> { + mInputEventReceiver = new PipResizeInputEventReceiver( + mInputMonitor.getInputChannel(), Looper.myLooper()); + }); + } catch (InterruptedException e) { + throw new RuntimeException("Failed to create input event receiver", e); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index c9f5ae28175c..2b8b53cdf285 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -95,7 +95,6 @@ public class PipTouchHandler { private int mDeferResizeToNormalBoundsUntilRotation = -1; private int mDisplayRotation; - private final Handler mHandler = new Handler(); private final PipAccessibilityInteractionConnection mConnection; // Behaviour states diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java index 5f5c30bb6207..bf84a6e30c98 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java @@ -40,6 +40,11 @@ public class TestShellExecutor implements ShellExecutor { } @Override + public void removeAllCallbacks() { + mRunnables.clear(); + } + + @Override public void removeCallbacks(Runnable r) { mRunnables.remove(r); } @@ -49,11 +54,6 @@ public class TestShellExecutor implements ShellExecutor { return mRunnables.contains(r); } - @Override - public Looper getLooper() { - return null; - } - public void flushAll() { for (Runnable r : mRunnables) { r.run(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java index 9219f15afc7f..bbe8891817d6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java @@ -33,6 +33,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.ShellExecutor; import org.junit.Before; @@ -48,7 +49,7 @@ import java.util.ArrayList; @RunWith(AndroidTestingRunner.class) public class OneHandedTimeoutHandlerTest extends OneHandedTestCase { private OneHandedTimeoutHandler mTimeoutHandler; - private ShellExecutor mMainExecutor; + private TestShellExecutor mMainExecutor; @Before public void setUp() throws Exception { @@ -104,34 +105,4 @@ public class OneHandedTimeoutHandlerTest extends OneHandedTestCase { mTimeoutHandler.resetTimer(); assertTrue(mTimeoutHandler.hasScheduledTimeout()); } - - private class TestShellExecutor implements ShellExecutor { - private ArrayList<Runnable> mExecuted = new ArrayList<>(); - private ArrayList<Runnable> mDelayed = new ArrayList<>(); - - @Override - public void execute(Runnable runnable) { - mExecuted.add(runnable); - } - - @Override - public void executeDelayed(Runnable r, long delayMillis) { - mDelayed.add(r); - } - - @Override - public void removeCallbacks(Runnable r) { - mDelayed.remove(r); - } - - @Override - public boolean hasCallback(Runnable r) { - return mDelayed.contains(r); - } - - @Override - public Looper getLooper() { - return Looper.myLooper(); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java index 93a8df41c673..cd3d6a84a352 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java @@ -26,8 +26,7 @@ public class AppOpItem { private String mPackageName; private long mTimeStarted; private StringBuilder mState; - // This is only used for items with mCode == AppOpsManager.OP_RECORD_AUDIO - private boolean mSilenced; + private boolean mIsDisabled; public AppOpItem(int code, int uid, String packageName, long timeStarted) { this.mCode = code; @@ -58,16 +57,16 @@ public class AppOpItem { return mTimeStarted; } - public void setSilenced(boolean silenced) { - mSilenced = silenced; + public void setDisabled(boolean misDisabled) { + this.mIsDisabled = misDisabled; } - public boolean isSilenced() { - return mSilenced; + public boolean isDisabled() { + return mIsDisabled; } @Override public String toString() { - return mState.append(mSilenced).append(")").toString(); + return mState.append(mIsDisabled).append(")").toString(); } } diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 1036c9916c8b..d8ca63960b30 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -16,6 +16,8 @@ package com.android.systemui.appops; +import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; +import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED; import android.Manifest; @@ -45,6 +47,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; +import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.util.Assert; import java.io.FileDescriptor; @@ -64,7 +67,8 @@ import javax.inject.Inject; @SysUISingleton public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController, AppOpsManager.OnOpActiveChangedInternalListener, - AppOpsManager.OnOpNotedListener, Dumpable { + AppOpsManager.OnOpNotedListener, IndividualSensorPrivacyController.Callback, + Dumpable { // This is the minimum time that we will keep AppOps that are noted on record. If multiple // occurrences of the same (op, package, uid) happen in a shorter interval, they will not be @@ -77,8 +81,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon private final AppOpsManager mAppOps; private final AudioManager mAudioManager; private final LocationManager mLocationManager; - // TODO ntmyren: remove t private final PackageManager mPackageManager; + private final IndividualSensorPrivacyController mSensorPrivacyController; // mLocationProviderPackages are cached and updated only occasionally private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000; @@ -91,6 +95,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon private final PermissionFlagsCache mFlagsCache; private boolean mListening; private boolean mMicMuted; + private boolean mCameraDisabled; @GuardedBy("mActiveItems") private final List<AppOpItem> mActiveItems = new ArrayList<>(); @@ -118,6 +123,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon DumpManager dumpManager, PermissionFlagsCache cache, AudioManager audioManager, + IndividualSensorPrivacyController sensorPrivacyController, BroadcastDispatcher dispatcher ) { mDispatcher = dispatcher; @@ -129,7 +135,10 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mCallbacksByCode.put(OPS[i], new ArraySet<>()); } mAudioManager = audioManager; - mMicMuted = audioManager.isMicrophoneMute(); + mSensorPrivacyController = sensorPrivacyController; + mMicMuted = audioManager.isMicrophoneMute() + || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); + mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); mLocationManager = context.getSystemService(LocationManager.class); mPackageManager = context.getPackageManager(); dumpManager.registerDumpable(TAG, this); @@ -147,6 +156,12 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mAppOps.startWatchingActive(OPS, this); mAppOps.startWatchingNoted(OPS, this); mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler); + mSensorPrivacyController.addCallback(this); + + mMicMuted = mAudioManager.isMicrophoneMute() + || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); + mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); + mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged( mAudioManager.getActiveRecordingConfigurations())); mDispatcher.registerReceiverWithHandler(this, @@ -156,6 +171,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mAppOps.stopWatchingActive(this); mAppOps.stopWatchingNoted(this); mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback); + mSensorPrivacyController.removeCallback(this); mBGHandler.removeCallbacksAndMessages(null); // null removes all mDispatcher.unregisterReceiver(this); @@ -235,11 +251,13 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon if (item == null && active) { item = new AppOpItem(code, uid, packageName, System.currentTimeMillis()); if (code == AppOpsManager.OP_RECORD_AUDIO) { - item.setSilenced(isAnyRecordingPausedLocked(uid)); + item.setDisabled(isAnyRecordingPausedLocked(uid)); + } else if (code == AppOpsManager.OP_CAMERA) { + item.setDisabled(mCameraDisabled); } mActiveItems.add(item); if (DEBUG) Log.w(TAG, "Added item: " + item.toString()); - return !item.isSilenced(); + return !item.isDisabled(); } else if (item != null && !active) { mActiveItems.remove(item); if (DEBUG) Log.w(TAG, "Removed item: " + item.toString()); @@ -409,7 +427,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = mActiveItems.get(i); if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) - && isUserVisible(item) && !item.isSilenced()) { + && isUserVisible(item) && !item.isDisabled()) { list.add(item); } } @@ -512,22 +530,27 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon return false; } - private void updateRecordingPausedStatus() { + private void updateSensorDisabledStatus() { synchronized (mActiveItems) { int size = mActiveItems.size(); for (int i = 0; i < size; i++) { AppOpItem item = mActiveItems.get(i); + + boolean paused = false; if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) { - boolean paused = isAnyRecordingPausedLocked(item.getUid()); - if (item.isSilenced() != paused) { - item.setSilenced(paused); - notifySuscribers( - item.getCode(), - item.getUid(), - item.getPackageName(), - !item.isSilenced() - ); - } + paused = isAnyRecordingPausedLocked(item.getUid()); + } else if (item.getCode() == AppOpsManager.OP_CAMERA) { + paused = mCameraDisabled; + } + + if (item.isDisabled() != paused) { + item.setDisabled(paused); + notifySuscribers( + item.getCode(), + item.getUid(), + item.getPackageName(), + !item.isDisabled() + ); } } } @@ -552,14 +575,27 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon recordings.add(recording); } } - updateRecordingPausedStatus(); + updateSensorDisabledStatus(); } }; @Override public void onReceive(Context context, Intent intent) { - mMicMuted = mAudioManager.isMicrophoneMute(); - updateRecordingPausedStatus(); + mMicMuted = mAudioManager.isMicrophoneMute() + || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); + updateSensorDisabledStatus(); + } + + @Override + public void onSensorBlockedChanged(int sensor, boolean blocked) { + mBGHandler.post(() -> { + if (sensor == INDIVIDUAL_SENSOR_CAMERA) { + mCameraDisabled = blocked; + } else if (sensor == INDIVIDUAL_SENSOR_MICROPHONE) { + mMicMuted = mAudioManager.isMicrophoneMute() || blocked; + } + updateSensorDisabledStatus(); + }); } protected class H extends Handler { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index c2c67903da86..9be3566e1f63 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -23,7 +23,6 @@ import android.content.Intent; import android.net.Uri; import android.os.UserHandle; import android.util.Log; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewTreeObserver.InternalInsetsInfo; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; @@ -59,7 +58,6 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener private final Executor mBgExecutor; private final ImageExporter mImageExporter; private final ImageTileSet mImageTileSet; - private final LayoutInflater mLayoutInflater; private ZonedDateTime mCaptureTime; private UUID mRequestId; @@ -81,7 +79,6 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener mBgExecutor = bgExecutor; mImageExporter = exporter; mImageTileSet = new ImageTileSet(); - mLayoutInflater = mContext.getSystemService(LayoutInflater.class); } /** @@ -114,7 +111,7 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener mEdit.setOnClickListener(this::onClicked); mShare.setOnClickListener(this::onClicked); - mPreview.setImageDrawable(mImageTileSet.getDrawable()); + //mPreview.setImageDrawable(mImageTileSet.getDrawable()); mConnection.start(this::startCapture); } @@ -242,6 +239,7 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener if (mImageTileSet.isEmpty()) { session.end(mCallback::onFinish); } else { + mPreview.setImageDrawable(mImageTileSet.getDrawable()); mExportFuture = mImageExporter.export( mBgExecutor, mRequestId, mImageTileSet.toBitmap(), mCaptureTime); // The user chose an action already, link it to the result diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java index 231fe08e6a99..32d15ed41648 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.policy; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; +import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; +import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; import android.hardware.SensorPrivacyManager; import android.hardware.SensorPrivacyManager.IndividualSensor; @@ -30,7 +30,8 @@ import java.util.Set; public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController { - private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE}; + private static final int[] SENSORS = new int[] {INDIVIDUAL_SENSOR_CAMERA, + INDIVIDUAL_SENSOR_MICROPHONE}; private final @NonNull SensorPrivacyManager mSensorPrivacyManager; private final SparseBooleanArray mState = new SparseBooleanArray(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index 02143a750cae..bc322f7f18fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -16,6 +16,9 @@ package com.android.systemui.appops; +import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; +import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; + import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; @@ -49,6 +52,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; +import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import org.junit.Before; import org.junit.Test; @@ -81,6 +85,8 @@ public class AppOpsControllerTest extends SysuiTestCase { private PermissionFlagsCache mFlagsCache; @Mock private PackageManager mPackageManager; + @Mock + private IndividualSensorPrivacyController mSensorPrivacyController; @Mock(stubOnly = true) private AudioManager mAudioManager; @Mock() @@ -118,12 +124,18 @@ public class AppOpsControllerTest extends SysuiTestCase { when(mAudioManager.getActiveRecordingConfigurations()) .thenReturn(List.of(mPausedMockRecording)); + when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA)) + .thenReturn(false); + when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA)) + .thenReturn(false); + mController = new AppOpsControllerImpl( mContext, mTestableLooper.getLooper(), mDumpManager, mFlagsCache, mAudioManager, + mSensorPrivacyController, mDispatcher ); } @@ -133,6 +145,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.setListening(true); verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController); verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any()); + verify(mSensorPrivacyController, times(1)).addCallback(mController); } @Test @@ -140,6 +153,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.setListening(false); verify(mAppOpsManager, times(1)).stopWatchingActive(mController); verify(mDispatcher, times(1)).unregisterReceiver(mController); + verify(mSensorPrivacyController, times(1)).removeCallback(mController); } @Test @@ -476,6 +490,71 @@ public class AppOpsControllerTest extends SysuiTestCase { AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false); } + @Test + public void testAudioFilteredWhenMicDisabled() { + mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA}, + mCallback); + mTestableLooper.processAllMessages(); + mController.onOpActiveChanged( + AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + List<AppOpItem> list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode()); + assertFalse(list.get(0).isDisabled()); + + // Add a camera op, and disable the microphone. The camera op should be the only op returned + mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, true); + mController.onOpActiveChanged( + AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode()); + + + // Re enable the microphone, and verify the op returns + mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, false); + mTestableLooper.processAllMessages(); + + list = mController.getActiveAppOps(); + assertEquals(2, list.size()); + int micIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 1 : 0; + assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(micIdx).getCode()); + } + + @Test + public void testCameraFilteredWhenCameraDisabled() { + mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA}, + mCallback); + mTestableLooper.processAllMessages(); + mController.onOpActiveChanged( + AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + List<AppOpItem> list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode()); + assertFalse(list.get(0).isDisabled()); + + // Add an audio op, and disable the camera. The audio op should be the only op returned + mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, true); + mController.onOpActiveChanged( + AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode()); + + // Re enable the camera, and verify the op returns + mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, false); + mTestableLooper.processAllMessages(); + + list = mController.getActiveAppOps(); + assertEquals(2, list.size()); + int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 0 : 1; + assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode()); + } + private class TestHandler extends AppOpsControllerImpl.H { TestHandler(Looper looper) { mController.super(looper); diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index b5b93d6d8ecd..142f64f0a510 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -74,7 +74,6 @@ import android.os.ICancellationSignal; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; -import android.os.SystemClock; import android.os.UserHandle; import android.os.WorkSource; import android.os.WorkSource.WorkChain; @@ -1297,10 +1296,7 @@ public class LocationManagerService extends ILocationManager.Stub { return null; } - long currentNanos = SystemClock.elapsedRealtimeNanos(); - long deltaMs = NANOSECONDS.toMillis( - location.getElapsedRealtimeAgeNanos(currentNanos)); - return new LocationTime(location.getTime() + deltaMs, currentNanos); + return new LocationTime(location.getTime(), location.getElapsedRealtimeNanos()); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b8d3e54639e5..d2fc5b4c0967 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2153,6 +2153,10 @@ public class PackageManagerService extends IPackageManager.Stub void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, boolean requireFullPermission, boolean checkShell, boolean requirePermissionWhenSameUser, String message); + SigningDetails getSigningDetails(@NonNull String packageName); + SigningDetails getSigningDetails(int uid); + boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId); + boolean filterAppAccess(String packageName, int callingUid, int userId); } /** @@ -4578,6 +4582,40 @@ public class PackageManagerService extends IPackageManager.Stub throw new SecurityException(errorMessage); } + public SigningDetails getSigningDetails(@NonNull String packageName) { + AndroidPackage p = mPackages.get(packageName); + if (p == null) { + return null; + } + return p.getSigningDetails(); + } + + public SigningDetails getSigningDetails(int uid) { + final int appId = UserHandle.getAppId(uid); + final Object obj = mSettings.getSettingLPr(appId); + if (obj != null) { + if (obj instanceof SharedUserSetting) { + return ((SharedUserSetting) obj).signatures.mSigningDetails; + } else if (obj instanceof PackageSetting) { + final PackageSetting ps = (PackageSetting) obj; + return ps.signatures.mSigningDetails; + } + } + return SigningDetails.UNKNOWN; + } + + public boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) { + PackageSetting ps = getPackageSetting(pkg.getPackageName()); + return shouldFilterApplicationLocked(ps, callingUid, + userId); + } + + public boolean filterAppAccess(String packageName, int callingUid, int userId) { + PackageSetting ps = getPackageSetting(packageName); + return shouldFilterApplicationLocked(ps, callingUid, + userId); + } + } /** @@ -4728,6 +4766,26 @@ public class PackageManagerService extends IPackageManager.Stub return super.getPackageUidInternal(packageName, flags, userId, callingUid); } } + public SigningDetails getSigningDetails(@NonNull String packageName) { + synchronized (mLock) { + return super.getSigningDetails(packageName); + } + } + public SigningDetails getSigningDetails(int uid) { + synchronized (mLock) { + return super.getSigningDetails(uid); + } + } + public boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) { + synchronized (mLock) { + return super.filterAppAccess(pkg, callingUid, userId); + } + } + public boolean filterAppAccess(String packageName, int callingUid, int userId) { + synchronized (mLock) { + return super.filterAppAccess(packageName, callingUid, userId); + } + } } @@ -26560,6 +26618,22 @@ public class PackageManagerService extends IPackageManager.Stub return snapshotComputer().getPackage(uid); } + private SigningDetails getSigningDetails(@NonNull String packageName) { + return snapshotComputer().getSigningDetails(packageName); + } + + private SigningDetails getSigningDetails(int uid) { + return snapshotComputer().getSigningDetails(uid); + } + + private boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) { + return snapshotComputer().filterAppAccess(pkg, callingUid, userId); + } + + private boolean filterAppAccess(String packageName, int callingUid, int userId) { + return snapshotComputer().filterAppAccess(packageName, callingUid, userId); + } + private class PackageManagerInternalImpl extends PackageManagerInternal { @Override public List<ApplicationInfo> getInstalledApplications(int flags, int userId, @@ -26615,29 +26689,11 @@ public class PackageManagerService extends IPackageManager.Stub } private SigningDetails getSigningDetails(@NonNull String packageName) { - synchronized (mLock) { - AndroidPackage p = mPackages.get(packageName); - if (p == null) { - return null; - } - return p.getSigningDetails(); - } + return PackageManagerService.this.getSigningDetails(packageName); } private SigningDetails getSigningDetails(int uid) { - synchronized (mLock) { - final int appId = UserHandle.getAppId(uid); - final Object obj = mSettings.getSettingLPr(appId); - if (obj != null) { - if (obj instanceof SharedUserSetting) { - return ((SharedUserSetting) obj).signatures.mSigningDetails; - } else if (obj instanceof PackageSetting) { - final PackageSetting ps = (PackageSetting) obj; - return ps.signatures.mSigningDetails; - } - } - return SigningDetails.UNKNOWN; - } + return PackageManagerService.this.getSigningDetails(uid); } @Override @@ -26652,20 +26708,12 @@ public class PackageManagerService extends IPackageManager.Stub @Override public boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) { - synchronized (mLock) { - PackageSetting ps = getPackageSetting(pkg.getPackageName()); - return PackageManagerService.this.shouldFilterApplicationLocked(ps, callingUid, - userId); - } + return PackageManagerService.this.filterAppAccess(pkg, callingUid, userId); } @Override public boolean filterAppAccess(String packageName, int callingUid, int userId) { - synchronized (mLock) { - PackageSetting ps = getPackageSetting(packageName); - return PackageManagerService.this.shouldFilterApplicationLocked(ps, callingUid, - userId); - } + return PackageManagerService.this.filterAppAccess(packageName, callingUid, userId); } @Override diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 73bcf47979fe..0aaa1a1752bf 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4107,8 +4107,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * Callbacks when the given type of {@link WindowContainer} animation finished running in the * hierarchy. */ - void onWindowAnimationFinished(int type) { + void onWindowAnimationFinished(@NonNull WindowContainer wc, int type) { if (type == ANIMATION_TYPE_APP_TRANSITION || type == ANIMATION_TYPE_RECENTS) { + // Unfreeze the insets state of the frozen target when the animation finished if exists. + final Task task = wc.asTask(); + if (task != null) { + task.forAllWindows(w -> { + w.clearFrozenInsetsState(); + }, true /* traverseTopToBottom */); + } removeImeSurfaceImmediately(); } } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 398049fb97c3..267f67759a24 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -74,7 +74,7 @@ class InsetsStateController { private final ArraySet<InsetsControlTarget> mPendingControlChanged = new ArraySet<>(); private final Consumer<WindowState> mDispatchInsetsChanged = w -> { - if (w.isVisible()) { + if (w.isReadyToDispatchInsetsState()) { w.notifyInsetsChanged(); } }; @@ -117,7 +117,8 @@ class InsetsStateController { final @InternalInsetsType int type = provider != null ? provider.getSource().getType() : ITYPE_INVALID; return getInsetsForTarget(type, target.getWindowingMode(), target.isAlwaysOnTop(), - isAboveIme(target)); + isAboveIme(target), + target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() : mState); } InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) { @@ -132,7 +133,7 @@ class InsetsStateController { final @WindowingMode int windowingMode = token != null ? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED; final boolean alwaysOnTop = token != null && token.isAlwaysOnTop(); - return getInsetsForTarget(type, windowingMode, alwaysOnTop, isAboveIme(token)); + return getInsetsForTarget(type, windowingMode, alwaysOnTop, isAboveIme(token), mState); } private boolean isAboveIme(WindowContainer target) { @@ -180,9 +181,8 @@ class InsetsStateController { * @see #getInsetsForWindowMetrics */ private InsetsState getInsetsForTarget(@InternalInsetsType int type, - @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme) { - InsetsState state = mState; - + @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme, + @NonNull InsetsState state) { if (type != ITYPE_INVALID) { state = new InsetsState(state); state.removeSource(type); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 03fca1137e47..dd4ee877c05b 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2684,6 +2684,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Nullable ArrayList<WindowContainer> sources) { final Task task = asTask(); if (task != null && !enter && !task.isHomeOrRecentsRootTask()) { + if (AppTransition.isClosingTransitOld(transit)) { + // Freezes the insets state when the window is in app exiting transition, to + // ensure the exiting window won't receive unexpected insets changes from the + // next window. + task.forAllWindows(w -> { + w.freezeInsetsState(); + }, true /* traverseTopToBottom */); + } mDisplayContent.showImeScreenshot(); } final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp, @@ -2831,7 +2839,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } mSurfaceAnimationSources.clear(); if (mDisplayContent != null) { - mDisplayContent.onWindowAnimationFinished(type); + mDisplayContent.onWindowAnimationFinished(this, type); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 9f3188be7623..9a7823e35a01 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -713,6 +713,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private @Nullable InsetsSourceProvider mControllableInsetProvider; private final InsetsState mRequestedInsetsState = new InsetsState(); + /** + * Freeze the insets state in some cases that not necessarily keeps up-to-date to the client. + * (e.g app exiting transition) + */ + private InsetsState mFrozenInsetsState; + @Nullable InsetsSourceProvider mPendingPositionChanged; private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f; @@ -758,6 +764,33 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } + /** + * Set a freeze state for the window to ignore dispatching its insets state to the client. + * + * Used to keep the insets state for some use cases. (e.g. app exiting transition) + */ + void freezeInsetsState() { + if (mFrozenInsetsState == null) { + mFrozenInsetsState = new InsetsState(getInsetsState(), true /* copySources */); + } + } + + void clearFrozenInsetsState() { + mFrozenInsetsState = null; + } + + InsetsState getFrozenInsetsState() { + return mFrozenInsetsState; + } + + /** + * Check if the insets state of the window is ready to dispatch to the client when invoking + * {@link InsetsStateController#notifyInsetsChanged}. + */ + boolean isReadyToDispatchInsetsState() { + return isVisible() && mFrozenInsetsState == null; + } + void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation, @Rotation int rotation, boolean requested) { // Invisible windows and the wallpaper do not participate in the seamless rotation animation diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java index 091e688743dd..5453de14a5f1 100644 --- a/services/people/java/com/android/server/people/PeopleService.java +++ b/services/people/java/com/android/server/people/PeopleService.java @@ -45,7 +45,6 @@ import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.people.data.DataManager; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -156,6 +155,13 @@ public class PeopleService extends SystemService { final IBinder mService = new IPeopleManager.Stub() { @Override + public ConversationChannel getConversation( + String packageName, int userId, String shortcutId) { + enforceSystemRootOrSystemUI(getContext(), "get conversation"); + return mDataManager.getConversation(packageName, userId, shortcutId); + } + + @Override public ParceledListSlice<ConversationChannel> getRecentConversations() { enforceSystemRootOrSystemUI(getContext(), "get recent conversations"); return new ParceledListSlice<>( diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index 752141598c9d..9a9a17112245 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -222,33 +222,65 @@ public class DataManager { mContext.getPackageName(), intentFilter, callingUserId); } + /** + * Returns a {@link ConversationChannel} with the associated {@code shortcutId} if existent. + * Otherwise, returns null. + */ + @Nullable + public ConversationChannel getConversation(String packageName, int userId, String shortcutId) { + UserData userData = getUnlockedUserData(userId); + if (userData != null) { + PackageData packageData = userData.getPackageData(packageName); + // App may have been uninstalled. + if (packageData != null) { + return getConversationChannel(packageData, shortcutId); + } + } + return null; + } + + @Nullable + private ConversationChannel getConversationChannel(PackageData packageData, String shortcutId) { + ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId); + if (conversationInfo == null) { + return null; + } + int userId = packageData.getUserId(); + String packageName = packageData.getPackageName(); + ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId); + if (shortcutInfo == null) { + return null; + } + int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId); + NotificationChannel parentChannel = + mNotificationManagerInternal.getNotificationChannel(packageName, uid, + conversationInfo.getParentNotificationChannelId()); + NotificationChannelGroup parentChannelGroup = null; + if (parentChannel != null) { + parentChannelGroup = + mNotificationManagerInternal.getNotificationChannelGroup(packageName, + uid, parentChannel.getId()); + } + return new ConversationChannel(shortcutInfo, uid, parentChannel, + parentChannelGroup, + conversationInfo.getLastEventTimestamp(), + hasActiveNotifications(packageName, userId, shortcutId)); + } + /** Returns the cached non-customized recent conversations. */ public List<ConversationChannel> getRecentConversations(@UserIdInt int callingUserId) { List<ConversationChannel> conversationChannels = new ArrayList<>(); forPackagesInProfile(callingUserId, packageData -> { - String packageName = packageData.getPackageName(); - int userId = packageData.getUserId(); packageData.forAllConversations(conversationInfo -> { if (!isCachedRecentConversation(conversationInfo)) { return; } String shortcutId = conversationInfo.getShortcutId(); - ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId); - int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId); - NotificationChannel parentChannel = - mNotificationManagerInternal.getNotificationChannel(packageName, uid, - conversationInfo.getParentNotificationChannelId()); - if (shortcutInfo == null || parentChannel == null) { + ConversationChannel channel = getConversationChannel(packageData, shortcutId); + if (channel == null || channel.getParentNotificationChannel() == null) { return; } - NotificationChannelGroup parentChannelGroup = - mNotificationManagerInternal.getNotificationChannelGroup(packageName, - uid, parentChannel.getId()); - conversationChannels.add( - new ConversationChannel(shortcutInfo, uid, parentChannel, - parentChannelGroup, - conversationInfo.getLastEventTimestamp(), - hasActiveNotifications(packageName, userId, shortcutId))); + conversationChannels.add(channel); }); }); return conversationChannels; diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 63330d518297..161d3163c1cf 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -515,6 +515,85 @@ public final class DataManagerTest { } @Test + public void testGetConversationReturnsCustomizedConversation() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.addOrUpdateConversationInfo(shortcut); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + + listenerService.onNotificationPosted(mStatusBarNotification); + shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); + mDataManager.addOrUpdateConversationInfo(shortcut); + + assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY, + TEST_SHORTCUT_ID)).isNotNull(); + + listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY), + mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + + assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY, + TEST_SHORTCUT_ID)).isNotNull(); + } + + @Test + public void testGetConversation() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY, + TEST_SHORTCUT_ID)).isNull(); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + shortcut.setCached(ShortcutInfo.FLAG_PINNED); + mDataManager.addOrUpdateConversationInfo(shortcut); + assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY, + TEST_SHORTCUT_ID)).isNotNull(); + assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY, + TEST_SHORTCUT_ID + "1")).isNull(); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + listenerService.onNotificationPosted(mStatusBarNotification); + + ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY, + TEST_SHORTCUT_ID); + assertThat(result).isNotNull(); + assertEquals(shortcut.getId(), result.getShortcutInfo().getId()); + assertEquals(1, result.getShortcutInfo().getPersons().length); + assertEquals(CONTACT_URI, result.getShortcutInfo().getPersons()[0].getUri()); + assertEquals(mParentNotificationChannel.getId(), + result.getParentNotificationChannel().getId()); + assertEquals(mStatusBarNotification.getPostTime(), result.getLastEventTimestamp()); + assertTrue(result.hasActiveNotifications()); + } + + @Test + public void testGetConversationGetsPersonsData() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + shortcut.setCached(ShortcutInfo.FLAG_PINNED); + mDataManager.addOrUpdateConversationInfo(shortcut); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + listenerService.onNotificationPosted(mStatusBarNotification); + + ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY, + TEST_SHORTCUT_ID); + + verify(mShortcutServiceInternal).getShortcuts( + anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(), + mQueryFlagsCaptor.capture(), anyInt(), anyInt(), anyInt()); + Integer queryFlags = mQueryFlagsCaptor.getValue(); + assertThat(hasFlag(queryFlags, ShortcutQuery.FLAG_GET_PERSONS_DATA)).isTrue(); + } + + @Test public void testNotificationChannelCreated() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); mDataManager.onUserUnlocked(USER_ID_SECONDARY); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index b0b8afd6c3a4..df5b48a038f3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -22,6 +22,8 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; @@ -978,6 +980,31 @@ public class WindowContainerTests extends WindowTestsBase { assertEquals(200, listener.mConfiguration.densityDpi); } + @Test + public void testFreezeInsetsStateWhenAppTransition() { + final Task stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE); + spyOn(win); + doReturn(true).when(task).okToAnimate(); + ArrayList<WindowContainer> sources = new ArrayList<>(); + sources.add(activity); + + // Simulate the task applying the exit transition, verify the main window of the task + // will be set the frozen insets state. + task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */, + false /* isVoiceInteraction */, sources); + verify(win).freezeInsetsState(); + + // Simulate the task transition finished, verify the frozen insets state of the window + // will be reset. + task.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, + task.mSurfaceAnimator.getAnimation()); + verify(win).clearFrozenInsetsState(); + } + /* Used so we can gain access to some protected members of the {@link WindowContainer} class */ private static class TestWindowContainer extends WindowContainer<TestWindowContainer> { private final int mLayer; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 263aa194a108..3231f8b6551a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -810,4 +810,27 @@ public class WindowStateTests extends WindowTestsBase { WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); assertFalse(sameTokenWindow.needsRelativeLayeringToIme()); } + + @Test + public void testSetFreezeInsetsState() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + spyOn(app); + doReturn(true).when(app).isVisible(); + + // Set freezing the insets state to make the window ignore to dispatch insets changed. + final InsetsState expectedState = new InsetsState(app.getInsetsState(), + true /* copySources */); + app.freezeInsetsState(); + assertEquals(expectedState, app.getFrozenInsetsState()); + assertFalse(app.isReadyToDispatchInsetsState()); + assertEquals(expectedState, app.getInsetsState()); + mDisplayContent.getInsetsStateController().notifyInsetsChanged(); + verify(app, never()).notifyInsetsChanged(); + + // Unfreeze the insets state to make the window can dispatch insets changed. + app.clearFrozenInsetsState(); + assertTrue(app.isReadyToDispatchInsetsState()); + mDisplayContent.getInsetsStateController().notifyInsetsChanged(); + verify(app).notifyInsetsChanged(); + } } |